async-redis 0.4.3 → 0.6.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
  SHA256:
3
- metadata.gz: 846a8f4d10913acf2d43343efb35a4ca9306db6a5fda6d9794774181939e6535
4
- data.tar.gz: 0d25f58941fc326a9500e50a63ead3ac017d734f8292e4ebcf63f1cad3ff6280
3
+ metadata.gz: 8a141b089c56a3e62a5e5f70d811127514105be73c208b1efec282e16427ba86
4
+ data.tar.gz: 28568c52ef21f29dcfdad24c2dd8f9460e558546de16e0599fc970b9d6581c1e
5
5
  SHA512:
6
- metadata.gz: d8881dfb830d61c74b55415d693f2eda5a5caf04daf789400df7d8e7229468c929300264d305b5182a706b44cca1677ae930ea757ca139dcfe2b7b639f0bc6c0
7
- data.tar.gz: b51195073ead610e2b95e67b9280c1b13472d51156985624782de72e9b3e57764ba2ad53b5758cedd9617e3ffc11a0668f89d6ee476e118f3300a600051c5973
6
+ metadata.gz: 69a6e6fb59f64444356f82a89ed3525e86ae0f465568265e4ff5b7103cefb0ff4433d41b3c7c8d325d3911649dc451dfe8fd82315ac94c4de47158d19da402de
7
+ data.tar.gz: 4ec4f9d4744508643cd01f9fcc4d4a937a74daca6ea7e61353e73d73e408490f8b501c0ee6434cf4127b1350eae279f1d49005515c6f9f86e940307ad757e3dc
data/lib/async/redis.rb CHANGED
@@ -22,3 +22,4 @@
22
22
 
23
23
  require_relative 'redis/version'
24
24
  require_relative 'redis/client'
25
+ require_relative 'redis/sentinels'
@@ -37,14 +37,14 @@ module Async
37
37
  # Legacy.
38
38
  ServerError = ::Protocol::Redis::ServerError
39
39
 
40
- def self.local_endpoint
41
- Async::IO::Endpoint.tcp('localhost', 6379)
40
+ def self.local_endpoint(port: 6379)
41
+ Async::IO::Endpoint.tcp('localhost', port)
42
42
  end
43
43
 
44
44
  class Client
45
45
  include ::Protocol::Redis::Methods
46
46
 
47
- def initialize(endpoint = Redis.local_endpoint, protocol = Protocol::RESP2, **options)
47
+ def initialize(endpoint = Redis.local_endpoint, protocol: Protocol::RESP2, **options)
48
48
  @endpoint = endpoint
49
49
  @protocol = protocol
50
50
 
@@ -74,10 +74,6 @@ module Async
74
74
  @pool.close
75
75
  end
76
76
 
77
- def publish(channel, message)
78
- call('PUBLISH', channel, message)
79
- end
80
-
81
77
  def subscribe(*channels)
82
78
  context = Context::Subscribe.new(@pool, channels)
83
79
 
@@ -90,18 +86,6 @@ module Async
90
86
  end
91
87
  end
92
88
 
93
- def multi(&block)
94
- context = Context::Multi.new(@pool)
95
-
96
- return context unless block_given?
97
-
98
- begin
99
- yield context
100
- ensure
101
- context.close
102
- end
103
- end
104
-
105
89
  def transaction(&block)
106
90
  context = Context::Transaction.new(@pool)
107
91
 
@@ -113,12 +97,14 @@ module Async
113
97
  context.close
114
98
  end
115
99
  end
116
-
100
+
101
+ alias multi transaction
102
+
117
103
  def pipeline(&block)
118
104
  context = Context::Pipeline.new(@pool)
119
-
105
+
120
106
  return context unless block_given?
121
-
107
+
122
108
  begin
123
109
  yield context
124
110
  ensure
@@ -145,7 +131,8 @@ module Async
145
131
  Async::Pool::Controller.wrap(**options) do
146
132
  peer = @endpoint.connect
147
133
 
