refilling_queue 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'rspec', '~>2'
6
+ gem 'fakeredis'
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
+ [![Build Status](https://secure.travis-ci.org/grosser/refilling_queue.png)](http://travis-ci.org/grosser/refilling_queue)
@@ -0,0 +1,3 @@
1
+ class RefillingQueue
2
+ VERSION = '0.0.1'
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ require "fakeredis"
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+ require 'refilling_queue'
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: []