simonmenke-background_services 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|