redis-unique-queue 1.0.3 → 2.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: 79dd06e09747ec1015a14103fd72c6e19a121b1c
4
- data.tar.gz: 5851bf6cd8e38b65915bc6c449796959c2600c5a
3
+ metadata.gz: de78d99306d017bd477d116439e987481571fcbc
4
+ data.tar.gz: 313940b8fe19ff797910d5b9fb0997d4e08879f7
5
5
  SHA512:
6
- metadata.gz: b202259ac4ef6ba1bea1b0785d2e40bc519c3aa9d7731b9fe1a1ae2df7ee6f07a4446cec41cbd04170d139059f4df5fab6489e69b48d19f9cd112002349aeea3
7
- data.tar.gz: 9f8ae2edf1e423b50a4e159108d560f069abb2961fe6c8047b761b8ab6766526c2dfe828497979142197cb2a25c569175fa0dc740f04d2a73d76866b15f963fa
6
+ metadata.gz: 1f918cd173c15ea8d164aa6f7ac277752701d2af2c6e90bfbd5876b12630445ee2453a8260e449ce8fc3c779514f8a279d5c543af16fb35da550eca34827402d
7
+ data.tar.gz: 4f48d23ef3510e98e76cc8270c4cda303ffa8749c185767a7055b56c74130cac10abd4a3df27cc8d7b1f7b2ab7e59e64ee816d7af706d230f11f84aba1a5a178
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Redis::Unique::Queue
1
+ # RedisUniqueQueue
2
2
 
3
3
  A unique FIFO queue with atomic operations built on top of Redis. Useful if you want to enqueue data without worrying about it existing multiple times in the queue.
4
4
 
@@ -22,19 +22,19 @@ Or install it yourself as:
22
22
  You can instantiate a named queue using your default Redis configuration.
23
23
 
24
24
  ```ruby
25
- q = Redis::Unique::Queue.new 'jobs'
25
+ q = RedisUniqueQueue.new 'jobs'
26
26
  ```
27
27
 
28
28
  Or you can pass in your own instance of the Redis class.
29
29
 
30
30
  ```ruby
31
- q = Redis::Unique::Queue.new 'jobs', Redis.new(:host => "10.0.1.1", :port => 6380, :db => 15)
31
+ q = RedisUniqueQueue.new 'jobs', Redis.new(:host => "10.0.1.1", :port => 6380, :db => 15)
32
32
  ```
33
33
 
34
34
  A third option is to instead pass your Redis configurations.
35
35
 
36
36
  ```ruby
37
- q = Redis::Unique::Queue.new 'jobs', :host => "10.0.1.1", :port => 6380, :db => 15
37
+ q = RedisUniqueQueue.new 'jobs', :host => "10.0.1.1", :port => 6380, :db => 15
38
38
  ```
39
39
 
40
40
  ## Using the queue
