queuel 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 (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