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.
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
@@ -2,78 +2,142 @@ module DRbQS
2
2
  class Node
3
3
  class State
4
4
  # Value of state is :sleep, :wait, or :calculate.
5
- attr_reader :state
5
+ attr_reader :calculating_task
6
6
 
7
- ALL_STATES = [:sleep, :wait, :calculate]
7
+ ALL_STATES = [:sleep, :wait, :calculate, :exit]
8
8
  DEFAULT_SLEEP_TIME = 300
9
9
  LOADAVG_PATH = '/proc/loadavg'
10
10
 
11
- def initialize(state, opts = {})
12
- @state = state
13
- @sleep_at_calculated = nil
11
+ def initialize(state_init, process_number, opts = {})
12
+ @process_number = process_number
13
+ @process_state = {}
14
+ @process_number.times do |i|
15
+ @process_state[i] = state_init
16
+ end
17
+ @calculating_task = {}
18
+ @state_after_task = nil
19
+
20
+
14
21
  @load_average_threshold = opts[:max_loadavg]
15
22
  @sleep_time = opts[:sleep_time] || DEFAULT_SLEEP_TIME
16
23
  @auto_wakeup = nil
17
24
  end
18
25
 
19
- def change(state)
20
- unless ALL_STATES.include?(state)
21
- raise ArgumentError, "Invalid state of node '#{state}'."
22
- end
23
- @state = state
26
+ def get_state(wid)
27
+ @process_state[wid]
24
28
  end
25
29
 
26
- def calculate?
27
- @state == :calculate
30
+ def each_worker_id(&block)
31
+ if block_given?
32
+ @process_state.each do |key, val|
33
+ yield(key)
34
+ end
35
+ else
36
+ to_enum(:each_worker_id)
37
+ end
28
38
  end
29
39
 
30
- def rest?
31
- !calculate?
40
+ def waiting_worker_id
41
+ ary = []
42
+ @process_state.each do |wid, state|
43
+ if state == :wait
44
+ ary << wid
45
+ end
46
+ end
47
+ ary
32
48
  end
33
49
 
34
- def stop?
35
- @state == :sleep
50
+ def request_task_number
51
+ waiting = @process_state.select do |wid, state|
52
+ state == :wait
53
+ end
54
+ waiting.size
36
55
  end
37
56
 
38
57
  def request?
39
- @state == :wait
58
+ request_task_number > 0
40
59
  end
41
60
 
42
- def change_to_wait
43
- unless calculate?
44
- change(:wait)
61
+ def all_workers_waiting?
62
+ each_worker_id.all? do |wid|
63
+ st = get_state(wid)
64
+ st == :wait || st == :exit
45
65
  end
46
66
  end
47
67
 
48
- def change_to_sleep
49
- if calculate?
50
- @sleep_at_calculated = true
51
- else
52
- change(:sleep)
68
+ def set_calculating_task(wid, task_id)
69
+ @calculating_task[task_id] = wid
70
+ change(wid, :calculate)
71
+ end
72
+
73
+ def set_exit_after_task
74
+ @state_after_task = :exit
75
+ each_worker_id do |wid|
76
+ st = get_state(wid)
77
+ if (st == :wait) && (st == :sleep)
78
+ change(wid, :exit)
79
+ end
53
80
  end
54
81
  end
55
82
 
56
- def change_to_calculate
57
- change(:calculate)
83
+ def set_finish_of_task(sent_task_id)
84
+ sent_task_id.each do |task_id|
85
+ if wid = @calculating_task.delete(task_id)
86
+ case @state_after_task
87
+ when :exit
88
+ @process_state[wid] = :exit
89
+ when :sleep
90
+ @process_state[wid] = :sleep
91
+ else
92
+ @process_state[wid] = :wait
93
+ end
94
+ end
95
+ end
58
96
  end
59
97
 
