drbqs 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +674 -0
- data/README.rdoc +19 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/drbqs.rb +12 -0
- data/lib/drbqs/client.rb +44 -0
- data/lib/drbqs/connection.rb +35 -0
- data/lib/drbqs/message.rb +92 -0
- data/lib/drbqs/queue.rb +96 -0
- data/lib/drbqs/server.rb +106 -0
- data/lib/drbqs/task_client.rb +44 -0
- data/spec/drbqs_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +130 -0
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= drbqs
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to drbqs
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Takayuki YAMAGUCHI. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "drbqs"
|
16
|
+
gem.homepage = "http://github.com/ytaka/drbqs"
|
17
|
+
gem.license = "GPL3"
|
18
|
+
gem.summary = "dRuby Queueing System"
|
19
|
+
gem.description = "Queuing system over network that is implemented by dRuby."
|
20
|
+
gem.email = "d@ytak.info"
|
21
|
+
gem.authors = ["Takayuki YAMAGUCHI"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'yard'
|
43
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/drbqs.rb
ADDED
data/lib/drbqs/client.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'drbqs/connection'
|
2
|
+
require 'drbqs/task_client'
|
3
|
+
|
4
|
+
module DRbQS
|
5
|
+
|
6
|
+
class Client
|
7
|
+
def initialize(access_uri, opts = {})
|
8
|
+
@access_uri = access_uri
|
9
|
+
@logger = Logger.new(opts[:log_file] || 'drbqs_client.log')
|
10
|
+
@logger.level = opts[:log_level] || Logger::ERROR
|
11
|
+
@connection = nil
|
12
|
+
@task_client = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def connect
|
16
|
+
obj = DRbObject.new_with_uri(@access_uri)
|
17
|
+
@connection = ConnectionClient.new(obj[:message], @logger)
|
18
|
+
node_id = @connection.get_id
|
19
|
+
@task_client = TaskClient.new(node_id, obj[:queue], obj[:result], @logger)
|
20
|
+
end
|
21
|
+
|
22
|
+
def calculate
|
23
|
+
cn = Thread.new do
|
24
|
+
loop do
|
25
|
+
@task_client.add_new_task
|
26
|
+
@connection.respond_alive_signal
|
27
|
+
@task_client.send_result
|
28
|
+
sleep(1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
exec = Thread.new do
|
32
|
+
loop do
|
33
|
+
marshal_obj, method_sym, args = @task_client.get
|
34
|
+
obj = Marshal.load(marshal_obj)
|
35
|
+
result = obj.__send__(method_sym, *args)
|
36
|
+
@task_client.transmit(result)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
exec.join
|
40
|
+
cn.join
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DRbQS
|
2
|
+
# The class of connection to s.erver.
|
3
|
+
class ConnectionClient
|
4
|
+
def initialize(message, logger = nil)
|
5
|
+
@message = message
|
6
|
+
@logger = logger
|
7
|
+
@id = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_id_string
|
11
|
+
t = Time.now
|
12
|
+
sprintf("%d%d%d", t.to_i, t.usec, rand(1000))
|
13
|
+
end
|
14
|
+
private :create_id_string
|
15
|
+
|
16
|
+
def get_id
|
17
|
+
s = create_id_string
|
18
|
+
@message.write([:connect, s])
|
19
|
+
@id = @message.take([s, Fixnum])[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_alive_signal
|
23
|
+
begin
|
24
|
+
node_id, sym = @message.take([@id, Symbol], 0)
|
25
|
+
case sym
|
26
|
+
when :alive_p
|
27
|
+
@message.write([:alive, @id])
|
28
|
+
when :exit
|
29
|
+
Kernel.exit
|
30
|
+
end
|
31
|
+
rescue
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module DRbQS
|
2
|
+
class NodeList
|
3
|
+
def initialize
|
4
|
+
@id = 0
|
5
|
+
@list = {}
|
6
|
+
@check = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_new_id(id_str)
|
10
|
+
@id += 1
|
11
|
+
@list[@id] = id_str
|
12
|
+
@id
|
13
|
+
end
|
14
|
+
|
15
|
+
def each(&block)
|
16
|
+
@list.each(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_check_connection
|
20
|
+
@check = @list.keys
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete_not_alive
|
24
|
+
@check.each do |id|
|
25
|
+
@list.delete(id)
|
26
|
+
end
|
27
|
+
deleted = @check
|
28
|
+
@check = []
|
29
|
+
deleted
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_alive(id)
|
33
|
+
@check.delete(id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty?
|
37
|
+
@list.size == 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class MessageServer
|
42
|
+
def initialize(message, logger = nil)
|
43
|
+
@message = message
|
44
|
+
@node_list = NodeList.new
|
45
|
+
@logger = logger
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_message
|
49
|
+
begin
|
50
|
+
mes = @message.take([Symbol, nil], 0)
|
51
|
+
manage_message(*mes)
|
52
|
+
rescue Rinda::RequestExpiredError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def manage_message(mes, arg)
|
57
|
+
@logger.debug("Get message") { [mes, arg] } if @logger
|
58
|
+
case mes
|
59
|
+
when :connect
|
60
|
+
a = [arg, @node_list.get_new_id(arg)]
|
61
|
+
@logger.debug("New node") { a } if @logger
|
62
|
+
@message.write(a)
|
63
|
+
when :alive
|
64
|
+
@node_list.set_alive(arg)
|
65
|
+
else
|
66
|
+
puts "Invalid message from #{arg.to_s}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
private :manage_message
|
70
|
+
|
71
|
+
def check_connection
|
72
|
+
deleted = @node_list.delete_not_alive
|
73
|
+
@logger.info("IDs of deleted nodes") { deleted } if @logger
|
74
|
+
@node_list.each do |id, str|
|
75
|
+
@message.write([id, :alive_p])
|
76
|
+
end
|
77
|
+
@node_list.set_check_connection
|
78
|
+
deleted
|
79
|
+
end
|
80
|
+
|
81
|
+
def send_exit
|
82
|
+
@node_list.each do |node_id, id_str|
|
83
|
+
@message.write([node_id, :exit])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def node_not_exist?
|
88
|
+
@node_list.empty?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/lib/drbqs/queue.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module DRbQS
|
2
|
+
class Task
|
3
|
+
attr_reader :hook
|
4
|
+
|
5
|
+
def initialize(obj, method_sym, args = [], &hook)
|
6
|
+
begin
|
7
|
+
@marshal_obj = Marshal.dump(obj)
|
8
|
+
rescue
|
9
|
+
raise "Can not dump an instance of #{obj.class}."
|
10
|
+
end
|
11
|
+
@method_sym = method_sym
|
12
|
+
@args = args
|
13
|
+
@hook = hook
|
14
|
+
end
|
15
|
+
|
16
|
+
def drb_args(task_id)
|
17
|
+
[task_id, @marshal_obj, @method_sym, @args]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class QueueServer
|
22
|
+
|
23
|
+
def initialize(queue, result, logger = nil)
|
24
|
+
@queue = queue
|
25
|
+
@result = result
|
26
|
+
@task_id = 0
|
27
|
+
@cache = {}
|
28
|
+
@calculating = {}
|
29
|
+
@logger = logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def queue_task(task_id)
|
33
|
+
@queue.write(@cache[task_id].drb_args(task_id))
|
34
|
+
end
|
35
|
+
private :queue_task
|
36
|
+
|
37
|
+
# &hook take two arguments: a QueueServer object and a result of task.
|
38
|
+
def add(task)
|
39
|
+
@task_id += 1
|
40
|
+
@logger.info("New task: #{@task_id}") if @logger
|
41
|
+
@cache[@task_id] = task
|
42
|
+
queue_task(@task_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_accept_signal
|
46
|
+
begin
|
47
|
+
sym, task_id, node_id = @result.take([:accept, Fixnum, Fixnum], 0)
|
48
|
+
@calculating[node_id] = task_id
|
49
|
+
@logger.info("Accept: task #{task_id} by node #{node_id}.") if @logger
|
50
|
+
rescue
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def requeue_for_deleted_node_id(deleted)
|
55
|
+
deleted.each do |node_id|
|
56
|
+
if task_id = @calculating[node_id]
|
57
|
+
queue_task(task_id)
|
58
|
+
@calculating.delete(task_id)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_result
|
64
|
+
begin
|
65
|
+
loop do
|
66
|
+
sym, task_id, result = @result.take([:result, Fixnum, nil], 0)
|
67
|
+
@logger.info("Get: result of #{task_id}.") if @logger
|
68
|
+
@calculating.delete(task_id)
|
69
|
+
task = @cache.delete(task_id)
|
70
|
+
if hook = task.hook
|
71
|
+
hook.call(self, result)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
rescue
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# If queue is empty, return true. Otherwise, false.
|
79
|
+
# Even if there are calculating tasks,
|
80
|
+
# the method can return true.
|
81
|
+
def empty?
|
82
|
+
@cache.size - @calculating.size == 0
|
83
|
+
end
|
84
|
+
|
85
|
+
# If there are no tasks in queue and calculating,
|
86
|
+
# return true. Otherwise, false.
|
87
|
+
def finished?
|
88
|
+
@cache.size == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def calculating_task_number
|
92
|
+
@calculating.size
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/lib/drbqs/server.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'drbqs/message'
|
2
|
+
require 'drbqs/queue'
|
3
|
+
|
4
|
+
module DRbQS
|
5
|
+
class CheckAlive
|
6
|
+
def initialize(interval)
|
7
|
+
@interval = interval || 300
|
8
|
+
@last = Time.now
|
9
|
+
end
|
10
|
+
|
11
|
+
def significant_interval?
|
12
|
+
(Time.now - @last) >= @interval
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_checking
|
16
|
+
@last = Time.now
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ROOT_DEFAULT_PORT = 13500
|
21
|
+
|
22
|
+
class Server
|
23
|
+
attr_reader :queue
|
24
|
+
|
25
|
+
def initialize(opts = {})
|
26
|
+
@port = opts[:port] || ROOT_DEFAULT_PORT
|
27
|
+
@acl = opts[:acl]
|
28
|
+
@ts = {
|
29
|
+
:message => Rinda::TupleSpace.new,
|
30
|
+
:queue => Rinda::TupleSpace.new,
|
31
|
+
:result => Rinda::TupleSpace.new
|
32
|
+
}
|
33
|
+
@logger = Logger.new(opts[:log_file] || 'drbqs_server.log')
|
34
|
+
@logger.level = opts[:log_level] || Logger::ERROR
|
35
|
+
@message = MessageServer.new(@ts[:message], @logger)
|
36
|
+
@queue= QueueServer.new(@ts[:queue], @ts[:result], @logger)
|
37
|
+
@check_alive = CheckAlive.new(opts[:check_alive])
|
38
|
+
@empty_queue_hook = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def start
|
42
|
+
DRb.install_acl(@acl) if @acl
|
43
|
+
uri = "druby://:#{@port}"
|
44
|
+
DRb.start_service(uri, @ts)
|
45
|
+
@logger.info("Start DRb service") { uri } if @logger
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_connection(force = nil)
|
49
|
+
if force || @check_alive.significant_interval?
|
50
|
+
@logger.info("Check connection") if @logger
|
51
|
+
@message.check_connection
|
52
|
+
@check_alive.set_checking
|
53
|
+
end
|
54
|
+
end
|
55
|
+
private :check_connection
|
56
|
+
|
57
|
+
def set_empty_queue_hook(&block)
|
58
|
+
if block_given?
|
59
|
+
@empty_queue_hook = block
|
60
|
+
else
|
61
|
+
@empty_queue_hook = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_finish_hook(&block)
|
66
|
+
if block_given?
|
67
|
+
@finish_hook = block
|
68
|
+
else
|
69
|
+
@finish_hook = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def exec_hook
|
74
|
+
if @empty_queue_hook && @queue.empty?
|
75
|
+
@logger.info("Execute empty queue hook.") if @logger
|
76
|
+
@empty_queue_hook.call(self)
|
77
|
+
end
|
78
|
+
if @finish_hook && @queue.finished?
|
79
|
+
@logger.info("Execute finish hook.") if @logger
|
80
|
+
@finish_hook.call(self)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
private :exec_hook
|
84
|
+
|
85
|
+
WAIT_NODE_EXIT = 3
|
86
|
+
|
87
|
+
def exit
|
88
|
+
@message.send_exit
|
89
|
+
until @message.node_not_exist?
|
90
|
+
sleep(WAIT_NODE_EXIT)
|
91
|
+
check_connection(true)
|
92
|
+
end
|
93
|
+
Kernel.exit
|
94
|
+
end
|
95
|
+
|
96
|
+
def wait
|
97
|
+
loop do
|
98
|
+
@message.get_message
|
99
|
+
check_connection
|
100
|
+
@queue.get_result
|
101
|
+
exec_hook
|
102
|
+
sleep(1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|