reliable-queue-rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e4da5c91348c8e894a37d4c8d260160cb5d7bbca55a30fe1f69f99c333f349c8
4
+ data.tar.gz: ddde902f582ec54278f70ca149b98574476fd6e39b40e9755c1d87c8bbbe7af2
5
+ SHA512:
6
+ metadata.gz: 282b4a5e12f6dcbf3039c6402356f53f3c3a2eaa6fedfa8102a535c5ef75a6b38e9d7296e47dad9b66c686ff8c232d34bf2d20c83bbfc143fb070314d244501a
7
+ data.tar.gz: 041ffe4a0f9fa508b4e059c461efe2dffbc5d216c8f56e72c1afc54e36e863c60f275e3086e2df964e1fa101cef8d70716379be84f2fb354d4f1f4d6853c615a
@@ -0,0 +1,7 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file. This
3
+ project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [0.1.0] - 2020-12-14
6
+ ### Added
7
+ - Initial release
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020-2021 Altmetric LLP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,48 @@
1
+ # Reliable Queue [![Build Status](https://api.travis-ci.com/altmetric/reliable-queue-rb.svg?branch=main)](https://travis-ci.com/github/altmetric/reliable-queue-rb)
2
+
3
+ Ruby reliable queue implementation on top of Redis. It makes sure that message is not lost between popping it from Redis queue and compeleting the task.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's `Gemfile`:
8
+
9
+ ```ruby
10
+ gem 'reliable-queue-rb', '~> 0.1.0'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install reliable-queue-rb
20
+
21
+ ## Usage
22
+ Reliable Queue
23
+ ```ruby
24
+ queue = ReliableQueue.new(redis_queue, redis_client)
25
+
26
+ queue.each do |message|
27
+ # process message
28
+ end
29
+ ```
30
+
31
+ ChunkedReliableQueue
32
+ ```ruby
33
+ queue = ChunkedReliableQueue.new(working_on_queue_suffix, redis_queue, redis_client)
34
+
35
+ queue.each_slice(batch_size) do |messages|
36
+ # process array of messages
37
+ end
38
+ ```
39
+
40
+ ## Contributing
41
+
42
+ Bug reports and pull requests are welcome on GitHub at https://github.com/altmetric/reliable-queue-rb.
43
+
44
+ ## License
45
+
46
+ Copyright © 2020-2021 Altmetric LLP
47
+
48
+ Distributed under the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,43 @@
1
+ class ChunkedReliableQueue
2
+ DEFAULT_SIZE = 100
3
+
4
+ attr_reader :name, :queue, :size, :working_queue, :redis
5
+
6
+ def initialize(name, queue, redis)
7
+ @name = name
8
+ @queue = queue
9
+ @redis = redis
10
+ @working_queue = "#{queue}.working_on.#{name}"
11
+
12
+ requeue_unfinished_work
13
+ end
14
+
15
+ def each_slice(size = DEFAULT_SIZE)
16
+ return enum_for(:each_slice, size) unless block_given?
17
+
18
+ loop do
19
+ blocking_reply = redis.brpoplpush(queue, working_queue, 30)
20
+ next unless blocking_reply
21
+
22
+ replies = [blocking_reply]
23
+ replies += redis.multi { |multi|
24
+ (size - 1).times do
25
+ multi.rpoplpush(queue, working_queue)
26
+ end
27
+ }.compact
28
+
29
+ yield replies
30
+ redis.multi do |multi|
31
+ replies.each do |reply|
32
+ multi.lrem(working_queue, 0, reply)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def requeue_unfinished_work
41
+ loop while redis.rpoplpush(working_queue, queue)
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ class ReliableQueue
2
+ include Enumerable
3
+
4
+ attr_reader :queue, :working_queue, :redis
5
+
6
+ def initialize(queue, redis)
7
+ @queue = queue
8
+ @redis = redis
9
+ @working_queue = "#{queue}.working_on"
10
+
11
+ requeue_unfinished_work
12
+ end
13
+
14
+ def each
15
+ return enum_for(:each) unless block_given?
16
+
17
+ loop do
18
+ reply = redis.brpoplpush(queue, working_queue, 30)
19
+ next unless reply
20
+
21
+ yield reply
22
+ redis.lrem(working_queue, 0, reply)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def requeue_unfinished_work
29
+ loop while redis.rpoplpush(working_queue, queue)
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ require 'chunked_reliable_queue'
2
+ require "redis"
3
+
4
+ RSpec.describe ChunkedReliableQueue do
5
+ let(:redis) { Redis.new }
6
+
7
+ after do
8
+ redis.flushall
9
+ end
10
+
11
+ describe '.new' do
12
+ it 'requeues any unfinished work for the given queue' do
13
+ redis.lpush('foo.working_on.name1', '1')
14
+ queue = described_class.new('name1', 'foo', redis)
15
+ enum = queue.each_slice(1)
16
+
17
+ expect(enum.next).to eq(['1'])
18
+ end
19
+
20
+ it 'does not block if there is no unfinished work' do
21
+ queue = described_class.new('name1', 'foo', redis)
22
+
23
+ expect(queue).to be_a(described_class)
24
+ end
25
+ end
26
+
27
+ describe '#each_slice' do
28
+ it 'returns work from the queue in order' do
29
+ redis.lpush('foo', '1')
30
+ redis.lpush('foo', '2')
31
+ queue = described_class.new('name1', 'foo', redis)
32
+ enum = queue.each_slice(2)
33
+
34
+ expect(enum.next).to eq(['1', '2'])
35
+ end
36
+
37
+ it 'return chunks using a block' do
38
+ redis.lpush('foo', '1')
39
+ redis.lpush('foo', '2')
40
+ redis.lpush('foo', '3')
41
+ redis.lpush('foo', '4')
42
+ queue = described_class.new('name1', 'foo', redis)
43
+
44
+ result = []
45
+ iteration = 0
46
+ queue.each_slice(2) do |slice|
47
+ result += slice
48
+ iteration += 1
49
+ break if iteration >= 2
50
+ end
51
+
52
+ expect(result).to eq(['1', '2', '3', '4'])
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ require 'reliable_queue'
2
+ require "redis"
3
+
4
+ RSpec.describe ReliableQueue do
5
+ let(:redis) { Redis.new }
6
+
7
+ after do
8
+ redis.del('foo')
9
+ end
10
+
11
+ describe '.new' do
12
+ it 'requeues any unfinished work for the given queue' do
13
+ redis.rpush('foo.working_on', '1')
14
+ queue = described_class.new('foo', redis)
15
+
16
+ expect(queue.first).to eq('1')
17
+ end
18
+
19
+ it 'does not block if there is no unfinished work' do
20
+ queue = described_class.new('foo', redis)
21
+
22
+ expect(queue).to be_a(described_class)
23
+ end
24
+ end
25
+
26
+ describe '#each' do
27
+ it 'returns work from the queue in order' do
28
+ redis.lpush('foo', '1')
29
+ redis.lpush('foo', '2')
30
+ queue = described_class.new('foo', redis)
31
+
32
+ expect(queue.take(2)).to eq(['1', '2'])
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reliable-queue-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Anna Klimas
8
+ - Jonathan Hernandez
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2020-12-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.3'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.3'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.10'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.10'
56
+ description:
57
+ email:
58
+ - support@altmetric.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - LICENSE.txt
65
+ - README.md
66
+ - lib/chunked_reliable_queue.rb
67
+ - lib/reliable_queue.rb
68
+ - spec/chunked_reliable_queue_spec.rb
69
+ - spec/reliable_queue_spec.rb
70
+ homepage: https://github.com/altmetric/reliable-queue-rb
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.0.6
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Ruby library for reliable queue processing.
93
+ test_files:
94
+ - spec/reliable_queue_spec.rb
95
+ - spec/chunked_reliable_queue_spec.rb