60
- def change_to_finish_calculating
61
- if @sleep_at_calculated
62
- change(:sleep)
63
- else
64
- change(:wait)
98
+ def change(proc_id, state)
99
+ unless ALL_STATES.include?(state)
100
+ raise ArgumentError, "Invalid state of node '#{state}'."
101
+ end
102
+ @process_state[proc_id] = state
103
+ end
104
+
105
+ def wakeup_sleeping_worker
106
+ each_worker_id do |wid|
107
+ if get_state(wid) == :sleep
108
+ change(wid, :wait)
109
+ end
110
+ end
111
+ @state_after_task = nil
112
+ end
113
+
114
+ def change_to_sleep
115
+ each_worker_id do |wid|
116
+ st = get_state(wid)
117
+ if st == :calculate
118
+ @state_after_task = :sleep
119
+ elsif st != :exit
120
+ change(wid, :sleep)
121
+ end
65
122
  end
66
- @sleep_at_calculated = nil
67
123
  end
68
124
 
69
125
  def sleep_with_auto_wakeup
70
- change(:sleep)
126
+ each_worker_id do |wid|
127
+ if get_state(wid) == :wait
128
+ change(wid, :sleep)
129
+ end
130
+ end
71
131
  @auto_wakeup = Time.now + @sleep_time
72
132
  end
73
133
 
74
134
  def wakeup_automatically_for_unbusy_system
75
135
  if @auto_wakeup && Time.now > @auto_wakeup && !system_busy?
76
- change(:wait)
136
+ each_worker_id do |wid|
137
+ if get_state(wid) == :sleep
138
+ change(wid, :wait)
139
+ end
140
+ end
77
141
  @auto_wakeup = nil
78
142
  return true
79
143
  end
@@ -96,6 +160,7 @@ module DRbQS
96
160
  end
97
161
  nil
98
162
  end
163
+ private :system_busy?
99
164
 
100
165
  def change_to_sleep_for_busy_system
101
166
  if system_busy?
@@ -1,23 +1,18 @@
1
1
  module DRbQS
2
2
  class Node
3
3
  class TaskClient
4
- attr_reader :node_number, :calculating_task
4
+ attr_reader :node_number, :group
5
5
 
6
- def initialize(node_number, queue, result, logger = DRbQS::Misc::LoggerDummy.new)
6
+ def initialize(node_number, queue, result, group, logger = DRbQS::Misc::LoggerDummy.new)
7
7
  @node_number = node_number
8
8
  @queue = queue
9
9
  @result = result
10
- @calculating_task = nil
11
- @exit_after_task = nil
12
10
  @task_queue = Queue.new
13
11
  @result_queue = Queue.new
12
+ @group = group || []
14
13
  @logger = logger
15
14
  end
16
15
 
17
- def calculating?
18
- !!@calculating_task
19
- end
20
-
21
16
  def task_empty?
22
17
  @task_queue.empty?
23
18
  end
@@ -31,57 +26,75 @@ module DRbQS
31
26
  end
32
27
  private :dequeue_result
33
28
 
34
- def queue_task(task_id, ary)
35
- @calculating_task = task_id
29
+ # @param [Array] ary An array is [task_id, obj, method_name, args]
30
+ def queue_task(ary)
36
31
  @task_queue.enq(ary)
37
32
  end
38
33
 
34
+ # @return [nil,Array] If @task_queue is empty then return nil.
35
+ # Otherwise, an array [task_id, obj, method_name, args] is returned.
39
36
  def dequeue_task
40
- @task_queue.deq
37
+ if @task_queue.empty?
38
+ nil
39
+ else
40
+ @task_queue.deq
41
+ end
41
42
  end
42
43
 
43
- def get_task
44
+ def get_task_by_group(grp)
44
45
  begin
45
- @queue.take([Fixnum, nil, Symbol, nil], 0)
46
+ @queue.take([grp, Fixnum, nil, Symbol, nil], 0)[1..-1]
46
47
  rescue Rinda::RequestExpiredError
47
48
  nil
48
49
  end
49
50
  end
50
51
 
51
- def set_exit_after_task
52
- @exit_after_task = true
52
+ def get_task
53
+ @group.each do |grp|
54
+ if task = get_task_by_group(grp)
55
+ return task
56
+ end
57
+ end
58
+ get_task_by_group(DRbQS::Task::DEFAULT_GROUP)
53
59
  end
54
60
 