148
- peer.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
134
+ # We will manage flushing ourselves:
135
+ peer.sync = true
149
136
 
150
137
  stream = IO::Stream.new(peer)
151
138
 
@@ -27,48 +27,35 @@ module Async
27
27
  module Redis
28
28
  module Context
29
29
  class Subscribe < Generic
30
+ MESSAGE = 'message'
31
+
30
32
  def initialize(pool, channels)
31
33
  super(pool)
32
34
 
33
- @channels = channels
34
-
35
35
  subscribe(channels)
36
36
  end
37
37
 
38
- def listen
39
- return @connection.read_response
38
+ def close
39
+ # There is no way to reset subscription state. On Redis v6+ you can use RESET, but this is not supported in <= v6.
40
+ @connection&.close
41
+
42
+ super
40
43
  end
41
44
 
42
- private
45
+ def listen
46
+ while response = @connection.read_response
47
+ return response if response.first == MESSAGE
48
+ end
49
+ end
43
50
 
44
51
  def subscribe(channels)
45
52
  @connection.write_request ['SUBSCRIBE', *channels]
46
53
  @connection.flush
47
-
48
- response = nil
49
-
50
- channels.length.times do |i|
51
- response = @connection.read_response
52
- end
53
-
54
- return response
55
54
  end
56
55
 
57
- def unsubscribe(*channels)
58
- if channels.empty? # unsubscribe from everything if no specific channels are given
59
- @connection.write_request ['UNSUBSCRIBE']
60
-
61
- response = nil
62
-
63
- @channels.length.times do |i|
64
- response = @connection.read_response
65
- end
66
-
67
- return response
68
- else
69
- @channels.subtract(channels)
70
- return call 'UNSUBSCRIBE', *channels
71
- end
56
+ def unsubscribe(channels)
57
+ @connection.write_request ['UNSUBSCRIBE', *channels]
58
+ @connection.flush
72
59
  end
73
60
  end
74
61
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async
4
+ module Redis
5
+ class SentinelsClient < Client
6
+ def initialize(master_name, sentinels, role = :master, protocol = Protocol::RESP2, **options)
7
+ @master_name = master_name
8
+ @sentinel_endpoints = sentinels.map do |sentinel|
9
+ Async::IO::Endpoint.tcp(sentinel[:host], sentinel[:port])
10
+ end
11
+ @role = role
12
+
13
+ @protocol = protocol
14
+ @pool = connect(**options)
15
+ end
16
+
17
+ private
18
+
19
+ # Override the parent method. The only difference is that this one needs
20
+ # to resolve the master/slave address.
21
+ def connect(**options)
22
+ Async::Pool::Controller.wrap(**options) do
23
+ endpoint = resolve_address
24
+ peer = endpoint.connect
25
+ stream = IO::Stream.new(peer)
26
+
27
+ @protocol.client(stream)
28
+ end
29
+ end
30
+
31
+ def resolve_address
32
+ address = case @role
33
+ when :master then resolve_master
34
+ when :slave then resolve_slave
35
+ else raise ArgumentError, "Unknown instance role #{@role}"
36
+ end
37
+
38
+ address or raise RuntimeError, "Unable to fetch #{@role} via Sentinel."
39
+ end
40
+
41
+ def resolve_master
42
+ @sentinel_endpoints.each do |sentinel_endpoint|
43
+ client = Client.new(sentinel_endpoint)
44
+
45
+ begin
46
+ address = client.call('sentinel', 'get-master-addr-by-name', @master_name)
47
+ rescue Errno::ECONNREFUSED
48
+ next
49
+ end
50
+
51
+ return Async::IO::Endpoint.tcp(address[0], address[1]) if address
52
+ end
53
+
54
+ nil
55
+ end
56
+
57
+ def resolve_slave
58
+ @sentinel_endpoints.each do |sentinel_endpoint|
59
+ client = Client.new(sentinel_endpoint)
60
+
61
+ begin
62
+ reply = client.call('sentinel', 'slaves', @master_name)
63
+ rescue Errno::ECONNREFUSED
64
+ next
65
+ end
66
+
67
+ slaves = available_slaves(reply)
68
+ next if slaves.empty?
69
+
70
+ slave = select_slave(slaves)
71
+ return Async::IO::Endpoint.tcp(slave['ip'], slave['port'])
72
+ end
73
+
74
+ nil
75
+ end
76
+
77
+ def available_slaves(slaves_cmd_reply)
78
+ # The reply is an array with the format: [field1, value1, field2,
79
+ # value2, etc.].
80
+ # When a slave is marked as down by the sentinel, the "flags" field
81
+ # (comma-separated array) contains the "s_down" value.
82
+ slaves_cmd_reply.map { |s| s.each_slice(2).to_h }
83
+ .reject { |s| s.fetch('flags').split(',').include?('s_down') }
84
+ end
85
+
86
+ def select_slave(available_slaves)
87
+ available_slaves.sample
88
+ end
89
+ end
90
+ end
91
+ end
@@ -22,6 +22,6 @@
22
22
 
