qless 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Gemfile +8 -0
  2. data/HISTORY.md +168 -0
  3. data/README.md +571 -0
  4. data/Rakefile +28 -0
  5. data/bin/qless-campfire +106 -0
  6. data/bin/qless-growl +99 -0
  7. data/bin/qless-web +23 -0
  8. data/lib/qless.rb +185 -0
  9. data/lib/qless/config.rb +31 -0
  10. data/lib/qless/job.rb +259 -0
  11. data/lib/qless/job_reservers/ordered.rb +23 -0
  12. data/lib/qless/job_reservers/round_robin.rb +34 -0
  13. data/lib/qless/lua.rb +25 -0
  14. data/lib/qless/qless-core/cancel.lua +71 -0
  15. data/lib/qless/qless-core/complete.lua +218 -0
  16. data/lib/qless/qless-core/config.lua +44 -0
  17. data/lib/qless/qless-core/depends.lua +65 -0
  18. data/lib/qless/qless-core/fail.lua +107 -0
  19. data/lib/qless/qless-core/failed.lua +83 -0
  20. data/lib/qless/qless-core/get.lua +37 -0
  21. data/lib/qless/qless-core/heartbeat.lua +50 -0
  22. data/lib/qless/qless-core/jobs.lua +41 -0
  23. data/lib/qless/qless-core/peek.lua +155 -0
  24. data/lib/qless/qless-core/pop.lua +278 -0
  25. data/lib/qless/qless-core/priority.lua +32 -0
  26. data/lib/qless/qless-core/put.lua +156 -0
  27. data/lib/qless/qless-core/queues.lua +58 -0
  28. data/lib/qless/qless-core/recur.lua +181 -0
  29. data/lib/qless/qless-core/retry.lua +73 -0
  30. data/lib/qless/qless-core/ruby/lib/qless-core.rb +1 -0
  31. data/lib/qless/qless-core/ruby/lib/qless/core.rb +13 -0
  32. data/lib/qless/qless-core/ruby/lib/qless/core/version.rb +5 -0
  33. data/lib/qless/qless-core/ruby/spec/qless_core_spec.rb +13 -0
  34. data/lib/qless/qless-core/stats.lua +92 -0
  35. data/lib/qless/qless-core/tag.lua +100 -0
  36. data/lib/qless/qless-core/track.lua +79 -0
  37. data/lib/qless/qless-core/workers.lua +69 -0
  38. data/lib/qless/queue.rb +141 -0
  39. data/lib/qless/server.rb +411 -0
  40. data/lib/qless/tasks.rb +10 -0
  41. data/lib/qless/version.rb +3 -0
  42. data/lib/qless/worker.rb +195 -0
  43. metadata +239 -0
