queuel 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/.cane +3 -0
  2. data/.gitignore +1 -1
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile.lock +112 -0
  6. data/Guardfile +22 -0
  7. data/README.md +17 -11
  8. data/Rakefile +7 -0
  9. data/lib/queuel.rb +50 -27
  10. data/lib/queuel/base/engine.rb +35 -0
  11. data/lib/queuel/base/message.rb +32 -0
  12. data/lib/queuel/base/poller.rb +161 -0
  13. data/lib/queuel/base/queue.rb +59 -0
  14. data/lib/queuel/client.rb +15 -3
  15. data/lib/queuel/configurator.rb +65 -0
  16. data/lib/queuel/introspect.rb +11 -0
  17. data/lib/queuel/iron_mq/engine.rb +11 -17
  18. data/lib/queuel/iron_mq/message.rb +5 -26
  19. data/lib/queuel/iron_mq/poller.rb +8 -111
  20. data/lib/queuel/iron_mq/queue.rb +13 -24
  21. data/lib/queuel/null/engine.rb +3 -7
  22. data/lib/queuel/null/message.rb +1 -16
  23. data/lib/queuel/null/poller.rb +2 -114
  24. data/lib/queuel/null/queue.rb +6 -9
  25. data/lib/queuel/version.rb +1 -1
  26. data/queuel.gemspec +10 -0
  27. data/spec/lib/queuel/base/queue_spec.rb +23 -0
  28. data/spec/lib/queuel/client_spec.rb +30 -0
  29. data/spec/lib/queuel/configurator_spec.rb +32 -0
  30. data/spec/lib/queuel/iron_mq/engine_spec.rb +28 -0
  31. data/spec/lib/queuel/iron_mq/message_spec.rb +2 -2
  32. data/spec/lib/queuel/iron_mq/poller_spec.rb +15 -1
  33. data/spec/lib/queuel/iron_mq/queue_spec.rb +2 -2
  34. data/spec/lib/queuel_spec.rb +12 -7
  35. data/spec/spec_helper.rb +4 -0
  36. data/spec/support/engine_shared_example.rb +4 -2
  37. data/spec/support/message_shared_example.rb +2 -11
  38. data/spec/support/poller_shared_example.rb +23 -57
  39. data/spec/support/queue_shared_example.rb +14 -1
  40. metadata +163 -4
data/.cane ADDED
@@ -0,0 +1,3 @@
1
+ --no-doc
2
+ --style-measure 120
3
+ --abc-max 25
data/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
@@ -15,3 +14,4 @@ spec/reports
15
14
  test/tmp
16
15
  test/version_tmp
17
16
  tmp
