throttle-queue 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 602c620f6b090b5696a141185d5e1d65c8240c07
4
- data.tar.gz: 852eec61c6d3d3c1200e83b63a202d03d57a4842
3
+ metadata.gz: 0d0f4a538c9d23d2a21d79b63122c4a0074e4095
4
+ data.tar.gz: 16859c8f40a47777526ec299ed4721ba955ca55f
5
5
  SHA512:
6
- metadata.gz: 53b009d91bafd808374a49e10a98e19fd926f98bee250e163f77bf9da7ffad395528ec33df9855e57b75f338dd3579c7ce5234b1a6954df8769470700511a6e3
7
- data.tar.gz: a430b7bb92bde90fe9bf937198b2337171bf98292d4ae72fbc3924998b827d9298c33020c3fa939bffaad021107533992110aebc8c11fbe8ca15e39e42f7cfe3
6
+ metadata.gz: a39194f269635e9b8ece84714010df1e1cedd0194c5133a77fdfb277d71227aa4bf88e1d57e4b5fba2c84e470831a04b5ede2d8f8f287dd83baac54154f29229
7
+ data.tar.gz: dc984657a36e8238294813d3f10e36ae837bd77764ee0c6a8c2be81648cedaeb2dda1a5334658a75ffab4cf01435ab6e02fe1f24284b73cb74c9029345c7b0ba
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
data/README.md CHANGED
@@ -18,10 +18,24 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install throttle-queue
20
20
 
21
+ ## Purpose
22
+
23
+ Sometimes server resources are rate-limited. If you as a client exceed the server's
24
+ limit, you become temporarily black-listed. A popular set of free APIs, which rhymes
25
+ with Foogle Maps, has strict limits in place after which you start seeing 403s instead
26
+ of 200s.
27
+
28
+ Let's say, for the above reason or another, you want to accumulate a cache of objects
29
+ no faster than N times a second. You're happy to startup your app quickly and let your
30
+ cache grow in the background. When a user requests a resource, you can bump it to the
31
+ front of your queue and block until it is ready.
32
+
21
33
  ## Usage
22
34
 
23
35
  Create a queue and add background work
24
36
 
37
+ require 'throttle-queue'
38
+
25
39
  q = ThrottleQueue.new 3
