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 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: []