qless 0.9.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/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: []
|