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
@@ -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"