@@ -0,0 +1,179 @@
1
+ require "redis"
2
+
3
+ class RedisUniqueQueue
4
+ attr_reader :name
5
+
6
+ VERSION = "2.0"
7
+
8
+ class InvalidNameException < StandardError; end;
9
+ class InvalidRedisConfigException < StandardError; end;
10
+
11
+ def initialize(name, redis_or_options = {}, more_options = {})
12
+ name = name.to_s if name.kind_of? Symbol
13
+
14
+ raise InvalidNameException.new unless name.kind_of?(String) && name.size > 0
15
+ @name = name
16
+ @redis = if redis_or_options.kind_of?(Redis)
17
+ redis_or_options
18
+ elsif redis_or_options.kind_of? Hash
19
+ ::Redis.new redis_or_options
20
+ elsif defined?(ActiveSupport::Cache::RedisStore) && redis_or_options.kind_of?(ActiveSupport::Cache::RedisStore)
21
+ @pooled = redis_or_options.data.kind_of?(ConnectionPool)
22
+ redis_or_options.data
23
+ elsif defined?(ConnectionPool) && redis_or_options.kind_of?(ConnectionPool)
24
+ @pooled = true
25
+ redis_or_options
26
+ else
27
+ raise InvalidRedisConfigException.new
28
+ end
29
+
30
+ if more_options.kind_of?(Hash) && more_options[:expire]
31
+ expire more_options[:expire]
32
+ end
33
+ end
34
+
35
+ def push data
36
+ [block_on_atomic_attempt { attempt_atomic_push_multi(data) }].flatten.first
37
+ end
38
+
39
+ def push_multi *values
40
+ if values.size > 0
41
+ block_on_atomic_attempt { attempt_atomic_push_multi(*values) }
42
+ end
43
+ end
44
+
45
+ def pop
46
+ block_on_atomic_attempt { attempt_atomic_pop }
47
+ end
48
+
49
+ def pop_all
50
+ block_on_atomic_attempt { attempt_atomic_pop_all }
51
+ end
52
+
53
+ def pop_multi amount
54
+ block_on_atomic_attempt { attempt_atomic_pop_multi amount }
55
+ end
56
+
57
+ def front
58
+ with { |redis| redis.zrange(name, 0, 0).first }
59
+ end
60
+
61
+ def back
62
+ with { |redis| redis.zrevrange(name, 0, 0).first }
63
+ end
64
+
65
+ def remove data
66
+ with { |redis| redis.zrem name, data }
67
+ end
68
+
69
+ def remove_item_by_index index
70
+ with { |redis| redis.zremrangebyrank name, index, index }
71
+ end
72
+
73
+ def size
74
+ with { |redis| redis.zcard name }
75
+ end
76
+
77
+ def all
78
+ peek 0, size
79
+ end
80
+
81
+ def peek index, amount = 1
82
+ with { |redis| redis.zrange name, index, index + amount - 1 }
83
+ end
84
+
85
+ def include? data
86
+ !with { |redis| redis.zscore(name, data).nil? }
87
+ end
88
+
89
+ def clear
90
+ with { |redis| redis.del name }
91
+ []
92
+ end
93
+
94
+ def expire seconds
95
+ with { |redis| redis.expire name, seconds }
96
+ end
97
+
98
+ private
99
+
100
+ def max_score
101
+ with { |redis| redis.zscore name, back }
102
+ end
103
+
104
+ def attempt_atomic_push_multi *values
105
+ with do |redis|
106
+ success = redis.watch(name) do
107
+ score = [Time.now.to_f, max_score].compact.max
108
+ values = values.first if 1 == values.size && values.first.kind_of?(Array)
109
+ scored_values = []
110
+ values.each_with_index do |value, i|
111
+ scored_values << [score + i, value]
112
+ end
113
+ redis.multi do |multi|
114
+ multi.zadd name, scored_values
115
+ end
116
+ end
117
+
118
+ [success, values]
119
+ end
120
+ end
121
+
122
+ def attempt_atomic_pop_multi amount
123
+ attempt_atomic_read_write lambda { peek 0, amount }, lambda { |multi, read_result| multi.zremrangebyrank name, 0, amount - 1 }
124
+ end
125
+
126
+ def attempt_atomic_pop_all
127
+ attempt_atomic_read_write lambda { all }, lambda { |multi, read_result| multi.del name }
128
+ end
129
+
130
+ def attempt_atomic_pop
131
+ read = lambda do
132
+ with{|redis| redis.zrangebyscore(name, 0, max_score, :with_scores => false, :limit => [0, 1]).first}
133
+ end
134
+
135
+ write = lambda do |multi, read_result|
136
+ multi.zrem name, read_result
137
+ end
138
+
139
+ attempt_atomic_read_write read, write
140
+ end
141
+
142
+ def block_on_atomic_attempt
143
+ begin
144
+ success, result = yield
145
+ end while !success && result
146
+ result
147
+ end
148
+
149
+ def attempt_atomic_read_write read_op, write_op
150
+ result = nil
151
+ success = with do |redis|
152
+ redis.watch(name) do
153
+ result = read_op.call
154
+ if result
155
+ redis.multi do |multi|
156
+ write_op.call multi, result
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ [success, result]
163
+ end
164
+
165
+ private
166
+
167
+ def with(&block)
168
+ if pooled?
169
+ @redis.with(&block)
170
+ else
171
+ block.call(@redis)
172
+ end
173
+ end
174
+
175
+ def pooled?
176
+ !!@pooled
177
+ end
178
+ end
179
+
@@ -1,11 +1,11 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'redis/unique/queue'
4
+ require 'redis_unique_queue'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "redis-unique-queue"
8
- spec.version = Redis::Unique::Queue::VERSION
8
+ spec.version = RedisUniqueQueue::VERSION
9
9
  spec.authors = ["Misha Conway"]
