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
@@ -26,15 +26,15 @@ module DRbQS
26
26
  end
27
27
  private :create_proc_name
28
28
 
29
- def add(key, name = nil, &block)
29
+ def add(key, opts = {}, &block)
30
30
  unless block_given?
31
31
  raise ArgumentError, "The main part of hook must be specified as a block."
32
32
  end
33
33
  if (n = @argument_number[key]) && (block.arity != n)
34
34
  raise ArgumentError, "Invalid argument number of hook of #{key.inspect}."
35
35
  end
36
- name ||= create_proc_name(key)
37
- @hook[key] << [name, block]
36
+ name = opts[:name] || create_proc_name(key)
37
+ @hook[key] << [name, block, opts[:repeat]]
38
38
  name
39
39
  end
40
40
 
@@ -68,10 +68,24 @@ module DRbQS
68
68
  @hook[key].map { |a| a[0] }
69
69
  end
70
70
 
71
+ def delete_unused_hook
72
+ @hook.keys.each do |key|
73
+ @hook[key].delete_if do |ary|
74
+ ary[2] && ary[2] == 0
75
+ end
76
+ end
77
+ end
78
+ private :delete_unused_hook
79
+
71
80
  def exec(key, *args, &cond)
81
+ delete_unused_hook
72
82
  @hook[key].each do |ary|
73
- if !cond || cond.call(ary[0])
74
- ary[1].call(*args)
83
+ name, proc, repeat = ary
84
+ if !cond || cond.call(name)
85
+ if !repeat || repeat > 0
86
+ proc.call(*args)
87
+ ary[2] -= 1 if repeat
88
+ end
75
89
  else
76
90
  return nil
77
91
  end
@@ -6,26 +6,51 @@ module DRbQS
6
6
  def initialize(log_level, transfer, queue)
7
7
  super(nil, :log_file => $stdout, :log_level => log_level)
8
8
  DRbQS::Transfer::Client.set(transfer.get_client(true)) if transfer
9
- @task_client = DRbQS::Node::TaskClient.new(nil, queue, nil)
9
+ @task_client = DRbQS::Node::TaskClient.new(nil, queue, nil, [DRbQS::Node::SAME_HOST_GROUP], 1)
10
+ @special_task_number = 0
10
11
  end
11
12
 
12
13
  def server_on_same_host?
13
14
  true
14
15
  end
15
16
 
17
+ def subdirectory_name(task_id)
18
+ if task_id
19
+ sprintf("T%08d", task_id)
20
+ else
21
+ sprintf("S%08d", (@special_task_number += 1))
22
+ end
23
+ end
24
+ private :subdirectory_name
25
+
26
+ def execute_task(task_id, marshal_obj, method_sym, args)
27
+ DRbQS::Temporary.set_sub_directory(subdirectory_name(task_id))
28
+ result = DRbQS::Task.execute_task(marshal_obj, method_sym, args)
29
+ if files = DRbQS::Transfer.dequeue_all
30
+ transfer_file(files)
31
+ end
32
+ if subdir = DRbQS::Temporary.subdirectory
33
+ FileUtils.rm_r(subdir)
34
+ end
35
+ result
36
+ end
37
+ private :execute_task
38
+
16
39
  def calc
17
40
  if ary = @task_client.get_task
18
41
  task_id, marshal_obj, method_sym, args = ary
19
- result = execute_task(marshal_obj, method_sym, args)
42
+ result = execute_task(task_id, marshal_obj, method_sym, args)
20
43
  return [task_id, result]
21
44
  end
22
45
  nil
23
46
  end
24
47
 
25
- def finalize(finalization_task)
26
- if finalization_task
27
- args = finalization_task.drb_args(nil)[1..-1]
28
- execute_task(*args)
48
+ def finalize(finalization_task_ary)
49
+ if finalization_task_ary
50
+ finalization_task_ary.each do |task|
51
+ args = task.simple_drb_args
52
+ execute_task(nil, *args)
53
+ end
29
54
  end
30
55
  clear_node_files
31
56
  end
@@ -7,6 +7,7 @@ module DRbQS
7
7
  super(:all_keys_defined => true, :log_level => true, :daemon => true) do
8
8
  register_key(:load, :check => [:>, 0], :add => true)
9
9
  register_key(:process, :check => 1, :default => [1])
10
+ register_key(:group, :add => true)
10
11
  register_key(:loadavg, :check => 1)
11
12
  register_key(:log_prefix, :check => 1, :default => [LOG_PREFIX_DEFAULT])
12
13
  register_key(:log_stdout, :bool => true)
@@ -28,6 +29,7 @@ module DRbQS
28
29
  def parse!
