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 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
+