55
- def add_new_task
56
- if !@calculating_task && !@exit_after_task && (ary = get_task)
57
- task_id, obj, method_sym, args = ary
58
- @logger.info("Send accept signal: node #{@node_number} caluclating #{task_id}")
59
- @result.write([:accept, task_id, @node_number])
60
- queue_task(task_id, [obj, method_sym, args])
61
- return true
61
+ def add_new_task(num)
62
+ get_task_id = []
63
+ num.times do |i|
64
+ if ary = get_task
65
+ task_id = ary[0]
66
+ @logger.info("Send accept signal: node #{@node_number} caluclating #{task_id}")
67
+ @result.write([:accept, task_id, @node_number])
68
+ queue_task(ary)
69
+ get_task_id << task_id
70
+ else
71
+ break
72
+ end
62
73
  end
63
- nil
74
+ get_task_id.empty? ? nil: get_task_id
64
75
  end
65
76
 
66
- # If the method return true, a node should finilize and exit.
77
+ # Return an array of task ID that is sent to the server.
67
78
  def send_result
68
- if !result_empty?
69
- result = dequeue_result
70
- @logger.info("Send result: #{@calculating_task}") { result.inspect }
71
- @result.write([:result, @calculating_task, @node_number, result])
72
- @calculating_task = nil
79
+ sent_task_id = []
80
+ while !result_empty?
81
+ task_id, result = dequeue_result
82
+ @logger.info("Send result: #{task_id}") { result.inspect }
83
+ @result.write([:result, task_id, @node_number, result])
84
+ sent_task_id << task_id
73
85
  end
74
- !@calculating_task && @exit_after_task
86
+ sent_task_id.empty? ? nil : sent_task_id
75
87
  end
76
88
 
77
- def queue_result(result)
78
- @result_queue.enq(result)
89
+ def queue_result(task_id, result)
90
+ @result_queue.enq([task_id, result])
79
91
  end
80
92
 
81
93
  def dump_result_queue
82
94
  results = []
83
95
  while !result_empty?
84
- results << dequeue_result
96
+ task_id, res = dequeue_result
97
+ results << res
85
98
  end
86
99
  if results.size > 0
87
100
  Marshal.dump(results)
@@ -20,6 +20,8 @@ module DRbQS
20
20
  # * [:wake_node, node_id]
21
21
  # * [:sleep_node, node_id]
22
22
  # * [:node_error, [node_id, error_message]]
23
+ # * [:initialize, Array]
24
+ # * [:finalize, Array]
23
25
  #
24
26
  # @return [Array] Message array
25
27
  def get_message
@@ -148,23 +150,27 @@ module DRbQS
148
150
  @node_list.exist?(node_id)
149
151
  end
150
152
 
151
- def set_special_task(label, task)
153
+ def set_special_task(label, *tasks)
154
+ task_ary = []
152
155
  begin
153
- @message.take([label, nil, Symbol, nil], 0)
156
+ task_ary = @message.take([label, Array], 0)[1]
154
157
  rescue Rinda::RequestExpiredError
155
158
  end
156
- @message.write(task.drb_args(label))
159
+ tasks.each do |task|
160
+ task_ary << task.simple_drb_args
161
+ end
162
+ @message.write([label, task_ary])
157
163
  end
158
164
  private :set_special_task
159
165
 
160
166
  # If the task has already set,
161
167
  # the method overwrite old task of initialization by new task.
162
- def set_initialization(task)
163
- set_special_task(:initialize, task)
168
+ def set_initialization_tasks(task_ary)
169
+ set_special_task(:initialize, *task_ary)
164
170
  end
165
171
 
166
- def set_finalization(task)
167
- set_special_task(:finalization, task)
172
+ def set_finalization_tasks(task_ary)
173
+ set_special_task(:finalize, *task_ary)
168
174
  end
169
175
 
170
176
  def shutdown_unused_nodes(calculating_nodes)
@@ -18,18 +18,33 @@ module DRbQS
18
18
  attr_reader :queue, :uri
19
19
 
20
20
  # @param [Hash] opts The options of server