23
23
  module Async
24
24
  module Redis
25
- VERSION = "0.4.3"
25
+ VERSION = "0.6.0"
26
26
  end
27
27
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Huba Nagy
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-02-11 00:00:00.000000000 Z
12
+ date: 2021-05-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: async
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: 0.4.0
62
+ version: 0.6.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: 0.4.0
69
+ version: 0.6.0
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: async-rspec
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -82,7 +82,7 @@ dependencies:
82
82
  - !ruby/object:Gem::Version
83
83
  version: '1.1'
84
84
  - !ruby/object:Gem::Dependency
85
- name: redis
85
+ name: benchmark-ips
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - ">="
@@ -96,7 +96,7 @@ dependencies:
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
98
  - !ruby/object:Gem::Dependency
99
- name: benchmark-ips
99
+ name: bundler
100
100
  requirement: !ruby/object:Gem::Requirement
101
101
  requirements:
102
102
  - - ">="
@@ -124,7 +124,7 @@ dependencies:
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  - !ruby/object:Gem::Dependency
127
- name: bundler
127
+ name: hiredis
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
130
  - - ">="
@@ -138,21 +138,21 @@ dependencies:
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
140
  - !ruby/object:Gem::Dependency
141
- name: rspec
141
+ name: rake
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - "~>"
144
+ - - ">="
145
145
  - !ruby/object:Gem::Version
146
- version: '3.6'
146
+ version: '0'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - "~>"
151
+ - - ">="
152
152
  - !ruby/object:Gem::Version
153
- version: '3.6'
153
+ version: '0'
154
154
  - !ruby/object:Gem::Dependency
155
- name: rake
155
+ name: redis
156
156
  requirement: !ruby/object:Gem::Requirement
157
157
  requirements:
158
158
  - - ">="
@@ -166,35 +166,25 @@ dependencies:
166
166
  - !ruby/object:Gem::Version
167
167
  version: '0'
168
168
  - !ruby/object:Gem::Dependency
169
- name: hiredis
169
+ name: rspec
170
170
  requirement: !ruby/object:Gem::Requirement
171
171
  requirements:
172
- - - ">="
172
+ - - "~>"
173
173
  - !ruby/object:Gem::Version
174
- version: '0'
174
+ version: '3.6'
175
175
  type: :development
176
176
  prerelease: false
177
177
  version_requirements: !ruby/object:Gem::Requirement
178
178
  requirements:
179
- - - ">="
179
+ - - "~>"
180
180
  - !ruby/object:Gem::Version
181
- version: '0'
182
- description:
181
+ version: '3.6'
182
+ description:
183
183
  email:
184
- - samuel.williams@oriontransfer.co.nz
185
- - 12huba@gmail.com
186
184
  executables: []
187
185
  extensions: []
188
186
  extra_rdoc_files: []
189
187
  files:
