async-redis 0.1.0 → 0.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
  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.