sqew 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +117 -0
- data/Rakefile +7 -0
- data/ext/qu.rb +36 -0
- data/ext/slave.rb +14 -0
- data/lib/sqew.rb +95 -0
- data/lib/sqew/backend/immediate.rb +17 -0
- data/lib/sqew/backend/leveldb.rb +104 -0
- data/lib/sqew/manager.rb +124 -0
- data/lib/sqew/payload.rb +14 -0
- data/lib/sqew/rails.rb +14 -0
- data/lib/sqew/railtie.rb +12 -0
- data/lib/sqew/server.rb +73 -0
- data/lib/sqew/tasks.rb +13 -0
- data/lib/sqew/version.rb +3 -0
- data/lib/sqew/worker.rb +26 -0
- data/lib/tasks.rb +13 -0
- data/spec/leveldb_spec.rb +57 -0
- data/spec/manager_spec.rb +35 -0
- data/spec/server_spec.rb +71 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/sqew_spec.rb +41 -0
- data/spec/support/jobs.rb +29 -0
- data/sqew.gemspec +32 -0
- metadata +243 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Zach Moazeni
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# sqew (pronounced "skew")
|
2
|
+
|
3
|
+
sqew is a lightweight background processor. You start a single process that will act as a queue manager and will work multiple jobs concurrently. sqew is short for "small queue" and is not meant to be an all-in-one scalable solution. sqew adopts a similiar API to make migrating to other background processors easy.
|
4
|
+
|
5
|
+
## When would sqew be a good fit for my project?
|
6
|
+
|
7
|
+
* You don't need to split workers across multiple machines.
|
8
|
+
* You don't want to manage multiple background worker processes, but you do want multiple jobs to run concurrently.
|
9
|
+
* You don't want to worry about threading issues.
|
10
|
+
* You don't want to worry about long running processes memory leaking.
|
11
|
+
* You don't care about the enqueueing or job forking performance.
|
12
|
+
* You don't need multiple queues (this may change soon).
|
13
|
+
|
14
|
+
If these don't fit the bill or you need more power, I recommend you try the great other great gems such as [Resque](https://github.com/defunkt/resque), [Sidekiq](https://github.com/mperham/sidekiq), and [Qu](https://github.com/bkeepers/qu).
|
15
|
+
|
16
|
+
## Is it any good?
|
17
|
+
|
18
|
+
[Yes.](http://news.ycombinator.com/item?id=3067434)
|
19
|
+
|
20
|
+
## Rails Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
gem sqew, :require "sqew/rails"
|
25
|
+
|
26
|
+
Add an initializer in `config/initializers/sqew.rb`
|
27
|
+
|
28
|
+
Sqew.configure do |config|
|
29
|
+
config.db = "#{Rails.root}/tmp/"
|
30
|
+
config.server = "http://0.0.0.0:8884"
|
31
|
+
end
|
32
|
+
|
33
|
+
The `db` config will be a directory where sqew will manage its databases, and the `server` config is what what the worker will connect at as well as where the application will post jobs to.
|
34
|
+
|
35
|
+
Once you have sqew configured, you can start the queue manager by running `rake sqew:work`. This will manage the queues, it'll act as a server where the application post jobs, and it will work the jobs as the arrive.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
Add this line to your application's Gemfile:
|
40
|
+
|
41
|
+
gem sqew
|
42
|
+
|
43
|
+
And then execute:
|
44
|
+
|
45
|
+
$ bundle
|
46
|
+
|
47
|
+
Or install it yourself as:
|
48
|
+
|
49
|
+
$ gem install sqew
|
50
|
+
|
51
|
+
## The Sqew Manager
|
52
|
+
|
53
|
+
The Sqew manager is a JSON API for inspecting the queue, pushing work onto the queue, and manipulating the queue and workers. Actions you can perform are:
|
54
|
+
|
55
|
+
# enqueue a job
|
56
|
+
# Sqew.push(TheJobClass, 1, 2, 3)
|
57
|
+
POST /enqueue
|
58
|
+
{"job":"TheJobClass", "args":[1, 2, 3]}
|
59
|
+
|
60
|
+
# ping the server to programatically see if it is alive
|
61
|
+
# Sqew.ping
|
62
|
+
GET /ping
|
63
|
+
|
64
|
+
# get the status of the queue, running jobs, failed jobs, and how many workers the server will use
|
65
|
+
# Sqew.status
|
66
|
+
GET /status
|
67
|
+
|
68
|
+
# dynamically change the number of workers (processes) the manager will use
|
69
|
+
# Sqew.workers = 10 (the default is 3)
|
70
|
+
PUT /workers
|
71
|
+
"10"
|
72
|
+
|
73
|
+
# clear the entire queue
|
74
|
+
# Sqew.clear
|
75
|
+
DELETE /clear
|
76
|
+
|
77
|
+
# clear just the failed jobs
|
78
|
+
# Sqew.clear("failed")
|
79
|
+
DELETE /clear
|
80
|
+
failed
|
81
|
+
|
82
|
+
# delete a specific job by id
|
83
|
+
# Sqew.delete(11)
|
84
|
+
DELETE /11
|
85
|
+
|
86
|
+
## Enqueuing jobs
|
87
|
+
|
88
|
+
From your application you will create jobs just like [Resque](https://github.com/defunkt/resque), [Sidekiq](https://github.com/mperham/sidekiq), and [Qu](https://github.com/bkeepers/qu) in the form of
|
89
|
+
|
90
|
+
class MyJob
|
91
|
+
def perform(arg1, arg2)
|
92
|
+
# .. the job code
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
And the manager will receive the job and start working on it when it can. You can enqueue the job from Sqew:
|
97
|
+
|
98
|
+
Sqew.push(MyJob, 1, 2)
|
99
|
+
|
100
|
+
If you're using Rails 4 you can enqueue the job by using the Rails queuing API:
|
101
|
+
|
102
|
+
Rails.queue.push(MyJob, 1, 2)
|
103
|
+
|
104
|
+
|
105
|
+
## TODO Soon
|
106
|
+
|
107
|
+
* Reusable God/Bluepil/Monit config for managing the worker
|
108
|
+
* Javascript browser front-end for the manager
|
109
|
+
* Multiple queues with weight (possibly)
|
110
|
+
|
111
|
+
## Contributing
|
112
|
+
|
113
|
+
1. Fork it
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
115
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
117
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/ext/qu.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'qu/version'
|
2
|
+
require 'qu/logger'
|
3
|
+
require 'qu/failure'
|
4
|
+
require 'qu/payload'
|
5
|
+
require 'qu/backend/base'
|
6
|
+
|
7
|
+
require 'forwardable'
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
module Qu
|
11
|
+
autoload :Worker, 'qu/worker'
|
12
|
+
|
13
|
+
extend SingleForwardable
|
14
|
+
extend self
|
15
|
+
|
16
|
+
attr_accessor :backend, :failure, :logger
|
17
|
+
|
18
|
+
def_delegators :backend, :length, :queues, :reserve, :clear, :connection=
|
19
|
+
|
20
|
+
def backend
|
21
|
+
@backend || raise("Qu backend not configured. Install one of the backend gems like qu-redis.")
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure(&block)
|
25
|
+
block.call(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def enqueue(klass, *args)
|
29
|
+
backend.enqueue Payload.new(:klass => klass, :args => args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Qu.configure do |c|
|
34
|
+
c.logger = Logger.new(STDOUT)
|
35
|
+
c.logger.level = Logger::INFO
|
36
|
+
end
|
data/ext/slave.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class Slave
|
2
|
+
def shutdown opts = {}
|
3
|
+
quiet = getopts(opts)['quiet']
|
4
|
+
raise "already shutdown" if @shutdown unless quiet
|
5
|
+
begin; @lifeline.cut; rescue Exception; end
|
6
|
+
@shutdown = true
|
7
|
+
end
|
8
|
+
|
9
|
+
class LifeLine
|
10
|
+
def cling &b
|
11
|
+
on_cut{ b.call if b }.join
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/sqew.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "slave"
|
2
|
+
require File.expand_path("../ext/slave", File.dirname(__FILE__))
|
3
|
+
require File.expand_path("../ext/qu", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require "leveldb"
|
6
|
+
require "sinatra/base"
|
7
|
+
require "thin"
|
8
|
+
require "multi_json"
|
9
|
+
|
10
|
+
require "sqew/version"
|
11
|
+
require "sqew/worker"
|
12
|
+
require "sqew/manager"
|
13
|
+
require "sqew/server"
|
14
|
+
require "sqew/payload"
|
15
|
+
require "sqew/backend/leveldb"
|
16
|
+
|
17
|
+
require "forwardable"
|
18
|
+
require "net/http"
|
19
|
+
|
20
|
+
module Sqew
|
21
|
+
module ClassMethods
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
attr_accessor :server
|
25
|
+
|
26
|
+
def qu
|
27
|
+
Qu
|
28
|
+
end
|
29
|
+
|
30
|
+
def server=(raw)
|
31
|
+
URI.parse(raw) # verify it's parsable
|
32
|
+
@server = raw
|
33
|
+
end
|
34
|
+
|
35
|
+
def_delegators :qu, :backend, :backend=, :length, :queues, :reserve, :logger, :logger=, :failure, :failure=
|
36
|
+
end
|
37
|
+
extend ClassMethods
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def configure(*args, &block)
|
41
|
+
self.backend = Sqew::Backend::LevelDB.new
|
42
|
+
block.call(self)
|
43
|
+
self.server ||= "http://0.0.0.0:9962"
|
44
|
+
self.db ||= "/tmp/"
|
45
|
+
end
|
46
|
+
|
47
|
+
def http
|
48
|
+
uri = URI.parse(server)
|
49
|
+
@http ||= Net::HTTP.new(uri.host, uri.port)
|
50
|
+
end
|
51
|
+
|
52
|
+
def push(job, *args)
|
53
|
+
request = Net::HTTP::Post.new("/enqueue")
|
54
|
+
request.body = MultiJson.encode("job" => job.to_s, "args" => args)
|
55
|
+
http.request(request)
|
56
|
+
end
|
57
|
+
alias_method :enqueue, :push
|
58
|
+
|
59
|
+
def ping
|
60
|
+
request = Net::HTTP::Get.new("/ping")
|
61
|
+
http.request(request)
|
62
|
+
end
|
63
|
+
|
64
|
+
def status
|
65
|
+
request = Net::HTTP::Get.new("/status")
|
66
|
+
response = http.request(request)
|
67
|
+
if response.code == "200"
|
68
|
+
MultiJson.decode(http.request(request).body)
|
69
|
+
else
|
70
|
+
raise "Error connecting to server #{response.code}:#{response.body}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_workers(count)
|
75
|
+
request = Net::HTTP::Put.new("/workers")
|
76
|
+
request.body = count.to_s
|
77
|
+
http.request(request)
|
78
|
+
end
|
79
|
+
alias_method :workers=, :set_workers
|
80
|
+
|
81
|
+
def clear(*queues)
|
82
|
+
request = Net::HTTP::Delete.new("/clear")
|
83
|
+
request.body = queues.join(",")
|
84
|
+
http.request(request)
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete(id)
|
88
|
+
request = Net::HTTP::Delete.new("/#{id}")
|
89
|
+
http.request(request)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
extend SingleForwardable
|
94
|
+
def_delegators :backend, :failed_jobs, :running_jobs, :queued_jobs, :db, :db=
|
95
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "qu/backend/immediate"
|
2
|
+
|
3
|
+
module Sqew
|
4
|
+
module Backend
|
5
|
+
class Immediate < Qu::Backend::Immediate
|
6
|
+
def queued_jobs
|
7
|
+
[]
|
8
|
+
end
|
9
|
+
alias_method :running_jobs, :queued_jobs
|
10
|
+
alias_method :failed_jobs, :queued_jobs
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Sqew
|
2
|
+
module Backend
|
3
|
+
class LevelDB < Qu::Backend::Base
|
4
|
+
attr_accessor :db
|
5
|
+
|
6
|
+
def enqueue(payload)
|
7
|
+
id = Time.now.to_f.to_s
|
8
|
+
queue.put(id, MultiJson.encode(klass:payload.klass.to_s, args:payload.args), :sync => true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def length(*)
|
12
|
+
queue.keys.length
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear(*queues)
|
16
|
+
drop_all(queue) if queues.include?("queue") || queues.empty?
|
17
|
+
drop_all(errors) if queues.include?("failed") || queues.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear_running
|
21
|
+
drop_all(running)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(id)
|
25
|
+
if queue.exists?(id)
|
26
|
+
queue.delete(id)
|
27
|
+
elsif errors.exists?(id)
|
28
|
+
errors.delete(id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def reserve(_, options = {block:false})
|
33
|
+
loop do
|
34
|
+
if raw = queue.first
|
35
|
+
id, job = raw
|
36
|
+
queue.delete(id, :sync => true)
|
37
|
+
running.put(id, job, :sync => true)
|
38
|
+
return Sqew::Payload.new(MultiJson.decode(job).update(id:id))
|
39
|
+
end
|
40
|
+
|
41
|
+
if options[:block]
|
42
|
+
sleep 3
|
43
|
+
else
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def completed(payload)
|
50
|
+
running.delete(payload.id, :sync => true)
|
51
|
+
end
|
52
|
+
|
53
|
+
def failed(payload, error)
|
54
|
+
running.delete(payload.id, :sync => true)
|
55
|
+
errors.put(payload.id, MultiJson.encode("klass" => payload.klass.to_s, "args" => payload.args, "error" => error.message, "backtrace" => error.backtrace.join("\n")), :sync => true)
|
56
|
+
end
|
57
|
+
|
58
|
+
def failed_jobs
|
59
|
+
errors.to_a.map {|k,v| MultiJson.decode(v).update("id" => k) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def running_jobs
|
63
|
+
running.to_a.map {|k,v| MultiJson.decode(v).update("id" => k) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def queued_jobs
|
67
|
+
queue.to_a.map {|k,v| MultiJson.decode(v).update("id" => k) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def release(*)
|
71
|
+
end
|
72
|
+
|
73
|
+
def register_worker(*)
|
74
|
+
end
|
75
|
+
|
76
|
+
def unregister_worker(*)
|
77
|
+
end
|
78
|
+
|
79
|
+
def close
|
80
|
+
queue.close
|
81
|
+
running.close
|
82
|
+
errors.close
|
83
|
+
@queue, @running, @errors = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def queue
|
88
|
+
@queue ||= ::LevelDB::DB.new("#{db}/queue.ldb")
|
89
|
+
end
|
90
|
+
|
91
|
+
def running
|
92
|
+
@running ||= ::LevelDB::DB.new("#{db}/running.ldb")
|
93
|
+
end
|
94
|
+
|
95
|
+
def errors
|
96
|
+
@errors ||= ::LevelDB::DB.new("#{db}/errors.ldb")
|
97
|
+
end
|
98
|
+
|
99
|
+
def drop_all(db)
|
100
|
+
db.each {|k,_| db.delete(k) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/sqew/manager.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module Sqew
|
2
|
+
class Manager < Qu::Worker
|
3
|
+
attr_accessor :max_workers
|
4
|
+
|
5
|
+
def initialize(max_workers = 3)
|
6
|
+
raise "Configure sqew before starting the manager" unless Sqew.server
|
7
|
+
super([])
|
8
|
+
@max_workers = max_workers
|
9
|
+
@uri = URI.parse(Sqew.server)
|
10
|
+
@poll = 1
|
11
|
+
@thin_server = nil
|
12
|
+
|
13
|
+
@group = ThreadGroup.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def pause_workers
|
17
|
+
@paused_workers ||= @max_workers
|
18
|
+
@max_workers = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def resume_workers
|
22
|
+
if @paused_workers
|
23
|
+
@max_workers = @paused_workers
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def max_workers=(count)
|
28
|
+
@paused_workers = nil
|
29
|
+
@max_workers = count
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_server
|
33
|
+
logger.info "Starting server on #{@uri}"
|
34
|
+
Thread.new do
|
35
|
+
Thin::Logging.silent = true
|
36
|
+
@thin_server = Thin::Server.new(@uri.host, @uri.port, Server.new(self), {signals:false})
|
37
|
+
@thin_server.start
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop_server
|
42
|
+
@thin_server.stop
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_signals
|
46
|
+
logger.debug "Worker #{id} registering traps for INT and TERM signals"
|
47
|
+
%W(INT TERM).each do |sig|
|
48
|
+
trap(sig) do
|
49
|
+
logger.info "Worker #{id} received #{sig}, will wait for workers to finish then quit"
|
50
|
+
@exiting = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def work_off
|
56
|
+
Qu.backend.clear_running
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def start
|
61
|
+
logger.warn "Worker #{id} starting"
|
62
|
+
start_slave
|
63
|
+
handle_signals
|
64
|
+
start_server
|
65
|
+
Qu.backend.clear_running
|
66
|
+
loop do
|
67
|
+
work
|
68
|
+
sleep @poll
|
69
|
+
|
70
|
+
if @exiting
|
71
|
+
stop_server
|
72
|
+
@group.list.map {|t| t.join }
|
73
|
+
Qu.backend.close
|
74
|
+
stop_slave
|
75
|
+
break
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ensure
|
79
|
+
logger.debug "Worker #{id} done"
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def work
|
84
|
+
if available?
|
85
|
+
job = Qu.reserve(self)
|
86
|
+
if job
|
87
|
+
thread = Thread.new do
|
88
|
+
begin
|
89
|
+
logger.debug "Worker #{id}:#{Process.pid} reserved job #{job}"
|
90
|
+
remote_thread = @worker.fork_job(job)
|
91
|
+
success, error = remote_thread.value
|
92
|
+
logger.debug "Worker #{id}:#{Process.pid} completed job #{job}"
|
93
|
+
|
94
|
+
if success
|
95
|
+
Qu.backend.completed(job)
|
96
|
+
else
|
97
|
+
Qu.failure.create(job, error) if Qu.failure
|
98
|
+
Qu.backend.failed(job, error)
|
99
|
+
end
|
100
|
+
rescue Exception => e
|
101
|
+
logger.error "Thread Error"
|
102
|
+
log_exception(e)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
@group.add(thread)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def available?
|
111
|
+
@group.list.size < @max_workers
|
112
|
+
end
|
113
|
+
|
114
|
+
def start_slave
|
115
|
+
trap("INT") { }
|
116
|
+
@worker_server = Slave.new(:threadsafe => true) { Sqew::Worker.new }
|
117
|
+
@worker = @worker_server.object
|
118
|
+
end
|
119
|
+
|
120
|
+
def stop_slave
|
121
|
+
@worker_server.shutdown
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/sqew/payload.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Sqew
|
2
|
+
class Payload < Qu::Payload
|
3
|
+
def perform_forked(pipe)
|
4
|
+
klass.perform(*args)
|
5
|
+
pipe.write(Marshal.dump([true, nil]))
|
6
|
+
rescue Exception => e
|
7
|
+
pipe.write(Marshal.dump([false, e]))
|
8
|
+
raise e
|
9
|
+
# raise special exception
|
10
|
+
ensure
|
11
|
+
pipe.close
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/sqew/rails.rb
ADDED
data/lib/sqew/railtie.rb
ADDED
data/lib/sqew/server.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Sqew
|
2
|
+
class Server < Sinatra::Base
|
3
|
+
|
4
|
+
def initialize(manager)
|
5
|
+
super
|
6
|
+
@manager = manager
|
7
|
+
end
|
8
|
+
|
9
|
+
def route_missing
|
10
|
+
raise Sinatra::NotFound
|
11
|
+
end
|
12
|
+
|
13
|
+
set :show_exceptions, false
|
14
|
+
|
15
|
+
not_found do
|
16
|
+
[404, {}, ""]
|
17
|
+
end
|
18
|
+
|
19
|
+
post "/enqueue" do
|
20
|
+
begin
|
21
|
+
request.body.rewind
|
22
|
+
json = MultiJson.decode(request.body)
|
23
|
+
request.body.rewind
|
24
|
+
|
25
|
+
Qu.enqueue(json["job"], *json["args"])
|
26
|
+
[202, {"Content-Type" => "application/json"}, ""]
|
27
|
+
rescue Exception => e
|
28
|
+
json = MultiJson.encode({"error" => "#{e.message}\n#{e.backtrace}"})
|
29
|
+
[500, {"Content-Type" => "application/json"}, json]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
get "/status" do
|
34
|
+
json = MultiJson.encode({
|
35
|
+
"queued" => Sqew.queued_jobs,
|
36
|
+
"failed" => Sqew.failed_jobs,
|
37
|
+
"running" => Sqew.running_jobs,
|
38
|
+
"workers" => @manager.max_workers
|
39
|
+
})
|
40
|
+
[200, {"Content-Type" => "application/json"}, json]
|
41
|
+
end
|
42
|
+
|
43
|
+
get "/ping" do
|
44
|
+
[200, {}, ""]
|
45
|
+
end
|
46
|
+
|
47
|
+
put "/workers" do
|
48
|
+
b = request.body.read
|
49
|
+
request.body.rewind
|
50
|
+
case b
|
51
|
+
when "pause"
|
52
|
+
@manager.pause_workers
|
53
|
+
when "resume"
|
54
|
+
@manager.resume_workers
|
55
|
+
else
|
56
|
+
@manager.max_workers = b.to_i
|
57
|
+
end
|
58
|
+
[200, {}, ""]
|
59
|
+
end
|
60
|
+
|
61
|
+
delete "/clear" do
|
62
|
+
b = request.body.read
|
63
|
+
request.body.rewind
|
64
|
+
Qu.clear(*b.split(","))
|
65
|
+
[200, {}, ""]
|
66
|
+
end
|
67
|
+
|
68
|
+
delete "/:id" do
|
69
|
+
Qu.backend.delete(params[:id])
|
70
|
+
[200, {}, ""]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/sqew/tasks.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :sqew do
|
2
|
+
desc "Start a worker"
|
3
|
+
task :work => :environment do
|
4
|
+
Sqew::Manager.new.start
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Convenience tasks compatibility
|
9
|
+
task 'jobs:work' => 'sqew:work'
|
10
|
+
task 'resque:work' => 'sqew:work'
|
11
|
+
|
12
|
+
# No-op task in case it doesn't already exist
|
13
|
+
task :environment
|
data/lib/sqew/version.rb
ADDED
data/lib/sqew/worker.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Sqew
|
2
|
+
class Worker
|
3
|
+
include Qu::Logger
|
4
|
+
|
5
|
+
def fork_job(job)
|
6
|
+
rd, wr = IO.pipe
|
7
|
+
pid = fork do
|
8
|
+
srand
|
9
|
+
rd.close
|
10
|
+
job.perform_forked(wr)
|
11
|
+
# catch special exception and regular/internalXS ones
|
12
|
+
# add logging to regular (internal)
|
13
|
+
end
|
14
|
+
|
15
|
+
Thread.new do
|
16
|
+
begin
|
17
|
+
Process.wait(pid)
|
18
|
+
wr.close
|
19
|
+
Marshal.load(rd.read)
|
20
|
+
ensure
|
21
|
+
rd.close
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/tasks.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :sqew do
|
2
|
+
desc "Start a worker"
|
3
|
+
task :work => :environment do
|
4
|
+
Sqew::Manager.new.start
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Convenience tasks compatibility
|
9
|
+
task 'jobs:work' => 'sqew:work'
|
10
|
+
task 'resque:work' => 'sqew:work'
|
11
|
+
|
12
|
+
# No-op task in case it doesn't already exist
|
13
|
+
task :environment
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sqew::Backend::LevelDB do
|
4
|
+
before do
|
5
|
+
@backend = Sqew::Backend::LevelDB.new()
|
6
|
+
@backend.db = DB_PATH
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should report queued jobs" do
|
10
|
+
Timecop.freeze(Time.utc(2012, 5, 2)) { @backend.enqueue(Qu::Payload.new(:klass => TestJob, :args => 1)) }
|
11
|
+
Timecop.freeze(Time.utc(2012, 5, 3)) { @backend.enqueue(Qu::Payload.new(:klass => TestJob, :args => 2)) }
|
12
|
+
Timecop.freeze(Time.utc(2012, 5, 1)) { @backend.enqueue(Qu::Payload.new(:klass => TestJob, :args => 3)) }
|
13
|
+
@backend.queued_jobs.should == [
|
14
|
+
{"klass"=>"TestJob", "args"=>3, "id" => "1335830400.0"},
|
15
|
+
{"klass"=>"TestJob", "args"=>1, "id" => "1335916800.0"},
|
16
|
+
{"klass"=>"TestJob", "args"=>2, "id" => "1336003200.0"}
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should report the failed jobs" do
|
21
|
+
error1 = double("error1", :message => "some error1", :backtrace => ["backtrace1"])
|
22
|
+
error2 = double("error2", :message => "some error2", :backtrace => ["backtrace2"])
|
23
|
+
error3 = double("error3", :message => "some error3", :backtrace => ["backtrace3"])
|
24
|
+
|
25
|
+
@backend.failed(Qu::Payload.new(:klass => TestJob, :args => 1, :id => "2"), error2)
|
26
|
+
@backend.failed(Qu::Payload.new(:klass => TestJob, :args => 1, :id => "3"), error3)
|
27
|
+
@backend.failed(Qu::Payload.new(:klass => TestJob, :args => 1, :id => "1"), error1)
|
28
|
+
|
29
|
+
@backend.failed_jobs.should == [
|
30
|
+
{"klass"=>"TestJob", "args"=>1, "id" => "1", "error" => "some error1", "backtrace" => "backtrace1"},
|
31
|
+
{"klass"=>"TestJob", "args"=>1, "id" => "2", "error" => "some error2", "backtrace" => "backtrace2"},
|
32
|
+
{"klass"=>"TestJob", "args"=>1, "id" => "3", "error" => "some error3", "backtrace" => "backtrace3"}
|
33
|
+
]
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should report running jobs" do
|
38
|
+
Timecop.freeze(Time.utc(2012, 5, 2)) { @backend.enqueue(Qu::Payload.new(:klass => TestJob, :args => 1)) }
|
39
|
+
Timecop.freeze(Time.utc(2012, 5, 3)) { @backend.enqueue(Qu::Payload.new(:klass => TestJob, :args => 2)) }
|
40
|
+
|
41
|
+
job = @backend.reserve(nil)
|
42
|
+
@backend.queued_jobs.should == [{"klass"=>"TestJob", "args"=>2, "id" => "1336003200.0"}]
|
43
|
+
@backend.running_jobs.should == [{"klass"=>"TestJob", "args"=>1, "id" => "1335916800.0"}]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should allow jobs to be deleted" do
|
47
|
+
error1 = double("error1", :message => "some error1", :backtrace => ["backtrace1"])
|
48
|
+
@backend.failed(Qu::Payload.new(:klass => TestJob, :args => 1, :id => "1"), error1)
|
49
|
+
Timecop.freeze(Time.utc(2012, 5, 2)) { @backend.enqueue(Qu::Payload.new(:klass => TestJob, :args => 1)) }
|
50
|
+
|
51
|
+
@backend.delete("1")
|
52
|
+
@backend.failed_jobs.should == []
|
53
|
+
|
54
|
+
@backend.delete("1335916800.0")
|
55
|
+
@backend.queued_jobs.should == []
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sqew::Manager do
|
4
|
+
it "allows the server to be started and stopped" do
|
5
|
+
manager = Sqew::Manager.new(9962)
|
6
|
+
manager.start_server
|
7
|
+
sleep 1
|
8
|
+
response = Net::HTTP.get_response(URI.parse("http://0.0.0.0:9962/ping"))
|
9
|
+
response.code.should == "200"
|
10
|
+
|
11
|
+
manager.stop_server
|
12
|
+
sleep 1
|
13
|
+
expect { Net::HTTP.get_response(URI.parse("http://0.0.0.0:9962/ping")) }.to raise_error(SystemCallError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows queues to be cleared" do
|
17
|
+
Qu.enqueue(FailJob, -1)
|
18
|
+
manager = Sqew::Manager.new
|
19
|
+
manager.work_off
|
20
|
+
Qu.enqueue(TestJob, 5)
|
21
|
+
Qu.clear
|
22
|
+
Sqew.queued_jobs.should == []
|
23
|
+
Sqew.failed_jobs.should == []
|
24
|
+
end
|
25
|
+
|
26
|
+
it "allows queues to be cleared by name" do
|
27
|
+
Qu.enqueue(FailJob, -1)
|
28
|
+
manager = Sqew::Manager.new
|
29
|
+
manager.work_off
|
30
|
+
Qu.enqueue(TestJob, 5)
|
31
|
+
Qu.clear("failed")
|
32
|
+
Sqew.queued_jobs.should_not == []
|
33
|
+
Sqew.failed_jobs.should == []
|
34
|
+
end
|
35
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sqew::Server do
|
4
|
+
before do
|
5
|
+
@old_backend = Sqew.backend
|
6
|
+
Sqew.backend = Sqew::Backend::Immediate.new
|
7
|
+
|
8
|
+
@manager = Sqew::Manager.new(3)
|
9
|
+
Artifice.activate_with(Sqew::Server.new(@manager))
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
Artifice.deactivate
|
14
|
+
Sqew.backend = @old_backend
|
15
|
+
end
|
16
|
+
|
17
|
+
it "enqueues jobs" do
|
18
|
+
TestJob.testing.should == 0
|
19
|
+
Sqew.push("TestJob", 15)
|
20
|
+
TestJob.testing.should == 15
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows pings" do
|
24
|
+
response = Sqew.ping
|
25
|
+
response.code.should == "200"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the status" do
|
29
|
+
status = Sqew.status
|
30
|
+
status.should == {"queued" => [], "running" => [], "failed" => [], "workers" => 3}
|
31
|
+
end
|
32
|
+
|
33
|
+
it "allows workers to be configured" do
|
34
|
+
response = Sqew.set_workers(5)
|
35
|
+
response.code.should == "200"
|
36
|
+
@manager.max_workers.should == 5
|
37
|
+
|
38
|
+
Sqew.workers = 2
|
39
|
+
@manager.max_workers.should == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
it "allows workers to be paused" do
|
43
|
+
Sqew.workers = :pause
|
44
|
+
@manager.max_workers.should == 0
|
45
|
+
|
46
|
+
Sqew.workers = :resume
|
47
|
+
@manager.max_workers.should == 3
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should remember the number of workers, even if paused multiple times" do
|
51
|
+
Sqew.workers = :pause
|
52
|
+
Sqew.workers = :pause
|
53
|
+
Sqew.workers = :resume
|
54
|
+
@manager.max_workers.should == 3
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should allow queues to be cleared" do
|
58
|
+
Qu.should_receive(:clear).with("queued", "failed")
|
59
|
+
Sqew.clear("queued", "failed")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should allow all queues to be cleared" do
|
63
|
+
Qu.should_receive(:clear).with()
|
64
|
+
Sqew.clear
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should allow jobs to be deleted" do
|
68
|
+
Qu.backend.should_receive(:delete).with("10")
|
69
|
+
Sqew.delete("10")
|
70
|
+
end
|
71
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "sqew"
|
3
|
+
require "fileutils"
|
4
|
+
require "timeout"
|
5
|
+
require "artifice"
|
6
|
+
require "timecop"
|
7
|
+
require "sqew/backend/immediate"
|
8
|
+
|
9
|
+
Dir[File.expand_path("support/*.rb", File.dirname(__FILE__))].each {|r| require r}
|
10
|
+
|
11
|
+
DB_PATH = File.expand_path("./tmp/db", File.dirname(__FILE__))
|
12
|
+
|
13
|
+
Sqew.configure do |c|
|
14
|
+
c.db = DB_PATH
|
15
|
+
c.logger.level = Logger::UNKNOWN
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |c|
|
19
|
+
c.debug = true
|
20
|
+
|
21
|
+
c.before do
|
22
|
+
Sqew.backend.close
|
23
|
+
FileUtils.rm_rf(DB_PATH)
|
24
|
+
FileUtils.mkdir_p(DB_PATH)
|
25
|
+
TestJob.reset
|
26
|
+
Timecop.return
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def save_and_open_page(response)
|
31
|
+
filename = File.expand_path("tmp/#{Digest::SHA1.hexdigest(Time.now.to_f.to_s)}.html", File.dirname(__FILE__))
|
32
|
+
File.open(filename, "w") do |file|
|
33
|
+
file << response.body
|
34
|
+
end
|
35
|
+
`open #{filename}`
|
36
|
+
end
|
data/spec/sqew_spec.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sqew do
|
4
|
+
it "allows jobs to be enqueued and worked" do
|
5
|
+
TestJob.testing.should == 0
|
6
|
+
Qu.enqueue(TestJob, 20)
|
7
|
+
manager = Sqew::Manager.new
|
8
|
+
manager.work_off
|
9
|
+
TestJob.testing.should == 20
|
10
|
+
Sqew.running_jobs.should == []
|
11
|
+
end
|
12
|
+
|
13
|
+
it "reports the size of the queue" do
|
14
|
+
Sqew.length.should == 0
|
15
|
+
Qu.enqueue(TestJob, 20)
|
16
|
+
Sqew.length.should == 1
|
17
|
+
end
|
18
|
+
|
19
|
+
it "provides failed jobs" do
|
20
|
+
Qu.enqueue(FailJob, -1)
|
21
|
+
manager = Sqew::Manager.new
|
22
|
+
manager.work_off
|
23
|
+
failed = Sqew.failed_jobs
|
24
|
+
failed.size.should == 1
|
25
|
+
failed[0]["klass"].should == "FailJob"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "provides running jobs" do
|
29
|
+
Qu.enqueue(SlowJob, 10)
|
30
|
+
manager = Sqew::Manager.new
|
31
|
+
begin
|
32
|
+
thread = Thread.new { manager.work_off }
|
33
|
+
sleep 1
|
34
|
+
running = Sqew.running_jobs
|
35
|
+
running.size.should == 1
|
36
|
+
running[0]["klass"].should == "SlowJob"
|
37
|
+
ensure
|
38
|
+
thread.terminate
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class TestJob
|
2
|
+
@testing = 0
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def perform(arg)
|
6
|
+
@testing = arg
|
7
|
+
end
|
8
|
+
|
9
|
+
def testing
|
10
|
+
@testing
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset
|
14
|
+
@testing = 0
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class FailJob
|
20
|
+
def self.perform(*)
|
21
|
+
raise "failed in FailJob"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class SlowJob
|
26
|
+
def self.perform(secs)
|
27
|
+
sleep secs
|
28
|
+
end
|
29
|
+
end
|
data/sqew.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sqew/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Zach Moazeni"]
|
6
|
+
gem.email = ["zach.moazeni@gmail.com"]
|
7
|
+
gem.description = "a lightweight background processor"
|
8
|
+
gem.summary = "sqew is a lightweight background processor. You start a single process that will act as a queue manager and will work multiple jobs concurrently.
|
9
|
+
|
10
|
+
sqew is not meant to be an all encompassing scalable solution. If you need more management over worker processes or need to split it among multiple machines, it's recommend to use other background processors such as resque, sidekiq, and qu"
|
11
|
+
|
12
|
+
gem.homepage = "https://github.com/zmoazeni/sqew"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.name = "sqew"
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.version = Sqew::VERSION
|
20
|
+
|
21
|
+
gem.add_dependency "leveldb-ruby", "~> 0.14"
|
22
|
+
gem.add_dependency "qu", "~> 0.1"
|
23
|
+
gem.add_dependency "multi_json", "~> 1.0"
|
24
|
+
gem.add_dependency "sinatra", "~> 1.0"
|
25
|
+
gem.add_dependency "thin", "~> 1.0"
|
26
|
+
gem.add_dependency "slave", "1.3"
|
27
|
+
|
28
|
+
gem.add_development_dependency "rspec"
|
29
|
+
gem.add_development_dependency "debugger"
|
30
|
+
gem.add_development_dependency "artifice"
|
31
|
+
gem.add_development_dependency "timecop"
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sqew
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Zach Moazeni
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: leveldb-ruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.14'
|
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: '0.14'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: qu
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.1'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.1'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: multi_json
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sinatra
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: thin
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: slave
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - '='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.3'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.3'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
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: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: debugger
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '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: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: artifice
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
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'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: timecop
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
description: a lightweight background processor
|
175
|
+
email:
|
176
|
+
- zach.moazeni@gmail.com
|
177
|
+
executables: []
|
178
|
+
extensions: []
|
179
|
+
extra_rdoc_files: []
|
180
|
+
files:
|
181
|
+
- .gitignore
|
182
|
+
- Gemfile
|
183
|
+
- LICENSE
|
184
|
+
- README.md
|
185
|
+
- Rakefile
|
186
|
+
- ext/qu.rb
|
187
|
+
- ext/slave.rb
|
188
|
+
- lib/sqew.rb
|
189
|
+
- lib/sqew/backend/immediate.rb
|
190
|
+
- lib/sqew/backend/leveldb.rb
|
191
|
+
- lib/sqew/manager.rb
|
192
|
+
- lib/sqew/payload.rb
|
193
|
+
- lib/sqew/rails.rb
|
194
|
+
- lib/sqew/railtie.rb
|
195
|
+
- lib/sqew/server.rb
|
196
|
+
- lib/sqew/tasks.rb
|
197
|
+
- lib/sqew/version.rb
|
198
|
+
- lib/sqew/worker.rb
|
199
|
+
- lib/tasks.rb
|
200
|
+
- spec/leveldb_spec.rb
|
201
|
+
- spec/manager_spec.rb
|
202
|
+
- spec/server_spec.rb
|
203
|
+
- spec/spec_helper.rb
|
204
|
+
- spec/sqew_spec.rb
|
205
|
+
- spec/support/jobs.rb
|
206
|
+
- spec/tmp/.gitkeep
|
207
|
+
- sqew.gemspec
|
208
|
+
homepage: https://github.com/zmoazeni/sqew
|
209
|
+
licenses: []
|
210
|
+
post_install_message:
|
211
|
+
rdoc_options: []
|
212
|
+
require_paths:
|
213
|
+
- lib
|
214
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
215
|
+
none: false
|
216
|
+
requirements:
|
217
|
+
- - ! '>='
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
version: '0'
|
220
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
221
|
+
none: false
|
222
|
+
requirements:
|
223
|
+
- - ! '>='
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
requirements: []
|
227
|
+
rubyforge_project:
|
228
|
+
rubygems_version: 1.8.21
|
229
|
+
signing_key:
|
230
|
+
specification_version: 3
|
231
|
+
summary: sqew is a lightweight background processor. You start a single process that
|
232
|
+
will act as a queue manager and will work multiple jobs concurrently. sqew is not
|
233
|
+
meant to be an all encompassing scalable solution. If you need more management over
|
234
|
+
worker processes or need to split it among multiple machines, it's recommend to
|
235
|
+
use other background processors such as resque, sidekiq, and qu
|
236
|
+
test_files:
|
237
|
+
- spec/leveldb_spec.rb
|
238
|
+
- spec/manager_spec.rb
|
239
|
+
- spec/server_spec.rb
|
240
|
+
- spec/spec_helper.rb
|
241
|
+
- spec/sqew_spec.rb
|
242
|
+
- spec/support/jobs.rb
|
243
|
+
- spec/tmp/.gitkeep
|