async-redis 0.1.0 → 0.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
  SHA256:
3
- metadata.gz: 3f51bfc647fd3f6a3f8a066cdfddd4147d072efb240fccce0e911ca6d0968831
4
- data.tar.gz: e35d2061ecf625ad6dfd818e07718bc78af8b2c4095e2bf9b42b1ea02a385b0d
3
+ metadata.gz: cd8adb00be17d1c72515a20f6c73dbba0e4f995780b6bec7e4fefef0aca53853
4
+ data.tar.gz: 39c7bef188aafb043d7efb1fe437bf18fc215bc1ebff5834e118a28fd37a3cfb
5
5
  SHA512:
6
- metadata.gz: 36d4b12adcaec403cf91f9ebfaa07d44f2e0381f72ca23c3420dd1359277d9ecc33de90c0413bb130ac8308aa88a6317bdcf0d722e7dcb34c1f8d5afbbee7cfe
7
- data.tar.gz: b46435ea68404c5656631b362b130755a8b8a0837af03a29a950b988141ed61279eccc18d9711d3dd1fb03afc1b33fbe54d00d550158d719d6e1f38b24ee7132
6
+ metadata.gz: 872b269771f7bda3fd312cc70d334687062aefbc27b70e2cc155b06614a26290ee6db0aa3901ea2390b5e2a245617e12c8641eff88608ef8c72a17d83cf4cadc
7
+ data.tar.gz: 7cf5d03872e8d7197a00b2493af8838cd8591faa4f6843a02f4ece26d80ae31a58054b96e8b14c03c042fa879ff0a8781954f7c9091515c85cf60ef48f909e62
@@ -0,0 +1,6 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 2
6
+
@@ -4,15 +4,12 @@ cache: bundler
4
4
  services:
5
5
  - redis-server
6
6
 
7
- before_script:
8
- - gem update --system
9
- - gem install bundler
10
-
11
7
  matrix:
12
8
  include:
13
9
  - rvm: 2.3
14
10
  - rvm: 2.4
15
11
  - rvm: 2.5
12
+ - rvm: 2.6
16
13
  - rvm: jruby-head
17
14
  env: JRUBY_OPTS="--debug -X+O"
18
15
  - rvm: ruby-head
data/README.md CHANGED
@@ -39,8 +39,8 @@ Or install it yourself as:
39
39
 
40
40
  Released under the MIT license.
41
41
 