17
+ .rbx
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ queuel
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3
data/Gemfile.lock ADDED
@@ -0,0 +1,112 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ queuel (0.1.0)
5
+ mono_logger
6
+ thread
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ cane (2.5.2)
12
+ parallel
13
+ coderay (1.0.9)
14
+ diff-lcs (1.2.4)
15
+ ethon (0.5.12)
16
+ ffi (>= 1.3.0)
17
+ mime-types (~> 1.18)
18
+ ffi (1.8.1)
19
+ ffi (1.8.1-java)
20
+ formatador (0.2.4)
21
+ guard (1.8.0)
22
+ formatador (>= 0.2.4)
23
+ listen (>= 1.0.0)
24
+ lumberjack (>= 1.0.2)
25
+ pry (>= 0.9.10)
26
+ thor (>= 0.14.6)
27
+ guard-bundler (1.0.0)
28
+ bundler (~> 1.0)
29
+ guard (~> 1.1)
30
+ guard-cane (0.1.2)
31
+ cane
32
+ guard
33
+ guard-rspec (3.0.0)
34
+ guard (>= 1.8)
35
+ rspec (~> 2.13)
36
+ iron_core (0.6.2)
37
+ rest (>= 2.2.0)
38
+ iron_mq (4.0.3)
39
+ iron_core (>= 0.5.1)
40
+ json (1.7.7)
41
+ json (1.7.7-java)
42
+ listen (1.1.1)
43
+ rb-fsevent (>= 0.9.3)
44
+ rb-inotify (>= 0.9)
45
+ rb-kqueue (>= 0.2)
46
+ lumberjack (1.0.3)
47
+ method_source (0.8.1)
48
+ mime-types (1.23)
49
+ mono_logger (1.0.1)
50
+ multi_json (1.7.3)
51
+ parallel (0.6.5)
52
+ pry (0.9.12.2)
53
+ coderay (~> 1.0.5)
54
+ method_source (~> 0.8)
55
+ slop (~> 3.4)
56
+ pry (0.9.12.2-java)
57
+ coderay (~> 1.0.5)
58
+ method_source (~> 0.8)
59
+ slop (~> 3.4)
60
+ spoon (~> 0.0)
61
+ rake (10.0.4)
62
+ rb-fchange (0.0.6)
63
+ ffi
64
+ rb-fsevent (0.9.3)
65
+ rb-inotify (0.9.0)
66
+ ffi (>= 0.5.0)
67
+ rb-kqueue (0.2.0)
68
+ ffi (>= 0.5.0)
69
+ rest (2.6.0)
70
+ rest-client (>= 0.3.0)
71
+ rest-client (1.6.7)
72
+ mime-types (>= 1.16)
73
+ rspec (2.13.0)
74
+ rspec-core (~> 2.13.0)
75
+ rspec-expectations (~> 2.13.0)
76
+ rspec-mocks (~> 2.13.0)
77
+ rspec-core (2.13.1)
78
+ rspec-expectations (2.13.0)
79
+ diff-lcs (>= 1.1.3, < 2.0)
80
+ rspec-mocks (2.13.1)
81
+ simplecov (0.7.1)
82
+ multi_json (~> 1.0)
83
+ simplecov-html (~> 0.7.1)
84
+ simplecov-html (0.7.1)
85
+ slop (3.4.5)
86
+ spoon (0.0.4)
87
+ ffi
88
+ thor (0.18.1)
89
+ thread (0.0.8)
90
+ typhoeus (0.6.3)
91
+ ethon (~> 0.5.11)
92
+
93
+ PLATFORMS
94
+ java
95
+ ruby
96
+
97
+ DEPENDENCIES
98
+ bundler (~> 1.3)
99
+ cane
100
+ guard-bundler
101
+ guard-cane
102
+ guard-rspec
103
+ iron_mq
104
+ json (~> 1.7.7)
105
+ queuel!
106
+ rake
107
+ rb-fchange
108
+ rb-fsevent
109
+ rb-inotify
110
+ rspec
111
+ simplecov
112
+ typhoeus
data/Guardfile ADDED
@@ -0,0 +1,22 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ watch(/^.+\.gemspec/)
7
+ end
8
+
9
+ guard :cane do
10
+ watch(%r{^lib/.+\.rb$})
11
+ end
12
+
13
+ guard 'rspec',
14
+ all_on_start: true,
15
+ keep_failed: true,
16
+ rvm: ["1.9.3-p392@queuel"] do
17
+ # would like to add "jruby-1.7.3@queuel", "rbx-head@queuel"
18
+ watch(%r{^spec/.+\.rb$}) { "spec" }
19
+ watch(%r{^lib/queuel/base/(.+)\.rb$}) { |m| ["spec/lib/queuel/iron_mq/#{m[1]}_spec.rb", "spec/lib/queuel/null/#{m[1]}_spec.rb"] }
20
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
21
+ watch('spec/spec_helper.rb') { "spec" }
22
+ end
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Queuel
2
+ [![Gem Version](https://badge.fury.io/rb/queuel.png)](http://badge.fury.io/rb/queuel)
2
3
  [![Build Status](https://travis-ci.org/sportngin/queuel.png?branch=master)](https://travis-ci.org/sportngin/queuel)
3
4
 
4
5
  Queuel is a kewl, lite wrapper around Queue interfaces. Currently it implements:
@@ -41,6 +42,16 @@ Queuel.configure do
41
42
 
42
43
  # currently only [:iron_mq, :null] available
43
44
  engine :iron_mq
45
+
46
+ # For Queuel.recevier {} you can configure more than one thread to
47
+ # handle incoming messages
48
+ receiver_threads 3 # default: 1
49
+
50
+ # Logging: Default is MonoLogger, because its a non-blocking log-extension
51
+ # To the standard lib Logger. Any Log4r solution should work.
52
+ logger Logger # default: MonoLogger.new(STDOUT)
53
+
54
+ log_level MonoLogger::DEBUG # default: MonoLogger::ERROR # => 3
44
55
  end
45
56
  ```
46
57
 
@@ -49,7 +60,7 @@ end
49
60
  ### General Queue API
50
61
 
51
62
  ```ruby
52
- # Using default Queue
63
+ # Using default Queue from config
53
64
  Queuel.pop
54
65
  Queuel.push "My message to you"
55
66
  Queuel.receive do |message|
@@ -63,22 +74,16 @@ Queuel.with("officials").receive do |message|
63
74
  puts "I received #{message.body}" # NOTE the message interface may change, this is currently not wrapped by the gem
64
75
  end
65
76
 
66
- # Timeout the receiving
67
- Queuel.receive poll_timeout: 60 do |message|
68
- puts "I received #{message.body}" # NOTE the message interface may change, this is currently not wrapped by the gem
69
- end
70
-
71
- # Don't receive more than 10 nil messages
72
- Queuel.receive max_consecutive_fails: 10 do |message|
73
- puts "I received #{message.body}" # NOTE the message interface may change, this is currently not wrapped by the gem
74
- end
75
-
76
77
  # Break on nil
77
78
  Queuel.receive break_if_nil: true do |message|
78
79
  puts "I received #{message.body}" # NOTE the message interface may change, this is currently not wrapped by the gem
79
80
  end
80
81
  ```
81
82
 
83
+ #### Caveats of the receiver
84
+
85
+ * Your block must return true in order to not replace the message to the Queue
86
+
82
87
  ### The message
83
88
 
84
89
  ```ruby
@@ -91,6 +96,7 @@ message.delete # => Delete the message
91
96
 
92
97
  * Implement AMQP
93
98
  * Configureable exponential back-off on `receive`
99
+ * Provide a Daemon
94
100
 
95
101
  ## Contributing
96
102
 
data/Rakefile CHANGED
@@ -4,3 +4,10 @@ require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
6
  task :default => :spec
7
+
8
+ namespace :spec do
9
+ desc "run just tasks flagged for perf checks"
10
+ RSpec::Core::RakeTask.new(:perf) do |t|
11
+ t.rspec_opts = ['--tag perf', '--profile']
12
+ end
13
+ end
data/lib/queuel.rb CHANGED
@@ -1,26 +1,38 @@
1
1
  require "queuel/version"
2
2
  require "forwardable"
3
- require "queuel/null/message"
3
+ require "queuel/configurator"
4
+ require "queuel/introspect"
5
+
6
+ require "queuel/base/engine"
7
+ require "queuel/base/queue"
8
+ require "queuel/base/message"
9
+ require "queuel/base/poller"
10
+
4
11
  require "queuel/null/engine"
5
- require "queuel/null/poller"
6
12
  require "queuel/null/queue"
7
- require "queuel/iron_mq/message"
13
+ require "queuel/null/message"
14
+ require "queuel/null/poller"
15
+
8
16
  require "queuel/iron_mq/engine"
9
- require "queuel/iron_mq/poller"
10
17
  require "queuel/iron_mq/queue"
18
+ require "queuel/iron_mq/message"
19
+ require "queuel/iron_mq/poller"
20
+
11
21
  require "queuel/client"
12
22
 
13
23
  module Queuel
24
+ extend Introspect
14
25
  class << self
15
26
  extend Forwardable
16
27
  def_delegators :client, :push, :pop, :receive, :with
17
- def_delegators :config, :credentials, :default_queue
28
+ def_delegators :config, :credentials, :default_queue, :receiver_threads
18
29
  alias << pop
19
30
  end
20
31
 
21
32
  def self.engine
22
33
  requires
23
- Object.module_eval("::#{const_name}", __FILE__, __LINE__)
34
+ warn_engine_selection
35
+ const_with_nesting engine_const_name
24
36
  end
25
37
 
26
38
  def self.configure(&block)
@@ -35,34 +47,45 @@ module Queuel
35
47
  Client.new engine, credentials
36
48
  end
37
49
 
50
+ def self.logger
51
+ config.logger.tap { |log|
52
+ log.level = config.log_level
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ def self.warn_engine_selection
59
+ @warned_null_engine ||= logger.warn(engine_config[:message])
60
+ end
61
+
62
+ def self.engine_config
63
+ engines.fetch(config.engine) { engines[:null] }
64
+ end
65
+
66
+ def self.configured_engine_name
67
+ engine_config[:const]
68
+ end
69
+
38
70
  def self.engines
39
71
  {
40
- iron_mq: { require: 'iron_mq', const: "IronMq" },
41
- null: { const: "Null" }
72
+ iron_mq: {
73
+ require: 'iron_mq',
74
+ const: "IronMq",
75
+ message: "Using IronMQ"
76
+ },
77
+ null: {
78
+ const: "Null",
79
+ message: "Using Null Engine, for compatability."
80
+ }
42
81
  }
43
82
  end
44
83
 
45
84
  def self.requires
46
- require engines[config.engine][:require] if engines.fetch(config.engine, {})[:require]
47
- end
48
-
49
- def self.const_name
50
- "Queuel::#{engines.fetch(config.engine, {}).fetch(:const, nil) || engines[:null][:const]}::Engine"
85
+ require engine_config[:require] if engine_config[:require]
51
86
  end
52
87
 
53
- class Configurator
54
- def self.param(*params)
55
- params.each do |name|
56
- attr_accessor name
57
- define_method name do |*values|
58
- value = values.first
59
- value ? self.send("#{name}=", value) : instance_variable_get("@#{name}")
60
- end
61
- end
62
- end
63
-
64
- param :credentials
65
- param :engine
66
- param :default_queue
88
+ def self.engine_const_name
89
+ "Queuel::#{configured_engine_name}::Engine"
67
90
  end
68
91
  end
@@ -0,0 +1,35 @@
1
+ module Queuel
2
+ module Base
3
+ class Engine
4
+ extend Introspect
5
+ def self.inherited(klass)
6
+ klass.class_eval do
7
+ def queue_klass
8
+ self.class.const_with_nesting "Queue"
9
+ end
10
+ end
11
+ end
12
+
13
+ def initialize(credentials = {})
14
+ self.credentials = credentials
15
+ self.memoized_queues = {}
16
+ end
17
+
18
+ def queue(which_queue)
19
+ memoized_queues[which_queue.to_s] ||= queue_klass.new(client, which_queue)
20
+ end
21
+
22
+ private
23
+ attr_accessor :credentials
24
+ attr_accessor :memoized_queues
25
+
26
+ def client
27
+ @client ||= client_klass.new credentials
28
+ end
29
+
30
+ def client_klass
31
+ raise NotImplementedError, "Must define a Queue class"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ module Queuel
2
+ module Base
3
+ class Message
4
+ def initialize(message_object)
5
+ self.message_object = message_object
6
+ end
7
+
8
+ def delete
9
+ raise NotImplementedError, "must define method #delete"
10
+ end
11
+
12
+ def empty?
13
+ body.to_s.empty?
14
+ end
15
+ alias blank? empty?
16
+
17
+ def present?
18
+ !empty?
19
+ end
20
+
21
+ attr_reader :id
22
+ attr_reader :body
23
+ attr_reader :queue
24
+
25
+ private
26
+ attr_accessor :message_object
27
+ attr_writer :id
28
+ attr_writer :body
29
+ attr_writer :queue
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,161 @@
1
+ require 'thread'
2
+ require "thread/pool"
3
+ require "forwardable"
4
+ module Queuel
5
+ module Base
6
+ class Poller
7
+ extend Forwardable
8
+ def_delegators :Queuel, :logger
9
+
10
+ def initialize(queue, param_block, options = {}, workers = 1)
11
+ self.workers = workers
12
+ self.queue = queue
13
+ self.options = options || {}
14
+ self.inst_block = param_block
15
+ self.tries = 0
16
+ self.continue_looping = true
17
+ end
18
+
19
+ def poll
20
+ register_trappers
21
+ logger.debug("Beginning Poll...")
22
+ self.master = master_thread
23
+ log_action(:joining, :master) { master.join }
24
+ rescue SignalException => e
25
+ logger.warn "Caught (#{e}), shutting Poller down"
26
+ log_action(:killing, :poller, :warn) { shutdown }
27
+ end
28
+
29
+ protected
30
+ attr_accessor :tries
31
+ attr_accessor :workers
32
+ attr_accessor :inst_block
33
+
34
+ private
35
+ attr_accessor :master
36
+ attr_accessor :queue
37
+ attr_accessor :args
38
+ attr_accessor :options
39
+ attr_accessor :continue_looping
40
+
41
+ def register_trappers
42
+ trap(:SIGINT) { shutdown }
43
+ trap(:INT) { shutdown }
44
+ end
45
+
46
+ def log_action(verb, subject, level = :debug)
47
+ verb = verb.to_s.upcase.gsub(/\_/, " ")
48
+ subject = subject.to_s.upcase.gsub(/\_/, " ")
49
+ logger.public_send level, "#{verb} #{subject} START"
50
+ yield
51
+ logger.public_send level, "#{verb} #{subject} COMPLETE"
52
+ end
53
+
54
+ def shutdown
55
+ log_action(:shutting_down, :thread_pool) { pool.shutdown }
56
+ log_action(:killing, :master_thread) { master.kill }
57
+ log_action(:killing, :loop) { quit_looping! }
58
+ end
59
+
60
+ def pool
61
+ @pool ||= Thread.pool workers
62
+ end
63
+
64
+ def master_thread
65
+ Thread.new do
66
+ master_looper
67
+ end
68
+ end
69
+
70
+ def peek_options
71
+ {}
72
+ end
73
+
74
+ def peek
75
+ queue.peek peek_options
76
+ end
77
+
78
+ def queue_size # TODO optionize the peek options
79
+ Array(peek).size
80
+ end
81
+
82
+ def process_off_peek
83
+ mem_queue_size = queue_size
84
+ if mem_queue_size > 0
85
+ reset_tries
86
+ mem_queue_size.times do
87
+ process_on_thread
88
+ end
89
+ else
90
+ tried
91
+ quit_looping! if quit_on_empty?
92
+ end
93
+ end
94
+
95
+ def process_on_thread
96
+ pool.process do
97
+ process_message
98
+ end
99
+ end
100
+
101
+ def process_message
102
+ register_trappers
103
+ message = pop_new_message
104
+ message.delete if self.inst_block.call message
105
+ rescue => e
106
+ logger.warn "Received #{e} when processing #{message}"
107
+ logger.warn "Backtrace:"
108
+ logger.warn e.backtrace
109
+ end
110
+
111
+ def master_looper
112
+ loop do
113
+ break unless continue_looping?
114
+ process_off_peek
115
+ sleep sleep_time
116
+ end
117
+ end
118
+
119
+ def built_options
120
+ raise NotImplementedError
121
+ end
122
+
123
+ def continue_looping?
124
+ !!continue_looping
125
+ end
126
+
127
+ def break_if_nil?
128
+ !!options[:break_if_nil]
129
+ end
130
+ alias quit_on_empty? break_if_nil?
131
+
132
+ def quit_looping!
133
+ self.continue_looping = false
134
+ end
135
+
136
+ def pop_new_message
137
+ queue.pop built_options
138
+ end
139
+
140
+ def start_sleep_time
141
+ 0
142
+ end
143
+
144
+ def increment_sleep_time
145
+ 0.1
146
+ end
147
+
148
+ def sleep_time
149
+ tries < 30 ? ((start_sleep_time + increment_sleep_time) * tries) : 3
150
+ end
151
+
152
+ def reset_tries
153
+ self.tries = 0
154
+ end
155
+
156
+ def tried
157
+ self.tries += 1
158
+ end
159
+ end
160
+ end
161
+ end