@@ -0,0 +1,10 @@
1
+ namespace :qless do
2
+ task :setup # no-op; users should define their own setup
3
+
4
+ desc "Start a Qless worker using env vars: QUEUES, JOB_RESERVER, REDIS_URL, INTERVAL, VERBOSE, VVERBOSE"
5
+ task :work => :setup do
6
+ require 'qless/worker'
7
+ Qless::Worker.start
8
+ end
9
+ end
10
+
@@ -0,0 +1,3 @@
1
+ module Qless
2
+ VERSION = "0.9.1"
3
+ end
@@ -0,0 +1,195 @@
1
+ require 'qless'
2
+ require 'time'
3
+ require 'qless/job_reservers/ordered'
4
+ require 'qless/job_reservers/round_robin'
5
+
6
+ module Qless
7
+ # This is heavily inspired by Resque's excellent worker:
8
+ # https://github.com/defunkt/resque/blob/v1.20.0/lib/resque/worker.rb
9
+ class Worker
10
+ def initialize(client, job_reserver, options = {})
11
+ @client, @job_reserver = client, job_reserver
12
+ @shutdown = @paused = false
13
+ self.very_verbose = options[:very_verbose]
14
+ self.verbose = options[:verbose]
15
+ self.run_as_single_process = options[:run_as_single_process]
16
+ end
17
+
18
+ # Whether the worker should log basic info to STDOUT
19
+ attr_accessor :verbose
20
+
21
+ # Whether the worker should log lots of info to STDOUT
22
+ attr_accessor :very_verbose
23
+
24
+ # Whether the worker should run in a single prcoess
25
+ # i.e. not fork a child process to do the work
26
+ # This should only be true in a dev/test environment
27
+ attr_accessor :run_as_single_process
28
+
29
+ # Starts a worker based on ENV vars. Supported ENV vars:
30
+ # - REDIS_URL=redis://host:port/db-num (the redis gem uses this automatically)
31
+ # - QUEUES=high,medium,low or QUEUE=blah
32
+ # - JOB_RESERVER=Ordered or JOB_RESERVER=RoundRobin
33
+ # - INTERVAL=3.2
34
+ # - VERBOSE=true (to enable logging)
35
+ # - VVERBOSE=true (to enable very verbose logging)
36
+ # - RUN_AS_SINGLE_PROCESS=true (false will fork children to do work, true will keep it single process)
37
+ # This is designed to be called from a rake task
38
+ def self.start
39
+ client = Qless::Client.new
40
+ queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',').map { |q| client.queues[q.strip] }
41
+ if queues.none?
42
+ raise "No queues provided. You must pass QUEUE or QUEUES when starting a worker."
43
+ end
44
+
45
+ reserver = JobReservers.const_get(ENV.fetch('JOB_RESERVER', 'Ordered')).new(queues)
46
+ interval = Float(ENV.fetch('INTERVAL', 5.0))
47
+
48
+ options = {}
49
+ options[:verbose] = !!ENV['VERBOSE']
50
+ options[:very_verbose] = !!ENV['VVERBOSE']
51
+ options[:run_as_single_process] = !!ENV['RUN_AS_SINGLE_PROCESS']
52
+
53
+ new(client, reserver, options).work(interval)
54
+ end
55
+
56
+ def work(interval = 5.0)
57
+ procline "Starting #{@job_reserver.description}"
58
+ register_signal_handlers
59
+
60
+ loop do
61
+ break if shutdown?
62
+ next if paused?
63
+
64
+ unless job = @job_reserver.reserve
65
+ break if interval.zero?
66
+ procline "Waiting for #{@job_reserver.description}"
67
+ log! "Sleeping for #{interval} seconds"
68
+ sleep interval
69
+ next
70
+ end
71
+
72
+ log "got: #{job.inspect}"
73
+
74
+ if run_as_single_process
75
+ # We're staying in the same process
76
+ procline "Single processing #{job.description}"
77
+ perform(job)
78
+ elsif @child = fork
79
+ # We're in the parent process
80
+ procline "Forked #{@child} for #{job.description}"
81
+ Process.wait(@child)
82
+ else
83
+ # We're in the child process
84
+ procline "Processing #{job.description}"
85
+ perform(job)
86
+ exit!
87
+ end
88
+ end
89
+ end
90
+
91
+ def perform(job)
92
+ around_perform(job)
93
+ rescue Exception => error
94
+ fail_job(job, error)
95
+ else
96
+ job.complete unless job.state_changed?
97
+ end
98
+
99
+ def shutdown
100
+ @shutdown = true
101
+ end
102
+
103
+ def shutdown!
104
+ shutdown
105
+ kill_child unless run_as_single_process
106
+ end
107
+
108
+ def shutdown?
109
+ @shutdown
110
+ end
111
+
112
+ def paused?
113
+ @paused
114
+ end
115
+
116
+ def pause_processing
117
+ log "USR2 received; pausing job processing"
118
+ @paused = true
119
+ procline "Paused -- #{@job_reserver.description}"
120
+ end
121
+
122
+ def unpause_processing
123
+ log "CONT received; resuming job processing"
124
+ @paused = false
125
+ end
126
+
127
+ private
128
+
129
+ # Allow middleware modules to be mixed in and override the
130
+ # definition of around_perform while providing a default
131
+ # implementation so our code can assume the method is present.
132
+ include Module.new {
133
+ def around_perform(job)
134
+ job.perform
135
+ end
136
+ }
137
+
138
+ def fail_job(job, error)
139
+ group = "#{job.klass}:#{error.class}"
140
+ message = "#{error.message}\n\n#{error.backtrace.join("\n")}"
141
+ log "Got #{group} failure from #{job.inspect}"
142
+ job.fail(group, message)
143
+ end
144
+
145
+ def procline(value)
146
+ $0 = "Qless-#{Qless::VERSION}: #{value} at #{Time.now.iso8601}"
147
+ log! $0
148
+ end
149
+
150
+ def kill_child
151
+ return unless @child
152
+ return unless system("ps -o pid,state -p #{@child}")
153
+ Process.kill("KILL", @child) rescue nil
154
+ end
155
+
156
+ # This is stolen directly from resque... (thanks, @defunkt!)
157
+ # Registers the various signal handlers a worker responds to.
158
+ #
159
+ # TERM: Shutdown immediately, stop processing jobs.
160
+ # INT: Shutdown immediately, stop processing jobs.
161
+ # QUIT: Shutdown after the current job has finished processing.
162
+ # USR1: Kill the forked child immediately, continue processing jobs.
163
+ # USR2: Don't process any new jobs
164
+ # CONT: Start processing jobs again after a USR2
165
+ def register_signal_handlers
166
+ trap('TERM') { shutdown! }
167
+ trap('INT') { shutdown! }
168
+
169
+ begin
170
+ trap('QUIT') { shutdown }
171
+ trap('USR1') { kill_child }
172
+ trap('USR2') { pause_processing }
173
+ trap('CONT') { unpause_processing }
174
+ rescue ArgumentError
175
+ warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
176
+ end
177
+ end
178
+
179
+ # Log a message to STDOUT if we are verbose or very_verbose.
180
+ def log(message)
181
+ if verbose
182
+ puts "*** #{message}"
183
+ elsif very_verbose
184
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
185
+ puts "** [#{time}] #$$: #{message}"
186
+ end
187
+ end
188
+
189
+ # Logs a very verbose message to STDOUT.
190
+ def log!(message)
191
+ log message if very_verbose
192
+ end
193
+ end
194
+ end
195
+
metadata ADDED
@@ -0,0 +1,239 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Lecocq
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sinatra
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.3.2
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: 1.3.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: vegas
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.1.11
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.11
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.9.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.9.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec-fire
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '0.4'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '0.4'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.9.2.2
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.9.2.2
110
+ - !ruby/object:Gem::Dependency
111
+ name: capybara
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 1.1.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 1.1.2
126
+ - !ruby/object:Gem::Dependency
127
+ name: launchy
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 2.1.0
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 2.1.0
142
+ - !ruby/object:Gem::Dependency
143
+ name: simplecov
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: 0.6.2
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 0.6.2
158
+ description: ! "\n `qless` is meant to be a performant alternative to other queueing\n
159
+ \ systems, with statistics collection, a browser interface, and \n strong guarantees
160
+ about job losses.\n \n It's written as a collection of Lua scipts that are
161
+ loaded into the\n Redis instance to be used, and then executed by the client
162
+ library.\n As such, it's intended to be extremely easy to port to other languages,\n
163
+ \ without sacrificing performance and not requiring a lot of logic\n replication
164
+ between clients. Keep the Lua scripts updated, and your\n language-specific extension
165
+ will also remain up to date.\n "
166
+ email:
167
+ - dan@seomoz.org
168
+ executables:
169
+ - qless-web
170
+ extensions: []
171
+ extra_rdoc_files: []
172
+ files:
173
+ - README.md
174
+ - Gemfile
175
+ - Rakefile
176
+ - HISTORY.md
177
+ - lib/qless/config.rb
178
+ - lib/qless/job.rb
179
+ - lib/qless/job_reservers/ordered.rb
180
+ - lib/qless/job_reservers/round_robin.rb
181
+ - lib/qless/lua.rb
182
+ - lib/qless/qless-core/ruby/lib/qless/core/version.rb
183
+ - lib/qless/qless-core/ruby/lib/qless/core.rb
184
+ - lib/qless/qless-core/ruby/lib/qless-core.rb
185
+ - lib/qless/qless-core/ruby/spec/qless_core_spec.rb
186
+ - lib/qless/queue.rb
187
+ - lib/qless/server.rb
188
+ - lib/qless/tasks.rb
189
+ - lib/qless/version.rb
190
+ - lib/qless/worker.rb
191
+ - lib/qless.rb
192
+ - lib/qless/qless-core/cancel.lua
193
+ - lib/qless/qless-core/complete.lua
194
+ - lib/qless/qless-core/config.lua
195
+ - lib/qless/qless-core/depends.lua
196
+ - lib/qless/qless-core/fail.lua
197
+ - lib/qless/qless-core/failed.lua
198
+ - lib/qless/qless-core/get.lua
199
+ - lib/qless/qless-core/heartbeat.lua
200
+ - lib/qless/qless-core/jobs.lua
201
+ - lib/qless/qless-core/peek.lua
202
+ - lib/qless/qless-core/pop.lua
203
+ - lib/qless/qless-core/priority.lua
204
+ - lib/qless/qless-core/put.lua
205
+ - lib/qless/qless-core/queues.lua
206
+ - lib/qless/qless-core/recur.lua
207
+ - lib/qless/qless-core/retry.lua
208
+ - lib/qless/qless-core/stats.lua
209
+ - lib/qless/qless-core/tag.lua
210
+ - lib/qless/qless-core/track.lua
211
+ - lib/qless/qless-core/workers.lua
212
+ - bin/qless-campfire
213
+ - bin/qless-growl
214
+ - bin/qless-web
215
+ homepage: http://github.com/seomoz/qless
216
+ licenses: []
217
+ post_install_message:
218
+ rdoc_options: []
219
+ require_paths:
220
+ - lib
221
+ required_ruby_version: !ruby/object:Gem::Requirement
222
+ none: false
223
+ requirements:
224
+ - - ! '>='
225
+ - !ruby/object:Gem::Version
226
+ version: '0'
227
+ required_rubygems_version: !ruby/object:Gem::Requirement
228
+ none: false
229
+ requirements:
230
+ - - ! '>='
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ requirements: []
234
+ rubyforge_project: qless
235
+ rubygems_version: 1.8.24
236
+ signing_key:
237
+ specification_version: 3
238
+ summary: A Redis-Based Queueing System
239
+ test_files: []