29
30
  super
30
31
  parse_load
32
+ parse_group
31
33
  parse_loadavg
32
34
  if !get(:log_stdout)
33
35
  @options[:log_prefix] = get_first(:log_prefix) do |val|
@@ -49,7 +51,7 @@ module DRbQS
49
51
  private :parse_load
50
52
 
51
53
  def parse_loadavg
52
- @options[:node_opts] = {}
54
+ @options[:node_opts] ||= {}
53
55
  if args = get(:loadavg)
54
56
  max_loadavg, sleep_time = args[0].split(':', -1)
55
57
  @options[:node_opts][:max_loadavg] = max_loadavg && max_loadavg.size > 0 ? max_loadavg.to_f : nil
@@ -58,6 +60,14 @@ module DRbQS
58
60
  end
59
61
  private :parse_loadavg
60
62
 
63
+ def parse_group
64
+ if args = get(:group)
65
+ @options[:node_opts] ||= {}
66
+ @options[:node_opts][:group] = args.map { |a| a.intern }
67
+ end
68
+ end
69
+ private :parse_group
70
+
61
71
  def exec(io = nil)
62
72
  return true if exec_as_daemon
63
73
 
@@ -76,7 +86,6 @@ module DRbQS
76
86
 
77
87
  exec_node = DRbQS::Execution::ExecuteNode.new(@uri, @options[:log_prefix], @options[:log_level], @options[:node_opts])
78
88
  exec_node.execute(@process_num)
79
- exec_node.wait
80
89
  true
81
90
  end
82
91
  end
@@ -133,7 +133,6 @@ module DRbQS
133
133
  end
134
134
  exec_node = DRbQS::Execution::ExecuteNode.new(uri, node_log_file, @options[:log_level])
135
135
  exec_node.execute(@execute_node_number, NODE_INTERVAL_TIME)
136
- exec_node.wait
137
136
  end
138
137
  private :execute_node_and_wait
139
138
 
@@ -149,6 +148,7 @@ module DRbQS
149
148
  end
150
149
  uri = current_server_uri
151
150
  wait_server_process(uri, server_pid)
151
+ $PROGRAM_NAME = "drbqs-node with drbqs-server (PID #{server_pid})"
152
152
  execute_node_and_wait(uri)
153
153
  end
154
154
  private :command_server_with_nodes
@@ -8,11 +8,13 @@ module DRbQS
8
8
  # The tasks defined by this class are sent to nodes and
9
9
  # calculated by the nodes.
10
10
  class Task
11
+ DEFAULT_GROUP = :default
12
+
11
13
  attr_reader :obj
12
14
  attr_reader :args
13
15
  attr_reader :method_name
14
16
  attr_reader :hook
15
- attr_accessor :note
17
+ attr_accessor :note, :group
16
18
 
17
19
  # Nodes calculate by obj.method_name(*opts[:args]) and send the result to their server.
18
20
  # Then the server executes &hook with a server instance and an object of result.
@@ -26,6 +28,7 @@ module DRbQS
26
28
  # @option opts [String] :note Note for a task
27
29
  # @option opts [Symbol] :hook Method name for hook
28
30
  # that takes two arguments server and the result object.
31
+ # @option opts [Symbol] :group Group of nodes to execute the task.
29
32
  # @param [Proc] hook A server execute hook as a callback when the server receive the result
30
33
  # hook take two arguments: a DRbQS::Server object and a result of task.
31
34
  # @note Changes of obj on a node are not sent to a server.
@@ -49,10 +52,15 @@ module DRbQS
49
52
  end
50
53
  @note = opts[:note]
51
54
  @hook = hook || opts[:hook]
55
+ @group = opts[:group] || DRbQS::Task::DEFAULT_GROUP
56
+ end
57
+
58
+ def simple_drb_args
59
+ [@marshal_obj, @method_name, @marshal_args]
52
60
  end
53
61
 
54
62
  def drb_args(task_id)
55
- [task_id, @marshal_obj, @method_name, @marshal_args]
63
+ [@group, task_id] + simple_drb_args
56
64
  end
57
65
 
58
66
  def exec_hook(server, result)
@@ -130,13 +138,13 @@ module DRbQS
130
138
  class ContainerWithoutHook < DRbQS::Task::TaskSet::Container
131
139
  def initialize(task_ary)
132
140
  @data = task_ary.map.with_index do |task, i|
133
- task.drb_args(i)
141
+ task.drb_args(i)[2..-1]
134
142
  end
135
143
  end
136
144
 
137
145
  def exec
138
146
  @data.map do |ary|
