eq 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|
+
[](https://secure.travis-ci.org/dpree/eq)
|
8
|
+
[](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:
|