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 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,8 @@
1
+ module BackgroundServices
2
+
3
+ module Queue
4
+
5
+
6
+ end
7
+
8
+ 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,9 @@
1
+ module BackgroundServices
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+
2
+ module BS
3
+ def self.load(*args)
4
+ BackgroundServices::DSL.load(*args)
5
+ end
6
+ def self.logger
7
+ BackgroundServices::Server.logger
8
+ end
9
+ end
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
+