26
40
  files.each {|file|
27
41
  q.background(file) {
@@ -39,6 +53,39 @@ Wait for everything to finish
39
53
 
40
54
  q.wait
41
55
 
56
+ ## Details
57
+
58
+ Each resource is assumed to have a unique identifier, e.g. a filename or a reproducible
59
+ hash value. The queue does not check if the resource exists first, but it will check if
60
+ the id has already been queued. Any time an id is added to the queue, the previous block
61
+ is dropped in favor of the new.
62
+
63
+ Once an id has made it through the queue and been processed, the same id can be added
64
+ again and will be blindly processed again. It is assumed the user of the object knows
65
+ whether the resource already exists, and will decide whether or not a given id should be
66
+ added.
67
+
68
+ ## Multiple Processes
69
+
70
+ Just bring in the multi-process-aware queue wrapper
71
+
72
+ require 'throttle-queue/multi-process'
73
+
74
+ q = ThrottleQueue::MultiProcess.new 3
75
+ files.each {|file|
76
+ q.background(file) {
77
+ fetch file
78
+ }
79
+ }
80
+
81
+ Use this queue the same way in each process. Make sure each queue id has the same
82
+ meaning in each process (e.g. file paths should be absolute or relative to the same
83
+ working directory). If your processes race to stack up a bunch of background work, the
84
+ first process to add each work will be the only one to execute its block.
85
+
86
+ Do not fork after a multi-process queue is created. If your process forks itself,
87
+ create each queue after the fork.
88
+
42
89
  ## Contributing
43
90
 
44
91
  1. Fork it
@@ -46,3 +93,4 @@ Wait for everything to finish
46
93
  3. Commit your changes (`git commit -am 'Add some feature'`)
47
94
  4. Push to the branch (`git push origin my-new-feature`)
48
95
  5. Create new Pull Request
96
+
data/Rakefile CHANGED
@@ -6,3 +6,4 @@ Rake::TestTask.new {|t|
6
6
 
7
7
  desc 'Run tests'
8
8
  task :default => :test
9
+
@@ -1,222 +1 @@
1
- require 'thread'
2
- # ThrottleQueue is a thread-safe rate-limited work queue. It allows both
3
- # background and foreground operations.
4
- #
5
- # Example:
6
- # q = ThrottleQueue 3
7
- # files.each {|file|
8
- # q.background(file) {|id|
9
- # fetch file
10
- # }
11
- # }
12
- class ThrottleQueue
13
- # Creates a new ThrottleQueue with the given rate limit (per second).
14
- def initialize(limit)
15
- raise "refusing to do zero work per second" if limit <= 0
16
- @limit = limit
17
-
18
- @queue = PriorityQueue.new
19
-
20
- @mutex = Mutex.new
21
- @pausing = ConditionVariable.new
22
- @idle = ConditionVariable.new
23
- @in_flight = nil
24
- @processing_thread = nil
25
- @items = {}
26
-
27
- @throttling = nil
28
- @state = :idle
29
- @t0 = Time.now
30
- end
31
- # Signals the queue to stop processing and shutdown.
32
- #
33
- # Items still in the queue are dropped. Any item
34
- # currently in flight will finish.
35
- def shutdown
36
- @queue.shutdown
37
- @pausing.signal
38
- end
39
- # Returns true if there is nothing queued and no
40
- # threads are running
41
- def idle?
42
- @state == :idle
43
- end
44
- # Blocks the calling thread while the queue processes work.
45
- #
46
- # Returns after the timeout has expired, or after the
47
- # queue returns to the idle state.
48
- def wait(timeout = nil)
49
- @mutex.synchronize {
50
- @idle.wait(@mutex, timeout) unless idle?
51
- }
52
- end
53
- # Adds work to the queue to run in the background, and
54
- # returns immediately.
55
- #
56
- # If the block takes an argument, it will be passed the
57
- # same id used to queue the work.
58
- def background(id, &block)
59
- @mutex.synchronize {
60
- if id != @in_flight
61
- @items[id] = block
62
- @queue.background id
63
- run
64
- end
65
- }
66
- end
67
- # Adds work to the queue ahead of all background work, and
68
- # blocks until the given block has been called.
69
- #
70
- # Will preempt an id of the same value in either the
71
- # background or foreground queues.
72
- #
73
- # If the block takes an argument, it will be passed the
74
- # same id used to queue the work.
75
- def foreground(id, &block)
76
- t = nil
77
- @mutex.synchronize {
78
- if id == @in_flight
79
- t = @processing_thread unless @processing_thread == Thread.current
80
- else
81
- b = @items[id]
82
- b.kill if b.is_a? FG
83
-
84
- t = @items[id] = FG.new block, self
85
-
86
- @queue.foreground id
87
- run
88
- end
89
- }
90
- t.join if t
91
- end
92
-
93
- private
94
- def run
95
- return unless @state == :idle
96
- @state = :running
97
- @throttling = Thread.new {
98
- loop {
99
- break if @queue.shutdown? or @queue.empty?
100
-
101
- elapsed = Time.now - @t0
102
- wait_time = 1.0 / @limit + 0.01
103
- if @processing_thread and elapsed < wait_time
104
- @mutex.synchronize {
105
- @pausing.wait @mutex, wait_time - elapsed
106
- }
107
- end
108
-
109
- if id = @queue.pop
110
- @mutex.synchronize {
111
- @in_flight = id
112
- @processing_thread = Thread.new {
113
- block = @items[@in_flight]
114
- if block.arity == 0
115
- block.call
116
- else
117
- block.call @in_flight
118
- end
119
- }
120
- }
121
- @processing_thread.join if @processing_thread
122
- end
123
-
124
- @t0 = Time.now
125
- }
126
-
127
- @mutex.synchronize {
128
- @state = :idle
129
- if @queue.shutdown? or @queue.empty?
130
- @idle.signal
131
- else
132
- # Restart to prevent a join deadlock
133
- send :run
134
- end
135
- }
136
- }
137
- end
138
- class FG #:nodoc: all
139
- def initialize(block, h)
140
- @block = block
141
- @thread = Thread.new {
142
- Thread.stop
143
- @block.call *@args
144
- }
145
- @h = h
146
- end
147
- def arity
148
- @block.arity
149
- end
150
- def call(*args)
151
- @args = args
152
- @thread.run
153
- end
154
- def kill
155
- @thread.kill
156
- end
157
- def join
158
- @thread.join
159
- end
160
- end
161
- class PriorityQueue #:nodoc: all
162
- def initialize
163
- @mutex = Mutex.new
164
- @fg = []
165
- @bg = []
166
- @received = ConditionVariable.new
167
- @shutdown = false
168
- end
169
-
170
- def shutdown
171
- @shutdown = true
172
- @received.signal
173
- end
174
-
175
- def shutdown?
176
- @shutdown
177
- end
178
-
179
- def empty?
180
- @mutex.synchronize {
181
- @fg.empty? and @bg.empty?
182
- }
183
- end
184
-
185
- def background(id)
186
- @mutex.synchronize {
187
- unless @shutdown || @bg.include?(id)
188
- @bg << id
189
- @received.signal
190
- end
191
- }
192
- end
193
-
194
- def foreground(id)
195
- @mutex.synchronize {
196
- unless @shutdown || @fg.include?(id)
197
- @fg << id
198
- if @bg.include?(id)
199
- @bg.delete id
200
- else
201
- @received.signal
202
- end
203
- end
204
- }
205
- end
206
-
207
- def pop
208
- @mutex.synchronize {
209
- if @fg.empty? and @bg.empty?
210
- @received.wait(@mutex) unless @shutdown
211
- end
212
-
213
- if @shutdown
214
- elsif ! @fg.empty?
215
- @fg.shift
216
- else
217
- @bg.shift
218
- end
219
- }
220
- end
221
- end
222
- end
1
+ require_relative 'throttle-queue/single-process'
@@ -0,0 +1,103 @@
1
+ require 'drb'
2
+ require 'fileutils'
3
+ require_relative 'single-process.rb'
4
+
5
+ class ThrottleQueue
6
+ # ThrottleQueue::MultiProcess is a wrapper around ThrottleQueue
7
+ # that shares the queue between multiple processes.
8
+ #
9
+ # Example:
10
+ # q = ThrottleQueue::MultiProcess 3
11
+ # files.each {|file|
12
+ # q.background(file) {|id|
13
+ # fetch file
14
+ # }
15
+ # }
16
+ class MultiProcess
17
+ # Creates a new ThrottleQueue::MultiProcess with the given rate limit (per second).
18
+ #
19
+ # If this is the first instace of the shared queue, it becomes the master queue and
20
+ # starts a DRbServer instace. If a DRbServer is already running, it connects to the
21
+ # queue as a remote DRbObject.
22
+ def initialize(limit, name = 'ThrottleQueue')
23
+ tmp = "/tmp/#{name}.sock"
24
+ FileUtils.touch tmp
25
+ File.open(tmp, 'r+') {|f|
26
+ f.flock File::LOCK_EX
27
+ begin
28
+ port = f.read.to_i
29
+ if port == 0
30
+ @queue = ThrottleQueue.new(limit)
31
+ @drb = DRb.start_service nil, @queue
32
+ f.seek 0, IO::SEEK_SET
33
+ f.truncate 0
34
+ f.write @drb.uri[/\d+$/]
35
+ f.flock File::LOCK_UN
36
+ else
37
+ @queue = DRbObject.new_with_uri("druby://localhost:#{port}")
38
+ @queue.idle?
39
+ @drb = DRb.start_service
40
+ f.flock File::LOCK_UN
41
+ end
42
+ rescue DRb::DRbConnError
43
+ f.seek 0, IO::SEEK_SET
44
+ f.truncate 0
45
+ retry
46
+ end
47
+ }
48
+
49
+ end
50
+ # Signals the queue to stop processing and shutdown.
51
+ #
52
+ # The DRbServer is shutdown in either the master process or any
53
+ # client process.
54
+ def shutdown
55
+ @queue.shutdown
56
+ @drb.stop_service if @drb
57
+ end
58
+ # Returns true if there is nothing queued and no
59
+ # threads are running
60
+ def idle?
61
+ @queue.idle?
62
+ end
63
+ # Blocks the calling thread while the queue processes work.
64
+ #
65
+ # Returns after the timeout has expired, or after the
66
+ # queue returns to the idle state.
67
+ def wait(timeout = nil)
68
+ begin
69
+ @queue.wait(timeout)
70
+ rescue DRb::DRbConnError
71
+ end
72
+ end
73
+ # Adds work to the queue to run in the background, and
74
+ # returns immediately.
75
+ #
76
+ # If the block takes an argument, it will be passed the
77
+ # same id used to queue the work.
78
+ #
79
+ # The block may be preempted by a foreground job started in
80
+ # this or another process. If not preempted, the block will
81
+ # run in this process.
82
+ def background(id, &block)
83
+ @queue.background(id, &block)
84
+ end
85
+ # Adds work to the queue ahead of all background work, and
86
+ # blocks until the given block has been called.
87
+ #
88
+ # Will preempt an id of the same value in the
89
+ # background queue, and wait on an id of the same value already
90
+ # in the foreground queue.
91
+ #
92
+ # If the block takes an argument, it will be passed the
93
+ # same id used to queue the work.
94
+ #
95
+ # The block may wait on an already queued foreground job in
96
+ # this or another process. If so queued, this block will not
97
+ # run. If the block does run, it will run in this process.
98
+ def foreground(id, &block)
99
+ @queue.foreground(id, &block)
100
+ end
101
+ end
102
+ end
103
+
@@ -0,0 +1,224 @@
1
+ require 'thread'
2
+ # ThrottleQueue is a thread-safe rate-limited work queue. It allows both
3
+ # background and foreground operations.
4
+ #
5
+ # Example:
6
+ # q = ThrottleQueue 3
7
+ # files.each {|file|
8
+ # q.background(file) {|id|
9
+ # fetch file
10
+ # }
11
+ # }
12
+ class ThrottleQueue
13
+ # Creates a new ThrottleQueue with the given rate limit (per second).
14
+ def initialize(limit)
15
+ raise "refusing to do zero work per second" if limit <= 0
16
+ @limit = limit
17
+
18
+ @queue = PriorityQueue.new
19
+
20
+ @mutex = Mutex.new
21
+ @pausing = ConditionVariable.new
22
+ @idle = ConditionVariable.new
23
+ @in_flight = nil
24
+ @processing_thread = nil
25
+ @items = {}
26
+
27
+ @throttling = nil
28
+ @state = :idle
29
+ @t0 = Time.now
30
+ end
31
+ # Signals the queue to stop processing and shutdown.
32
+ #
33
+ # Items still in the queue are dropped. Any item
34
+ # currently in flight will finish.
35
+ def shutdown
36
+ @queue.shutdown
37
+ @pausing.signal
38
+ end
39
+ # Returns true if there is nothing queued and no
40
+ # threads are running
41
+ def idle?
42
+ @state == :idle
43
+ end
44
+ # Blocks the calling thread while the queue processes work.
45
+ #
46
+ # Returns after the timeout has expired, or after the
47
+ # queue returns to the idle state.
48
+ def wait(timeout = nil)
49
+ @mutex.synchronize {
50
+ @idle.wait(@mutex, timeout) unless idle?
51
+ }
52
+ end
53
+ # Adds work to the queue to run in the background, and
54
+ # returns immediately.
55
+ #
56
+ # If the block takes an argument, it will be passed the
57
+ # same id used to queue the work.
58
+ def background(id, &block)
59
+ @mutex.synchronize {
60
+ unless @items.has_key? id
61
+ @items[id] = block
62
+ @queue.background id
63
+ run
64
+ end
65
+ }
66
+ end
67
+ # Adds work to the queue ahead of all background work, and
68
+ # blocks until the given block has been called.
69
+ #
70
+ # Will preempt an id of the same value in the
71
+ # background queue, and wait on an id of the same value already
72
+ # in the foreground queue.
73
+ #
74
+ # If the block takes an argument, it will be passed the
75
+ # same id used to queue the work.
76
+ def foreground(id, &block)
77
+ t = nil
78
+ @mutex.synchronize {
79
+ if id == @in_flight
80
+ t = @processing_thread unless @processing_thread == Thread.current
81
+ else
82
+ t = @items[id]
83
+ unless t.is_a? FG
84
+ t = @items[id] = FG.new block, self
85
+ @queue.foreground id
86
+ run
87
+ end
88
+ end
89
+ }
90
+ t.join if t
91
+ end
92
+
93
+ private
94
+ def run
95
+ return unless @state == :idle
96
+ @state = :running
97
+ @throttling = Thread.new {
98
+ loop {
99
+ break if @queue.shutdown? or @queue.empty?
100
+
101
+ elapsed = Time.now - @t0
102
+ wait_time = 1.0 / @limit + 0.01
103
+ if @processing_thread and elapsed < wait_time
104
+ @mutex.synchronize {
105
+ @pausing.wait @mutex, wait_time - elapsed
106
+ }
107
+ end
108
+
109
+ if id = @queue.pop
110
+ @mutex.synchronize {
111
+ @in_flight = id
112
+ @processing_thread = Thread.new {
113
+ block = @items[@in_flight]
114
+ if block.arity == 0
115
+ block.call
116
+ else
117
+ block.call @in_flight
118
+ end
119
+ }
120
+ }
121
+ @processing_thread.join if @processing_thread
122
+
123
+ @mutex.synchronize {
124
+ @items.delete @in_flight
125
+ @in_flight = nil
126
+ }
127
+ end
128
+
129
+ @t0 = Time.now
130
+ }
131
+
132
+ @mutex.synchronize {
133
+ @state = :idle
134
+ if @queue.shutdown? or @queue.empty?
135
+ @idle.broadcast
136
+ else
137
+ # Restart to prevent a join deadlock
138
+ send :run
139
+ end
140
+ }
141
+ }
142
+ end
143
+ class FG #:nodoc: all
144
+ def initialize(block, h)
145
+ @block = block
146
+ @thread = Thread.new {
147
+ Thread.stop unless @args
148
+ @block.call *@args
149
+ }
150
+ @h = h
151
+ end
152
+ def arity
153
+ @block.arity
154
+ end
155
+ def call(*args)
156
+ @args = args
157
+ @thread.run
158
+ end
159
+ def join
160
+ @thread.join
161
+ end
162
+ end
163
+ class PriorityQueue #:nodoc: all
164
+ def initialize
165
+ @mutex = Mutex.new
166
+ @fg = []
167
+ @bg = []
168
+ @received = ConditionVariable.new
169
+ @shutdown = false
170
+ end
171
+
172
+ def shutdown
173
+ @shutdown = true
174
+ @received.signal
175
+ end
176
+
177
+ def shutdown?
178
+ @shutdown
179
+ end
180
+
181
+ def empty?
182
+ @mutex.synchronize {
183
+ @fg.empty? and @bg.empty?
184
+ }
185
+ end
186
+
187
+ def background(id)
188
+ @mutex.synchronize {
189
+ unless @shutdown || @bg.include?(id)
190
+ @bg << id
191
+ @received.signal
192
+ end
193
+ }
194
+ end
195
+
196
+ def foreground(id)
197
+ @mutex.synchronize {
198
+ unless @shutdown || @fg.include?(id)
199
+ @fg << id
200
+ if @bg.include?(id)
201
+ @bg.delete id
202
+ else
203
+ @received.signal
204
+ end
205
+ end
206
+ }
207
+ end
208
+
209
+ def pop
210
+ @mutex.synchronize {
211
+ if @fg.empty? and @bg.empty?
212
+ @received.wait(@mutex) unless @shutdown
213
+ end
214
+
215
+ if @shutdown
216
+ elsif ! @fg.empty?
217
+ @fg.shift
218
+ else
219
+ @bg.shift
220
+ end
221
+ }
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,88 @@
1
+ require_relative '../lib/throttle-queue/multi-process'
2
+ require 'test/unit'
3
+ require 'thread'
4
+
5
+ class ThrottleQueueMultiProcessTest < Test::Unit::TestCase
6
+
7
+ def testSingleProcess
8
+ t = ThrottleQueue::MultiProcess.new 10
9
+
10
+ results = []
11
+ %w(apple banana cake donut egg).each {|w|
12
+ t.background(w) {
13
+ results << w.capitalize
14
+ }
15
+ }
16
+ t.wait
17
+ assert_equal %w(Apple Banana Cake Donut Egg), results
18
+ ensure
19
+ t.shutdown
20
+ end
21
+
22
+ def testTwoProcesses
23
+ p = fork {
24
+ t = ThrottleQueue::MultiProcess.new 10
25
+ %w(fig grape ham ice jelly).each {|w|
26
+ t.background(w) {
27
+ File.open('results.txt', 'a') {|f|
28
+ f.puts w.capitalize
29
+ }
30
+ }
31
+ }
32
+ t.wait
33
+ }
34
+
35
+ t = ThrottleQueue::MultiProcess.new 10
36
+
37
+ results = []
38
+ %w(apple banana cake donut egg).each {|w|
39
+ t.background(w) {
40
+ results << w.capitalize
41
+ }
42
+ }
43
+ t.wait
44
+ assert_equal %w(Apple Banana Cake Donut Egg), results
45
+ assert_equal %w(Fig Grape Ham Ice Jelly), File.open('results.txt') {|f| f.readlines.map &:chomp}
46
+ ensure
47
+ t.shutdown
48
+ FileUtils.rm_f 'results.txt'
49
+ end
50
+
51
+ def testTwoProcessesWithFG
52
+ rd, wr = IO.pipe
53
+
54
+ p = fork {
55
+ rd.close
56
+ t = ThrottleQueue::MultiProcess.new 10
57
+ %w(fig grape ham ice jelly).each {|w|
58
+ t.background(w) {
59
+ File.open('results.txt', 'a') {|f|
60
+ f.puts w.capitalize
61
+ if w == 'grape'
62
+ wr.close
63
+ end
64
+ }
65
+ }
66
+ }
67
+ t.wait
68
+ }
69
+
70
+ wr.close
71
+
72
+ t = ThrottleQueue::MultiProcess.new 10
73
+ rd.read
74
+ rd.close
75
+
76
+ t.foreground('apple') {|w|
77
+ File.open('results.txt', 'a') {|f|
78
+ f.puts w.capitalize
79
+ }
80
+ }
81
+
82
+ t.wait
83
+ assert_equal %w(Fig Grape Apple Ham Ice Jelly), File.open('results.txt') {|f| f.readlines.map &:chomp}
84
+ ensure
85
+ t.shutdown
86
+ FileUtils.rm_f 'results.txt'
87
+ end
88
+ end
@@ -1,5 +1,6 @@
1
1
  require_relative '../lib/throttle-queue'
2
2
  require 'test/unit'
3
+ require 'thread'
3
4
 
4
5
  class ThrottleQueueTest < Test::Unit::TestCase
5
6
 
@@ -61,7 +62,7 @@ class ThrottleQueueTest < Test::Unit::TestCase
61
62
  }
62
63
 
63
64
  @t.wait
64
- assert_equal %w(Apple Banana CAKEYO Donut Egg), results
65
+ assert_equal %w(Apple Banana Cake Donut Egg), results
65
66
  end
66
67
  def testForegroundInFlight
67
68
  results = []
@@ -88,7 +89,6 @@ class ThrottleQueueTest < Test::Unit::TestCase
88
89
  results << 'CAKEYO'
89
90
  }
90
91
  }