21
- # @option opts [Hash] :port Set the port of server.
22
- # @option opts [Hash] :unix Set the path of unix domain socket. If :port is specified then :port is preceded.
23
- # @option opts [Hash] :acl Set the ACL instance.
24
- # @option opts [Hash] :log_file Set the path of log files.
25
- # @option opts [Hash] :log_level Set the level of logging.
26
- # @option opts [Hash] :check_alive Set the time interval of checking alive nodes.
27
- # @option opts [Hash] :not_exit Not exit programs when all tasks are finished.
28
- # @option opts [Hash] :shutdown_unused_nodes Shutdown unused nodes.
29
- # @option opts [Hash] :signal_trap Set trapping signal. Default is true.
30
- # @option opts [Hash] :sftp_user Set user of sftp.
31
- # @option opts [Hash] :sftp_host Set host of sftp.
32
- # @option opts [Hash] :file_directory Set the directory for nodes to send files.
21
+ # @option opts [Fixnum] :port Set the port of server.
22
+ # @option opts [String] :unix Set the path of unix domain socket. If :port is specified then :port is preceded.
23
+ # @option opts [Array] :acl Set the array of ACL.
24
+ # @option opts [String] :acl Set the file path of ACL.
25
+ # @option opts [String] :log_file Set the path of log files.
26
+ # @option opts [Fixnum] :log_level Set the level of logging.
27
+ # @option opts [Fixnum] :check_alive Set the time interval of checking alive nodes.
28
+ # @option opts [Boolean] :not_exit Not exit programs when all tasks are finished.
29
+ # @option opts [Boolean] :shutdown_unused_nodes Shutdown unused nodes.
30
+ # @option opts [Boolean] :signal_trap Set trapping signal. Default is true.
31
+ # @option opts [String] :sftp_user Set user of sftp.
32
+ # @option opts [String] :sftp_host Set host of sftp.
33
+ # @option opts [String] :file_directory Set the directory for nodes to send files.
34
+ #
35
+ # :nodoc:
36
+ # * Note of tuple spaces
37
+ # @ts[:message]
38
+ # - used in node/connection.rb
39
+ # - some messages to both server and node
40
+ # - special tasks from server to nodes
41
+ # @ts[:queue]
42
+ # - used in node/task_client.rb
43
+ # - tasks from server to nodes
44
+ # @ts[:result]
45
+ # - used in node/task_client.rb
46
+ # - accept signal from nodes
47
+ # - results from nodes
33
48
  def initialize(opts = {})
34
49
  @uri = DRbQS::Misc.create_uri(opts)
35
50
  @acl = acl_init(opts[:acl])
@@ -48,7 +63,7 @@ module DRbQS
48
63
  @task_generator = []
49
64
  hook_init(!opts[:not_exit], opts[:shutdown_unused_nodes])
50
65
  set_signal_trap if !opts.has_key?(:signal_trap) || opts[:signal_trap]
51
- @finalization_task = nil
66
+ @finalization_task = []
52
67
  @data_storage = []
53
68
  @transfer_setting = DRbQS::Server::TransferSetting.new(opts[:sftp_host], opts[:sftp_user], opts[:file_directory])
54
69
  @config = DRbQS::Config.new
@@ -107,7 +122,9 @@ module DRbQS
107
122
  end
108
123
 
109
124
  # Create new task generator and add it.
110
- # The arguments are same as {DRbQS::Task::Generator#set}.
125
+ # @param [Hash] opts An argument is same as {DRbQS::Task::Generator#set}
126
+ # @yield [tgen] Block is same as {DRbQS::Task::Generator#set}
127
+ # @yieldparam [DRbQS::TaskGenerator] tgen Task generator to add to the server
111
128
  def task_generator(opts = {}, &block)
112
129
  gen = DRbQS::Task::Generator.new
113
130
  gen.set(opts, &block)
@@ -145,29 +162,30 @@ module DRbQS
145
162
  private :all_tasks_assigned?
146
163
 
147
164
  # @param [DRbQS::task] task
148
- def set_initialization_task(task)
149
- @message.set_initialization(task)
165
+ def set_initialization_task(*tasks)
166
+ @message.set_initialization_tasks(tasks)
150
167
  end
