qless 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/HISTORY.md +168 -0
- data/README.md +571 -0
- data/Rakefile +28 -0
- data/bin/qless-campfire +106 -0
- data/bin/qless-growl +99 -0
- data/bin/qless-web +23 -0
- data/lib/qless.rb +185 -0
- data/lib/qless/config.rb +31 -0
- data/lib/qless/job.rb +259 -0
- data/lib/qless/job_reservers/ordered.rb +23 -0
- data/lib/qless/job_reservers/round_robin.rb +34 -0
- data/lib/qless/lua.rb +25 -0
- data/lib/qless/qless-core/cancel.lua +71 -0
- data/lib/qless/qless-core/complete.lua +218 -0
- data/lib/qless/qless-core/config.lua +44 -0
- data/lib/qless/qless-core/depends.lua +65 -0
- data/lib/qless/qless-core/fail.lua +107 -0
- data/lib/qless/qless-core/failed.lua +83 -0
- data/lib/qless/qless-core/get.lua +37 -0
- data/lib/qless/qless-core/heartbeat.lua +50 -0
- data/lib/qless/qless-core/jobs.lua +41 -0
- data/lib/qless/qless-core/peek.lua +155 -0
- data/lib/qless/qless-core/pop.lua +278 -0
- data/lib/qless/qless-core/priority.lua +32 -0
- data/lib/qless/qless-core/put.lua +156 -0
- data/lib/qless/qless-core/queues.lua +58 -0
- data/lib/qless/qless-core/recur.lua +181 -0
- data/lib/qless/qless-core/retry.lua +73 -0
- data/lib/qless/qless-core/ruby/lib/qless-core.rb +1 -0
- data/lib/qless/qless-core/ruby/lib/qless/core.rb +13 -0
- data/lib/qless/qless-core/ruby/lib/qless/core/version.rb +5 -0
- data/lib/qless/qless-core/ruby/spec/qless_core_spec.rb +13 -0
- data/lib/qless/qless-core/stats.lua +92 -0
- data/lib/qless/qless-core/tag.lua +100 -0
- data/lib/qless/qless-core/track.lua +79 -0
- data/lib/qless/qless-core/workers.lua +69 -0
- data/lib/qless/queue.rb +141 -0
- data/lib/qless/server.rb +411 -0
- data/lib/qless/tasks.rb +10 -0
- data/lib/qless/version.rb +3 -0
- data/lib/qless/worker.rb +195 -0
- metadata +239 -0
data/lib/qless/tasks.rb
ADDED
@@ -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
|
+
|
data/lib/qless/worker.rb
ADDED
@@ -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: []
|