10
10
  spec.email = ["mishaAconway@gmail.com"]
11
11
  spec.summary = %q{A unique queue with atomic operations implemented in Redis}
@@ -0,0 +1,150 @@
1
+ require 'spec_helper'
2
+
3
+ describe RedisUniqueQueue do
4
+ let(:redis) { Redis.new }
5
+ let(:name) { "some_queue" }
6
+ let(:queue) { described_class.new(name, redis) }
7
+
8
+ before do
9
+ redis.flushall
10
+ end
11
+
12
+ context "instance methods" do
13
+ describe '#push' do
14
+ it 'should push items to the queue' do
15
+ expect(queue.size).to eq(0)
16
+ queue.push('a')
17
+ queue.push('b')
18
+ queue.push('c')
19
+ expect(queue.size).to eq(3)
20
+ expect(queue.all).to eq(%w(a b c))
21
+ end
22
+ end
23
+
24
+ describe '#push_multi' do
25
+ it 'should push multiple items to the queue' do
26
+ expect(queue.size).to eq(0)
27
+ queue.push_multi('a', 'b', 'c')
28
+ expect(queue.size).to eq(3)
29
+ expect(queue.all).to eq(%w(a b c))
30
+ end
31
+ end
32
+
33
+ context do
34
+ before do
35
+ expect(queue.size).to eq(0)
36
+ queue.push('a')
37
+ queue.push('b')
38
+ queue.push('c')
39
+ expect(queue.size).to eq(3)
40
+ end
41
+
42
+ describe '#pop' do
43
+ it 'should pop single items from the queue' do
44
+ expect(queue.pop).to eq('a')
45
+ expect(queue.size).to eq(2)
46
+
47
+ expect(queue.pop).to eq('b')
48
+ expect(queue.size).to eq(1)
49
+
50
+ expect(queue.pop).to eq('c')
51
+ expect(queue.size).to eq(0)
52
+ end
53
+ end
54
+
55
+ describe '#pop_all' do
56
+ it 'should pop all items from the queue' do
57
+ expect(queue.pop_all).to eq(%w(a b c))
58
+ expect(queue.size).to eq(0)
59
+ end
60
+ end
61
+
62
+ describe '#pop_multi' do
63
+ it 'should pop multiple items from the queue' do
64
+ expect(queue.pop_multi(2)).to eq(%w(a b))
65
+ expect(queue.size).to eq(1)
66
+ end
67
+ end
68
+
69
+ describe '#front' do
70
+ it 'shows the front of the queue' do
71
+ expect(queue.front).to eq('a')
72
+ end
73
+ end
74
+
75
+ describe '#back' do
76
+ it 'shows the back of the queue' do
77
+ expect(queue.back).to eq('c')
78
+ end
79
+ end
80
+
81
+ describe '#remove' do
82
+ it 'removes single items from the queue' do
83
+ queue.remove('b')
84
+ expect(queue.size).to eq(2)
85
+ expect(queue.all).to eq(%w(a c))
86
+ end
87
+ end
88
+
89
+ describe '#remove_item_by_index' do
90
+ it 'removes single items by index' do
91
+ queue.remove_item_by_index(1)
92
+ expect(queue.size).to eq(2)
93
+ expect(queue.all).to eq(%w(a c))
94
+ end
95
+ end
96
+
97
+ describe '#size' do
98
+ it 'returns the size of the queue' do
99
+ expect(queue.size).to eq(3)
100
+ end
101
+ end
102
+
103
+ describe '#all' do
104
+ it 'returns all of the items in the queue' do
105
+ expect(queue.all).to eq(%w(a b c))
106
+ end
107
+ end
108
+
109
+ describe '#peek' do
110
+ it 'allows you to peek at items in the queue without mutating the queue' do
111
+ expect(queue.peek(0,1)).to eq(%w(a))
112
+ expect(queue.peek(1,1)).to eq(%w(b))
113
+ expect(queue.peek(2,1)).to eq(%w(c))
114
+ expect(queue.peek(3,1)).to eq([])
115
+
116
+ expect(queue.peek(0,2)).to eq(%w(a b))
117
+ expect(queue.peek(0,3)).to eq(%w(a b c))
118
+ expect(queue.peek(0,4)).to eq(%w(a b c))
119
+
120
+ expect(queue.peek(1,2)).to eq(%w(b c))
121
+ expect(queue.peek(1,3)).to eq(%w(b c))
122
+
123
+ expect(queue.size).to eq(3)
124
+ expect(queue.all).to eq(%w(a b c))
125
+ end
126
+ end
127
+
128
+ describe '#include?' do
129
+ it 'indicates if an item is in the queue or not' do
130
+ expect(queue.include?('a')).to be true
131
+ expect(queue.include?('A')).to be false
132
+
133
+ expect(queue.include?('b')).to be true
134
+ expect(queue.include?('B')).to be false
135
+
136
+ expect(queue.include?('c')).to be true
137
+ expect(queue.include?('C')).to be false
138
+ end
139
+ end
140
+
141
+ describe '#clear' do
142
+ it 'empties the queue' do
143
+ queue.clear
144
+ expect(queue.size).to eq(0)
145
+ expect(queue.all).to eq([])
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,2 @@
1
+ RSpec.configure do |config|
2
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-unique-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: '2.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Misha Conway
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-17 00:00:00.000000000 Z
11
+ date: 2019-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -64,8 +64,10 @@ files:
64
64
  - LICENSE.txt
