redis-unique-queue 1.0.3 → 2.0

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.
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