forkworker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cbe42b40a14925ceef50b2ca98e2428eedf4385fbdbe24b83e8d7036ef478c6b
4
+ data.tar.gz: a9a150452ff9f4d2df1c7f7f31f5bc3d4247aec0977c1774ae897ed306021e05
5
+ SHA512:
6
+ metadata.gz: 3324c1e818b7d9fd7111cbed80e0ffa94712b0125581bb29d445561acb4a1f433ae0bf383c6290b79da6d9af196bfa0cd764b4ea434a1a2ed8a44e9075de4e57
7
+ data.tar.gz: 9e0e70f96f16b3f8b41953e372be5ad36509ba2cf877cb0dcf30eb8294b6eb9fc964d6682c55a532ad6cb6b1ac0d9d370829a7760e07e799b0156c3ed33ffedd
@@ -0,0 +1,7 @@
1
+ require "forkworker/logger"
2
+ require "forkworker/leader"
3
+ require "forkworker/worker"
4
+
5
+ module Forkworker
6
+ class NoMoreWork < StandardError; end
7
+ end
@@ -0,0 +1,162 @@
1
+ module Forkworker
2
+ class Leader
3
+ include Forkworker::Logger
4
+
5
+ def initialize(worker_num, pidfile: nil, setup_block: nil, prefork_block: nil, fork_block: nil, reporting_block: nil)
6
+ @wanted_number_of_workers = worker_num
7
+ @running = true
8
+ @worker_pids = []
9
+ @signals_received = []
10
+ @pidfile = pidfile
11
+
12
+ @setup_block = setup_block
13
+ @prefork_block = prefork_block
14
+ @fork_block = fork_block
15
+ @reporting_block = reporting_block
16
+
17
+ write_pid if @pidfile
18
+ end
19
+
20
+ def start!
21
+ traps
22
+ update_leader_title
23
+ @setup_block.call if @setup_block
24
+ spawn_missing_workers
25
+
26
+ gameloop = 1
27
+
28
+ until @worker_pids.dup.length == 0 && @running == false
29
+ sleep 0.25
30
+
31
+ # Handle actions
32
+ while(signal = @signals_received.shift)
33
+ case signal
34
+ when 'CHLD'
35
+ @worker_pids.dup.each do |wpid|
36
+ begin
37
+ wpid, _status = Process.waitpid(wpid, Process::WNOHANG)
38
+ @worker_pids.delete(wpid)
39
+ rescue Errno::ECHILD
40
+ end
41
+ end
42
+ when 'TTIN'
43
+ debug "-- handled #{signal}: wanted number of workers are now: #{@wanted_number_of_workers}"
44
+
45
+ @wanted_number_of_workers += 1
46
+ when 'TTOU'
47
+ unless @wanted_number_of_workers == 0
48
+ @wanted_number_of_workers -= 1
49
+ end
50
+
51
+ debug "-- handled #{signal}: wanted number of workers are now: #{@wanted_number_of_workers}"
52
+ when 'TERM'
53
+ debug "-- handled #{signal}"
54
+
55
+ @running = false
56
+ shutdown_each_worker(:TERM)
57
+ when 'QUIT'
58
+ debug "-- handled #{signal}"
59
+
60
+ @running = false
61
+ shutdown_each_worker(:QUIT)
62
+ end
63
+ end
64
+
65
+ # Spawn missing workers if we are not getting shut down
66
+ if @running
67
+ spawn_missing_workers
68
+ end
69
+
70
+ if gameloop % 20 == 0 && @reporting_block
71
+ @reporting_block.call
72
+ end
73
+
74
+ update_leader_title
75
+
76
+ gameloop += 1
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def spawn_missing_workers
83
+ begin
84
+ while (@worker_pids.length + 1) <= @wanted_number_of_workers
85
+ worker_data = if @prefork_block
86
+ @prefork_block.call
87
+ else
88
+ nil
89
+ end
90
+
91
+ if(pid = fork)
92
+ @worker_pids << pid
93
+ update_leader_title
94
+ else
95
+ Worker.new.work!(worker_data, &@fork_block)
96
+ end
97
+ end
98
+ rescue Forkworker::NoMoreWork
99
+ debug "-- No more work, so we're just finishing up running processes"
100
+ @running = false
101
+ end
102
+ end
103
+
104
+ def traps
105
+ # By trapping the :CHLD signal our process will be notified by the kernel when one of its children exits.
106
+ trap(:CHLD) do
107
+ @signals_received << 'CHLD'
108
+ end
109
+
110
+ trap(:TERM) do
111
+ @signals_received << 'TERM'
112
+ end
113
+
114
+ trap(:TTIN) do
115
+ @signals_received << 'TTIN'
116
+ end
117
+
118
+ trap(:TTOU) do
119
+ @signals_received << 'TTOU'
120
+ end
121
+
122
+ [:QUIT, :INT].each do |signal|
123
+ trap(signal) do
124
+ @signals_received << 'QUIT'
125
+ end
126
+ end
127
+ end
128
+
129
+ def update_leader_title
130
+ run_state = @running ? 'running' : 'shutting down'
131
+ $PROGRAM_NAME = "Leader ##{Process.pid} | #{run_state} | Workers=#{@worker_pids.length}/#{@wanted_number_of_workers}"
132
+ end
133
+
134
+ def shutdown_worker(signal, wpid)
135
+ begin
136
+ Process.kill(signal, wpid)
137
+ rescue Errno::ESRCH
138
+ end
139
+ end
140
+
141
+ def shutdown_each_worker(signal)
142
+ @worker_pids.dup.each { |wpid| shutdown_worker(signal, wpid) }
143
+ end
144
+
145
+ def write_pid
146
+ if File.exist?(@pidfile) && (pid = File.read(@pidfile))
147
+ begin
148
+ Process.getpgid(pid.to_i) # throws Errno::ESRCH if process with pid exists
149
+ debug "Program is already running on pid #{pid} specified in #{@pidfile}"
150
+ exit 1
151
+ rescue Errno::ESRCH
152
+ false
153
+ end
154
+ end
155
+
156
+ File.open(@pidfile, 'w') do |f|
157
+ f.write Process.pid
158
+ end
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,7 @@
1
+ module Forkworker
2
+ module Logger
3
+ def debug(logline)
4
+ puts logline if ENV["DEBUG"] == "1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ module Forkworker
2
+ class Worker
3
+ include Forkworker::Logger
4
+
5
+ def work!(worker_data, &block)
6
+ @worker_data = worker_data
7
+ @running = true
8
+ update_title("spawned")
9
+ traps
10
+
11
+ instance_eval(&block)
12
+
13
+ exit(0)
14
+ end
15
+
16
+ private
17
+
18
+ def traps
19
+ trap(:TERM) do
20
+ @running = false
21
+ update_title
22
+ end
23
+ end
24
+
25
+ def update_title(status = nil)
26
+ @last_status = status if status
27
+ run_state = @running ? 'running' : 'shutting down'
28
+
29
+ $PROGRAM_NAME = "Worker ##{Process.pid} | #{run_state} | #{@last_status}"
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forkworker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kasper Grubbe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-07-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.9.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.9.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.13.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.13.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-remote
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.8
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.8
55
+ description: Forkworker lets you manage forking workloads easily
56
+ email: rubygems@kaspergrubbe.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/forkworker.rb
62
+ - lib/forkworker/leader.rb
63
+ - lib/forkworker/logger.rb
64
+ - lib/forkworker/worker.rb
65
+ homepage: https://rubygems.org/gems/forkworker
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.1.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Manage forking workloads with ease
88
+ test_files: []