139
- DRbQS::Task.execute_task(*ary[1..-1])
147
+ DRbQS::Task.execute_task(*ary)
140
148
  end
141
149
  end
142
150
  end
@@ -147,13 +155,14 @@ module DRbQS
147
155
  @original_note = task_ary.map do |task|
148
156
  task.note
149
157
  end.compact!
158
+ group_sym = get_group_sym(task_ary)
150
159
  if task_ary.all? { |task| !(Proc === task.hook) }
151
160
  container = DRbQS::Task::TaskSet::ContainerTask.new(task_ary)
152
- super(container, :exec, hook: :exec_all_hooks, note: note_string)
161
+ super(container, :exec, hook: :exec_all_hooks, note: note_string, group: group_sym)
153
162
  else
154
163
  container = DRbQS::Task::TaskSet::ContainerWithoutHook.new(task_ary)
155
164
  @original_task = task_ary
156
- super(container, :exec, note: note_string) do |srv, result|
165
+ super(container, :exec, note: note_string, group: group_sym) do |srv, result|
157
166
  result.each_with_index do |res, i|
158
167
  @original_task[i].exec_hook(srv, res)
159
168
  end
@@ -161,6 +170,17 @@ module DRbQS
161
170
  end
162
171
  end
163
172
 
173
+ def get_group_sym(task_ary)
174
+ group_names = task_ary.map do |task|
175
+ task.group
176
+ end
177
+ group_names.compact!
178
+ group_names.uniq!
179
+ raise ArgumentError, "Can not collect tasks with different groups." if group_names.size > 1
180
+ group_names[0]
181
+ end
182
+ private :get_group_sym
183
+
164
184
  def note_string
165
185
  str = "TaskSet"
166
186
  unless @original_note.empty?
@@ -1,7 +1,8 @@
1
1
  module DRbQS
2
2
  class Task
3
3
  class Generator
4
- # @param [Hash] data Names of instance variables and their values,
4
+ # @param [Hash] data This argument is unnecessary and so deprecated
5
+ # Names of instance variables and their values,
5
6
  # which can be accessed in {DRbQS::Task::Generator#set}.
6
7
  def initialize(data = {})
7
8
  @registrar = DRbQS::Task::Registrar.new(data)
@@ -3,15 +3,36 @@ require 'tmpdir'
3
3
  module DRbQS
4
4
  module Temporary
5
5
  @@root = nil
6
+ @@subdir = nil
6
7
  @@filename = nil
7
8
 
8
- # Return FileName object to generate names of temporary files on DRbQS nodes.
9
- def self.filename
10
- unless @@filename
9
+ def self.set_root_directory
10
+ if !@@root
11
11
  pid = Process.pid
12
12
  @@root = File.join(Dir.tmpdir, sprintf("drbqs_%s_%d_%d", ENV['USER'], pid, rand(10000)))
13
13
  FileUtils.mkdir_p(@@root, :mode => 0700)
14
- @@filename = FileName.new(File.join(@@root, sprintf("temp_%d_%d", pid, rand(10000))))
14
+ end
15
+ end
16
+
17
+ def self.set_sub_directory(dir)
18
+ self.set_root_directory
19
+ @@filename = nil
20
+ @@subdir = File.join(@@root, dir)
21
+ end
22
+
23
+ def self.subdirectory
24
+ @@subdir && File.exist?(@@subdir) ? @@subdir : nil
25
+ end
26
+
27
+ # Return FileName object to generate names of temporary files on DRbQS nodes.
28
+ def self.filename
29
+ unless @@filename
30
+ self.set_root_directory
31
+ if @@subdir
32
+ @@filename = FileName.new(File.join(@@subdir, sprintf("temp_%d", rand(10000))))
33
+ else
34
+ @@filename = FileName.new(File.join(@@root, sprintf("temp_%d", rand(10000))))
35
+ end
15
36
  end
16
37
  @@filename
17
38
  end
@@ -27,7 +48,7 @@ module DRbQS
27
48
  if basename
28
49
  File.join(self.directory, basename)
29
50
  else
30
- filename.create(:add => :always)
51
+ filename.create(:add => :always, :directory => :parent)
31
52
  end
32
53
  end
33
54
 
@@ -35,7 +56,7 @@ module DRbQS
35
56
  def self.delete
36
57
  if @@root
37
58
  FileUtils.rm_r(@@root)
38
- FileUtils.mkdir_p(@@root)
59
+ FileUtils.mkdir_p(@@root, :mode => 0700)
39
60
  end
40
61
  end
41
62
 
@@ -68,19 +68,17 @@ module DRbQS
68
68
  @transfer = transfer