65
65
  - README.md
66
66
  - Rakefile
67
- - lib/redis/unique/queue.rb
67
+ - lib/redis_unique_queue.rb
68
68
  - redis-unique-queue.gemspec
69
+ - spec/redis_unique_queue_spec.rb
70
+ - spec/spec_helper.rb
69
71
  homepage: https://github.com/MishaConway/ruby-redis-unique-queue
70
72
  licenses:
71
73
  - MIT
@@ -86,8 +88,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
88
  version: '0'
87
89
  requirements: []
88
90
  rubyforge_project:
89
- rubygems_version: 2.6.12
91
+ rubygems_version: 2.6.14
90
92
  signing_key:
91
93
  specification_version: 4
92
94
  summary: A unique queue with atomic operations implemented in Redis
93
- test_files: []
95
+ test_files:
96
+ - spec/redis_unique_queue_spec.rb
97
+ - spec/spec_helper.rb
@@ -1,155 +0,0 @@
1
- require "redis"
2
-
3
- class Redis
4
- module Unique
5
- class Queue
6
- attr_reader :name
7
-
8
- VERSION = "1.0.3"
9
-
10
- class InvalidNameException < StandardError; end;
11
- class InvalidRedisConfigException < StandardError; end;
12
-
13
- def initialize(name, redis_or_options = {})
14
- name = name.to_s if name.kind_of? Symbol
15
-
16
- raise InvalidNameException.new unless name.kind_of?(String) && name.size > 0
17
- @name = name
18
- @redis = if redis_or_options.kind_of? Redis
19
- redis_or_options
20
- elsif redis_or_options.kind_of? Hash
21
- Redis.new redis_or_options
22
- else
23
- raise InvalidRedisConfigException.new
24
- end
25
- end
26
-
27
- def push data
28
- [block_on_atomic_attempt{ attempt_atomic_push_multi(data) }].flatten.first
29
- end
30
-
31
- def push_multi *values
32
- if values.size > 0
33
- block_on_atomic_attempt{ attempt_atomic_push_multi(*values) }
34
- end
35
- end
36
-
37
- def pop
38
- block_on_atomic_attempt{ attempt_atomic_pop }
39
- end
40
-
41
- def pop_all
42
- block_on_atomic_attempt{ attempt_atomic_pop_all }
43
- end
44
-
45
- def pop_multi amount
46
- block_on_atomic_attempt{ attempt_atomic_pop_multi amount }
47
- end
48
-
49
- def front
50
- @redis.zrange(name, 0, 0).first
51
- end
52
-
53
- def back
54
- @redis.zrevrange(name, 0, 0).first
55
- end
56
-
57
- def remove data
58
- @redis.zrem name, data
59
- end
60
-
61
- def remove_item_by_index index
62
- @redis.zremrangebyrank name, index, index
63
- end
64
-
65
- def size
66
- @redis.zcard name
67
- end
68
-
69
- def all
70
- peek 0, size
71
- end
72
-
73
- def peek index, amount=1
74
- @redis.zrange name, index, index + amount - 1
75
- end
76
-
77
- def include? data
78
- !@redis.zscore(name, data).nil?
79
- end
80
-
81
- def clear
82
- @redis.del name
83
- []
84
- end
85
-
86
- def expire seconds
87
- @redis.expire name, seconds
88
- end
89
-
90
- private
91
-
92
- def max_score
93
- @redis.zscore name, back
94
- end
95
-
96
- def attempt_atomic_push_multi *values
97
- success = @redis.watch(name) do
98
- score = [Time.now.to_f, max_score].compact.max
99
- values = values.first if 1 == values.size && values.first.kind_of?(Array)
100
- scored_values = []
101
- values.each_with_index do |value, i|
102
- scored_values << [score + i, value]
103
- end
104
- @redis.multi do |multi|
105
- multi.zadd name, scored_values
106
- end
107
- end
108
-
109
- [success, values]
110
- end
111
-
112
- def attempt_atomic_pop_multi amount
113
- attempt_atomic_read_write lambda{ peek 0, amount }, lambda{ |multi, read_result| multi.zremrangebyrank name, 0, amount - 1 }
114
- end
115
-
116
- def attempt_atomic_pop_all
117
- attempt_atomic_read_write lambda{ all }, lambda{ |multi, read_result| multi.del name}
118
- end
119
-
120
- def attempt_atomic_pop
121
- read = lambda do
122
- @redis.zrangebyscore(name, 0, max_score, :with_scores => false, :limit => [0, 1]).first
123
- end
124
-
125
- write = lambda do |multi, read_result|
126
- multi.zrem name, read_result
127
- end
128
-
129
- attempt_atomic_read_write read, write
130
- end
131
-
132
- def block_on_atomic_attempt
133
- begin
134
- success, result = yield
135
- end while !success && result
136
- result
137
- end
138
-
139
- def attempt_atomic_read_write read_op, write_op
140
-
141
- result = nil
142
- success = @redis.watch(name) do
143
- result = read_op.call
144
- if result
145
- @redis.multi do |multi|
146
- write_op.call multi, result
147
- end
148
- end
149
- end
150
-
151
- [success, result]
152
- end
153
- end
154
- end
155
- end