refilling_queue 0.0.1
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.
- data/.travis.yml +4 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +30 -0
- data/Rakefile +22 -0
- data/Readme.md +32 -0
- data/lib/refilling_queue/version.rb +3 -0
- data/lib/refilling_queue.rb +57 -0
- data/refilling_queue.gemspec +12 -0
- data/spec/refilling_queue_spec.rb +99 -0
- data/spec/spec_helper.rb +4 -0
- metadata +62 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
refilling_queue (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
fakeredis (0.4.0)
|
11
|
+
redis (~> 3.0.0)
|
12
|
+
rake (0.9.2)
|
13
|
+
redis (3.0.1)
|
14
|
+
rspec (2.6.0)
|
15
|
+
rspec-core (~> 2.6.0)
|
16
|
+
rspec-expectations (~> 2.6.0)
|
17
|
+
rspec-mocks (~> 2.6.0)
|
18
|
+
rspec-core (2.6.4)
|
19
|
+
rspec-expectations (2.6.0)
|
20
|
+
diff-lcs (~> 1.1.2)
|
21
|
+
rspec-mocks (2.6.0)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
fakeredis
|
28
|
+
rake
|
29
|
+
refilling_queue!
|
30
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
task :default do
|
4
|
+
sh "rspec spec/"
|
5
|
+
end
|
6
|
+
|
7
|
+
# extracted from https://github.com/grosser/project_template
|
8
|
+
rule /^version:bump:.*/ do |t|
|
9
|
+
sh "git status | grep 'nothing to commit'" # ensure we are not dirty
|
10
|
+
index = ['major', 'minor','patch'].index(t.name.split(':').last)
|
11
|
+
file = 'lib/refilling_queue/version.rb'
|
12
|
+
|
13
|
+
version_file = File.read(file)
|
14
|
+
old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
|
15
|
+
version_parts[index] = version_parts[index].to_i + 1
|
16
|
+
version_parts[2] = 0 if index < 2 # remove patch for minor
|
17
|
+
version_parts[1] = 0 if index < 1 # remove minor for major
|
18
|
+
new_version = version_parts * '.'
|
19
|
+
File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
|
20
|
+
|
21
|
+
sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
|
22
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
A queue that refreshes itself when it gets empty or stale, so you can keep popping
|
2
|
+
|
3
|
+
Install
|
4
|
+
=======
|
5
|
+
|
6
|
+
gem install refilling_queue
|
7
|
+
|
8
|
+
Usage
|
9
|
+
=====
|
10
|
+
|
11
|
+
queue = RefillingQueue.new resque_client, "my_queue", :refresh_every => 30.seconds do
|
12
|
+
expensive_operation.map(&:id)
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
queue.pop
|
17
|
+
rescue
|
18
|
+
RefillingQueue::EmptyRefill # queue was empty, refilled but is still empty
|
19
|
+
end
|
20
|
+
|
21
|
+
queue.pop -> return id
|
22
|
+
... # queue empty ?
|
23
|
+
queue.pop -> run block -> store new ids -> return id
|
24
|
+
... # 30 seconds elapsed (global expires_at stored in reque_client) ?
|
25
|
+
queue.pop -> run block -> store new ids -> return id
|
26
|
+
|
27
|
+
Author
|
28
|
+
======
|
29
|
+
[Michael Grosser](http://grosser.it)<br/>
|
30
|
+
michael@grosser.it<br/>
|
31
|
+
License: MIT<br/>
|
32
|
+
[](http://travis-ci.org/grosser/refilling_queue)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'refilling_queue/version'
|
2
|
+
|
3
|
+
class RefillingQueue
|
4
|
+
class EmptyRefill < RuntimeError
|
5
|
+
end
|
6
|
+
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
:lock_timeout => 60,
|
9
|
+
:refresh_every => nil
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(client, name, options={}, &block)
|
13
|
+
@client, @name, @block = client, name, block
|
14
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
15
|
+
raise "Invalid keys" if (options.keys - DEFAULT_OPTIONS.keys).any?
|
16
|
+
end
|
17
|
+
|
18
|
+
def pop
|
19
|
+
item = @client.lpop @name
|
20
|
+
return item unless item.nil?
|
21
|
+
|
22
|
+
refill
|
23
|
+
|
24
|
+
item = @client.lpop @name
|
25
|
+
return item unless item.nil?
|
26
|
+
|
27
|
+
raise RefillingQueue::EmptyRefill
|
28
|
+
end
|
29
|
+
|
30
|
+
def refill
|
31
|
+
lock do
|
32
|
+
results = @block.call
|
33
|
+
@client.pipelined do
|
34
|
+
@client.del @name
|
35
|
+
results.each{ |r| @client.rpush @name, r } # TODO https://github.com/redis/redis-rb/issues/253
|
36
|
+
@client.expire @name, @options[:refresh_every] if @options[:refresh_every]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def empty?
|
42
|
+
@client.llen(@name) == 0
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def lock
|
48
|
+
lock = "#{@name}_lock"
|
49
|
+
return unless @client.setnx lock, "1"
|
50
|
+
@client.expire lock, @options[:lock_timeout]
|
51
|
+
begin
|
52
|
+
yield
|
53
|
+
ensure
|
54
|
+
@client.del lock, "1"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
name = "refilling_queue"
|
3
|
+
require "#{name}/version"
|
4
|
+
|
5
|
+
Gem::Specification.new name, RefillingQueue::VERSION do |s|
|
6
|
+
s.summary = "A queue that refreshes itself when it gets empty or stale, so you can keep popping"
|
7
|
+
s.authors = ["Michael Grosser"]
|
8
|
+
s.email = "michael@grosser.it"
|
9
|
+
s.homepage = "http://github.com/grosser/#{name}"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.license = 'MIT'
|
12
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fakeredis'
|
3
|
+
|
4
|
+
describe RefillingQueue do
|
5
|
+
def kill_all_threads
|
6
|
+
Thread.list.each {|thread| thread.exit unless thread == Thread.current }
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:client){ FakeRedis::Redis.new }
|
10
|
+
|
11
|
+
before do
|
12
|
+
kill_all_threads
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has a VERSION" do
|
16
|
+
RefillingQueue::VERSION.should =~ /^[\.\da-z]+$/
|
17
|
+
end
|
18
|
+
|
19
|
+
context "#initialize" do
|
20
|
+
it "does not fill itself when started full" do
|
21
|
+
x = 0
|
22
|
+
RefillingQueue.new(client, "x"){ x = 1 }
|
23
|
+
x.should == 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#pop" do
|
28
|
+
it "removes an element" do
|
29
|
+
queue = RefillingQueue.new(client, "x"){ [1,2,3,4] }
|
30
|
+
queue.pop.should == 1
|
31
|
+
queue.pop.should == 2
|
32
|
+
queue.pop.should == 3
|
33
|
+
end
|
34
|
+
|
35
|
+
it "only tries to refill once / raises on empty refill" do
|
36
|
+
calls = []
|
37
|
+
queue = RefillingQueue.new(client, "x"){ calls << 1; [] }
|
38
|
+
expect{
|
39
|
+
queue.pop
|
40
|
+
}.to raise_error(RefillingQueue::EmptyRefill)
|
41
|
+
calls.should == [1]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "refills itself if queue gets empty" do
|
45
|
+
content = [1]
|
46
|
+
queue = RefillingQueue.new(client, "x"){ content }
|
47
|
+
queue.pop.should == 1
|
48
|
+
content.replace [4,5]
|
49
|
+
queue.pop.should == 4
|
50
|
+
queue.pop.should == 5
|
51
|
+
queue.pop.should == 4
|
52
|
+
end
|
53
|
+
|
54
|
+
it "refills itself when it expires" do
|
55
|
+
content = [1,2,3]
|
56
|
+
queue = RefillingQueue.new(client, "x", :refresh_every => 0.1){ content }
|
57
|
+
|
58
|
+
queue.pop.should == 1
|
59
|
+
content.replace [4,5]
|
60
|
+
queue.pop.should == 2
|
61
|
+
sleep 0.5
|
62
|
+
|
63
|
+
queue.pop.should == 4
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with multiple actors" do
|
68
|
+
it "only refills once" do
|
69
|
+
called = []
|
70
|
+
Thread.new do
|
71
|
+
RefillingQueue.new(client, "x"){ sleep 0.2; called << 1; [] }.pop
|
72
|
+
end
|
73
|
+
Thread.new do
|
74
|
+
RefillingQueue.new(client, "x"){ sleep 0.2; called << 1; [] }.pop
|
75
|
+
end
|
76
|
+
sleep 0.3
|
77
|
+
called.should == [1]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can refill after refill is complete" do
|
81
|
+
called = []
|
82
|
+
RefillingQueue.new(client, "x"){ called << 1; [1] }.pop
|
83
|
+
RefillingQueue.new(client, "x"){ called << 2; [1] }.pop
|
84
|
+
called.should == [1,2]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can refill if lock expired" do
|
88
|
+
called = []
|
89
|
+
RefillingQueue.new(client, "x", :lock_timeout => 1){ called << 1; [1] }.pop
|
90
|
+
Thread.new do
|
91
|
+
# lock-blocker
|
92
|
+
RefillingQueue.new(client, "x", :lock_timeout => 1){ sleep 2; called << 2; [1] }.pop
|
93
|
+
end
|
94
|
+
sleep 1.5
|
95
|
+
RefillingQueue.new(client, "x", :lock_timeout => 1){ called << 3; [1] }.pop
|
96
|
+
called.should == [1,3]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: refilling_queue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Grosser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-12 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: michael@grosser.it
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- .travis.yml
|
21
|
+
- Gemfile
|
22
|
+
- Gemfile.lock
|
23
|
+
- Rakefile
|
24
|
+
- Readme.md
|
25
|
+
- lib/refilling_queue.rb
|
26
|
+
- lib/refilling_queue/version.rb
|
27
|
+
- refilling_queue.gemspec
|
28
|
+
- spec/refilling_queue_spec.rb
|
29
|
+
- spec/spec_helper.rb
|
30
|
+
homepage: http://github.com/grosser/refilling_queue
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
hash: -2693512841144003384
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
hash: -2693512841144003384
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.8.24
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: A queue that refreshes itself when it gets empty or stale, so you can keep
|
61
|
+
popping
|
62
|
+
test_files: []
|