69
69
  end
70
70
 
71
- def transfer_to_server
72
- if files = DRbQS::Transfer.dequeue_all
73
- if @transfer
74
- begin
75
- @transfer.transfer(files)
76
- rescue Exception => err
77
- err_new = err.class.new("#{err.to_s} (#{err.class}); Can not send file: #{files.join(", ")}")
78
- err_new.set_backtrace(err.backtrace)
79
- raise err_new
80
- end
81
- else
82
- raise "Server does not set transfer settings. Can not send file: #{files.join(", ")}"
71
+ def transfer_to_server(files)
72
+ if files && @transfer
73
+ begin
74
+ @transfer.transfer(files)
75
+ rescue Exception => err
76
+ err_new = err.class.new("#{err.to_s} (#{err.class}); Can not send file: #{files.join(", ")}")
77
+ err_new.set_backtrace(err.backtrace)
78
+ raise err_new
83
79
  end
80
+ else
81
+ raise "Server does not set transfer settings. Can not send file: #{files.join(", ")}"
84
82
  end
85
83
  end
86
84
  end
data/lib/drbqs/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module DRbQS
2
- VERSION = '0.0.17'
2
+ VERSION = '0.0.18'
3
3
  end
@@ -0,0 +1,2 @@
1
+ require 'drbqs/task/task'
2
+ require 'drbqs/worker/worker'
@@ -0,0 +1,100 @@
1
+ module DRbQS
2
+ class Worker
3
+ class SimpleForkedProcess
4
+ def initialize(io_r, io_w)
5
+ @io_r = io_r
6
+ @io_w = io_w
7
+ @queue = []
8
+ @special_task_number = 0
9
+ end
10
+
11
+ def calculate(marshal_obj, method_sym, args)
12
+ DRbQS::Task.execute_task(marshal_obj, method_sym, args)
13
+ end
14
+
15
+ def send_response(obj)
16
+ @io_w.print DRbQS::Worker::Serialize.dump(obj)
17
+ @io_w.flush
18
+ end
19
+ private :send_response
20
+
21
+ def handle_task(obj)
22
+ task_id, marshal_obj, method_sym, args = obj
23
+ begin
24
+ res = calculate(marshal_obj, method_sym, args)
25
+ if task_id
26
+ send_response([:result, [task_id, res]])
27
+ end
28
+ rescue => err
29
+ send_response([:node_error, err])
30
+ end
31
+ end
32
+ private :handle_task
33
+
34
+ def start
35
+ unpacker = DRbQS::Worker::Serialize::Unpacker.new
36
+ loop do
37
+ if @queue.empty?
38
+ begin
39
+ chunk = @io_r.readpartial(READ_BYTE_SIZE)
40
+ unpacker.feed_each(chunk) do |ary|
41
+ @queue << ary
42
+ end
43
+ rescue EOFError
44
+ break
45
+ end
46
+ else
47
+ obj = @queue.shift
48
+ case obj
49
+ when Array
50
+ handle_task(obj)
51
+ when :prepare_to_exit
52
+ send_response([:finish_preparing_to_exit])
53
+ when :exit
54
+ break
55
+ else
56
+ send_response([:node_error, "Invalid object from server."])
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ class ForkedProcess < DRbQS::Worker::SimpleForkedProcess
64
+ def calculate(marshal_obj, method_sym, args)
65
+ result = super(marshal_obj, method_sym, args)
66
+ transfer_files = DRbQS::Transfer.dequeue_all
67
+ { :result => result, :transfer => transfer_files }
68
+ end
69
+
70
+ def subdirectory_name(task_id)
71
+ if task_id
72
+ sprintf("T%08d", task_id)
73
+ else
74
+ sprintf("S%08d", (@special_task_number += 1))
75
+ end
76
+ end
77
+ private :subdirectory_name
78
+
79
+ def handle_task(obj)
80
+ task_id, marshal_obj, method_sym, args = obj
81
+ DRbQS::Temporary.set_sub_directory(subdirectory_name(task_id))
82
+ begin
83
+ result_hash = calculate(marshal_obj, method_sym, args)
84
+ # If task_id is nil then the task is initialization or finalization.
85
+ # So we do not return results.
86
+ if task_id
87
+ if subdir = DRbQS::Temporary.subdirectory
88
+ result_hash[:tmp] = subdir
89
+ end
90
+ result_hash[:id] = task_id
91
+ send_response([:result, [task_id, result_hash]])
92
+ end
93
+ rescue => err
94
+ send_response([:node_error, err])
95
+ end
96
+ end
97
+ private :handle_task
98
+ end
99
+ end
100
+ end