151
168
 
152
169
  # @param [DRbQS::task] task
153
- def set_finalization_task(task)
154
- @finalization_task = task
155
- @message.set_finalization(@finalization_task)
170
+ def set_finalization_task(*tasks)
171
+ @finalization_task.concat(tasks)
156
172
  end
157
173
 
158
174
  # Set a hook of server.
159
175
  # @note When we set both :empty_queue and task generators,
160
176
  # hook of :empty_queue is prior to task generators.
161
177
  # @param [:empty_queue,:process_data,:finish] key Set the type of hook.
162
- # @param [String] name Name of the hook. If the value is nil then the name is automatically created.
178
+ # @option opts [Fixnum] :repeat If we execute the hook specified times then the hook is deleted.
179
+ # If the value is nil, the hook is repeated without limit.
180
+ # @option opts [String] :name Name of the hook. If the value is nil then the name is automatically created.
163
181
  # @param [Proc] &block block is obligatory and takes server itself as an argument.
164
- def add_hook(key, name = nil, &block)
182
+ def add_hook(key, opts = {}, &block)
165
183
  if key == :process_data
166
184
  if @hook.number_of_hook(:process_data) != 0
167
185
  raise "Hook :process_data has already set."
168
186
  end
169
187
  end
170
- @hook.add(key, name, &block)
188
+ @hook.add(key, opts, &block)
171
189
  end
172
190
 
173
191
  # @param [:empty_queue,:process_data,:finish] key Set the type of hook.
@@ -238,7 +256,8 @@ module DRbQS
238
256
  private :shutdown_unused_nodes
239
257
 
240
258
  def exit
241
- if @finalization_task
259
+ if !@finalization_task.empty?
260
+ @message.set_finalization_tasks(@finalization_task)
242
261
  @message.send_finalization
243
262
  wait_time = WAIT_TIME_NODE_FINALIZE
244
263
  else
@@ -263,8 +282,8 @@ module DRbQS
263
282
 
264
283
  # @param [String] directory Set the directory to save files from nodes.
265
284
  # @param [Hash] opts The options for SFTP.
266
- # @option opts [Symbol] :host Hostname for SFTP.
267
- # @option opts [Symbol] :user User name for SFTP.
285
+ # @option opts [String] :host Hostname for SFTP.
286
+ # @option opts [String] :user User name for SFTP.
268
287
  def set_file_transfer(directory, opts = {})
269
288
  if @transfer_setting.setup_server(directory, opts)
270
289
  @ts[:transfer] = @transfer_setting
@@ -295,6 +314,7 @@ module DRbQS
295
314
  private :process_data
296
315
 
297
316
  def send_status_for_request
317
+ task_message = []
298
318
  messages = @queue.calculating_task_message
299
319
  s = ''
300
320
  @message.each_node_history do|node_id, events|
@@ -313,10 +333,9 @@ module DRbQS
313
333
  task_ids = @queue.calculating[node_id].to_a
314
334
  s << "task: "
315
335
  if messages[node_id]
316
- s << messages[node_id].map do |task_id, mes|
317
- calc_task = task_id.to_s
318
- calc_task << ": " << mes.to_s if mes
319
- calc_task
336
+ s << messages[node_id].map do |ary|
337
+ task_message << ary
338
+ ary[0].to_s
320
339
  end.join(', ')
321
340
  else
322
341
  s << "none"
@@ -327,6 +346,15 @@ module DRbQS
327
346
  end
328
347
  s << " none\n" if s.size == 0
329
348
  s = "Nodes:\n" << s
349
+ unless task_message.empty?
350
+ s << "Tasks:\n"
351
+ task_message.sort_by! do |task_id, mes|
352
+ task_id
353
+ end
354
+ task_message.each do |task_id, mes|
355
+ s << sprintf("%4d: %s\n", task_id, (mes ? mes.to_s : ''))
356
+ end
357
+ end
330
358
  s << "Server:\n"
331
359
  s << " calculating tasks: #{@queue.calculating_task_number}\n"
332
360
  s << " finished tasks : #{@queue.finished_task_number}\n"