creeper 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,14 +2,18 @@
2
2
 
3
3
  Can be used as an in place drop in for stalker but it is multi threaded so you can easily do more without using more memory
4
4
 
5
+ *Note*: Creeper requires Ruby 1.9
6
+
5
7
  Creeper - an improvement on Stalker
6
- ==========================================
8
+ ===================================
7
9
 
8
10
  The big difference is how you "work" jobs
9
11
 
10
- all you need is a thread count arguement :)
12
+ all you need is a runner (thread) count argument :)
11
13
 
12
- Creeper.work(<jobs>, <thread_count>)
14
+ ```
15
+ Creeper.work([<job>, ...], <runner_count>)
16
+ ```
13
17
 
14
18
  [Beanstalkd](http://kr.github.com/beanstalkd/) is a fast, lightweight queueing backend inspired by mmemcached.
15
19
 
@@ -18,62 +22,81 @@ Queueing jobs
18
22
 
19
23
  From anywhere in your app:
20
24
 
21
- require 'creeper'
25
+ ```ruby
26
+ require 'creeper'
22
27
 
23
- Creeper.enqueue('email.send', :to => 'joe@example.com')
24
- Creeper.enqueue('post.cleanup.all')
25
- Creeper.enqueue('post.cleanup', :id => post.id)
28
+ Creeper.enqueue('email.send', :to => 'joe@example.com')
29
+ Creeper.enqueue('post.cleanup.all')
30
+ Creeper.enqueue('post.cleanup', :id => post.id)
31
+ ```
26
32
 
27
33
  Working jobs
28
34
  ------------
29
35
 
30
36
  In a standalone file, typically jobs.rb or worker.rb:
31
37
 
32
- require 'creeper'
33
- include Creeper
38
+ ```ruby
39
+ require 'creeper'
40
+ include Creeper::Creep
41
+
42
+ job 'email.send' do |args|
43
+ Pony.send(:to => args['to'], :subject => "Hello there")
44
+ end
45
+
46
+ job 'post.cleanup.all' do |args|
47
+ Post.all.each do |post|
48
+ enqueue('post.cleanup', :id => post.id)
49
+ end
50
+ end
51
+
52
+ job 'post.cleanup' do |args|
53
+ Post.find(args['id']).cleanup
54
+ end
34
55
 
35
- job 'email.send' do |args|
36
- Pony.send(:to => args['to'], :subject => "Hello there")
37
- end
56
+ # All of these Creeper.work calls are equivalent:
38
57
 
39
- job 'post.cleanup.all' do |args|
40
- Post.all.each do |post|
41
- enqueue('post.cleanup', :id => post.id)
42
- end
43
- end
58
+ Creeper.work(:all, 3) # work all jobs, 3 threads
59
+ Creeper.work(nil, 3) # same as previous line
60
+ Creeper.work([ 'email.send', 'post.cleanup.all', 'post.cleanup' ], 3) # same as previous line
44
61
 
45
- job 'post.cleanup' do |args|
46
- Post.find(args['id']).cleanup
47
- end
48
-
49
- Creeper.work(<jobs>, <thread_count>)
62
+ # Here we work just one job:
63
+ Creeper.work('email.send', 5) # work 'email.send', 5 threads
64
+ ```
50
65
 
51
66
  Running
52
67
  -------
53
68
 
54
69
  First, make sure you have Beanstalkd installed and running:
55
70
 
56
- $ sudo brew install beanstalkd
57
- $ beanstalkd
71
+ ```bash
72
+ $ sudo brew install beanstalkd
73
+ $ beanstalkd
74
+ ```
58
75
 
59
76
  Creeper:
60
77
 
61
- $ sudo gem install creeper
78
+ ```bash
79
+ $ sudo gem install creeper
80
+ ```
62
81
 
63
82
  Error Handling
64
83
  -------------
65
84
 
66
85
  If you include an `error` block in your jobs definition, that block will be invoked when a worker encounters an error. You might use this to report errors to an external monitoring service:
67
86
 
68
- error do |e, job, args|
69
- Exceptional.handle(e)
70
- end
87
+ ```ruby
88
+ error do |e, job, args|
89
+ Exceptional.handle(e)
90
+ end
91
+ ```
71
92
 
72
93
  Before filter
73
94
  -------------
74
95
 
75
96
  If you wish to run a block of code prior to any job:
76
97
 
77
- before do |job|
78
- puts "About to work #{job}"
79
- end
98
+ ```ruby
99
+ before do |job|
100
+ puts "About to work #{job}"
101
+ end
102
+ ```
data/bin/creeper ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'creeper/launcher'
6
+ require 'optparse'
7
+
8
+ options = { runner_count: 1 }
9
+
10
+ op = OptionParser.new("", 24, ' ') do |opts|
11
+ cmd = File.basename($0)
12
+ opts.banner = "Usage: #{cmd} [ruby options] [#{cmd} options]"
13
+
14
+ opts.separator "Ruby options:"
15
+
16
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
17
+ $DEBUG = true
18
+ end
19
+
20
+ opts.separator "#{cmd} options:"
21
+
22
+ opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
23
+ options[:daemonize] = !!d
24
+ end
25
+
26
+ opts.on("-j", "--job-file FILE", "Creeper-specific job file") do |f|
27
+ options[:job_file] = f
28
+ end
29
+
30
+ opts.on("-r", "--runner-count NUMBER", "Threads to run (default: #{options[:runner_count]})") do |r|
31
+ options[:runner_count] = (r.to_i rescue 1)
32
+ end
33
+
34
+ opts.separator "Common options:"
35
+
36
+ opts.on_tail("-h", "--help", "Show this message") do
37
+ puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
38
+ exit
39
+ end
40
+
41
+ opts.on_tail("-v", "--version", "Show version") do
42
+ puts "#{cmd} v#{Creeper::VERSION}"
43
+ exit
44
+ end
45
+
46
+ opts.parse! ARGV
47
+
48
+ end
49
+
50
+ unless options[:job_file]
51
+ $stderr.puts "ERROR: job file required", ''
52
+ puts op.to_s.gsub(/^.*DEPRECATED.*$/s, '')
53
+ exit 1
54
+ end
55
+
56
+ if $DEBUG
57
+ require 'pp'
58
+ pp(options)
59
+ end
60
+
61
+ Creeper::Launcher.launch!(options) if options[:daemonize]
62
+ Creeper.new(options).start.join
data/creeper.gemspec CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Creeper::VERSION
17
17
 
18
18
  gem.add_development_dependency 'pry'
19
+ gem.add_development_dependency 'rake'
19
20
  gem.add_development_dependency 'rspec'
20
21
  # gem.add_development_dependency 'stalker'
21
22
 
data/examples/jack.rb ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env creeper -j
2
+ #
3
+ # There are 2 ways to run this file:
4
+ # creeper -j jack.rb
5
+ # OR
6
+ # ./jack.rb
7
+ #
8
+ # If you remove the shebang, it can also be run with:
9
+ # ruby jack.rb
10
+
11
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib') unless $LOAD_PATH.include?(File.dirname(__FILE__) + '/../lib')
12
+
13
+ require 'creeper'
14
+
15
+ include Creeper::Creep
16
+
17
+ job('jack.work') do |*args|
18
+ Creeper.logger.info "[JACK.WORK] #{$0.inspect} #{args.inspect}"
19
+ end
20
+
21
+ Creeper.work(:all, 1) unless defined?(Creeper::Launcher)
data/lib/creeper.rb CHANGED
@@ -12,14 +12,10 @@ require 'kgio'
12
12
  require 'logger'
13
13
 
14
14
  require 'creeper/version'
15
+ require 'creeper/creep'
15
16
  require 'creeper/session'
16
17
  require 'creeper/worker'
17
18
 
18
- $stdout.sync = $stderr.sync = true
19
- $stdin.binmode
20
- $stdout.binmode
21
- $stderr.binmode
22
-
23
19
  module Creeper
24
20
 
25
21
  # These hashes map Threads to Workers
@@ -47,9 +43,10 @@ module Creeper
47
43
  end
48
44
 
49
45
  attr_accessor :logger, :error_logger
50
- attr_accessor :jobs, :patience, :runner_count, :soft_quit, :timeout
46
+ attr_accessor :job_file, :jobs, :patience, :ready_pipe, :runner_count, :soft_quit, :timeout
51
47
 
52
48
  extend self
49
+ extend Creeper::Creep
53
50
 
54
51
  ## utilities ##
55
52
 
@@ -74,6 +71,12 @@ module Creeper
74
71
 
75
72
  ### config ###
76
73
 
74
+ def job_file=(job_file)
75
+ (@job_file = job_file).tap do
76
+ require File.expand_path(job_file) if job_file
77
+ end
78
+ end
79
+
77
80
  def patience
78
81
  @patience ||= 60
79
82
  end
@@ -105,6 +108,14 @@ module Creeper
105
108
 
106
109
  ###
107
110
 
111
+ def new(options = {})
112
+ tap do
113
+ options.each do |key, value|
114
+ send("#{key}=", value) if respond_to?("#{key}=")
115
+ end
116
+ end
117
+ end
118
+
108
119
  def work(jobs = nil, runner_count = 1)
109
120
  self.jobs, self.runner_count = jobs, runner_count
110
121
 
@@ -134,6 +145,10 @@ module Creeper
134
145
 
135
146
  reset_proc_name
136
147
  logger.info "creeper process ready"
148
+ if ready_pipe
149
+ ready_pipe.syswrite($$.to_s)
150
+ ready_pipe = ready_pipe.close rescue nil
151
+ end
137
152
  begin
138
153
  reap_all_runners
139
154
  case SIG_QUEUE.shift
@@ -228,7 +243,6 @@ module Creeper
228
243
  GRAVEYARD[thread] = worker
229
244
  else
230
245
  logger.debug "creeper [murder] => hard quit" if $DEBUG
231
- worker.session.disconnect
232
246
  thread.kill
233
247
  thread.join
234
248
  end
@@ -268,6 +282,10 @@ module Creeper
268
282
  end
269
283
 
270
284
  def proc_name(tag)
285
+ if defined?(Navy) and defined?($officer)
286
+ Creeper::START_CTX.merge!(Navy::Admiral::START_CTX)
287
+ tag = "(#{$officer.captain.label}) officer[#{$officer.number}] #{tag}"
288
+ end
271
289
  $0 = ([
272
290
  File.basename(Creeper::START_CTX[0]),
273
291
  tag
@@ -276,31 +294,4 @@ module Creeper
276
294
 
277
295
  ##
278
296
 
279
- public
280
-
281
- def clear!
282
- @default_session.disconnect if @default_session
283
- @default_session = nil
284
- end
285
-
286
- def default_session
287
- @default_session ||= Creeper::Session.new
288
- end
289
-
290
- def enqueue(job, args = {}, opts = {})
291
- default_session.enqueue(job, args, opts)
292
- end
293
-
294
- def job(name, &block)
295
- default_session.job(name, &block)
296
- end
297
-
298
- def before(&block)
299
- default_session.before(&block)
300
- end
301
-
302
- def error(&block)
303
- default_session.error(&block)
304
- end
305
-
306
297
  end
@@ -0,0 +1,31 @@
1
+ module Creeper
2
+ module Creep
3
+
4
+ def clear!
5
+ Creeper.default_session.disconnect if Creeper.instance_variable_defined?(:@default_session) and Creeper.instance_variable_get(:@default_session)
6
+ Creeper.instance_variable_set(:@default_session, nil)
7
+ end
8
+
9
+ def default_session
10
+ return Creeper.instance_variable_get(:@default_session) if Creeper.instance_variable_defined?(:@default_session)
11
+ Creeper.instance_variable_set(:@default_session, Creeper::Session.new)
12
+ end
13
+
14
+ def enqueue(job, args = {}, opts = {})
15
+ default_session.enqueue(job, args, opts)
16
+ end
17
+
18
+ def job(name, &block)
19
+ default_session.job(name, &block)
20
+ end
21
+
22
+ def before(&block)
23
+ default_session.before(&block)
24
+ end
25
+
26
+ def error(&block)
27
+ default_session.error(&block)
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ $stdout.sync = $stderr.sync = true
2
+ $stdin.binmode
3
+ $stdout.binmode
4
+ $stderr.binmode
5
+
6
+ require 'creeper'
7
+
8
+ module Creeper
9
+ module Launcher
10
+
11
+ extend self
12
+
13
+ def launch!(options)
14
+ $stdin.reopen("/dev/null")
15
+
16
+ # grandparent - reads pipe, exits when master is ready
17
+ # \_ parent - exits immediately ASAP
18
+ # \_ creeper master - writes to pipe when ready
19
+
20
+ rd, wr = IO.pipe
21
+ grandparent = $$
22
+ if fork
23
+ wr.close # grandparent does not write
24
+ else
25
+ rd.close # creeper master does not read
26
+ Process.setsid
27
+ exit if fork # parent dies now
28
+ end
29
+
30
+ if grandparent == $$
31
+ # this will block until Creeper.join runs (or it dies)
32
+ creeper_pid = (rd.readpartial(16) rescue nil).to_i
33
+ unless creeper_pid > 1
34
+ warn "creeper failed to start, check stderr log for details"
35
+ exit!(1)
36
+ end
37
+ exit 0
38
+ else # creeper master process
39
+ options[:ready_pipe] = wr
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module Creeper
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -68,7 +68,9 @@ module Creeper
68
68
  def prepare
69
69
  raise NoJobsDefined if session.handlers.empty?
70
70
 
71
- jobs = options[:jobs] || session.all_jobs
71
+ jobs = session.all_jobs if options[:jobs] == :all
72
+ jobs ||= options[:jobs] ? [*options[:jobs]] : session.all_jobs
73
+ jobs = session.all_jobs if jobs.empty?
72
74
 
73
75
  jobs.each do |job|
74
76
  raise(NoSuchJob, job) unless session.handlers.has_key?(job)
@@ -107,16 +109,17 @@ module Creeper
107
109
  end
108
110
 
109
111
  job.delete
110
- log_job_end(name)
112
+ log_job_end(name, args)
111
113
  rescue Beanstalk::NotConnected => e
112
114
  failed_connection(e)
113
115
  rescue SystemExit
114
- puts "FART"
116
+
115
117
  raise
116
118
  rescue => e
117
119
  log_error exception_message(e)
118
120
  job.bury rescue nil
119
- log_job_end(name, 'failed') if @job_begun
121
+ args ||= []
122
+ log_job_end(name, args, 'failed') if @job_begun
120
123
  if session.error_handler
121
124
  if session.error_handler.arity == 1
122
125
  session.error_handler.call(e)
@@ -133,7 +136,7 @@ module Creeper
133
136
  end
134
137
 
135
138
  def stop
136
- logger.info "worker dying: #{Thread.current.inspect}"
139
+ logger.info "worker dying: (current=#{Thread.current.inspect}#{@thread.inspect}"
137
140
  session.disconnect
138
141
  @thread.kill
139
142
  end
@@ -150,23 +153,28 @@ module Creeper
150
153
 
151
154
  def log_job_begin(name, args)
152
155
  @working = true
153
- args_flat = unless args.empty?
154
- '(' + args.inject([]) do |accum, (key,value)|
155
- accum << "#{key}=#{value}"
156
- end.join(' ') + ')'
157
- else
158
- ''
159
- end
156
+ args_flat = flatten_args(args)
160
157
 
161
158
  log [ "Working", name, args_flat ].join(' ')
162
159
  @job_begun = Time.now
163
160
  end
164
161
 
165
- def log_job_end(name, failed=false)
162
+ def log_job_end(name, args, failed=false)
166
163
  @working = false
167
164
  ellapsed = Time.now - @job_begun
168
165
  ms = (ellapsed.to_f * 1000).to_i
169
- log "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}"
166
+ args_flat = flatten_args(args)
167
+ log [ "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}", args_flat ].join(' ')
168
+ end
169
+
170
+ def flatten_args(args)
171
+ unless args.empty?
172
+ '(' + args.inject([]) do |accum, (key,value)|
173
+ accum << "#{key}=#{value}"
174
+ end.join(' ') + ')'
175
+ else
176
+ ''
177
+ end
170
178
  end
171
179
 
172
180
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: creeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-15 00:00:00.000000000 Z
12
+ date: 2012-05-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pry
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: rspec
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -78,7 +94,8 @@ dependencies:
78
94
  description: Stalker with threads
79
95
  email:
80
96
  - lyondhill@gmail.com
81
- executables: []
97
+ executables:
98
+ - creeper
82
99
  extensions: []
83
100
  extra_rdoc_files: []
84
101
  files:
@@ -88,8 +105,12 @@ files:
88
105
  - LICENSE
89
106
  - README.md
90
107
  - Rakefile
108
+ - bin/creeper
91
109
  - creeper.gemspec
110
+ - examples/jack.rb
92
111
  - lib/creeper.rb
112
+ - lib/creeper/creep.rb
113
+ - lib/creeper/launcher.rb
93
114
  - lib/creeper/session.rb
94
115
  - lib/creeper/version.rb
95
116
  - lib/creeper/worker.rb
@@ -109,15 +130,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
130
  - - ! '>='
110
131
  - !ruby/object:Gem::Version
111
132
  version: '0'
133
+ segments:
134
+ - 0
135
+ hash: -2251731633976259583
112
136
  required_rubygems_version: !ruby/object:Gem::Requirement
113
137
  none: false
114
138
  requirements:
115
139
  - - ! '>='
116
140
  - !ruby/object:Gem::Version
117
141
  version: '0'
142
+ segments:
143
+ - 0
144
+ hash: -2251731633976259583
118
145
  requirements: []
119
146
  rubyforge_project:
120
- rubygems_version: 1.8.20
147
+ rubygems_version: 1.8.23
121
148
  signing_key:
122
149
  specification_version: 3
123
150
  summary: A better solution for io bound jobs, same as stalker in functionality but
@@ -127,4 +154,3 @@ test_files:
127
154
  - spec/lib/creeper/worker_spec.rb
128
155
  - spec/lib/creeper_spec.rb
129
156
  - spec/spec_helper.rb
130
- has_rdoc: