drbqs 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/docs/FormatExecute.md +44 -2
- data/example/group/execute.rb +19 -0
- data/example/group/server.rb +27 -0
- data/example/group/sum.rb +9 -0
- data/example/mandelbrot/README.md +8 -0
- data/example/mandelbrot/execute.rb +4 -0
- data/lib/drbqs/command_line/command_node.rb +1 -0
- data/lib/drbqs/execute/execute_node.rb +4 -21
- data/lib/drbqs/node/connection.rb +1 -2
- data/lib/drbqs/node/node.rb +163 -102
- data/lib/drbqs/node/state.rb +100 -35
- data/lib/drbqs/node/task_client.rb +46 -33
- data/lib/drbqs/server/message.rb +13 -7
- data/lib/drbqs/server/server.rb +57 -29
- data/lib/drbqs/server/server_hook.rb +19 -5
- data/lib/drbqs/server/test/node.rb +31 -6
- data/lib/drbqs/setting/node.rb +11 -2
- data/lib/drbqs/setting/server.rb +1 -1
- data/lib/drbqs/task/task.rb +26 -6
- data/lib/drbqs/task/task_generator.rb +2 -1
- data/lib/drbqs/utility/temporary.rb +27 -6
- data/lib/drbqs/utility/transfer/transfer_client.rb +10 -12
- data/lib/drbqs/version.rb +1 -1
- data/lib/drbqs/worker.rb +2 -0
- data/lib/drbqs/worker/forked_process.rb +100 -0
- data/lib/drbqs/worker/serialize.rb +66 -0
- data/lib/drbqs/worker/worker.rb +133 -0
- data/lib/drbqs/worker/worker_process_set.rb +219 -0
- data/spec/integration_test/01_basic_usage_spec.rb +3 -2
- data/spec/integration_test/06_node_exit_after_task_spec.rb +3 -2
- data/spec/integration_test/07_command_server_with_node_spec.rb +1 -0
- data/spec/integration_test/08_shutdown_unused_nodes_spec.rb +3 -2
- data/spec/integration_test/10_test_server_spec.rb +2 -2
- data/spec/integration_test/11_special_tasks_spec.rb +61 -0
- data/spec/integration_test/12_multiple_workers_spec.rb +43 -0
- data/spec/integration_test/definition/task_obj_definition.rb +33 -6
- data/spec/node/connection_spec.rb +6 -6
- data/spec/node/node_spec.rb +10 -2
- data/spec/node/state_spec.rb +146 -62
- data/spec/node/task_client_spec.rb +58 -53
- data/spec/server/message_spec.rb +10 -6
- data/spec/server/queue_spec.rb +7 -4
- data/spec/server/server_hook_spec.rb +28 -1
- data/spec/task/task_spec.rb +43 -6
- data/spec/utility/temporary_spec.rb +32 -9
- data/spec/worker/forked_process_spec.rb +66 -0
- data/spec/worker/serialize_spec.rb +73 -0
- data/spec/worker/worker_process_set_spec.rb +104 -0
- data/spec/worker/worker_spec.rb +127 -0
- metadata +34 -19
@@ -0,0 +1,66 @@
|
|
1
|
+
module DRbQS
|
2
|
+
class Worker
|
3
|
+
class Serialize
|
4
|
+
HEADER_BYTE_SIZE = 4
|
5
|
+
|
6
|
+
class Unpacker
|
7
|
+
def initialize
|
8
|
+
@chunk = ''
|
9
|
+
@next_size = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def feed(data)
|
13
|
+
@chunk << data
|
14
|
+
end
|
15
|
+
|
16
|
+
def next_object
|
17
|
+
unless @next_size
|
18
|
+
if @chunk.bytesize >= HEADER_BYTE_SIZE
|
19
|
+
@chunk.force_encoding('BINARY')
|
20
|
+
@next_size = @chunk[0, HEADER_BYTE_SIZE].unpack('N')[0]
|
21
|
+
@chunk = @chunk[HEADER_BYTE_SIZE..-1]
|
22
|
+
else
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
if @next_size && @chunk.bytesize >= @next_size
|
27
|
+
data = @chunk[0, @next_size]
|
28
|
+
@chunk = @chunk[@next_size..-1]
|
29
|
+
@next_size = nil
|
30
|
+
[:loaded, Marshal.load(data)]
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
private :next_object
|
36
|
+
|
37
|
+
def each(&block)
|
38
|
+
if block_given?
|
39
|
+
while ary = next_object
|
40
|
+
sym, obj = ary
|
41
|
+
yield(obj)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
to_enum(:each)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def feed_each(data, &block)
|
49
|
+
feed(data)
|
50
|
+
each(&block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.dump(obj)
|
55
|
+
str = Marshal.dump(obj)
|
56
|
+
[str.size].pack('N') << str
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.load(s)
|
60
|
+
size = s[0, HEADER_BYTE_SIZE].unpack('N')[0]
|
61
|
+
data = s[HEADER_BYTE_SIZE, size]
|
62
|
+
Marshal.load(data)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'drbqs/worker/worker_process_set'
|
2
|
+
|
3
|
+
module DRbQS
|
4
|
+
# We can use DRbQS::Worker to send some child processes.
|
5
|
+
# Note that DRbQS::Worker is not used in DRbQS::Node class and then
|
6
|
+
# is not included in main part of DRbQS.
|
7
|
+
class Worker
|
8
|
+
attr_reader :process
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@process = DRbQS::Worker::ProcessSet.new(opts[:class])
|
12
|
+
if opts[:key]
|
13
|
+
opts[:key].each do |key|
|
14
|
+
@process.create_process(key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@state = Hash.new { |h, k| h[k] = Hash.new }
|
18
|
+
@task_pool = {}
|
19
|
+
@task_group = Hash.new { |h, k| h[k] = Array.new }
|
20
|
+
@task_num = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def calculating?
|
24
|
+
!@task_pool.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def sleep(*keys)
|
28
|
+
keys.each do |key|
|
29
|
+
@state[key][:sleep] = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def wakeup(*keys)
|
34
|
+
keys.each do |key|
|
35
|
+
@state[key][:sleep] = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def group(grp, *keys)
|
40
|
+
keys.each do |key|
|
41
|
+
(@state[key][:group] ||= []) << grp
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_result(&block)
|
46
|
+
@process.on_result do |proc_key, ary|
|
47
|
+
task_id, result = ary
|
48
|
+
if task_data = @task_pool.delete(task_id)
|
49
|
+
task = task_data[:task]
|
50
|
+
@task_group[task.group].delete(task_id)
|
51
|
+
task.exec_hook(self, result)
|
52
|
+
end
|
53
|
+
block.call(proc_key, ary)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_error(&block)
|
58
|
+
@process.on_error(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def send_task(proc_key, task_id, task)
|
62
|
+
dumped = [task_id] + task.simple_drb_args
|
63
|
+
@process.send_task(proc_key, dumped)
|
64
|
+
end
|
65
|
+
private :send_task
|
66
|
+
|
67
|
+
def add_task(task, broadcast = nil)
|
68
|
+
if broadcast
|
69
|
+
@process.all_processes.each do |proc_key|
|
70
|
+
send_task(proc_key, nil, task)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
task_id = (@task_num += 1)
|
74
|
+
@task_pool[task_id] = { :task => task }
|
75
|
+
@task_group[task.group] << task_id
|
76
|
+
task_id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# This method sends a stored task for each process that is not calculating a task
|
81
|
+
# and responds signals from child processes.
|
82
|
+
def step
|
83
|
+
@process.waiting_processes.each do |proc_key|
|
84
|
+
if @state[proc_key][:sleep]
|
85
|
+
next
|
86
|
+
end
|
87
|
+
catch(:add) do
|
88
|
+
grps = (@state[proc_key][:group] || []) + [DRbQS::Task::DEFAULT_GROUP]
|
89
|
+
grps.each do |gr|
|
90
|
+
@task_group[gr].each do |task_id|
|
91
|
+
task_data = @task_pool[task_id]
|
92
|
+
if !task_data[:calculate]
|
93
|
+
send_task(proc_key, task_id, task_data[:task])
|
94
|
+
@task_pool[task_id][:calculate] = true
|
95
|
+
throw :add
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@process.respond_signal
|
102
|
+
end
|
103
|
+
|
104
|
+
# Wait finish of task +task_id+ with sleep +interval_time+.
|
105
|
+
# @param [Fixnum] task_id
|
106
|
+
# @param [Numeric] interval_time An argument of Kernel#sleep.
|
107
|
+
def wait(task_id, interval_time)
|
108
|
+
while @task_pool[task_id]
|
109
|
+
unless step
|
110
|
+
Kernel.sleep(interval_time)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Wait finishes of all tasks with sleep +interval_time+.
|
116
|
+
# @param [Numeric] interval_time An argument of Kernel#sleep.
|
117
|
+
def waitall(interval_time)
|
118
|
+
while calculating?
|
119
|
+
unless step
|
120
|
+
Kernel.sleep(interval_time)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Send signal to exit to all child processes and wait the completion
|
126
|
+
# with sleep +interval_time+.
|
127
|
+
# @param [Numeric] interval_time An argument of Kernel#sleep.
|
128
|
+
def finish(interval_time = 1)
|
129
|
+
@process.prepare_to_exit
|
130
|
+
@process.waitall(interval_time)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'drbqs/worker/serialize'
|
2
|
+
require 'drbqs/worker/forked_process'
|
3
|
+
require 'drbqs/utility/temporary.rb'
|
4
|
+
|
5
|
+
module DRbQS
|
6
|
+
class Worker
|
7
|
+
READ_BYTE_SIZE = 1024
|
8
|
+
|
9
|
+
class ProcessSet
|
10
|
+
attr_reader :process
|
11
|
+
|
12
|
+
def initialize(process_class = nil)
|
13
|
+
@process_class = process_class || DRbQS::Worker::SimpleForkedProcess
|
14
|
+
@process = {}
|
15
|
+
@result = Queue.new
|
16
|
+
@on_error = nil
|
17
|
+
@on_result = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_error(&block)
|
21
|
+
@on_error = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_result(&block)
|
25
|
+
@on_result = block
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_create(key)
|
29
|
+
io_r, io_w = IO.pipe('BINARY')
|
30
|
+
io_r2, io_w2 = IO.pipe('BINARY')
|
31
|
+
parent_pid = Process.pid
|
32
|
+
pid = fork do
|
33
|
+
$PROGRAM_NAME = "[worker:#{key.to_s}] for drbqs-node (PID #{parent_pid})"
|
34
|
+
io_w.close
|
35
|
+
io_r2.close
|
36
|
+
worker = @process_class.new(io_r, io_w2)
|
37
|
+
worker.start
|
38
|
+
end
|
39
|
+
@process[key] = {
|
40
|
+
:pid => pid, :out => io_w, :in => io_r2,
|
41
|
+
:unpacker => DRbQS::Worker::Serialize::Unpacker.new,
|
42
|
+
:task => []
|
43
|
+
}
|
44
|
+
end
|
45
|
+
private :process_create
|
46
|
+
|
47
|
+
def get_process(key)
|
48
|
+
if @process[key]
|
49
|
+
@process[key]
|
50
|
+
else
|
51
|
+
process_create(key)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private :get_process
|
55
|
+
|
56
|
+
def create_process(*keys)
|
57
|
+
keys.each do |key|
|
58
|
+
get_process(key)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return true if the process of +key+ exists.
|
63
|
+
# @param [Symbol,nil] key Key of child process or nil.
|
64
|
+
def exist?(key)
|
65
|
+
@process[key]
|
66
|
+
end
|
67
|
+
|
68
|
+
def has_process?
|
69
|
+
!@process.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return true if the process of +key+ is calculating.
|
73
|
+
def calculating?(key)
|
74
|
+
@process[key] && !@process[key][:task].empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return true if the process +key+ does not calculate any tasks.
|
78
|
+
def waiting?(key)
|
79
|
+
!calculating?(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return keys of processes not calculating a task.
|
83
|
+
def waiting_processes
|
84
|
+
@process.keys.select do |key|
|
85
|
+
@process[key][:task].empty?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def all_processes
|
90
|
+
@process.keys
|
91
|
+
end
|
92
|
+
|
93
|
+
def output_to_io(io, obj)
|
94
|
+
io.print Serialize.dump(obj)
|
95
|
+
io.flush
|
96
|
+
end
|
97
|
+
private :output_to_io
|
98
|
+
|
99
|
+
def send_object(key, obj)
|
100
|
+
if h = get_process(key)
|
101
|
+
output_to_io(h[:out], obj)
|
102
|
+
h
|
103
|
+
else
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
private :send_object
|
108
|
+
|
109
|
+
# @param [Array] dumped_task_ary is [task_id, obj, method_name, args].
|
110
|
+
def send_task(key, dumped_task_ary)
|
111
|
+
if h = send_object(key, dumped_task_ary)
|
112
|
+
if dumped_task_ary[0]
|
113
|
+
h[:task] << dumped_task_ary[0]
|
114
|
+
end
|
115
|
+
else
|
116
|
+
raise "Process #{key.inspect} does not exist."
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def prepare_to_exit(key = nil)
|
121
|
+
if key
|
122
|
+
if h = send_object(key, :prepare_to_exit)
|
123
|
+
h[:exit] = true
|
124
|
+
end
|
125
|
+
else
|
126
|
+
@process.each do |key, h|
|
127
|
+
prepare_to_exit(key)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def delete_process(key)
|
133
|
+
if h = get_process(key)
|
134
|
+
Process.detach(h[:pid])
|
135
|
+
output_to_io(h[:out], :exit)
|
136
|
+
else
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
private :delete_process
|
141
|
+
|
142
|
+
# Read IOs and respond signals from chiled processes.
|
143
|
+
# If there no signal from childe processes then the method returns false.
|
144
|
+
# Otherwise, true.
|
145
|
+
# Types of signals are :result, :node_error, :finish_preparing_to_exit.
|
146
|
+
# - :result
|
147
|
+
# Execute callback set by DRbQS::Worker::ProcessSet#on_result.
|
148
|
+
# - :node_error
|
149
|
+
# Execute callback set by DRbQS::Worker::ProcessSet#on_error.
|
150
|
+
# - :finish_preparing_to_exit
|
151
|
+
# Send :exit signale to the process and delete from list of child processes.
|
152
|
+
def respond_signal
|
153
|
+
num = 0
|
154
|
+
to_be_deleted = []
|
155
|
+
@process.each do |key, h|
|
156
|
+
if !h[:task].empty? || h[:exit]
|
157
|
+
begin
|
158
|
+
data = h[:in].read_nonblock(READ_BYTE_SIZE)
|
159
|
+
h[:unpacker].feed_each(data) do |ary|
|
160
|
+
num += 1
|
161
|
+
response_type, response = ary
|
162
|
+
case response_type
|
163
|
+
when :result
|
164
|
+
task_id, result = response
|
165
|
+
h[:task].delete(task_id)
|
166
|
+
if @on_result
|
167
|
+
@on_result.call(key, [task_id, result])
|
168
|
+
else
|
169
|
+
$stderr.puts "The instance of DRbQS::Worker::ProcessSet can not deal with results from child processes."
|
170
|
+
end
|
171
|
+
when :node_error
|
172
|
+
if @on_error
|
173
|
+
@on_error.call(key, response)
|
174
|
+
else
|
175
|
+
$stderr.puts "The instance of DRbQS::Worker::ProcessSet can not deal with error from child processes."
|
176
|
+
end
|
177
|
+
when :finish_preparing_to_exit
|
178
|
+
delete_process(key)
|
179
|
+
to_be_deleted << key
|
180
|
+
end
|
181
|
+
end
|
182
|
+
rescue IO::WaitReadable
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
to_be_deleted.each do |key|
|
187
|
+
@process.delete(key)
|
188
|
+
end
|
189
|
+
to_be_deleted.clear
|
190
|
+
num > 0
|
191
|
+
end
|
192
|
+
|
193
|
+
def kill_all_processes
|
194
|
+
@process.each do |key, h|
|
195
|
+
Process.detach(h[:pid])
|
196
|
+
Process.kill("KILL", h[:pid])
|
197
|
+
end
|
198
|
+
@process.clear
|
199
|
+
end
|
200
|
+
|
201
|
+
WAITALL_INTERVAL_TIME = 0.1
|
202
|
+
|
203
|
+
def waitall(interval_time = nil)
|
204
|
+
unless @process.all? { |key, h| h[:exit] }
|
205
|
+
return nil
|
206
|
+
end
|
207
|
+
t = interval_time || WAITALL_INTERVAL_TIME
|
208
|
+
until @process.empty?
|
209
|
+
respond_signal
|
210
|
+
Kernel.sleep(t)
|
211
|
+
end
|
212
|
+
until Process.waitall == []
|
213
|
+
Kernel.sleep(t)
|
214
|
+
end
|
215
|
+
true
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -6,7 +6,7 @@ require_relative 'definition/task_obj_definition.rb'
|
|
6
6
|
describe DRbQS do
|
7
7
|
before(:all) do
|
8
8
|
@tasks = 5.times.map do |i|
|
9
|
-
DRbQS::Task.new(
|
9
|
+
DRbQS::Task.new(TestCount.new, :echo, args: [i])
|
10
10
|
end
|
11
11
|
@process_id, @uri = drbqs_fork_server(14010, :task => @tasks)
|
12
12
|
@node = DRbQS::Node.new(@uri, :log_file => $stdout, :continue => true)
|
@@ -42,10 +42,11 @@ describe DRbQS do
|
|
42
42
|
lambda do
|
43
43
|
@node.calculate
|
44
44
|
end.should_not raise_error
|
45
|
-
|
45
|
+
TestCount.get_execute_echo_number.should == @tasks.size
|
46
46
|
end
|
47
47
|
|
48
48
|
after(:all) do
|
49
|
+
TestCount.clear
|
49
50
|
lambda do
|
50
51
|
drbqs_wait_kill_server(@process_id)
|
51
52
|
end.should_not raise_error
|
@@ -15,8 +15,9 @@ describe DRbQS::Server do
|
|
15
15
|
|
16
16
|
it "should send node exit" do
|
17
17
|
execute_node = DRbQS::Execution::ExecuteNode.new(@uri, nil, nil)
|
18
|
-
|
19
|
-
|
18
|
+
client_process_id = fork do
|
19
|
+
execute_node.execute(1)
|
20
|
+
end
|
20
21
|
sleep(@wait)
|
21
22
|
@manage.send_node_exit_after_task(1)
|
22
23
|
th = Process.detach(client_process_id)
|