simonmenke-background_services 0.0.2
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.
- data/README.textile +24 -0
- data/bin/background_services +15 -0
- data/bin/background_servicesd +15 -0
- data/lib/background_services.rb +36 -0
- data/lib/background_services/client.rb +154 -0
- data/lib/background_services/dsl.rb +66 -0
- data/lib/background_services/group.rb +41 -0
- data/lib/background_services/queue.rb +8 -0
- data/lib/background_services/queue/base.rb +70 -0
- data/lib/background_services/queue/beanstalkd.rb +52 -0
- data/lib/background_services/server.rb +226 -0
- data/lib/background_services/service.rb +64 -0
- data/lib/background_services/team.rb +97 -0
- data/lib/background_services/team_controller.rb +63 -0
- data/lib/background_services/version.rb +9 -0
- data/lib/background_services/worker.rb +43 -0
- data/lib/bs.rb +9 -0
- metadata +87 -0
data/README.textile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
h2. LICENSE:
|
2
|
+
|
3
|
+
(The MIT License)
|
4
|
+
|
5
|
+
Copyright (c) 2008 Simon Menke
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
'Software'), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
21
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
22
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
23
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
24
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created on 2008-9-26.
|
4
|
+
# Copyright (c) 2008. All rights reserved.
|
5
|
+
|
6
|
+
begin
|
7
|
+
require File.join(File.dirname(__FILE__), '../lib/background_services.rb')
|
8
|
+
rescue LoadError
|
9
|
+
# no rubygems to load, so we fail silently
|
10
|
+
require 'rubygems'
|
11
|
+
require 'background_services'
|
12
|
+
end
|
13
|
+
|
14
|
+
BackgroundServices::Client.setup
|
15
|
+
BackgroundServices::Client.run!
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created on 2008-9-26.
|
4
|
+
# Copyright (c) 2008. All rights reserved.
|
5
|
+
|
6
|
+
begin
|
7
|
+
require File.join(File.dirname(__FILE__), '../lib/background_services.rb')
|
8
|
+
rescue LoadError
|
9
|
+
# no rubygems to load, so we fail silently
|
10
|
+
require 'rubygems'
|
11
|
+
require 'background_services'
|
12
|
+
end
|
13
|
+
|
14
|
+
BackgroundServices::Server.setup
|
15
|
+
BackgroundServices::Server.run!
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'beanstalk-client'
|
3
|
+
require 'thread'
|
4
|
+
require 'timeout'
|
5
|
+
require 'logger'
|
6
|
+
require 'singleton'
|
7
|
+
require 'optparse'
|
8
|
+
require 'daemons'
|
9
|
+
require 'drb'
|
10
|
+
|
11
|
+
# HACK
|
12
|
+
class DRb::DRbTCPSocket
|
13
|
+
class << self
|
14
|
+
alias parse_uri_orig parse_uri
|
15
|
+
def parse_uri(*args)
|
16
|
+
ary = parse_uri_orig(*args)
|
17
|
+
ary[1] = (rand(100_000)+50_000) if ary[1] == 0
|
18
|
+
ary
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require File.join(File.dirname(__FILE__), 'bs')
|
24
|
+
require File.join(File.dirname(__FILE__), 'background_services/client')
|
25
|
+
require File.join(File.dirname(__FILE__), 'background_services/server')
|
26
|
+
require File.join(File.dirname(__FILE__), 'background_services/dsl')
|
27
|
+
require File.join(File.dirname(__FILE__), 'background_services/team')
|
28
|
+
require File.join(File.dirname(__FILE__), 'background_services/group')
|
29
|
+
require File.join(File.dirname(__FILE__), 'background_services/service')
|
30
|
+
require File.join(File.dirname(__FILE__), 'background_services/worker')
|
31
|
+
require File.join(File.dirname(__FILE__), 'background_services/version')
|
32
|
+
require File.join(File.dirname(__FILE__), 'background_services/team_controller')
|
33
|
+
|
34
|
+
module BackgroundServices
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class Client
|
3
|
+
|
4
|
+
include Singleton
|
5
|
+
include DRbUndumped
|
6
|
+
|
7
|
+
def self.load(*args)
|
8
|
+
include.load(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.run!
|
12
|
+
instance.run!
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.setup
|
16
|
+
instance.setup
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :options
|
20
|
+
|
21
|
+
def setup
|
22
|
+
parse_options
|
23
|
+
start_drb_client
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_options
|
27
|
+
@options = {
|
28
|
+
:log => STDOUT,
|
29
|
+
:host => "127.0.0.1:5000"
|
30
|
+
}
|
31
|
+
@opts = OptionParser.new do |opts|
|
32
|
+
opts.banner = "Usage: #{File.basename($0)} [options]"
|
33
|
+
|
34
|
+
opts.separator ""
|
35
|
+
opts.separator "Client options: "
|
36
|
+
|
37
|
+
opts.on("--stop", "Stop the server") do |v|
|
38
|
+
options[:client_service] = :stop
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-l", "--list WHAT", [:groups, :teams, :services], "list (teams, services, groups)") do |v|
|
42
|
+
options[:client_service] = "list_#{v}".intern
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-T", "--stop-team ID", "Stop a team") do |v|
|
46
|
+
options[:client_service] = :stop_team
|
47
|
+
options[:team_id] = v.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-t", "--start-team ID", "Start a team") do |v|
|
51
|
+
options[:client_service] = :start_team
|
52
|
+
options[:team_id] = v.to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-G", "--stop-group A,B,C", Array, "Stop a group or an intersection of groups") do |v|
|
56
|
+
options[:client_service] = :stop_group
|
57
|
+
options[:groups] = v.collect{|g|g.intern}
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-g", "--start-group A,B,C", Array, "Start a group or an intersection of groups") do |v|
|
61
|
+
options[:client_service] = :start_group
|
62
|
+
options[:groups] = v.collect{|g|g.intern}
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on("-H", "--host HOST", "Server location") do |v|
|
66
|
+
options[:host] = v
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on_tail("-h", "--help", "Show this message") do |v|
|
70
|
+
puts opts
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
@opts.parse!
|
76
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument
|
77
|
+
puts @opts
|
78
|
+
exit(1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def start_drb_client
|
82
|
+
DRb.start_service
|
83
|
+
@app = DRbObject.new_with_uri "druby://"+@options[:host]
|
84
|
+
end
|
85
|
+
|
86
|
+
def run!
|
87
|
+
begin
|
88
|
+
send "client_perform_#{@options[:client_service]}"
|
89
|
+
rescue Errno::ECONNREFUSED, DRb::DRbConnError => e
|
90
|
+
puts "Could not connect to host! (#{e.inspect})"
|
91
|
+
# puts @opts
|
92
|
+
exit(1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def client_perform_stop
|
97
|
+
begin
|
98
|
+
@app.stop!
|
99
|
+
rescue DRb::DRbConnError
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def client_perform_list_services
|
104
|
+
@app.services.keys.each do |name|
|
105
|
+
puts "- #{name}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def client_perform_list_teams
|
110
|
+
first = true
|
111
|
+
@app.team_controllers.each do |id, controller|
|
112
|
+
team = controller.team
|
113
|
+
service = team.service
|
114
|
+
puts unless first
|
115
|
+
first = false
|
116
|
+
puts "- #{service.name}[#{id}] (#{team.size} workers - #{controller.running? ? "running" : "stopped"})"
|
117
|
+
puts " hosts: #{team.hosts.join(', ')}"
|
118
|
+
puts " tubes: #{team.tubes.join(', ')}"
|
119
|
+
puts " groups: #{team.groups.join(', ')}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def client_perform_list_groups
|
124
|
+
@app.groups.keys.each do |name|
|
125
|
+
puts "- #{name}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def client_perform_stop_team
|
130
|
+
@app.team_controllers[@options[:team_id]].stop
|
131
|
+
end
|
132
|
+
|
133
|
+
def client_perform_start_team
|
134
|
+
@app.team_controllers[@options[:team_id]].start
|
135
|
+
end
|
136
|
+
|
137
|
+
def client_perform_start_group
|
138
|
+
group = @options[:groups].inject(nil) do |m, g|
|
139
|
+
m = m.union(@app.groups[g]) unless m.nil?
|
140
|
+
m ||= @app.groups[g]
|
141
|
+
end
|
142
|
+
group.start
|
143
|
+
end
|
144
|
+
|
145
|
+
def client_perform_stop_group
|
146
|
+
group = @options[:groups].inject(nil) do |m, g|
|
147
|
+
m = m.union(@app.groups[g]) unless m.nil?
|
148
|
+
m ||= @app.groups[g]
|
149
|
+
end
|
150
|
+
group.stop
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
|
3
|
+
class DSL
|
4
|
+
|
5
|
+
def team()
|
6
|
+
builder = BackgroundServices::DSL::TeamBuilder.new
|
7
|
+
yield(builder)
|
8
|
+
BackgroundServices::Server.register_team(builder.build)
|
9
|
+
end
|
10
|
+
|
11
|
+
def service(name, klass_or_module=nil, &block)
|
12
|
+
service = BackgroundServices::Service.new(name, klass_or_module || block)
|
13
|
+
BackgroundServices::Server.register_service(service)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load(*paths)
|
17
|
+
paths.collect do |path|
|
18
|
+
Dir.glob(File.expand_path(path)).each do |realpath|
|
19
|
+
if File.exists?(realpath)
|
20
|
+
code = File.read(realpath)
|
21
|
+
Dir.chdir(File.dirname(realpath)) do
|
22
|
+
dsl = self.new
|
23
|
+
dsl.instance_eval(code, realpath)
|
24
|
+
dsl
|
25
|
+
end
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TeamBuilder
|
34
|
+
def initialize
|
35
|
+
@hosts = Array.new
|
36
|
+
@groups = Array.new
|
37
|
+
@tubes = Array.new
|
38
|
+
@service = nil
|
39
|
+
@size = 2
|
40
|
+
end
|
41
|
+
def host(*hosts)
|
42
|
+
@hosts += hosts
|
43
|
+
end
|
44
|
+
def group(*groups)
|
45
|
+
@groups += groups
|
46
|
+
end
|
47
|
+
def tube(*tubes)
|
48
|
+
@tubes = tubes
|
49
|
+
end
|
50
|
+
def service(name)
|
51
|
+
@service = name
|
52
|
+
end
|
53
|
+
def size(size)
|
54
|
+
@size = size
|
55
|
+
end
|
56
|
+
|
57
|
+
def build
|
58
|
+
raise "Missing service" if @service == nil
|
59
|
+
raise "Must have at lease one host" if @hosts.empty?
|
60
|
+
BackgroundServices::Team.new(@service, @size, @tubes, @groups, @hosts)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class Group
|
3
|
+
|
4
|
+
attr_accessor :name, :teams
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name, @teams = name, {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
@teams.each do |k, team|
|
12
|
+
team.start
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop
|
17
|
+
@teams.each do |k, team|
|
18
|
+
team.stop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_team(team)
|
23
|
+
@teams[team.team_id] = team
|
24
|
+
end
|
25
|
+
|
26
|
+
def unregister_team(team)
|
27
|
+
@teams.delete(team.team_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def union(other)
|
31
|
+
group = Group.new("#{@name}_and_#{other.name}")
|
32
|
+
ids = @teams.keys & other.teams.keys
|
33
|
+
group.teams = ids.inject({}) do |m, id|
|
34
|
+
m[id] = @teams[id] || other.teams[id]
|
35
|
+
m
|
36
|
+
end
|
37
|
+
group
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
|
3
|
+
module Queue
|
4
|
+
|
5
|
+
class QueueError < Exception ; end
|
6
|
+
|
7
|
+
class Base
|
8
|
+
|
9
|
+
attr_accessor :configuration, :adapter
|
10
|
+
|
11
|
+
def synchronize(timeout=nil, &block)
|
12
|
+
@mutex ||= Mutex.new
|
13
|
+
unless timeout.nil?
|
14
|
+
user_block = block
|
15
|
+
block = Proc.new do
|
16
|
+
Timeout::timeout(timeout, &block)
|
17
|
+
end
|
18
|
+
@mutex.synchronize(&block)
|
19
|
+
rescue => e
|
20
|
+
raise QueueError
|
21
|
+
end
|
22
|
+
|
23
|
+
def process(&block)
|
24
|
+
job = synchronize(5) do
|
25
|
+
@adapter.dequeue
|
26
|
+
end
|
27
|
+
job.queue = self
|
28
|
+
job.before
|
29
|
+
yield job
|
30
|
+
job.after
|
31
|
+
rescue => e
|
32
|
+
job.recover unless job.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def connect
|
36
|
+
synchronize do
|
37
|
+
@adapter.connect(@configuration || {})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class Job
|
44
|
+
|
45
|
+
attr_accessor :queue
|
46
|
+
def synchronize(&block)
|
47
|
+
@queue.synchronize(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def before
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def after
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def recover
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def payload
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
|
3
|
+
module Queue
|
4
|
+
|
5
|
+
class BeanstalkdQueue < BackgroundServices::Queue::Base
|
6
|
+
|
7
|
+
def connect(configuration)
|
8
|
+
@connection = Beanstalk::Pool.new(configuration['hosts'])
|
9
|
+
|
10
|
+
tubes = configuration['tubes'] || []
|
11
|
+
unless tubes.empty?
|
12
|
+
tubes.each do |tube|
|
13
|
+
@connection.watch(tube)
|
14
|
+
end
|
15
|
+
@connection.ignore("default")
|
16
|
+
end
|
17
|
+
|
18
|
+
def dequeue
|
19
|
+
BeanstalkdJob.new(@connection.reserve)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class BeanstalkdJob < BackgroundServices::Queue::Job
|
25
|
+
|
26
|
+
attr_reader :private_job
|
27
|
+
|
28
|
+
def initialize(job)
|
29
|
+
@private_job=nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def payload
|
33
|
+
@private_job.body
|
34
|
+
end
|
35
|
+
|
36
|
+
def after
|
37
|
+
synchronize do
|
38
|
+
@private_job.delete
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def recover
|
43
|
+
synchronize do
|
44
|
+
@private_job.put_back
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class Server
|
3
|
+
|
4
|
+
include Singleton
|
5
|
+
include DRbUndumped
|
6
|
+
|
7
|
+
def self.load(*args)
|
8
|
+
include.load(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.logger
|
12
|
+
instance.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.setup
|
16
|
+
instance.setup
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.run!
|
20
|
+
instance.run!
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.stop!
|
24
|
+
instance.stop!
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.register_team(team)
|
28
|
+
instance.register_team(team)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.register_service(service)
|
32
|
+
instance.register_service(service)
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :logger
|
36
|
+
attr_accessor :options
|
37
|
+
attr_accessor :team_controllers
|
38
|
+
attr_accessor :services
|
39
|
+
attr_accessor :groups
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@services = Hash.new
|
43
|
+
@team_controllers = Hash.new
|
44
|
+
@groups = Hash.new { |h, k| h[k] = BackgroundServices::Group.new(k) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def setup
|
48
|
+
parse_options
|
49
|
+
damonize
|
50
|
+
setup_logger
|
51
|
+
load_files
|
52
|
+
register_signal_handlers
|
53
|
+
start_drb_server
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_options
|
57
|
+
@options = {
|
58
|
+
:log => STDOUT,
|
59
|
+
:level => Logger::INFO,
|
60
|
+
:files => [],
|
61
|
+
:run_server => true,
|
62
|
+
:host => "127.0.0.1:5000",
|
63
|
+
:daemonize => false
|
64
|
+
}
|
65
|
+
@opts = OptionParser.new do |opts|
|
66
|
+
opts.banner = "Usage: #{File.basename($0)} [options]"
|
67
|
+
opts.separator ""
|
68
|
+
opts.separator "Server options: "
|
69
|
+
|
70
|
+
opts.on("-c", "--config CONFIG_FILE", "Path to a configuration file") do |v|
|
71
|
+
options[:files] << File.expand_path(v)
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on("-L", "--log LOG_FILE", "Path to the log file") do |v|
|
75
|
+
options[:log] = File.expand_path(v)
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("-d", "--daemonize", "Run in the background") do |v|
|
79
|
+
options[:daemonize] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on("-H", "--host HOST", "Server location") do |v|
|
83
|
+
options[:host] = v
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on("--debug", "Run in debugging mode") do |v|
|
87
|
+
options[:level] = Logger::DEBUG
|
88
|
+
require 'ruby-debug'
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on_tail("-h", "--help", "Show this message") do |v|
|
92
|
+
puts opts
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
@opts.parse!
|
98
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument
|
99
|
+
puts @opts
|
100
|
+
exit(1)
|
101
|
+
end
|
102
|
+
|
103
|
+
def setup_logger
|
104
|
+
@logger = Logger.new(options[:log])
|
105
|
+
@logger.level = options[:level]
|
106
|
+
end
|
107
|
+
|
108
|
+
def load_files
|
109
|
+
@options[:files].each do |file|
|
110
|
+
DSL.load(file)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def start_drb_server
|
115
|
+
DRb.start_service "druby://"+@options[:host], self
|
116
|
+
puts "listening on: #{DRb.uri}"
|
117
|
+
end
|
118
|
+
|
119
|
+
def damonize
|
120
|
+
Daemons.daemonize if options[:daemonize]
|
121
|
+
end
|
122
|
+
|
123
|
+
def register_signal_handlers
|
124
|
+
terminator = Proc.new do
|
125
|
+
BackgroundServices::Server.stop!
|
126
|
+
end
|
127
|
+
Signal.trap('TERM', terminator)
|
128
|
+
Signal.trap('QUIT', terminator)
|
129
|
+
Signal.trap('INT', terminator)
|
130
|
+
Signal.trap("CHLD") do
|
131
|
+
@team_controllers.each do |id, controller|
|
132
|
+
controller.check_process
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def register_team(team)
|
138
|
+
@logger.debug "Registered team: #{team.inspect}"
|
139
|
+
@team_controllers[team.id] = BackgroundServices::TeamController.new(team)
|
140
|
+
end
|
141
|
+
|
142
|
+
def register_service(service)
|
143
|
+
@logger.debug "Registered service: #{service.name}"
|
144
|
+
@services[service.name] = service
|
145
|
+
end
|
146
|
+
|
147
|
+
def run!
|
148
|
+
@team_controllers.each do |id, controller|
|
149
|
+
controller.start unless controller.running?
|
150
|
+
end
|
151
|
+
Process.waitall
|
152
|
+
sleep(0.5) until @stopped
|
153
|
+
end
|
154
|
+
|
155
|
+
def stop!
|
156
|
+
@team_controllers.each do |id, controller|
|
157
|
+
controller.stop if controller.running?
|
158
|
+
end
|
159
|
+
DRb.stop_service
|
160
|
+
@stopped = true
|
161
|
+
end
|
162
|
+
|
163
|
+
def next_team_id
|
164
|
+
@next_team_id ||= 0
|
165
|
+
@next_team_id += 1
|
166
|
+
end
|
167
|
+
|
168
|
+
def client_perform_stop
|
169
|
+
begin
|
170
|
+
@app.stop!
|
171
|
+
rescue DRb::DRbConnError
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def client_perform_list_services
|
176
|
+
@app.services.keys.each do |name|
|
177
|
+
puts "- #{name}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def client_perform_list_teams
|
182
|
+
first = true
|
183
|
+
@app.team_controllers.each do |id, controller|
|
184
|
+
team = controller.team
|
185
|
+
service = team.service
|
186
|
+
puts unless first
|
187
|
+
first = false
|
188
|
+
puts "- #{service.name}[#{id}] (#{team.size} workers - #{controller.running? ? "running" : "stopped"})"
|
189
|
+
puts " hosts: #{team.hosts.join(', ')}"
|
190
|
+
puts " tubes: #{team.tubes.join(', ')}"
|
191
|
+
puts " groups: #{team.groups.join(', ')}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def client_perform_list_groups
|
196
|
+
@app.groups.keys.each do |name|
|
197
|
+
puts "- #{name}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def client_perform_stop_team
|
202
|
+
@app.team_controllers[@options[:team_id]].stop
|
203
|
+
end
|
204
|
+
|
205
|
+
def client_perform_start_team
|
206
|
+
@app.team_controllers[@options[:team_id]].start
|
207
|
+
end
|
208
|
+
|
209
|
+
def client_perform_start_group
|
210
|
+
group = @options[:groups].inject(nil) do |m, g|
|
211
|
+
m = m.union(@app.groups[g]) unless m.nil?
|
212
|
+
m ||= @app.groups[g]
|
213
|
+
end
|
214
|
+
group.start
|
215
|
+
end
|
216
|
+
|
217
|
+
def client_perform_stop_group
|
218
|
+
group = @options[:groups].inject(nil) do |m, g|
|
219
|
+
m = m.union(@app.groups[g]) unless m.nil?
|
220
|
+
m ||= @app.groups[g]
|
221
|
+
end
|
222
|
+
group.stop
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class Service
|
3
|
+
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
def initialize(name, implementation)
|
7
|
+
@name, @implementation = name, implementation
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
case @implementation
|
12
|
+
when Class then @implementation.send :start if @implementation.respond_to? :start
|
13
|
+
when Module then @implementation.send :start if @implementation.respond_to? :start
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop
|
18
|
+
case @implementation
|
19
|
+
when Class then @implementation.send :stop if @implementation.respond_to? :stop
|
20
|
+
when Module then @implementation.send :stop if @implementation.respond_to? :stop
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_worker(worker)
|
25
|
+
case @implementation
|
26
|
+
when Class
|
27
|
+
worker.implementation = @implementation.new
|
28
|
+
worker.implementation.send :start_worker if worker.implementation.respond_to? :start_worker
|
29
|
+
when Module
|
30
|
+
worker.implementation = @implementation
|
31
|
+
worker.implementation.send :start_worker if worker.implementation.respond_to? :start_worker
|
32
|
+
when Proc
|
33
|
+
worker.implementation = @implementation
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop_worker(worker)
|
38
|
+
case @implementation
|
39
|
+
when Class
|
40
|
+
worker.implementation.send :stop_worker if worker.implementation.respond_to? :stop_worker
|
41
|
+
worker.implementation = nil
|
42
|
+
when Module
|
43
|
+
worker.implementation.send :stop_worker if worker.implementation.respond_to? :stop_worker
|
44
|
+
worker.implementation = nil
|
45
|
+
when Proc
|
46
|
+
worker.implementation = nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_job(worker, job)
|
51
|
+
case @implementation
|
52
|
+
when Class
|
53
|
+
raise "must respond to #process_job" unless worker.implementation.respond_to? :process_job
|
54
|
+
worker.implementation.send :process_job, job
|
55
|
+
when Module
|
56
|
+
raise "must respond to #process_job" unless worker.implementation.respond_to? :process_job
|
57
|
+
worker.implementation.send :process_job, job
|
58
|
+
when Proc
|
59
|
+
worker.implementation.call job
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class Team
|
3
|
+
def synchronize(&block)
|
4
|
+
@mutex ||= Mutex.new
|
5
|
+
@mutex.synchronize(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :id
|
9
|
+
attr_accessor :service, :size, :tubes, :groups, :hosts
|
10
|
+
attr_accessor :workers
|
11
|
+
|
12
|
+
def initialize(service, size, tubes, groups, hosts)
|
13
|
+
@id = BackgroundServices::Server.instance.next_team_id
|
14
|
+
@service, @size, @tubes, @groups, @hosts = service, size, tubes, groups, hosts
|
15
|
+
@tubes ||= Array.new
|
16
|
+
@groups ||= Array.new
|
17
|
+
@workers = Array.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def bind_service
|
21
|
+
return if @service.is_a?(BackgroundServices::Service)
|
22
|
+
@service = BackgroundServices::Server.instance.services[@service]
|
23
|
+
end
|
24
|
+
|
25
|
+
def full?
|
26
|
+
@workers.size == @size
|
27
|
+
end
|
28
|
+
|
29
|
+
def empty?
|
30
|
+
@workers.size == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def start
|
34
|
+
@running = true
|
35
|
+
@service.start
|
36
|
+
until full?
|
37
|
+
worker = Worker.new(@service, self)
|
38
|
+
@workers << worker
|
39
|
+
@service.start_worker(worker)
|
40
|
+
worker.start
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
@running = false
|
46
|
+
conn = @connection
|
47
|
+
@connection = nil
|
48
|
+
conn.close unless conn.nil?
|
49
|
+
@workers.each do |worker|
|
50
|
+
worker.stop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def join
|
55
|
+
@workers.each do |worker|
|
56
|
+
worker.join
|
57
|
+
@service.stop_worker(worker)
|
58
|
+
end
|
59
|
+
@service.stop
|
60
|
+
@workers = Array.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def receive_job
|
64
|
+
with_connection do
|
65
|
+
@connection.reserve(1)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def open_connection
|
70
|
+
@connection = Beanstalk::Pool.new(@hosts)
|
71
|
+
unless @tubes.empty?
|
72
|
+
@tubes.each do |tube|
|
73
|
+
@connection.watch(tube)
|
74
|
+
end
|
75
|
+
@connection.ignore("default")
|
76
|
+
end
|
77
|
+
rescue
|
78
|
+
@connection = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def with_connection
|
82
|
+
synchronize do
|
83
|
+
r = nil
|
84
|
+
begin
|
85
|
+
raise Beanstalk::NotConnected if @connection.nil?
|
86
|
+
r = yield
|
87
|
+
rescue IOError , Errno::ECONNREFUSED, Beanstalk::NotConnected
|
88
|
+
sleep(1) and open_connection and retry if @running
|
89
|
+
rescue Beanstalk::TimedOut
|
90
|
+
r = nil
|
91
|
+
end
|
92
|
+
r
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class TeamController
|
3
|
+
|
4
|
+
include DRbUndumped
|
5
|
+
|
6
|
+
attr_accessor :team
|
7
|
+
|
8
|
+
def initialize(team)
|
9
|
+
@team = team
|
10
|
+
end
|
11
|
+
|
12
|
+
def running?
|
13
|
+
@running || false
|
14
|
+
end
|
15
|
+
|
16
|
+
def team_id
|
17
|
+
@team.id
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_process
|
21
|
+
return unless running?
|
22
|
+
pid, status = Process.waitpid2(@pid, Process::WNOHANG)
|
23
|
+
if !status.nil? and status.exited?
|
24
|
+
@running = false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
return if running?
|
30
|
+
@team.bind_service
|
31
|
+
@team.groups.each do |group|
|
32
|
+
BackgroundServices::Server.instance.groups[group].register_team(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
BackgroundServices::Server.logger.debug "will start team"
|
36
|
+
@pid = fork do
|
37
|
+
Signal.trap("TERM") do
|
38
|
+
BackgroundServices::Server.logger.debug "will stop team"
|
39
|
+
@team.stop
|
40
|
+
end
|
41
|
+
@team.start
|
42
|
+
BackgroundServices::Server.logger.debug "did start team"
|
43
|
+
@team.join
|
44
|
+
BackgroundServices::Server.logger.debug "did stop team"
|
45
|
+
exit!
|
46
|
+
end
|
47
|
+
@running = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop
|
51
|
+
return unless running?
|
52
|
+
begin
|
53
|
+
Process.kill("TERM", @pid)
|
54
|
+
Process.waitpid(@pid, 0)
|
55
|
+
rescue Errno::ECHILD, Errno::ESRCH
|
56
|
+
# already dead
|
57
|
+
ensure
|
58
|
+
@running = false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module BackgroundServices
|
2
|
+
class Worker
|
3
|
+
attr_reader :team, :service
|
4
|
+
attr_accessor :implementation
|
5
|
+
|
6
|
+
def initialize(service, team)
|
7
|
+
@team = team
|
8
|
+
@service = service
|
9
|
+
@implementation = nil
|
10
|
+
end
|
11
|
+
def working?
|
12
|
+
@working == true
|
13
|
+
end
|
14
|
+
def stop
|
15
|
+
@working = false
|
16
|
+
end
|
17
|
+
def join
|
18
|
+
@thread.join
|
19
|
+
end
|
20
|
+
def start
|
21
|
+
@working = true
|
22
|
+
@thread = Thread.new(self) do |this|
|
23
|
+
|
24
|
+
begin
|
25
|
+
while this.working?
|
26
|
+
job = this.team.receive_job
|
27
|
+
unless job.nil?
|
28
|
+
App.logger.info "processing job with id: #{job.id}"
|
29
|
+
this.service.process_job this, job
|
30
|
+
this.team.with_connection { job.delete }
|
31
|
+
else
|
32
|
+
sleep 0.5 if this.working?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue => e
|
36
|
+
puts e.inspect
|
37
|
+
puts e.backtrace
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/bs.rb
ADDED
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simonmenke-background_services
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Menke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-13 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: beanstalk-client
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.2
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: daemons
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.0.10
|
32
|
+
version:
|
33
|
+
description:
|
34
|
+
email: simon.menke@gmail.com
|
35
|
+
executables:
|
36
|
+
- background_services
|
37
|
+
- background_servicesd
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- bin/background_services
|
44
|
+
- bin/background_servicesd
|
45
|
+
- lib/background_services/client.rb
|
46
|
+
- lib/background_services/dsl.rb
|
47
|
+
- lib/background_services/group.rb
|
48
|
+
- lib/background_services/queue/base.rb
|
49
|
+
- lib/background_services/queue/beanstalkd.rb
|
50
|
+
- lib/background_services/queue.rb
|
51
|
+
- lib/background_services/server.rb
|
52
|
+
- lib/background_services/service.rb
|
53
|
+
- lib/background_services/team.rb
|
54
|
+
- lib/background_services/team_controller.rb
|
55
|
+
- lib/background_services/version.rb
|
56
|
+
- lib/background_services/worker.rb
|
57
|
+
- lib/background_services.rb
|
58
|
+
- lib/bs.rb
|
59
|
+
- README.textile
|
60
|
+
has_rdoc: true
|
61
|
+
homepage: http://github.com/simonmenke
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: "0"
|
78
|
+
version:
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project: background_services
|
82
|
+
rubygems_version: 1.2.0
|
83
|
+
signing_key:
|
84
|
+
specification_version: 2
|
85
|
+
summary: Manage larger sets of background workers.
|
86
|
+
test_files: []
|
87
|
+
|