smq 0.2.2 → 0.3.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.
@@ -81,6 +81,15 @@ This process, although heavy on `SELECT`s, results in a minimum of table locking
81
81
 
82
82
  These limitations are deemed acceptable due to the simple nature of this queueing system.
83
83
 
84
+ ### Workaround to Locking Limitations
85
+
86
+ There is a workaround to the implicit limit of 5 workers per queue, which is to use the "batching" facility. This works by splitting up the messages by the [modulo](http://en.wikipedia.org/wiki/Modulo_operation) of the ID; in effect this means you can then run up to 5 workers per batch:
87
+
88
+ SMQ::Worker.new("queue_name", total_batches, this_batch).work do |msg|
89
+ puts msg.data.inspect
90
+ msg.ack!
91
+ end
92
+
84
93
  ## Licensing and Attribution
85
94
 
86
95
  SMQ is released under the MIT license as detailed in the LICENSE file that should be distributed with this library; the source code is [freely available](http://github.com/timblair/smq).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.3.0
data/lib/smq.rb CHANGED
@@ -43,9 +43,23 @@ module SMQ
43
43
  end
44
44
 
45
45
  def self.flush_all_queues!
46
- # yes, not the most efficient, but *should* only be called during testing
47
- # so it's not that much of a concern
48
- SMQ::Message.delete_all
46
+ # we're totally resetting everything here, including any auto-increment
47
+ # keys: this should only ever be called for testing.
48
+ # adapted from: http://www.manu-j.com/blog/post/221/
49
+ case ActiveRecord::Base.connection.instance_variable_get(:@config)[:adapter]
50
+ when "mysql"
51
+ ActiveRecord::Base.connection.tables.each do |table|
52
+ ActiveRecord::Base.connection.execute("TRUNCATE #{SMQ::Message.table_name}")
53
+ end
54
+ when "sqlite", "sqlite3"
55
+ ActiveRecord::Base.connection.tables.each do |table|
56
+ ActiveRecord::Base.connection.execute("DELETE FROM #{SMQ::Message.table_name}")
57
+ ActiveRecord::Base.connection.execute("DELETE FROM sqlite_sequence where name='#{SMQ::Message.table_name}'")
58
+ end
59
+ ActiveRecord::Base.connection.execute("VACUUM")
60
+ else
61
+ raise "Unsupported connection type: #{ActiveRecord::Base.connection.instance_variable_get(:@config)[:adapter]}."
62
+ end
49
63
  end
50
64
 
51
65
  end
@@ -25,15 +25,23 @@ module SMQ
25
25
  end
26
26
 
27
27
  def reserve(worker)
28
- find_available.each do |msg|
28
+ find_available(worker.batches, worker.batch).each do |msg|
29
29
  m = msg.lock!(worker)
30
30
  return m unless m == nil
31
31
  end
32
32
  nil
33
33
  end
34
34
 
35
- def find_available
36
- SMQ::Message.find(:all, :select => 'id, updated_at', :conditions => ["queue = ? AND completed_at IS NULL AND locked_by IS NULL", @name], :order => "id ASC", :limit => @batch_size).sort_by { rand() }
35
+ def find_available(batches=1, batch=1)
36
+ SMQ::Message.find(
37
+ :all,
38
+ :select => 'id, updated_at',
39
+ :conditions => [
40
+ "queue = ? AND (id % ?) = ? AND completed_at IS NULL AND locked_by IS NULL",
41
+ @name, batches, batch-1
42
+ ],
43
+ :order => "id ASC", :limit => @batch_size
44
+ ).sort_by { rand() }
37
45
  end
38
46
 
39
47
  def length
@@ -57,12 +65,16 @@ module SMQ
57
65
  private
58
66
 
59
67
  def delete_queue_items(where = nil, limit = nil)
60
- # delete_all doesn't support a :limit clause, so we have to fake it
61
- msg = SMQ::Message.find(:first, :select => 'id', :conditions => ["queue = ?", @name], :offset => limit) if !limit.nil?
62
- if (msg.nil?)
68
+ if (limit.nil?)
63
69
  SMQ::Message.delete_all(["queue = ? #{where ? 'AND ' + where : ''}", @name])
64
70
  else
65
- SMQ::Message.delete_all(["queue = ? AND id < ? #{where ? 'AND ' + where : ''}", @name, msg.id])
71
+ # delete_all doesn't support a :limit clause, so we have to fake it
72
+ msg = SMQ::Message.find(
73
+ :first, :select => 'id',
74
+ :conditions => ["queue = ? #{where ? 'AND ' + where : ''}", @name],
75
+ :order => "id ASC", :offset => limit
76
+ )
77
+ SMQ::Message.delete_all(["queue = ? AND id < ? #{where ? 'AND ' + where : ''}", @name, msg.id]) unless msg.nil?
66
78
  end
67
79
  end
68
80
 
@@ -4,15 +4,16 @@ module SMQ
4
4
 
5
5
  class Worker
6
6
 
7
- attr_accessor :name
8
- attr_accessor :queue
7
+ attr_accessor :name, :queue, :batches, :batch
9
8
 
10
9
  @working = false
11
10
  @stopping = false
12
11
 
13
- def initialize(queue)
12
+ def initialize(queue, batches=1, batch=1)
14
13
  self.name = "#{Socket.gethostname}:#{Process.pid}" rescue "pid:#{Process.pid}"
15
14
  self.queue = queue.instance_of?(SMQ::Queue) ? queue : SMQ::Queue.new(queue)
15
+ self.batches = batches
16
+ self.batch = batch
16
17
  end
17
18
 
18
19
  def to_s
@@ -89,15 +89,24 @@ class QueueTest < Test::Unit::TestCase
89
89
  assert_equal 5, @queue.length
90
90
  end
91
91
 
92
+ def test_should_return_only_correctly_batched_messages
93
+ populate_queue(@queue, 5)
94
+ assert_equal 1, @queue.find_available(3,1).length
95
+ assert_equal 2, @queue.find_available(3,2).length
96
+ assert_equal 2, @queue.find_available(3,3).length
97
+ end
98
+
92
99
  private
93
100
 
94
101
  def populate_and_ack_all_but_fail_one(queue = nil)
95
102
  queue ||= @queue
96
103
  populate_queue(queue, 5)
97
- msg = SMQ::Message.find(:first)
98
- msg.fail
99
- msg.save!
100
- @worker.work(true) { |m| m.ack! }
104
+ SMQ::Message.find(:all, :conditions => ["queue = ?", queue.name]).each do |msg|
105
+ msg.ack!
106
+ end
107
+ failure = SMQ::Message.find(:first)
108
+ failure.fail
109
+ failure.save!
101
110
  end
102
111
 
103
112
  end
@@ -57,4 +57,12 @@ class WorkerTest < Test::Unit::TestCase
57
57
  assert !@worker.is_working?, "Working and should have stopped"
58
58
  end
59
59
 
60
+ def test_working_a_populated_queue_in_batches_should_work_all_batch_jobs
61
+ @worker.batches = 2
62
+ populate_queue(@worker.queue.name, 5)
63
+ block_calls = 0
64
+ @worker.work(true) { block_calls += 1 }
65
+ assert_equal 2, block_calls
66
+ end
67
+
60
68
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smq
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 19
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
- - 2
8
- - 2
9
- version: 0.2.2
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Tim Blair
@@ -14,16 +15,18 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-04-26 00:00:00 +01:00
18
+ date: 2011-05-16 00:00:00 +01:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: activerecord
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 3
27
30
  segments:
28
31
  - 0
29
32
  version: "0"
@@ -33,9 +36,11 @@ dependencies:
33
36
  name: yajl-ruby
34
37
  prerelease: false
35
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
36
40
  requirements:
37
41
  - - ">="
38
42
  - !ruby/object:Gem::Version
43
+ hash: 3
39
44
  segments:
40
45
  - 0
41
46
  version: "0"
@@ -45,9 +50,11 @@ dependencies:
45
50
  name: sqlite3-ruby
46
51
  prerelease: false
47
52
  requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
48
54
  requirements:
49
55
  - - ">="
50
56
  - !ruby/object:Gem::Version
57
+ hash: 3
51
58
  segments:
52
59
  - 0
53
60
  version: "0"
@@ -63,7 +70,6 @@ extra_rdoc_files:
63
70
  - LICENSE
64
71
  - README.markdown
65
72
  files:
66
- - .gitignore
67
73
  - LICENSE
68
74
  - README.markdown
69
75
  - Rakefile
@@ -84,34 +90,34 @@ homepage: http://github.com/timblair/smq
84
90
  licenses: []
85
91
 
86
92
  post_install_message:
87
- rdoc_options:
88
- - --charset=UTF-8
93
+ rdoc_options: []
94
+
89
95
  require_paths:
90
96
  - lib
91
97
  required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
92
99
  requirements:
93
100
  - - ">="
94
101
  - !ruby/object:Gem::Version
102
+ hash: 3
95
103
  segments:
96
104
  - 0
97
105
  version: "0"
98
106
  required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
99
108
  requirements:
100
109
  - - ">="
101
110
  - !ruby/object:Gem::Version
111
+ hash: 3
102
112
  segments:
103
113
  - 0
104
114
  version: "0"
105
115
  requirements: []
106
116
 
107
117
  rubyforge_project:
108
- rubygems_version: 1.3.6
118
+ rubygems_version: 1.5.0
109
119
  signing_key:
110
120
  specification_version: 3
111
121
  summary: Simple Message Queue
112
- test_files:
113
- - test/helper.rb
114
- - test/test_message.rb
115
- - test/test_queue.rb
116
- - test/test_worker.rb
117
- - examples/worker.rb
122
+ test_files: []
123
+
data/.gitignore DELETED
@@ -1 +0,0 @@
1
- pkg