drbqs 0.0.17 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/docs/FormatExecute.md +44 -2
  2. data/example/group/execute.rb +19 -0
  3. data/example/group/server.rb +27 -0
  4. data/example/group/sum.rb +9 -0
  5. data/example/mandelbrot/README.md +8 -0
  6. data/example/mandelbrot/execute.rb +4 -0
  7. data/lib/drbqs/command_line/command_node.rb +1 -0
  8. data/lib/drbqs/execute/execute_node.rb +4 -21
  9. data/lib/drbqs/node/connection.rb +1 -2
  10. data/lib/drbqs/node/node.rb +163 -102
  11. data/lib/drbqs/node/state.rb +100 -35
  12. data/lib/drbqs/node/task_client.rb +46 -33
  13. data/lib/drbqs/server/message.rb +13 -7
  14. data/lib/drbqs/server/server.rb +57 -29
  15. data/lib/drbqs/server/server_hook.rb +19 -5
  16. data/lib/drbqs/server/test/node.rb +31 -6
  17. data/lib/drbqs/setting/node.rb +11 -2
  18. data/lib/drbqs/setting/server.rb +1 -1
  19. data/lib/drbqs/task/task.rb +26 -6
  20. data/lib/drbqs/task/task_generator.rb +2 -1
  21. data/lib/drbqs/utility/temporary.rb +27 -6
  22. data/lib/drbqs/utility/transfer/transfer_client.rb +10 -12
  23. data/lib/drbqs/version.rb +1 -1
  24. data/lib/drbqs/worker.rb +2 -0
  25. data/lib/drbqs/worker/forked_process.rb +100 -0
  26. data/lib/drbqs/worker/serialize.rb +66 -0
  27. data/lib/drbqs/worker/worker.rb +133 -0
  28. data/lib/drbqs/worker/worker_process_set.rb +219 -0
  29. data/spec/integration_test/01_basic_usage_spec.rb +3 -2
  30. data/spec/integration_test/06_node_exit_after_task_spec.rb +3 -2
  31. data/spec/integration_test/07_command_server_with_node_spec.rb +1 -0
  32. data/spec/integration_test/08_shutdown_unused_nodes_spec.rb +3 -2
  33. data/spec/integration_test/10_test_server_spec.rb +2 -2
  34. data/spec/integration_test/11_special_tasks_spec.rb +61 -0
  35. data/spec/integration_test/12_multiple_workers_spec.rb +43 -0
  36. data/spec/integration_test/definition/task_obj_definition.rb +33 -6
  37. data/spec/node/connection_spec.rb +6 -6
  38. data/spec/node/node_spec.rb +10 -2
  39. data/spec/node/state_spec.rb +146 -62
  40. data/spec/node/task_client_spec.rb +58 -53
  41. data/spec/server/message_spec.rb +10 -6
  42. data/spec/server/queue_spec.rb +7 -4
  43. data/spec/server/server_hook_spec.rb +28 -1
  44. data/spec/task/task_spec.rb +43 -6
  45. data/spec/utility/temporary_spec.rb +32 -9
  46. data/spec/worker/forked_process_spec.rb +66 -0
  47. data/spec/worker/serialize_spec.rb +73 -0
  48. data/spec/worker/worker_process_set_spec.rb +104 -0
  49. data/spec/worker/worker_spec.rb +127 -0
  50. 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(Test1.new, :echo, args: [i])
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
- Test1.get_execute_echo_number.should == @tasks.size
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
- execute_node.execute(1)
19
- client_process_id = execute_node.pid[0]
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)