drbqs 0.0.17 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- data/docs/FormatExecute.md +44 -2
- data/example/group/execute.rb +19 -0
- data/example/group/server.rb +27 -0
- data/example/group/sum.rb +9 -0
- data/example/mandelbrot/README.md +8 -0
- data/example/mandelbrot/execute.rb +4 -0
- data/lib/drbqs/command_line/command_node.rb +1 -0
- data/lib/drbqs/execute/execute_node.rb +4 -21
- data/lib/drbqs/node/connection.rb +1 -2
- data/lib/drbqs/node/node.rb +163 -102
- data/lib/drbqs/node/state.rb +100 -35
- data/lib/drbqs/node/task_client.rb +46 -33
- data/lib/drbqs/server/message.rb +13 -7
- data/lib/drbqs/server/server.rb +57 -29
- data/lib/drbqs/server/server_hook.rb +19 -5
- data/lib/drbqs/server/test/node.rb +31 -6
- data/lib/drbqs/setting/node.rb +11 -2
- data/lib/drbqs/setting/server.rb +1 -1
- data/lib/drbqs/task/task.rb +26 -6
- data/lib/drbqs/task/task_generator.rb +2 -1
- data/lib/drbqs/utility/temporary.rb +27 -6
- data/lib/drbqs/utility/transfer/transfer_client.rb +10 -12
- data/lib/drbqs/version.rb +1 -1
- data/lib/drbqs/worker.rb +2 -0
- data/lib/drbqs/worker/forked_process.rb +100 -0
- data/lib/drbqs/worker/serialize.rb +66 -0
- data/lib/drbqs/worker/worker.rb +133 -0
- data/lib/drbqs/worker/worker_process_set.rb +219 -0
- data/spec/integration_test/01_basic_usage_spec.rb +3 -2
- data/spec/integration_test/06_node_exit_after_task_spec.rb +3 -2
- data/spec/integration_test/07_command_server_with_node_spec.rb +1 -0
- data/spec/integration_test/08_shutdown_unused_nodes_spec.rb +3 -2
- data/spec/integration_test/10_test_server_spec.rb +2 -2
- data/spec/integration_test/11_special_tasks_spec.rb +61 -0
- data/spec/integration_test/12_multiple_workers_spec.rb +43 -0
- data/spec/integration_test/definition/task_obj_definition.rb +33 -6
- data/spec/node/connection_spec.rb +6 -6
- data/spec/node/node_spec.rb +10 -2
- data/spec/node/state_spec.rb +146 -62
- data/spec/node/task_client_spec.rb +58 -53
- data/spec/server/message_spec.rb +10 -6
- data/spec/server/queue_spec.rb +7 -4
- data/spec/server/server_hook_spec.rb +28 -1
- data/spec/task/task_spec.rb +43 -6
- data/spec/utility/temporary_spec.rb +32 -9
- data/spec/worker/forked_process_spec.rb +66 -0
- data/spec/worker/serialize_spec.rb +73 -0
- data/spec/worker/worker_process_set_spec.rb +104 -0
- data/spec/worker/worker_spec.rb +127 -0
- metadata +34 -19
@@ -26,15 +26,15 @@ module DRbQS
|
|
26
26
|
end
|
27
27
|
private :create_proc_name
|
28
28
|
|
29
|
-
def add(key,
|
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
|
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
|
-
|
74
|
-
|
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(
|
26
|
-
if
|
27
|
-
|
28
|
-
|
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
|
data/lib/drbqs/setting/node.rb
CHANGED
@@ -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
|
data/lib/drbqs/setting/server.rb
CHANGED
@@ -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
|
data/lib/drbqs/task/task.rb
CHANGED
@@ -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
|
-
[
|
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
|
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
|
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
|
-
|
9
|
-
|
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
|
-
|
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
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
data/lib/drbqs/worker.rb
ADDED
@@ -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
|