190
- - ".editorconfig"
191
- - ".gitignore"
192
- - ".rspec"
193
- - ".travis.yml"
194
- - Gemfile
195
- - README.md
196
- - Rakefile
197
- - async-redis.gemspec
198
188
  - lib/async/redis.rb
199
189
  - lib/async/redis/client.rb
200
190
  - lib/async/redis/context/generic.rb
@@ -203,11 +193,13 @@ files:
203
193
  - lib/async/redis/context/transaction.rb
204
194
  - lib/async/redis/key.rb
205
195
  - lib/async/redis/protocol/resp2.rb
196
+ - lib/async/redis/sentinels.rb
206
197
  - lib/async/redis/version.rb
207
198
  homepage: https://github.com/socketry/async-redis
208
- licenses: []
199
+ licenses:
200
+ - MIT
209
201
  metadata: {}
210
- post_install_message:
202
+ post_install_message:
211
203
  rdoc_options: []
212
204
  require_paths:
213
205
  - lib
@@ -222,8 +214,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
214
  - !ruby/object:Gem::Version
223
215
  version: '0'
224
216
  requirements: []
225
- rubygems_version: 3.0.6
226
- signing_key:
217
+ rubygems_version: 3.3.0.dev
218
+ signing_key:
227
219
  specification_version: 4
228
220
  summary: A Redis client library.
229
221
  test_files: []
