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