91
-
92
92
  %w(apple banana cake donut egg).each {|w|
93
93
  @t.background(w) {
94
94
  results << w.capitalize
@@ -124,6 +124,32 @@ class ThrottleQueueTest < Test::Unit::TestCase
124
124
  @t.wait
125
125
  assert_equal %w(Apple Banana Fish Grape Cake Donut Egg), results
126
126
  end
127
+ def testForegroundWaitOnQueuedForeground
128
+ results = []
129
+ threads = []
130
+
131
+ ids = Queue.new
132
+ ids << 'apple' << 'banana' << 'banana' << 'banana'
133
+
134
+ values = Queue.new
135
+ values << 'Apple' << 'Banana' << 'BANANAYO' << 'DUDE'
136
+
137
+ ids.size.times {
138
+ threads << Thread.new {
139
+ @t.foreground(ids.pop) {
140
+ results << values.pop
141
+ }
142
+ }
143
+ }
144
+
145
+ threads.each {|t|
146
+ t.join
147
+ }
148
+ @t.wait
149
+ assert_equal %w(Apple Banana), results
150
+ assert_equal 0, ids.size
151
+ assert_equal 2, values.size
152
+ end
127
153
  def testShutdownWithoutWaiting
128
154
  results = []
129
155
  %w(apple banana cake donut egg).each {|w|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throttle-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Calhoun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-20 00:00:00.000000000 Z
11
+ date: 2014-12-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A thread-safe rate-limited work queue, which allows for background and
14
14
  foreground operations.
@@ -22,6 +22,9 @@ files:
22
22
  - README.md
23
23
  - Rakefile
24
24
  - lib/throttle-queue.rb
25
+ - lib/throttle-queue/multi-process.rb
26
+ - lib/throttle-queue/single-process.rb
27
+ - test/multiprocess-test.rb
25
28
  - test/throttle-queue-test.rb
26
29
  homepage: https://github.com/theryan/throttle-queue
27
30
  licenses:
@@ -49,4 +52,5 @@ specification_version: 4
49
52
  summary: A thread-safe rate-limited work queue
50
53
  test_files:
51
54
  - test/throttle-queue-test.rb
55
+ - test/multiprocess-test.rb
52
56
  - Rakefile