42
- Copyright, 2018, by [Samuel G. D. Williams](https://www.codeotaku.com/samuel-williams) and
43
- Huba Z. Nagy
42
+ Copyright, 2018, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
43
+ Copyright, 2018, by Huba Z. Nagy.
44
44
 
45
45
  Permission is hereby granted, free of charge, to any person obtaining a copy
46
46
  of this software and associated documentation files (the "Software"), to deal
@@ -16,8 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_dependency("async", "~> 1.6")
20
- spec.add_dependency("async-io", "~> 1.7")
19
+ spec.add_dependency("async", "~> 1.8")
20
+ spec.add_dependency("async-io", "~> 1.10.0")
21
21
 
22
22
  spec.add_development_dependency "async-rspec", "~> 1.1"
23
23
 
@@ -1,4 +1,4 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -20,8 +20,10 @@
20
20
 
21
21
  require_relative 'protocol/resp'
22
22
  require_relative 'pool'
23
+ require_relative 'context/multi'
24
+ require_relative 'context/subscribe'
23
25
 
24
- require 'async/io/endpoint'
26
+ require 'async/io'
25
27
 
26
28
  module Async
27
29
  module Redis
@@ -34,7 +36,7 @@ module Async
34
36
  @endpoint = endpoint
35
37
  @protocol = protocol
36
38
 
37
- @connections = connect(**options)
39
+ @pool = connect(**options)
38
40
  end
39
41
 
40
42
  attr :endpoint
@@ -53,11 +55,39 @@ module Async
53
55
  end
54
56
 
55
57
  def close
56
- @connections.close
58
+ @pool.close
59
+ end
60
+
61
+ def publish(channel, message)
62
+ call('PUBLISH', channel, message)
63
+ end
64
+
65
+ def subscribe(*channels)
66
+ context = Context::Subscribe.new(@pool, channels)
67
+
68
+ return context unless block_given?
69
+
70
+ begin
71
+ yield context
72
+ ensure
73
+ context.close
74
+ end
75
+ end
76
+
77
+ def multi(&block)
78
+ context = Context::Multi.new(@pool)
79
+
80
+ return context unless block_given?
81
+
82
+ begin
83
+ yield context
84
+ ensure
85
+ context.close
86
+ end
57
87
  end
58
88
 
59
89
  def call(*arguments)
60
- @connections.acquire do |connection|
90
+ @pool.acquire do |connection|
61
91
  connection.write_request(arguments)
62
92
  return connection.read_response
63
93
  end
@@ -0,0 +1,53 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # and Huba Nagy
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require_relative 'nested'
23
+
24
+ module Async
25
+ module Redis
26
+ module Context
27
+ class Multi < Nested
28
+ def initialize(pool, *args)
29
+ super(pool)
30
+
31
+ @connection.write_request(['MULTI'])
32
+ @connection.read_response
33
+ end
34
+
35
+ def set(key, value)
36
+ return send_command('SET', key, value)
37
+ end
38
+
39
+ def get(key)
40
+ return send_command 'GET', key
41
+ end
42
+
43
+ def execute
44
+ return send_command 'EXEC'
45
+ end
46
+
47
+ def discard
48
+ return send_command 'DISCARD'
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,45 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # and Huba Nagy
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ module Async
23
+ module Redis
24
+ module Context
25
+ class Nested
26
+ def initialize(pool, *args)
27
+ @pool = pool
28
+ @connection = pool.acquire
29
+ end
30
+
31
+ def close
32
+ if @connection
33
+ @pool.release(@connection)
34
+ @connection = nil
35
+ end
36
+ end
37
+
38
+ def send_command(command, *args)
39
+ @connection.write_request([command, *args])
40
+ return @connection.read_response
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,75 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ # and Huba Nagy
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require_relative 'nested'
23
+
24
+ require 'set'
25
+
26
+ module Async
27
+ module Redis
28
+ module Context
29
+ class Subscribe < Nested
30
+ def initialize(pool, channels)
31
+ super(pool)
32
+
33
+ @channels = channels
34
+
35
+ subscribe(channels)
36
+ end
37
+
38
+ def listen
39
+ return @connection.read_response
40
+ end
41
+
42
+ private
43
+
44
+ def subscribe(channels)
45
+ @connection.write_request ['SUBSCRIBE', *channels]
46
+
47
+ response = nil
48
+
49
+ channels.length.times do |i|
50
+ response = @connection.read_response
51
+ end
52
+
53
+ return response
54
+ end
55
+
56
+ def unsubscribe(*channels)
57
+ if channels.empty? # unsubscribe from everything if no specific channels are given
58
+ @connection.write_request ['UNSUBSCRIBE']
59
+
60
+ response = nil
61
+
62
+ @channels.length.times do |i|
63
+ response = @connection.read_response
64
+ end
65
+
66
+ return response
67
+ else
68
+ @channels.subtract(channels)
69
+ return send_command 'UNSUBSCRIBE', *channels
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -18,6 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require 'async/notification'
22
+
21
23
  module Async
22
24
  module Redis
23
25
  # It might make sense to add support for pipelining https://redis.io/topics/pipelining
@@ -25,16 +27,24 @@ module Async
25
27
  # The only problem would be blocking operations. It might also be confusing if order of operations affects commands.
26
28
  class Pool
27
29
  def initialize(limit = nil, &block)
28
- @available = []
29
- @waiting = []
30
+ @resources = []
31
+ @available = Async::Notification.new
30
32
 
31
33
  @limit = limit
34
+ @active = 0
32
35
 
33
36
  @constructor = block
34
37
  end
35
38
 
39
+
40
+ attr :resources
41
+
42
+ def empty?
43
+ @resources.empty?
44
+ end
45
+
36
46
  def acquire
37
- resource = wait_for_next_available
47
+ resource = wait_for_resource
38
48
 
39
49
  return resource unless block_given?
40
50
 
@@ -45,50 +55,77 @@ module Async
45
55
  end
46
56
  end
47
57
 
48
- # Make the resource available and let waiting tasks know that there is something available.
58
+ # Make the resource resources and let waiting tasks know that there is something resources.
49
59
  def release(resource)
50
- @available << resource
51
-
52
- if task = @waiting.pop
53
- task.resume
60
+ # A resource that is not good should also not be reusable.
61
+ unless resource.closed?
62
+ reuse(resource)
63
+ else
64
+ retire(resource)
54
65
  end
55
66
  end
56
67
 
57
68
  def close
58
- @available.each(&:close)
59
- @available.clear
69
+ @resources.each(&:close)
70
+ @resources.clear
71
+
72
+ @active = 0
73
+ end
74
+
75
+ def to_s
76
+ "\#<#{self.class} resources=#{resources.count} limit=#{@limit}>"
60
77
  end
61
78
 
62
79
  protected
63
80
 
64
- def wait_for_next_available
65
- until resource = next_available
66
- @waiting << Fiber.current
67
- Task.yield
68
- end
81
+ def reuse(resource)
82
+ Async.logger.debug(self) {"Reuse #{resource}"}
69
83
 
70
- return resource
84
+ @resources << resource
85
+
86
+ @available.signal
71
87
  end
72
88
 
73
- def create_resource
74
- begin
75
- # This might fail, which is okay :)
76
- resource = @constructor.call
77
- rescue StandardError
78
- Async.logger.error "#{$!}: #{$!.backtrace}"
79
- return nil
89
+ def retire(resource)
90
+ Async.logger.debug(self) {"Retire #{resource}"}
91
+
92
+ @active -= 1
93
+
94
+ resource.close
95
+
96
+ @available.signal
97
+ end
98
+
99
+ def wait_for_resource
100
+ # If we fail to create a resource (below), we will end up waiting for one to become resources.
101
+ until resource = available_resource
102
+ @available.wait
80
103
  end
81
104
 
105
+ Async.logger.debug(self) {"Wait for resource #{resource}"}
106
+
82
107
  return resource
83
108
  end
84
109
 
85
- def next_available
86
- if @available.any?
87
- @available.pop
88
- elsif !@limit or @available.count < @limit
89
- Async.logger.debug(self) {"No available resources, allocating new one..."}
90
- create_resource
110
+ def create
111
+ # This might return nil, which means creating the resource failed.
112
+ return @constructor.call
113
+ end
114
+
115
+ def available_resource
116
+ if @resources.any?
117
+ return @resources.pop
118
+ end
119
+
120
+ if !@limit or @active < @limit
121
+ Async.logger.debug(self) {"No resources resources, allocating new one..."}
122
+
123
+ @active += 1
124
+
125
+ return create
91
126
  end
127
+
128
+ return nil
92
129
  end
93
130
  end
94
131
  end
@@ -1,4 +1,4 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -37,6 +37,10 @@ module Async
37
37
  super(stream, CRLF)
38
38
  end
39
39
 
40
+ def closed?
41
+ @stream.closed?
42
+ end
43
+
40
44
  # The redis server doesn't want actual objects (e.g. integers) but only bulk strings. So, we inline it for performance.
41
45
  def write_request(arguments)
42
46
  write_lines("*#{arguments.count}")
@@ -77,7 +81,6 @@ module Async
77
81
 
78
82
  def read_object
79
83
  token = @stream.read(1)
80
- # puts "token: #{token}"
81
84
 
82
85
  case token
83
86
  when '$'
@@ -87,7 +90,8 @@ module Async
87
90
  return nil
88
91
  else
89
92
  buffer = @stream.read(length)
90
- read_line # Eat trailing whitespace?
93
+ # Eat trailing whitespace because length does not include the CRLF:
94
+ read_line
91
95
 
92
96
  return buffer
93
97
  end
@@ -106,6 +110,8 @@ module Async
106
110
  return read_line
107
111
 
108
112
  else
113
+ @stream.flush
114
+
109
115
  raise NotImplementedError, "Implementation for token #{token} missing"
110
116
  end
111
117
  end
@@ -1,4 +1,4 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module Redis
23
- VERSION = "0.1.0"
23
+ VERSION = "0.2.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-04-23 00:00:00.000000000 Z
12
+ date: 2018-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: async
@@ -17,28 +17,28 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.6'
20
+ version: '1.8'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.6'
27
+ version: '1.8'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: async-io
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '1.7'
34
+ version: 1.10.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '1.7'
41
+ version: 1.10.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: async-rspec
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +103,7 @@ executables: []
103
103
  extensions: []
104
104
  extra_rdoc_files: []
105
105
  files:
106
+ - ".editorconfig"
106
107
  - ".gitignore"
107
108
  - ".rspec"
108
109
  - ".travis.yml"
@@ -112,6 +113,9 @@ files:
112
113
  - async-redis.gemspec
113
114
  - lib/async/redis.rb
114
115
  - lib/async/redis/client.rb
116
+ - lib/async/redis/context/multi.rb
117
+ - lib/async/redis/context/nested.rb
118
+ - lib/async/redis/context/subscribe.rb
115
119
  - lib/async/redis/pool.rb
116
120
  - lib/async/redis/protocol/resp.rb
117
121
  - lib/async/redis/version.rb
@@ -134,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
138
  version: '0'
135
139
  requirements: []
136
140
  rubyforge_project:
137
- rubygems_version: 2.7.6
141
+ rubygems_version: 2.7.8
138
142
  signing_key:
139
143
  specification_version: 4
140
144
  summary: A Redis client library.