eq 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +1 -0
  2. data/README.md +51 -11
  3. data/TODO.md +10 -0
  4. data/benchmarks/all.rb +13 -0
  5. data/benchmarks/parallel.rb +23 -0
  6. data/benchmarks/queue_backend_benchmark.rb +27 -0
  7. data/benchmarks/queueing.rb +13 -23
  8. data/benchmarks/working.rb +14 -25
  9. data/eq.gemspec +12 -2
  10. data/examples/queueing.rb +2 -2
  11. data/examples/scheduling.rb +19 -0
  12. data/examples/simple_usage.rb +20 -8
  13. data/examples/working.rb +2 -2
  14. data/lib/eq-queueing.rb +4 -13
  15. data/lib/eq-queueing/backends.rb +30 -1
  16. data/lib/eq-queueing/backends/leveldb.rb +232 -0
  17. data/lib/eq-queueing/backends/sequel.rb +34 -17
  18. data/lib/eq-queueing/queue.rb +26 -20
  19. data/lib/eq-scheduling.rb +33 -0
  20. data/lib/eq-scheduling/scheduler.rb +19 -0
  21. data/lib/eq-web.rb +5 -0
  22. data/lib/eq-web/server.rb +39 -0
  23. data/lib/eq-web/views/index.erb +45 -0
  24. data/lib/eq-working.rb +15 -7
  25. data/lib/eq-working/worker.rb +30 -3
  26. data/lib/eq.rb +39 -31
  27. data/lib/eq/boot/all.rb +1 -0
  28. data/lib/eq/boot/scheduling.rb +1 -0
  29. data/lib/eq/error.rb +4 -0
  30. data/lib/eq/job.rb +22 -16
  31. data/lib/eq/version.rb +1 -1
  32. data/log/.gitkeep +1 -0
  33. data/spec/lib/eq-queueing/backends/leveldb_spec.rb +32 -0
  34. data/spec/lib/eq-queueing/backends/sequel_spec.rb +5 -4
  35. data/spec/lib/eq-queueing/queue_spec.rb +27 -58
  36. data/spec/lib/eq-queueing_spec.rb +16 -0
  37. data/spec/lib/eq-scheduling_spec.rb +7 -0
  38. data/spec/lib/eq-working/worker_spec.rb +13 -0
  39. data/spec/lib/eq/job_spec.rb +16 -11
  40. data/spec/lib/eq_spec.rb +1 -1
  41. data/spec/mocks/a_job.rb +4 -0
  42. data/spec/mocks/a_unique_job.rb +6 -0
  43. data/spec/spec_helper.rb +12 -0
  44. data/spec/support/shared_examples_for_queue.rb +60 -31
  45. metadata +80 -8
  46. data/lib/eq-working/manager.rb +0 -31
  47. data/lib/eq-working/system.rb +0 -10
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ log/*.log
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # EQ - Embedded Queueing
4
4
 
5
- EQ is a little framework to queue tasks within a process.
5
+ EQ is a little framework to queue and perform background tasks within a single-process ruby application. It uses the Celluloid actor framework to do the work in the background. Its queue backends persist your jobs. So your jobs will survive application stop/restart.
6
6
 
7
7
  [![Travis-CI Build Status](https://secure.travis-ci.org/dpree/eq.png)](https://secure.travis-ci.org/dpree/eq)
8
8
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/dpree/eq)
@@ -31,27 +31,67 @@ If you want to execute a simple example you can just run [examples/simple_usage.
31
31
 
32
32
  EQ.boot
33
33
 
34
- **3. Let EQ do some work you.**
34
+ **3. Enqueue some jobs in the EQ queue.**
35
35
 
36
- EQ.queue.push MyJob, 'foo'
37
- EQ.queue.push MyJob, 'bar'
36
+ EQ.push MyJob, 'foo'
37
+ EQ.push MyJob, 'bar'
38
38
 
39
39
 
40
+ **5. Let EQ do your work.**
41
+
42
+ # EQ will spawn and maintain worker threads that execute the following for you:
43
+ MyJob.perform 'foo'
44
+ MyJob.perform 'bar'
45
+
46
+ **6. Shutdown EQ gracefully when you're application is done.***
47
+
48
+ EQ.shutdown
49
+
50
+ ### Optional: Schedule jobs using Clockwork
51
+
52
+ module Clockwork
53
+ every(5.seconds, MyJob)
54
+ every(1.day, MyJob, :at => '00:00')
55
+ end
56
+
40
57
  ## Configuration
41
58
 
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.
59
+ Right now there are two queueing backends available, one that is based on the Sequel gem and one based on LevelDB. With Sequel basically any SQL database might be used. Just make sure that you install the Backend before running the application.
60
+
61
+ ### Sequel
43
62
 
44
- The default SQL database that is used is a . You can change it using any argument that Sequel.connect method would accept.
63
+ Gemfile
64
+
65
+ gem 'sequel'
66
+ gem 'sqlite3'
67
+
68
+ Configuration
69
+
70
+ EQ.config.queue = 'sequel'
45
71
 
46
- # SQLite3 in-memory (default) using String syntax
47
- EQ.config.sequel = 'sqlite:/'
72
+ # With SQLite3 in-memory (default) using String syntax
73
+ # Caution: This won't persist your jobs!
74
+ EQ.config.sequel = 'sqlite:/'
48
75
 
49
- # SQLite3 file using Hash syntax
76
+ # With SQLite3 file using Hash syntax
50
77
  EQ.config.sequel = {adapter: 'sqlite', database: 'my_db.sqlite3'}
51
78
 
52
- # Postgres
79
+ # With Postgres
53
80
  EQ.config.sequel = 'postgres://user:password@host:port/my_db'
54
81
 
82
+ # Mysql, Oracle, etc.
83
+ # ...
84
+
85
+ # LevelDB
86
+
87
+ Gemfile
88
+
89
+ gem 'leveldb-ruby'
90
+
91
+ Configuration
92
+
93
+ EQ.config.queue = 'leveldb'
94
+ EQ.config.leveldb = 'path/to/my/queue.leveldb'
55
95
 
56
96
  ### Logging
57
97
 
@@ -62,7 +102,7 @@ EQ uses the logging mechanism of the underlying Celluloid (`Celluloid.logger`) f
62
102
  # Use the logger of your Rails application.
63
103
  Celluloid.logger = Rails.logger
64
104
 
65
- # No more logging at all.
105
+ # No logging at all.
66
106
  Celluloid.logger = Logger.new('/dev/null')
67
107
 
68
108
  ## Contributing
data/TODO.md ADDED
@@ -0,0 +1,10 @@
1
+ # TODO
2
+
3
+ * tests
4
+ * job strategies
5
+ ** retry
6
+ ** drop
7
+ ** exponential backoff
8
+ * stats / each iterator
9
+ * delete all
10
+ * configurable timeout per job
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ puts ""
4
+ puts "QUEUEING"
5
+ system "#{File.join(File.dirname(__FILE__), 'queueing.rb')}"
6
+
7
+ puts ""
8
+ puts "WORKING"
9
+ system "#{File.join(File.dirname(__FILE__), 'working.rb')}"
10
+
11
+ puts ""
12
+ puts "BOTH"
13
+ system "#{File.join(File.dirname(__FILE__), 'parallel.rb')}"
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), 'queue_backend_benchmark')
4
+
5
+ class MyJob
6
+ def self.perform stuff
7
+ end
8
+ end
9
+
10
+ class Executor < Struct.new(:n, :benchmark)
11
+ def report name, &configure
12
+ EQ.config &configure
13
+ EQ.boot
14
+ benchmark.report name do
15
+ n.times { |i| EQ.push MyJob, i }
16
+ sleep 0.01 until EQ.count(:waiting) == 0
17
+ end
18
+ EQ.shutdown
19
+ sleep 0.05
20
+ end
21
+ end
22
+
23
+ QueueBackendBenchmark.new(Executor.new(100)).run
@@ -0,0 +1,27 @@
1
+ require 'benchmark'
2
+ require 'tmpdir'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
4
+
5
+ EQ.logger.level = Logger::Severity::ERROR
6
+
7
+ class QueueBackendBenchmark < Struct.new(:executor)
8
+ def run
9
+ Benchmark.bm(50) do |benchmark|
10
+ executor.benchmark = benchmark
11
+
12
+ executor.report 'sequel with sqlite3 (in-memory)' do |config|
13
+ config.queue = 'sequel'
14
+ end
15
+
16
+ executor.report 'sequel with sqlite3 (file)' do |config|
17
+ config.queue = 'sequel'
18
+ config.sequel = "sqlite://#{Dir.mktmpdir}/benchmark.sqlite3"
19
+ end
20
+
21
+ executor.report 'leveldb' do |config|
22
+ config.queue = 'leveldb'
23
+ config.leveldb = Dir.mktmpdir
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,33 +1,23 @@
1
1
  #!/usr/bin/env ruby
2
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
3
+ require File.join(File.dirname(__FILE__), 'queue_backend_benchmark')
9
4
 
10
5
  class MyJob
11
6
  def self.perform stuff
12
7
  end
13
8
  end
14
9
 
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
10
+ class Executor < Struct.new(:n, :benchmark)
11
+ def report name, &configure
12
+ EQ.config &configure
13
+ EQ.boot :queue
14
+ benchmark.report name do
15
+ n.times { |i| EQ.push! MyJob, i }
16
+ EQ.count
17
+ end
18
+ EQ.shutdown
19
+ sleep 0.05
31
20
  end
32
- EQ.shutdown
33
21
  end
22
+
23
+ QueueBackendBenchmark.new(Executor.new(1000)).run
@@ -1,35 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
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
3
+ require File.join(File.dirname(__FILE__), 'queue_backend_benchmark')
9
4
 
10
5
  class MyJob
11
6
  def self.perform stuff
12
7
  end
13
8
  end
14
9
 
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
10
+ class Executor < Struct.new(:n, :benchmark)
11
+ def report name, &configure
12
+ EQ.config &configure
13
+ EQ.boot :queue
14
+ n.times { |i| EQ.push MyJob, i }
15
+ benchmark.report name do
16
+ EQ.boot :worker
17
+ sleep 0.01 until EQ.count(:waiting) == 0
18
+ end
19
+ EQ.shutdown
20
+ sleep 0.05
33
21
  end
34
- EQ.shutdown
35
22
  end
23
+
24
+ QueueBackendBenchmark.new(Executor.new(1000)).run
data/eq.gemspec CHANGED
@@ -15,9 +15,19 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = EQ::VERSION
17
17
 
18
- gem.add_dependency "sqlite3"
19
- gem.add_dependency "sequel"
20
18
  gem.add_dependency "celluloid"
19
+
20
+ # just to test the web view
21
+ gem.add_development_dependency "sinatra"
22
+
23
+ # just to test the scheduling
24
+ gem.add_development_dependency "clockwork"
25
+
26
+ # just to test the queueing backends
27
+ gem.add_development_dependency "sequel" # sequel backend
28
+ gem.add_development_dependency "sqlite3" # sequel with sqlite
29
+ gem.add_development_dependency "leveldb-ruby" # leveldb backend
30
+
21
31
  gem.add_development_dependency "guard"
22
32
  gem.add_development_dependency "guard-rspec"
23
33
  gem.add_development_dependency "rspec"
@@ -3,7 +3,7 @@ require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'queueing')
3
3
 
4
4
  EQ.logger.level = Logger::Severity::INFO
5
5
  EQ.config do |config|
6
- config[:sqlite] = "foo.sqlite"
6
+ config.sequel = "sqlite://foo.sqlite"
7
7
  end
8
8
 
9
9
  def say words; EQ.logger.info(words); end
@@ -16,7 +16,7 @@ end
16
16
 
17
17
  require 'timeout'
18
18
  begin
19
- Timeout.timeout(120) do
19
+ Timeout.timeout(10) do
20
20
  EQ.boot
21
21
  loop do
22
22
  say "pushed!"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
3
+
4
+ class MyJob
5
+ def self.perform
6
+ puts 'perfoming...'
7
+ end
8
+ end
9
+
10
+ module Clockwork
11
+ every(1.seconds, MyJob)
12
+ every(1.day, MyJob, :at => '00:00')
13
+ end
14
+
15
+ EQ.boot
16
+
17
+ sleep 3
18
+
19
+ EQ.shutdown
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
3
3
 
4
+ Celluloid.logger = Logger.new('/dev/null')
5
+
4
6
  # Define a Job class with a perform method.
5
7
  class MyJob
6
8
  RESULT_PATH = 'my_job_result.txt'
@@ -21,15 +23,25 @@ end
21
23
  # Cleanup results file.
22
24
  File.delete MyJob::RESULT_PATH if File.exists? MyJob::RESULT_PATH
23
25
 
24
- # Start the EQ system.
25
- EQ.boot
26
+ EQ.config do |c|
27
+ c.sequel = 'sqlite://examples/foo'
28
+ end
29
+
30
+ require 'timeout'
31
+ begin
32
+ Timeout.timeout(12) do
26
33
 
27
- # Enqueue some work.
28
- EQ.queue.push MyJob, Time.now, 1
29
- EQ.queue.push MyJob, Time.now, 2
34
+ # Start the EQ system.
35
+ EQ.boot
30
36
 
31
- # Wait some time to get the work done.
32
- sleep 3
37
+ # Enqueue some work.
38
+ EQ.queue.push! MyJob, Time.now, 0.1
39
+ EQ.queue.push! MyJob, Time.now, 0.5
40
+
41
+ # Wait some time to get the work done.
42
+ sleep 1
43
+ end
44
+ end
33
45
 
34
46
  # Read the results file.
35
- puts File.read MyJob::RESULT_PATH
47
+ puts File.read(MyJob::RESULT_PATH)
@@ -3,7 +3,7 @@ require File.join(File.dirname(__FILE__), '..', 'lib', 'eq', 'boot', 'all')
3
3
 
4
4
  EQ.logger.level = Logger::Severity::INFO
5
5
  EQ.config do |config|
6
- config[:sqlite] = "foo.sqlite"
6
+ config.sequel = "sqlite://foo.sqlite"
7
7
  end
8
8
 
9
9
  def say words; EQ.logger.info(words); end
@@ -17,7 +17,7 @@ end
17
17
 
18
18
  require 'timeout'
19
19
  begin
20
- Timeout.timeout(120) do
20
+ Timeout.timeout(10) do
21
21
  EQ.boot
22
22
  sleep 0.5 while EQ.working?
23
23
  end
@@ -5,8 +5,10 @@ require File.join(File.dirname(__FILE__), 'eq-queueing', 'queue')
5
5
  module EQ::Queueing
6
6
  module_function
7
7
 
8
+ EQ_QUEUE = :_eq_queueing
9
+
8
10
  def boot
9
- EQ::Queueing::Queue.supervise_as :_eq_queueing, initialize_queueing_backend
11
+ EQ::Queueing::Queue.supervise_as EQ_QUEUE, EQ::Queueing::Backends.init(EQ.config)
10
12
  end
11
13
 
12
14
  def shutdown
@@ -14,17 +16,6 @@ module EQ::Queueing
14
16
  end
15
17
 
16
18
  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
19
+ Celluloid::Actor[EQ_QUEUE]
29
20
  end
30
21
  end
@@ -1,6 +1,35 @@
1
1
  module EQ::Queueing
2
2
  module Backends
3
+ class BackendLoadError < LoadError; end
4
+
5
+ module_function
6
+
7
+ # @params [#queue, #"#{queue}"] config
8
+ # @raise ConfigurationError when config.queue is not supported
9
+ def init config
10
+ if %w[ sequel leveldb ].include? config.queue
11
+ initialize_queue config
12
+ else
13
+ raise EQ::ConfigurationError, "config.queue = '#{config.queue}' is not supported!"
14
+ end
15
+ end
16
+
17
+ # @raise LoadError when required gem is not available
18
+ def initialize_queue config
19
+ queue_config = config.send(config.queue)
20
+ case EQ.config.queue
21
+ when 'sequel'
22
+ require_queue 'sequel'
23
+ EQ::Queueing::Backends::Sequel.new queue_config
24
+ when 'leveldb'
25
+ require_queue 'leveldb'
26
+ EQ::Queueing::Backends::LevelDB.new queue_config
27
+ end
28
+ end
29
+
30
+ def require_queue queue_name
31
+ require File.join(File.dirname(__FILE__), 'backends', queue_name)
32
+ end
3
33
  end
4
34
  end
5
35
 
6
- require File.join(File.dirname(__FILE__), 'backends', 'sequel')