eq 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Guardfile +24 -0
- data/LICENSE +22 -0
- data/README.md +79 -0
- data/Rakefile +6 -0
- data/benchmarks/queueing.rb +33 -0
- data/benchmarks/working.rb +35 -0
- data/eq.gemspec +26 -0
- data/examples/queueing.rb +30 -0
- data/examples/simple_usage.rb +35 -0
- data/examples/working.rb +27 -0
- data/lib/eq/boot/all.rb +2 -0
- data/lib/eq/boot/queueing.rb +1 -0
- data/lib/eq/boot/working.rb +1 -0
- data/lib/eq/job.rb +25 -0
- data/lib/eq/logging.rb +15 -0
- data/lib/eq/version.rb +3 -0
- data/lib/eq-queueing/backends/sequel.rb +133 -0
- data/lib/eq-queueing/backends.rb +6 -0
- data/lib/eq-queueing/queue.rb +54 -0
- data/lib/eq-queueing.rb +30 -0
- data/lib/eq-working/manager.rb +31 -0
- data/lib/eq-working/system.rb +10 -0
- data/lib/eq-working/worker.rb +18 -0
- data/lib/eq-working.rb +24 -0
- data/lib/eq.rb +65 -0
- data/spec/lib/eq/job_spec.rb +26 -0
- data/spec/lib/eq-queueing/backends/sequel_spec.rb +23 -0
- data/spec/lib/eq-queueing/queue_spec.rb +67 -0
- data/spec/lib/eq_spec.rb +5 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/shared_examples_for_queue.rb +75 -0
- metadata +217 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
13
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
+
watch('config/routes.rb') { "spec/routing" }
|
15
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
+
|
17
|
+
# Capybara request specs
|
18
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
19
|
+
|
20
|
+
# Turnip features and steps
|
21
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
22
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
23
|
+
end
|
24
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jens Bissinger
|
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,79 @@
|
|
1
|
+
# EXPERIMENTAL FOO, THE DEVILS RIDE...
|
2
|
+
|
3
|
+
# EQ - Embedded Queueing
|
4
|
+
|
5
|
+
EQ is a little framework to queue tasks within a process.
|
6
|
+
|
7
|
+
[![Travis-CI Build Status](https://secure.travis-ci.org/dpree/eq.png)](https://secure.travis-ci.org/dpree/eq)
|
8
|
+
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/dpree/eq)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Install it yourself using Rubygems.
|
13
|
+
|
14
|
+
$ gem install eq
|
15
|
+
|
16
|
+
Or use something like [Bundler](http://gembundler.com/).
|
17
|
+
|
18
|
+
## Example
|
19
|
+
|
20
|
+
If you want to execute a simple example you can just run [examples/simple_usage.rb](./examples/simple_usage.rb) from your commandline.
|
21
|
+
|
22
|
+
**1. Define a Job class with a perform method.**
|
23
|
+
|
24
|
+
class MyJob
|
25
|
+
def self.perform *some_args
|
26
|
+
# do some long running stuff here
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
**2. Start the EQ system.**
|
31
|
+
|
32
|
+
EQ.boot
|
33
|
+
|
34
|
+
**3. Let EQ do some work you.**
|
35
|
+
|
36
|
+
EQ.queue.push MyJob, 'foo'
|
37
|
+
EQ.queue.push MyJob, 'bar'
|
38
|
+
…
|
39
|
+
|
40
|
+
## Configuration
|
41
|
+
|
42
|
+
Right now there is only one queueing backend available that is based on the Sequel gem. Therefore, basically any SQL database supported by Sequel might be used.
|
43
|
+
|
44
|
+
The default SQL database that is used is a . You can change it using any argument that Sequel.connect method would accept.
|
45
|
+
|
46
|
+
# SQLite3 in-memory (default) using String syntax
|
47
|
+
EQ.config.sequel = 'sqlite:/'
|
48
|
+
|
49
|
+
# SQLite3 file using Hash syntax
|
50
|
+
EQ.config.sequel = {adapter: 'sqlite', database: 'my_db.sqlite3'}
|
51
|
+
|
52
|
+
# Postgres
|
53
|
+
EQ.config.sequel = 'postgres://user:password@host:port/my_db'
|
54
|
+
|
55
|
+
|
56
|
+
### Logging
|
57
|
+
|
58
|
+
EQ uses the logging mechanism of the underlying Celluloid (`Celluloid.logger`) framework. Basically you can just bind it to your application logger or re-configure it (see the Documentation of the `Logger` class from Ruby Standard Library).
|
59
|
+
|
60
|
+
**Changing the Logger:**
|
61
|
+
|
62
|
+
# Use the logger of your Rails application.
|
63
|
+
Celluloid.logger = Rails.logger
|
64
|
+
|
65
|
+
# No more logging at all.
|
66
|
+
Celluloid.logger = Logger.new('/dev/null')
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create new Pull Request
|
75
|
+
|
76
|
+
# LICENSE
|
77
|
+
|
78
|
+
Copyright (c) 2012 Jens Bissinger. See [LICENSE](LICENSE).
|
79
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
|
4
|
+
|
5
|
+
require 'benchmark'
|
6
|
+
require 'tempfile'
|
7
|
+
|
8
|
+
EQ.logger.level = Logger::Severity::ERROR
|
9
|
+
|
10
|
+
class MyJob
|
11
|
+
def self.perform stuff
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
n = 1000
|
16
|
+
Benchmark.bm(50) do |b|
|
17
|
+
EQ.boot
|
18
|
+
b.report('memory-based sqlite') do
|
19
|
+
n.times { |i| EQ.queue.push! MyJob, i }
|
20
|
+
EQ.queue.waiting_count # block
|
21
|
+
end
|
22
|
+
EQ.shutdown
|
23
|
+
|
24
|
+
EQ.config do |config|
|
25
|
+
config[:sqlite] = Tempfile.new('').path
|
26
|
+
end
|
27
|
+
EQ.boot
|
28
|
+
b.report('file-based sqlite') do
|
29
|
+
n.times { |i| EQ.queue.push! MyJob, i }
|
30
|
+
EQ.queue.waiting_count # block
|
31
|
+
end
|
32
|
+
EQ.shutdown
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
|
4
|
+
|
5
|
+
require 'benchmark'
|
6
|
+
require 'tempfile'
|
7
|
+
|
8
|
+
EQ.logger.level = Logger::Severity::ERROR
|
9
|
+
|
10
|
+
class MyJob
|
11
|
+
def self.perform stuff
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
n = 500
|
16
|
+
Benchmark.bm(50) do |b|
|
17
|
+
EQ.boot_queueing
|
18
|
+
n.times { |i| EQ.queue.push! MyJob, i }
|
19
|
+
b.report('memory-based sqlite') do
|
20
|
+
EQ.boot_working
|
21
|
+
sleep 0.01 until EQ.queue.waiting_count == 0
|
22
|
+
end
|
23
|
+
EQ.shutdown
|
24
|
+
|
25
|
+
EQ.config do |config|
|
26
|
+
config[:sqlite] = Tempfile.new('').path
|
27
|
+
end
|
28
|
+
EQ.boot_queueing
|
29
|
+
n.times { |i| EQ.queue.push! MyJob, i }
|
30
|
+
b.report('file-based sqlite') do
|
31
|
+
EQ.boot_working
|
32
|
+
sleep 0.01 until EQ.queue.waiting_count == 0
|
33
|
+
end
|
34
|
+
EQ.shutdown
|
35
|
+
end
|
data/eq.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/eq/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Jens Bissinger"]
|
6
|
+
gem.email = ["mail@jens-bissinger.de"]
|
7
|
+
gem.description = %q{Embedded Queueing. Background processing within a single process using multi-threading and a SQL database.}
|
8
|
+
gem.summary = %q{Based on Celluloid (multi-threading) and Sequel (SQLite3, MySQL, PostgreSQL, ...).}
|
9
|
+
gem.homepage = "https://github.com/dpree/eq"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "eq"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = EQ::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "sqlite3"
|
19
|
+
gem.add_dependency "sequel"
|
20
|
+
gem.add_dependency "celluloid"
|
21
|
+
gem.add_development_dependency "guard"
|
22
|
+
gem.add_development_dependency "guard-rspec"
|
23
|
+
gem.add_development_dependency "rspec"
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency "timecop"
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'queueing')
|
3
|
+
|
4
|
+
EQ.logger.level = Logger::Severity::INFO
|
5
|
+
EQ.config do |config|
|
6
|
+
config[:sqlite] = "foo.sqlite"
|
7
|
+
end
|
8
|
+
|
9
|
+
def say words; EQ.logger.info(words); end
|
10
|
+
|
11
|
+
class SingleJob
|
12
|
+
def self.perform
|
13
|
+
sleep 0.05
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'timeout'
|
18
|
+
begin
|
19
|
+
Timeout.timeout(120) do
|
20
|
+
EQ.boot
|
21
|
+
loop do
|
22
|
+
say "pushed!"
|
23
|
+
sleep 0.05
|
24
|
+
EQ.queue.push! SingleJob
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue Timeout::Error
|
28
|
+
say "shutdown: #{EQ.shutdown}"
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
|
3
|
+
|
4
|
+
# Define a Job class with a perform method.
|
5
|
+
class MyJob
|
6
|
+
RESULT_PATH = 'my_job_result.txt'
|
7
|
+
|
8
|
+
def self.perform enqueued_at, workload_in_seconds
|
9
|
+
# do some long running stuff here
|
10
|
+
sleep workload_in_seconds
|
11
|
+
File.open RESULT_PATH, 'a' do |file|
|
12
|
+
finished_at = Time.now
|
13
|
+
file.puts "Processed a job with workload of #{workload_in_seconds}s:\n"\
|
14
|
+
" - enqueued at = #{enqueued_at}\n"\
|
15
|
+
" - finished at = #{finished_at}\n"\
|
16
|
+
" - actual workload = #{finished_at - enqueued_at}s"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Cleanup results file.
|
22
|
+
File.delete MyJob::RESULT_PATH if File.exists? MyJob::RESULT_PATH
|
23
|
+
|
24
|
+
# Start the EQ system.
|
25
|
+
EQ.boot
|
26
|
+
|
27
|
+
# Enqueue some work.
|
28
|
+
EQ.queue.push MyJob, Time.now, 1
|
29
|
+
EQ.queue.push MyJob, Time.now, 2
|
30
|
+
|
31
|
+
# Wait some time to get the work done.
|
32
|
+
sleep 3
|
33
|
+
|
34
|
+
# Read the results file.
|
35
|
+
puts File.read MyJob::RESULT_PATH
|
data/examples/working.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
|
3
|
+
|
4
|
+
EQ.logger.level = Logger::Severity::INFO
|
5
|
+
EQ.config do |config|
|
6
|
+
config[:sqlite] = "foo.sqlite"
|
7
|
+
end
|
8
|
+
|
9
|
+
def say words; EQ.logger.info(words); end
|
10
|
+
|
11
|
+
class SingleJob
|
12
|
+
def self.perform
|
13
|
+
say "worked!"
|
14
|
+
sleep 0.05
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'timeout'
|
19
|
+
begin
|
20
|
+
Timeout.timeout(120) do
|
21
|
+
EQ.boot
|
22
|
+
sleep 0.5 while EQ.working?
|
23
|
+
end
|
24
|
+
rescue Timeout::Error
|
25
|
+
say "shutdown: #{EQ.shutdown}"
|
26
|
+
end
|
27
|
+
|
data/lib/eq/boot/all.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'eq-queueing')
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'eq-working')
|
data/lib/eq/job.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module EQ
|
2
|
+
class Job < Struct.new(:id, :serialized_payload)
|
3
|
+
class << self
|
4
|
+
def dump *unserialized_payload
|
5
|
+
Marshal.dump(unserialized_payload.flatten)
|
6
|
+
end
|
7
|
+
|
8
|
+
def load id, serialized_payload
|
9
|
+
Job.new id, serialized_payload
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# unmarshals the serialized_payload
|
14
|
+
def unpack
|
15
|
+
#[const_name.split("::").inject(Kernel){|res,current| res.const_get(current)}, *payload]
|
16
|
+
Marshal.load(serialized_payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
# calls MyJobClass.perform(*payload)
|
20
|
+
def perform
|
21
|
+
const, *payload = unpack
|
22
|
+
const.perform *payload
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/eq/logging.rb
ADDED
data/lib/eq/version.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module EQ::Queueing::Backends
|
4
|
+
|
5
|
+
# this class provides a queueing backend via Sequel ORM mapper
|
6
|
+
# basically any database adapter known by Sequel is supported
|
7
|
+
# configure via EQ::conig[:sequel]
|
8
|
+
class Sequel
|
9
|
+
include EQ::Logging
|
10
|
+
|
11
|
+
TABLE_NAME = :jobs
|
12
|
+
|
13
|
+
attr_reader :db
|
14
|
+
|
15
|
+
# establishes the connection to the database and ensures that
|
16
|
+
# the jobs table is created
|
17
|
+
def initialize config
|
18
|
+
connect config
|
19
|
+
create_table_if_not_exists!
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [#to_sequel_block] payload
|
23
|
+
# @return [Fixnum] id of the job
|
24
|
+
def push payload
|
25
|
+
jobs.insert payload: payload.to_sequel_blob, created_at: Time.now
|
26
|
+
rescue ::Sequel::DatabaseError => e
|
27
|
+
retry if on_error e
|
28
|
+
end
|
29
|
+
|
30
|
+
# pulls a job from the waiting stack and moves it to the
|
31
|
+
# working stack. sets a timestamp :started_working_at so that
|
32
|
+
# the working duration can be tracked.
|
33
|
+
# @param [Time] now
|
34
|
+
# @return [Array<Fixnum, String>] job data consisting of id and payload
|
35
|
+
def reserve
|
36
|
+
db.transaction do
|
37
|
+
if job = waiting.order(:id.asc).limit(1).first
|
38
|
+
job[:started_working_at] = Time.now
|
39
|
+
update_job!(job)
|
40
|
+
[job[:id], job[:payload]]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue ::Sequel::DatabaseError => e
|
44
|
+
retry if on_error e
|
45
|
+
end
|
46
|
+
|
47
|
+
# finishes a job in the working queue
|
48
|
+
# @param [Fixnum] id of the job
|
49
|
+
# @return [TrueClass, FalseClass] true, when there was a job that could be deleted
|
50
|
+
def pop id
|
51
|
+
jobs.where(id: id).delete == 1
|
52
|
+
rescue ::Sequel::DatabaseError => e
|
53
|
+
retry if on_error e
|
54
|
+
end
|
55
|
+
|
56
|
+
# list of jobs waiting to be worked on
|
57
|
+
def waiting
|
58
|
+
jobs.where(started_working_at: nil)
|
59
|
+
rescue ::Sequel::DatabaseError => e
|
60
|
+
retry if on_error e
|
61
|
+
end
|
62
|
+
|
63
|
+
# list of jobs currentyl being worked on
|
64
|
+
def working
|
65
|
+
waiting.invert
|
66
|
+
rescue ::Sequel::DatabaseError => e
|
67
|
+
retry if on_error e
|
68
|
+
end
|
69
|
+
|
70
|
+
# list of all jobs
|
71
|
+
def jobs
|
72
|
+
db[TABLE_NAME]
|
73
|
+
rescue ::Sequel::DatabaseError => e
|
74
|
+
retry if on_error e
|
75
|
+
end
|
76
|
+
|
77
|
+
# updates a changed job object, uses the :id key to identify the job
|
78
|
+
# @param [Hash] changed job
|
79
|
+
def update_job! changed_job
|
80
|
+
jobs.where(id: changed_job[:id]).update(changed_job)
|
81
|
+
rescue ::Sequel::DatabaseError => e
|
82
|
+
retry if on_error e
|
83
|
+
end
|
84
|
+
|
85
|
+
# this re-enqueues jobs that timed out
|
86
|
+
# @return [Fixnum] number of jobs that were re-enqueued
|
87
|
+
def requeue_timed_out_jobs
|
88
|
+
# 10 seconds ago
|
89
|
+
jobs.where{started_working_at <= (Time.now - EQ.config.job_timeout)}\
|
90
|
+
.update(started_working_at: nil)
|
91
|
+
end
|
92
|
+
|
93
|
+
# statistics:
|
94
|
+
# - #job_count
|
95
|
+
# - #working_count
|
96
|
+
# - #waiting_count
|
97
|
+
%w[ job working waiting ].each do |stats_name|
|
98
|
+
define_method "#{stats_name}_count" do
|
99
|
+
begin
|
100
|
+
send(stats_name).send(:count)
|
101
|
+
rescue ::Sequel::DatabaseError => e
|
102
|
+
retry if on_error e
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# connects to the given database config
|
110
|
+
def connect config
|
111
|
+
@db = ::Sequel.connect config
|
112
|
+
rescue ::Sequel::DatabaseError => e
|
113
|
+
retry if on_error e
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_table_if_not_exists!
|
117
|
+
db.create_table? TABLE_NAME do
|
118
|
+
primary_key :id
|
119
|
+
Timestamp :created_at
|
120
|
+
Timestamp :started_working_at
|
121
|
+
Blob :payload
|
122
|
+
end
|
123
|
+
rescue ::Sequel::DatabaseError => e
|
124
|
+
retry if on_error e
|
125
|
+
end
|
126
|
+
|
127
|
+
def on_error error
|
128
|
+
log_error error.inspect
|
129
|
+
sleep 0.05
|
130
|
+
true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module EQ::Queueing
|
2
|
+
|
3
|
+
# this class basically provides a API that wraps the low-level calls
|
4
|
+
# to the queueing backend that is configured and passed to the #initialize method
|
5
|
+
# furthermore this class adds some functionality to serialize / deserialze
|
6
|
+
# using the Job class
|
7
|
+
class Queue
|
8
|
+
include Celluloid
|
9
|
+
include EQ::Logging
|
10
|
+
|
11
|
+
%w[ job_count waiting_count working_count waiting working ].each do |method_name|
|
12
|
+
define_method method_name do
|
13
|
+
queue.send(method_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias :size :job_count
|
17
|
+
|
18
|
+
# @param [Object] queue_backend
|
19
|
+
def initialize queue_backend
|
20
|
+
@queue = queue_backend
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [Array<Class, *payload>] unserialized_payload
|
24
|
+
# @return [Fixnum] job_id
|
25
|
+
def push *unserialized_payload
|
26
|
+
debug "enqueing #{unserialized_payload.inspect} ..."
|
27
|
+
queue.push EQ::Job.dump(unserialized_payload)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [EQ::Job, nilClass] job instance
|
31
|
+
def reserve
|
32
|
+
requeue_timed_out_jobs
|
33
|
+
if serialized_job = queue.reserve
|
34
|
+
job_id, serialized_payload = *serialized_job
|
35
|
+
job = EQ::Job.load job_id, serialized_payload
|
36
|
+
debug "dequeud #{job.inspect}"
|
37
|
+
job
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [TrueClass, FalseClass]
|
42
|
+
def pop job_id
|
43
|
+
queue.pop job_id
|
44
|
+
end
|
45
|
+
|
46
|
+
# re-enqueues jobs that timed out
|
47
|
+
def requeue_timed_out_jobs
|
48
|
+
queue.requeue_timed_out_jobs
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :queue
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/eq-queueing.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'eq')
|
2
|
+
require File.join(File.dirname(__FILE__), 'eq-queueing', 'backends')
|
3
|
+
require File.join(File.dirname(__FILE__), 'eq-queueing', 'queue')
|
4
|
+
|
5
|
+
module EQ::Queueing
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def boot
|
9
|
+
EQ::Queueing::Queue.supervise_as :_eq_queueing, initialize_queueing_backend
|
10
|
+
end
|
11
|
+
|
12
|
+
def shutdown
|
13
|
+
queue.terminate! if queue
|
14
|
+
end
|
15
|
+
|
16
|
+
def queue
|
17
|
+
Celluloid::Actor[:_eq_queueing]
|
18
|
+
end
|
19
|
+
|
20
|
+
# @raise ConfigurationError when EQ.config.queue is not supported
|
21
|
+
def initialize_queueing_backend
|
22
|
+
queue_config = EQ.config.send(EQ.config.queue)
|
23
|
+
case EQ.config.queue
|
24
|
+
when 'sequel'
|
25
|
+
EQ::Queueing::Backends::Sequel.new queue_config
|
26
|
+
else
|
27
|
+
raise EQ::ConfigurationError, "EQ.config.queue = '#{EQ.config.queue}' is not supported!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module EQ::Working
|
2
|
+
class Manager
|
3
|
+
include Celluloid
|
4
|
+
include EQ::Logging
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
run!
|
8
|
+
end
|
9
|
+
|
10
|
+
# polls the EQ.queue via EQ.queue.reserve
|
11
|
+
def run
|
12
|
+
debug "worker manager running"
|
13
|
+
loop do
|
14
|
+
if EQ.queue && job = EQ.queue.reserve
|
15
|
+
debug "got #{job.inspect}"
|
16
|
+
if worker = EQ::Working.worker_pool
|
17
|
+
debug ' - found worker'
|
18
|
+
worker.process! job
|
19
|
+
else
|
20
|
+
debug ' - no worker'
|
21
|
+
end
|
22
|
+
else
|
23
|
+
# currently no job
|
24
|
+
end
|
25
|
+
sleep 0.01
|
26
|
+
end
|
27
|
+
rescue Celluloid::DeadActorError
|
28
|
+
retry
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module EQ::Working
|
2
|
+
class System < Celluloid::SupervisionGroup
|
3
|
+
include EQ::Logging
|
4
|
+
|
5
|
+
# TODO celluloid: replace this with the following in the next version
|
6
|
+
# pool Worker, as: _eq_working_pool
|
7
|
+
supervise EQ::Working::Worker, as: :_eq_working_pool, method: 'pool_link'
|
8
|
+
supervise EQ::Working::Manager, as: :_eq_working_manager
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module EQ::Working
|
2
|
+
class Worker
|
3
|
+
include Celluloid
|
4
|
+
include EQ::Logging
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
debug "initialized worker"
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param [EQ::Job] job instance
|
11
|
+
# @return [TrueClass, FalseClass] true when job is done and deleted
|
12
|
+
def process job
|
13
|
+
debug "processing #{job.inspect}"
|
14
|
+
job.perform
|
15
|
+
EQ.queue.pop job.id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/eq-working.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'eq')
|
2
|
+
require File.join(File.dirname(__FILE__), 'eq-working', 'worker')
|
3
|
+
require File.join(File.dirname(__FILE__), 'eq-working', 'manager')
|
4
|
+
require File.join(File.dirname(__FILE__), 'eq-working', 'system')
|
5
|
+
|
6
|
+
module EQ::Working
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def boot
|
10
|
+
Celluloid::Actor[:_eq_working] = EQ::Working::System.run!
|
11
|
+
end
|
12
|
+
|
13
|
+
def shutdown
|
14
|
+
worker.finalize! if worker
|
15
|
+
end
|
16
|
+
|
17
|
+
def worker
|
18
|
+
Celluloid::Actor[:_eq_working]
|
19
|
+
end
|
20
|
+
|
21
|
+
def worker_pool
|
22
|
+
Celluloid::Actor[:_eq_working_pool]
|
23
|
+
end
|
24
|
+
end
|
data/lib/eq.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'celluloid'
|
3
|
+
|
4
|
+
require File.join(File.dirname(__FILE__), 'eq', 'version')
|
5
|
+
require File.join(File.dirname(__FILE__), 'eq', 'logging')
|
6
|
+
require File.join(File.dirname(__FILE__), 'eq', 'job')
|
7
|
+
|
8
|
+
module EQ
|
9
|
+
class ConfigurationError < ArgumentError; end
|
10
|
+
|
11
|
+
DEFAULT_CONFIG = {
|
12
|
+
queue: 'sequel',
|
13
|
+
sequel: 'sqlite:/',
|
14
|
+
job_timeout: 5 # in seconds
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
module_function
|
18
|
+
|
19
|
+
def config
|
20
|
+
@config ||= OpenStruct.new DEFAULT_CONFIG
|
21
|
+
yield @config if block_given?
|
22
|
+
@config
|
23
|
+
end
|
24
|
+
|
25
|
+
# this boots queuing and working
|
26
|
+
# optional: to use another queuing or working subsystem just do
|
27
|
+
# require 'eq/working' or require 'eq/queueing' instead of require 'eq/all'
|
28
|
+
def boot
|
29
|
+
boot_queueing if defined? EQ::Queueing
|
30
|
+
boot_working if defined? EQ::Working
|
31
|
+
end
|
32
|
+
|
33
|
+
def shutdown
|
34
|
+
EQ::Working.shutdown if defined? EQ::Working
|
35
|
+
EQ::Queueing.shutdown if defined? EQ::Queueing
|
36
|
+
end
|
37
|
+
|
38
|
+
def boot_queueing
|
39
|
+
EQ::Queueing.boot
|
40
|
+
end
|
41
|
+
|
42
|
+
def boot_working
|
43
|
+
EQ::Working.boot
|
44
|
+
end
|
45
|
+
|
46
|
+
def queue
|
47
|
+
EQ::Queueing.queue
|
48
|
+
end
|
49
|
+
|
50
|
+
def worker
|
51
|
+
EQ::Working.worker
|
52
|
+
end
|
53
|
+
|
54
|
+
def queueing?
|
55
|
+
queue.alive?
|
56
|
+
end
|
57
|
+
|
58
|
+
def working?
|
59
|
+
worker.alive?
|
60
|
+
end
|
61
|
+
|
62
|
+
def logger
|
63
|
+
Celluloid.logger
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EQ::Job do |variable|
|
4
|
+
it 'dumps const and payload' do
|
5
|
+
payload = EQ::Job.dump([EQ, 'bar', 'baz'])
|
6
|
+
Marshal.load(payload).should == [EQ, 'bar', 'baz']
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'loads const and payload' do
|
10
|
+
serialized_payload = Marshal.dump [EQ, 'foo', 'bar']
|
11
|
+
job = EQ::Job.load(1, serialized_payload)
|
12
|
+
job.unpack.should == [EQ, 'foo', 'bar']
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'performs using const.perform(*payload)' do
|
16
|
+
class MyJob
|
17
|
+
def self.perform(*args)
|
18
|
+
{result: args}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
my_job_args = [1,2,3]
|
22
|
+
serialized_payload = EQ::Job.dump(MyJob, *my_job_args)
|
23
|
+
job = EQ::Job.load(1, serialized_payload)
|
24
|
+
job.perform.should == {result: my_job_args}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EQ::Queueing::Backends::Sequel do
|
4
|
+
subject { EQ::Queueing::Backends::Sequel.new 'sqlite:/' }
|
5
|
+
it_behaves_like 'abstract queue'
|
6
|
+
it_behaves_like 'queue backend'
|
7
|
+
|
8
|
+
it 'handles ::Sequel::DatabaseError with retry' do
|
9
|
+
db_method = subject.instance_eval('method(:jobs)')
|
10
|
+
raised = false
|
11
|
+
subject.stub(:jobs).and_return do |arg|
|
12
|
+
if raised
|
13
|
+
db_method.call
|
14
|
+
else
|
15
|
+
raised = true
|
16
|
+
raise ::Sequel::DatabaseError, "failed"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
subject.waiting_count.should == 0
|
20
|
+
subject.push "foo"
|
21
|
+
subject.waiting_count.should == 1
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EQ::Queueing::Queue do
|
4
|
+
let(:queue_backend) do
|
5
|
+
Class.new(Struct.new(:waiting, :working)) do
|
6
|
+
def push payload
|
7
|
+
raise ArgumentError, "queue_backend mock only supports one waiting job at a time" if waiting
|
8
|
+
self.waiting = [1, payload]
|
9
|
+
1
|
10
|
+
end
|
11
|
+
|
12
|
+
def reserve
|
13
|
+
raise ArgumentError, "queue_backend mock only supports one working job at a time" if working
|
14
|
+
if self.working = waiting
|
15
|
+
self.working << Time.now
|
16
|
+
self.waiting = nil
|
17
|
+
return working
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def requeue_timed_out_jobs
|
22
|
+
raise ArgumentError, "queue_backend mock only supports on waiting job at a time" if waiting && working
|
23
|
+
# timeout after EQ.config.job_timeout seconds
|
24
|
+
if working && working.last <= (Time.now - EQ.config.job_timeout)
|
25
|
+
working.pop
|
26
|
+
self.waiting = working
|
27
|
+
self.working = nil
|
28
|
+
1
|
29
|
+
else
|
30
|
+
0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def pop id
|
35
|
+
result = false
|
36
|
+
|
37
|
+
if waiting && id == waiting.first
|
38
|
+
self.waiting = nil
|
39
|
+
result = true
|
40
|
+
end
|
41
|
+
|
42
|
+
if working && id == working.first
|
43
|
+
self.working = nil
|
44
|
+
result = true
|
45
|
+
end
|
46
|
+
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def waiting_count; waiting ? 1 : 0; end
|
51
|
+
def working_count; working ? 1 : 0; end
|
52
|
+
end.new
|
53
|
+
end
|
54
|
+
subject { EQ::Queueing::Queue.new(queue_backend) }
|
55
|
+
it_behaves_like 'abstract queue'
|
56
|
+
|
57
|
+
it 'serializes jobs' do
|
58
|
+
EQ::Job.should_receive(:dump).with(["foo"])
|
59
|
+
subject.push "foo"
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'deserializes jobs' do
|
63
|
+
subject.push "foo"
|
64
|
+
EQ::Job.should_receive(:load).with(1, EQ::Job.dump(["foo"]))
|
65
|
+
subject.reserve
|
66
|
+
end
|
67
|
+
end
|
data/spec/lib/eq_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
|
8
|
+
Dir[File.join(File.dirname(__FILE__), '/support/**/*.rb')].each {|f| require f; puts f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
|
15
|
+
# Run specs in random order to surface order dependencies. If you find an
|
16
|
+
# order dependency and want to debug it, you can fix the order by providing
|
17
|
+
# the seed, which is printed after each run.
|
18
|
+
# --seed 1234
|
19
|
+
config.order = 'random'
|
20
|
+
end
|
21
|
+
|
22
|
+
require "timecop"
|
@@ -0,0 +1,75 @@
|
|
1
|
+
shared_examples_for 'queue backend' do
|
2
|
+
it 'pushes and pops' do
|
3
|
+
subject.push "foo"
|
4
|
+
job_id, payload = *subject.reserve
|
5
|
+
job_id.should == 1
|
6
|
+
payload.should == "foo"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
shared_examples_for 'abstract queue' do
|
11
|
+
it 'pushes jobs' do
|
12
|
+
subject.waiting_count.should == 0
|
13
|
+
subject.working_count.should == 0
|
14
|
+
subject.push("foo").should == 1 # job id
|
15
|
+
subject.waiting_count.should == 1
|
16
|
+
subject.working_count.should == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'reserves jobs' do
|
20
|
+
id = subject.push "foo"
|
21
|
+
subject.reserve
|
22
|
+
subject.waiting_count.should == 0
|
23
|
+
subject.working_count.should == 1
|
24
|
+
subject.pop id
|
25
|
+
subject.waiting_count.should == 0
|
26
|
+
subject.working_count.should == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'pops jobs' do
|
30
|
+
subject.pop(1).should be_false # no job
|
31
|
+
subject.push "foo"
|
32
|
+
subject.pop(1).should be_true # one job
|
33
|
+
subject.pop(1).should be_false # again no job"
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'puts working job back on waiting when they timeout via #requeue_timed_out_jobs' do
|
37
|
+
# freeze time on start of 1986
|
38
|
+
Timecop.freeze(Time.new(1986)) do
|
39
|
+
|
40
|
+
# create a job
|
41
|
+
id = subject.push "foo"
|
42
|
+
|
43
|
+
# start working
|
44
|
+
data = subject.reserve
|
45
|
+
|
46
|
+
# no on working at the beginning
|
47
|
+
subject.waiting_count.should == 0
|
48
|
+
subject.working_count.should == 1
|
49
|
+
|
50
|
+
# no one will be re-enqueued
|
51
|
+
subject.requeue_timed_out_jobs.should == 0
|
52
|
+
|
53
|
+
# no on working after senseless re-enqueueing
|
54
|
+
subject.waiting_count.should == 0
|
55
|
+
subject.working_count.should == 1
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# freeze the time to 10s in the future
|
60
|
+
Timecop.freeze(Time.new(1986, 01, 01, 00, 00, EQ.config.job_timeout)) do
|
61
|
+
|
62
|
+
# nothing happened yet...
|
63
|
+
subject.waiting_count.should == 0
|
64
|
+
subject.working_count.should == 1
|
65
|
+
|
66
|
+
# this time one will be re-enqueued
|
67
|
+
subject.requeue_timed_out_jobs.should == 1
|
68
|
+
|
69
|
+
# now the old job is available again
|
70
|
+
subject.waiting_count.should == 1
|
71
|
+
subject.working_count.should == 0
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jens Bissinger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sqlite3
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
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'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sequel
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
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'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: celluloid
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '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: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '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: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
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'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
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'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
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: timecop
|
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
|
+
description: Embedded Queueing. Background processing within a single process using
|
143
|
+
multi-threading and a SQL database.
|
144
|
+
email:
|
145
|
+
- mail@jens-bissinger.de
|
146
|
+
executables: []
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- .gitignore
|
151
|
+
- .rspec
|
152
|
+
- .travis.yml
|
153
|
+
- Gemfile
|
154
|
+
- Guardfile
|
155
|
+
- LICENSE
|
156
|
+
- README.md
|
157
|
+
- Rakefile
|
158
|
+
- benchmarks/queueing.rb
|
159
|
+
- benchmarks/working.rb
|
160
|
+
- eq.gemspec
|
161
|
+
- examples/queueing.rb
|
162
|
+
- examples/simple_usage.rb
|
163
|
+
- examples/working.rb
|
164
|
+
- lib/eq-queueing.rb
|
165
|
+
- lib/eq-queueing/backends.rb
|
166
|
+
- lib/eq-queueing/backends/sequel.rb
|
167
|
+
- lib/eq-queueing/queue.rb
|
168
|
+
- lib/eq-working.rb
|
169
|
+
- lib/eq-working/manager.rb
|
170
|
+
- lib/eq-working/system.rb
|
171
|
+
- lib/eq-working/worker.rb
|
172
|
+
- lib/eq.rb
|
173
|
+
- lib/eq/boot/all.rb
|
174
|
+
- lib/eq/boot/queueing.rb
|
175
|
+
- lib/eq/boot/working.rb
|
176
|
+
- lib/eq/job.rb
|
177
|
+
- lib/eq/logging.rb
|
178
|
+
- lib/eq/version.rb
|
179
|
+
- spec/lib/eq-queueing/backends/sequel_spec.rb
|
180
|
+
- spec/lib/eq-queueing/queue_spec.rb
|
181
|
+
- spec/lib/eq/job_spec.rb
|
182
|
+
- spec/lib/eq_spec.rb
|
183
|
+
- spec/spec_helper.rb
|
184
|
+
- spec/support/shared_examples_for_queue.rb
|
185
|
+
homepage: https://github.com/dpree/eq
|
186
|
+
licenses: []
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
none: false
|
193
|
+
requirements:
|
194
|
+
- - ! '>='
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
|
+
none: false
|
199
|
+
requirements:
|
200
|
+
- - ! '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubyforge_project:
|
205
|
+
rubygems_version: 1.8.23
|
206
|
+
signing_key:
|
207
|
+
specification_version: 3
|
208
|
+
summary: Based on Celluloid (multi-threading) and Sequel (SQLite3, MySQL, PostgreSQL,
|
209
|
+
...).
|
210
|
+
test_files:
|
211
|
+
- spec/lib/eq-queueing/backends/sequel_spec.rb
|
212
|
+
- spec/lib/eq-queueing/queue_spec.rb
|
213
|
+
- spec/lib/eq/job_spec.rb
|
214
|
+
- spec/lib/eq_spec.rb
|
215
|
+
- spec/spec_helper.rb
|
216
|
+
- spec/support/shared_examples_for_queue.rb
|
217
|
+
has_rdoc:
|