data/.editorconfig DELETED
@@ -1,6 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = tab
5
- indent_size = 2
6
-
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
-
11
- # rspec failure tracking
12
- .rspec_status
13
- .covered.db
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --warnings
3
- --require spec_helper
data/.travis.yml DELETED
@@ -1,23 +0,0 @@
1
- language: ruby
2
- dist: xenial
3
- cache: bundler
4
-
5
- services:
6
- - redis-server
7
-
8
- matrix:
9
- include:
10
- - rvm: 2.3
11
- - rvm: 2.4
12
- - rvm: 2.5
13
- - rvm: 2.6
14
- - rvm: 2.6
15
- env: COVERAGE=BriefSummary,Coveralls
16
- - rvm: ruby-head
17
- - rvm: truffleruby
18
- - rvm: jruby-head
19
- env: JRUBY_OPTS="--debug -X+O"
20
- allow_failures:
21
- - rvm: ruby-head
22
- - rvm: truffleruby
23
- - rvm: jruby-head
data/Gemfile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in async-io.gemspec
6
- gemspec
7
-
8
- # gem "protocol-redis", path: "../protocol-redis"
9
-
10
- group :development do
11
- gem 'pry'
12
- end
data/README.md DELETED
@@ -1,125 +0,0 @@
1
- # Async::Redis
2
-
3
- An asynchronous client for Redis including TLS. Support for streaming requests and responses. Built on top of [async] and [async-io].
4
-
5
- [![Build Status](https://secure.travis-ci.org/socketry/async-redis.svg)](https://travis-ci.org/socketry/async-redis)
6
- [![Code Climate](https://codeclimate.com/github/socketry/async-redis.svg)](https://codeclimate.com/github/socketry/async-redis)
7
- [![Coverage Status](https://coveralls.io/repos/socketry/async-redis/badge.svg)](https://coveralls.io/r/socketry/async-redis)
8
-
9
- [async]: https://github.com/socketry/async
10
- [async-io]: https://github.com/socketry/async-io
11
-
12
- ## Installation
13
-
14
- Add this line to your application's Gemfile:
15
-
16
- ```ruby
17
- gem 'async-redis'
18
- ```
19
-
20
- And then execute:
21
-
22
- $ bundle
23
-
24
- Or install it yourself as:
25
-
26
- $ gem install async-redis
27
-
28
- ## Usage
29
-
30
- ### Basic Local Connection
31
-
32
- ```ruby
33
- require 'async/redis'
34
-
35
- endpoint = Async::Redis.local_endpoint
36
- client = Async::Redis::Client.new(endpoint)
37
-
38
- Async do
39
- pp client.info
40
- ensure
41
- client.close
42
- end
43
- ```
44
-
45
- ### Variables
46
-
47
- ```ruby
48
- require 'async'
49
- require 'async/redis'
50
-
51
- endpoint = Async::Redis.local_endpoint
52
- client = Async::Redis::Client.new(endpoint)
53
-
54
- Async do
55
- client.set('X', 10)
56
- pp client.get('X')
57
- ensure
58
- client.close
59
- end
60
- ```
61
-
62
- ### Subscriptions
63
-
64
- ```ruby
65
- require 'async'
66
- require 'async/redis'
67
-
68
- endpoint = Async::Redis.local_endpoint
69
- client = Async::Redis::Client.new(endpoint)
70
-
71
- Async do |task|
72
- condition = Async::Condition.new
73
-
74
- publisher = task.async do
75
- condition.wait
76
-
77
- client.publish 'status.frontend', 'good'
78
- end
79
-
80
- subscriber = task.async do
81
- client.subscribe 'status.frontend' do |context|
82
- condition.signal # We are waiting for messages.
83
-
84
- type, name, message = context.listen
85
-
86
- pp type, name, message
87
- end
88
- end
89
- ensure
90
- client.close
91
- end
92
- ```
93
-
94
- ## Contributing
95
-
96
- 1. Fork it
97
- 2. Create your feature branch (`git checkout -b my-new-feature`)
98
- 3. Commit your changes (`git commit -am 'Add some feature'`)
99
- 4. Push to the branch (`git push origin my-new-feature`)
100
- 5. Create new Pull Request
101
-
102
- ## License
103
-
104
- Released under the MIT license.
105
-
106
- Copyright, 2018, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
107
- Copyright, 2018, by Huba Z. Nagy.
108
-
109
- Permission is hereby granted, free of charge, to any person obtaining a copy
110
- of this software and associated documentation files (the "Software"), to deal
111
- in the Software without restriction, including without limitation the rights
112
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
113
- copies of the Software, and to permit persons to whom the Software is
114
- furnished to do so, subject to the following conditions:
115
-
116
- The above copyright notice and this permission notice shall be included in
117
- all copies or substantial portions of the Software.
118
-
119
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
120
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
121
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
122
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
123
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
124
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
125
- THE SOFTWARE.
data/Rakefile DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:test)
7
-
8
- task :default => :test
9
-
10
- task :client do
11
- require 'irb'
12
- require 'async/redis/client'
13
-
14
- endpoint = Async::Redis.local_endpoint
15
- client = Async::Redis::Client.new(endpoint)
16
-
17
- Async do
18
- binding.irb
19
- end
20
- end
data/async-redis.gemspec DELETED
@@ -1,39 +0,0 @@
1
-
2
- require_relative 'lib/async/redis/version'
3
-
4
- Gem::Specification.new do |spec|
5
- spec.name = "async-redis"
6
- spec.version = Async::Redis::VERSION
7
- spec.authors = ["Samuel Williams", "Huba Nagy"]
8
- spec.email = ["samuel.williams@oriontransfer.co.nz", "12huba@gmail.com"]
9
-
10
- spec.summary = "A Redis client library."
11
- spec.homepage = "https://github.com/socketry/async-redis"
12
-
13
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
14
- f.match(%r{^(test|spec|features)/})
15
- end
16
-
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_dependency("async", "~> 1.8")
21
- spec.add_dependency("async-io", "~> 1.10")
22
- spec.add_dependency("async-pool", "~> 0.2")
23
-
24
- spec.add_dependency("protocol-redis", "~> 0.4.0")
25
-
26
- spec.add_development_dependency "async-rspec", "~> 1.1"
27
- spec.add_development_dependency "redis"
28
- spec.add_development_dependency "benchmark-ips"
29
-
30
- spec.add_development_dependency "covered"
31
- spec.add_development_dependency "bundler"
32
- spec.add_development_dependency "rspec", "~> 3.6"
33
- spec.add_development_dependency "rake"
34
-
35
- # Dependencies with C extensions
36
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
37
- spec.add_development_dependency "hiredis"
38
- end
39
- end