chapman 0.0.1
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +4 -0
- data/Rakefile +2 -0
- data/chapman.gemspec +17 -0
- data/lib/chapman/exceptions.rb +8 -0
- data/lib/chapman/version.rb +3 -0
- data/lib/chapman/worker.rb +162 -0
- data/lib/chapman.rb +123 -0
- metadata +58 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/chapman.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/chapman/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Tyler Flint", 'Lyon Hill']
|
6
|
+
gem.email = ["tylerflint@gmail.com", 'lyondhill@gmail.com']
|
7
|
+
gem.description = %q{Like stalker, but can stalk concurrently}
|
8
|
+
gem.summary = %q{Takes the concept of stalker and introduces a thread pool. It's great for scenarios where tasks are light and mostly IO bound.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "chapman"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Chapman::VERSION
|
17
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'beanstalk-client'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
module Chapman
|
7
|
+
class Worker
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@handlers = Chapman.job_handlers
|
11
|
+
@before_handlers = Chapman.before_handlers
|
12
|
+
@error_handler = Chapman.error_handler
|
13
|
+
end
|
14
|
+
|
15
|
+
def job_in_progress?
|
16
|
+
@in_progress ||= false
|
17
|
+
end
|
18
|
+
|
19
|
+
def prep(jobs=nil)
|
20
|
+
raise Chapman::Exceptions::NoJobsDefined unless defined?(@handlers)
|
21
|
+
@error_handler = nil unless defined?(@error_handler)
|
22
|
+
|
23
|
+
jobs ||= all_jobs
|
24
|
+
|
25
|
+
jobs.each do |job|
|
26
|
+
raise(Chapman::Exceptions::NoSuchJob, job) unless @handlers[job]
|
27
|
+
end
|
28
|
+
|
29
|
+
log "Working #{jobs.size} jobs: [ #{jobs.join(' ')} ]"
|
30
|
+
|
31
|
+
jobs.each { |job| beanstalk.watch(job) }
|
32
|
+
|
33
|
+
beanstalk.list_tubes_watched.each do |server, tubes|
|
34
|
+
tubes.each { |tube| beanstalk.ignore(tube) unless jobs.include?(tube) }
|
35
|
+
end
|
36
|
+
rescue Beanstalk::NotConnected => e
|
37
|
+
failed_connection(e)
|
38
|
+
end
|
39
|
+
|
40
|
+
def work(jobs=nil)
|
41
|
+
prep(jobs)
|
42
|
+
while not Chapman.soft_quit?
|
43
|
+
work_one_job
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def work_one_job
|
48
|
+
return if Chapman.soft_quit? # just to be safe
|
49
|
+
job = beanstalk.reserve
|
50
|
+
|
51
|
+
@in_progress = true
|
52
|
+
|
53
|
+
name, args = JSON.parse job.body
|
54
|
+
log_job_begin(name, args)
|
55
|
+
|
56
|
+
handler = @handlers[name]
|
57
|
+
raise(Chapman::Exceptions::NoSuchJob, name) unless handler
|
58
|
+
|
59
|
+
begin
|
60
|
+
if defined? @before_handlers and @before_handlers.respond_to? :each
|
61
|
+
@before_handlers.each do |block|
|
62
|
+
block.call(name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
handler.call(args)
|
66
|
+
end
|
67
|
+
|
68
|
+
job.delete
|
69
|
+
|
70
|
+
@in_progress = false
|
71
|
+
log_job_end(name)
|
72
|
+
rescue Beanstalk::NotConnected => e
|
73
|
+
failed_connection(e)
|
74
|
+
rescue SystemExit
|
75
|
+
raise
|
76
|
+
rescue => e
|
77
|
+
log_error exception_message(e)
|
78
|
+
job.bury rescue nil
|
79
|
+
log_job_end(name, 'failed') if @job_begun
|
80
|
+
if error_handler
|
81
|
+
if error_handler.arity == 1
|
82
|
+
error_handler.call(e)
|
83
|
+
else
|
84
|
+
error_handler.call(e, name, args)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def failed_connection(e)
|
90
|
+
log_error exception_message(e)
|
91
|
+
log_error "*** Failed connection to #{beanstalk_url}"
|
92
|
+
log_error "*** Check that beanstalkd is running (or set a different BEANSTALK_URL)"
|
93
|
+
exit 1
|
94
|
+
end
|
95
|
+
|
96
|
+
def log_job_begin(name, args)
|
97
|
+
args_flat = unless args.empty?
|
98
|
+
'(' + args.inject([]) do |accum, (key,value)|
|
99
|
+
accum << "#{key}=#{value}"
|
100
|
+
end.join(' ') + ')'
|
101
|
+
else
|
102
|
+
''
|
103
|
+
end
|
104
|
+
|
105
|
+
log [ "Working", name, args_flat ].join(' ')
|
106
|
+
@job_begun = Time.now
|
107
|
+
end
|
108
|
+
|
109
|
+
def log_job_end(name, failed=false)
|
110
|
+
ellapsed = Time.now - @job_begun
|
111
|
+
ms = (ellapsed.to_f * 1000).to_i
|
112
|
+
log "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def log(msg)
|
116
|
+
puts msg
|
117
|
+
end
|
118
|
+
|
119
|
+
def log_error(msg)
|
120
|
+
STDERR.puts msg
|
121
|
+
end
|
122
|
+
|
123
|
+
def beanstalk
|
124
|
+
@beanstalk ||= Beanstalk::Pool.new(beanstalk_addresses)
|
125
|
+
end
|
126
|
+
|
127
|
+
def beanstalk_url
|
128
|
+
Chapman.beanstalk_url
|
129
|
+
end
|
130
|
+
|
131
|
+
def beanstalk_addresses
|
132
|
+
uris = beanstalk_url.split(/[\s,]+/)
|
133
|
+
uris.map {|uri| beanstalk_host_and_port(uri)}
|
134
|
+
end
|
135
|
+
|
136
|
+
def beanstalk_host_and_port(uri_string)
|
137
|
+
uri = URI.parse(uri_string)
|
138
|
+
raise(Chapman::Exceptions::BadURL, uri_string) if uri.scheme != 'beanstalk'
|
139
|
+
"#{uri.host}:#{uri.port || 11300}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def exception_message(e)
|
143
|
+
msg = [ "Exception #{e.class} -> #{e.message}" ]
|
144
|
+
|
145
|
+
base = File.expand_path(Dir.pwd) + '/'
|
146
|
+
e.backtrace.each do |t|
|
147
|
+
msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
|
148
|
+
end
|
149
|
+
|
150
|
+
msg.join("\n")
|
151
|
+
end
|
152
|
+
|
153
|
+
def all_jobs
|
154
|
+
@handlers.keys
|
155
|
+
end
|
156
|
+
|
157
|
+
def error_handler
|
158
|
+
@error_handler
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
data/lib/chapman.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'chapman/version'
|
2
|
+
require 'chapman/exceptions'
|
3
|
+
require 'chapman/worker'
|
4
|
+
|
5
|
+
STDOUT.sync = true
|
6
|
+
|
7
|
+
module Chapman
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def job(j, &block)
|
11
|
+
@@handlers ||= {}
|
12
|
+
@@handlers[j] = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def before(&block)
|
16
|
+
@@before_handlers ||= []
|
17
|
+
@@before_handlers << block
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(&blk)
|
21
|
+
@@error_handler = blk
|
22
|
+
end
|
23
|
+
|
24
|
+
def running
|
25
|
+
@@running ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def soft_quit?
|
29
|
+
@@soft_quit ||= false
|
30
|
+
end
|
31
|
+
|
32
|
+
def soft_quit=(soft_quit)
|
33
|
+
@@soft_quit = soft_quit
|
34
|
+
end
|
35
|
+
|
36
|
+
def work(jobs=nil, thread_count=1)
|
37
|
+
|
38
|
+
# start a worker thread
|
39
|
+
thread_count.times do
|
40
|
+
w = Chapman::Worker.new()
|
41
|
+
t = Thread.new { w.work(jobs) }
|
42
|
+
running << {thread: t, worker: w}
|
43
|
+
end
|
44
|
+
|
45
|
+
# keep them alive
|
46
|
+
while not soft_quit?
|
47
|
+
maintain_workers
|
48
|
+
sleep 1
|
49
|
+
end
|
50
|
+
|
51
|
+
murder_workers!
|
52
|
+
|
53
|
+
reap_workers
|
54
|
+
|
55
|
+
log "SEPPUKU!!"
|
56
|
+
end
|
57
|
+
|
58
|
+
def maintain_workers
|
59
|
+
running.each_with_index do |runner, index|
|
60
|
+
if not runner[:thread].alive?
|
61
|
+
w = Creeper::Worker.new()
|
62
|
+
t = Thread.new do
|
63
|
+
w.work(jobs)
|
64
|
+
end
|
65
|
+
running[index] = {thread: t, worker: w}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def murder_workers!
|
71
|
+
running.each do |runner|
|
72
|
+
if runner[:worker].job_in_progress?
|
73
|
+
log "Murder [scheduling]"
|
74
|
+
else
|
75
|
+
log "Murder [now]"
|
76
|
+
runner[:thread].kill
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def reap_workers
|
82
|
+
running.each do |runner|
|
83
|
+
runner[:thread].join
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def log(msg)
|
88
|
+
puts msg
|
89
|
+
end
|
90
|
+
|
91
|
+
def log_error(msg)
|
92
|
+
STDERR.puts msg
|
93
|
+
end
|
94
|
+
|
95
|
+
def beanstalk_url
|
96
|
+
return @@url if defined?(@@url) and @@url
|
97
|
+
ENV['BEANSTALK_URL'] || 'beanstalk://localhost/'
|
98
|
+
end
|
99
|
+
|
100
|
+
def all_jobs
|
101
|
+
@@handlers.keys
|
102
|
+
end
|
103
|
+
|
104
|
+
def job_handlers
|
105
|
+
@@handlers ||= {}
|
106
|
+
end
|
107
|
+
|
108
|
+
def before_handlers
|
109
|
+
@@before_handlers ||= []
|
110
|
+
end
|
111
|
+
|
112
|
+
def error_handler
|
113
|
+
@@error_handler ||= nil
|
114
|
+
end
|
115
|
+
|
116
|
+
def reset!
|
117
|
+
@@soft_quit = false
|
118
|
+
@@running = []
|
119
|
+
@@handlers = nil
|
120
|
+
@@before_handlers = nil
|
121
|
+
@@error_handler = nil
|
122
|
+
end
|
123
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chapman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tyler Flint
|
9
|
+
- Lyon Hill
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-05-26 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description: Like stalker, but can stalk concurrently
|
16
|
+
email:
|
17
|
+
- tylerflint@gmail.com
|
18
|
+
- lyondhill@gmail.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- .gitignore
|
24
|
+
- Gemfile
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- chapman.gemspec
|
28
|
+
- lib/chapman.rb
|
29
|
+
- lib/chapman/exceptions.rb
|
30
|
+
- lib/chapman/version.rb
|
31
|
+
- lib/chapman/worker.rb
|
32
|
+
homepage: ''
|
33
|
+
licenses: []
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.20
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: Takes the concept of stalker and introduces a thread pool. It's great for
|
56
|
+
scenarios where tasks are light and mostly IO bound.
|
57
|
+
test_files: []
|
58
|
+
has_rdoc:
|