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