iopromise 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/Gemfile.lock +7 -2
- data/iopromise.gemspec +1 -0
- data/lib/iopromise.rb +14 -15
- data/lib/iopromise/dalli/client.rb +13 -17
- data/lib/iopromise/dalli/executor_pool.rb +36 -3
- data/lib/iopromise/dalli/patch_dalli.rb +118 -102
- data/lib/iopromise/dalli/promise.rb +13 -5
- data/lib/iopromise/deferred/executor_pool.rb +1 -3
- data/lib/iopromise/deferred/promise.rb +1 -1
- data/lib/iopromise/executor_context.rb +41 -58
- data/lib/iopromise/executor_pool/base.rb +23 -7
- data/lib/iopromise/executor_pool/batch.rb +5 -3
- data/lib/iopromise/executor_pool/sequential.rb +6 -14
- data/lib/iopromise/memcached/executor_pool.rb +26 -5
- data/lib/iopromise/version.rb +1 -1
- metadata +16 -9
- data/lib/iopromise/faraday.rb +0 -17
- data/lib/iopromise/faraday/connection.rb +0 -25
- data/lib/iopromise/faraday/continuable_hydra.rb +0 -29
- data/lib/iopromise/faraday/executor_pool.rb +0 -19
- data/lib/iopromise/faraday/multi_socket_action.rb +0 -107
- data/lib/iopromise/faraday/promise.rb +0 -42
- data/lib/iopromise/rack/context_middleware.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fc60525e45ec8cfa8a6cd5ba34bf396b77e4d42f3fd566137f14367a2ba31ae
|
4
|
+
data.tar.gz: 1e70b2147a204a220be6b268ae62f67dc06b79065b8ec07636da414264ae626b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b92e71ab4fa32e5c45f9b4a4fa607844f2558f270f69ddb3a63b1394633a4b3e7eb101f77b06e8bc4efe45ad657e00a7d4bad10f34374356f2dc660db17af525
|
7
|
+
data.tar.gz: 4034ef0ae2010b4cea930894d1f7cc05722e6dd0e3c112fe237ce6488441d24d670c098df525046c13d26514ff39ee672c3a26c49f601ddc40b757fbbe691079
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -8,8 +8,9 @@ GIT
|
|
8
8
|
PATH
|
9
9
|
remote: .
|
10
10
|
specs:
|
11
|
-
iopromise (0.1.
|
12
|
-
|
11
|
+
iopromise (0.1.1)
|
12
|
+
nio4r
|
13
|
+
promise.rb (~> 0.7.4)
|
13
14
|
|
14
15
|
GEM
|
15
16
|
remote: https://rubygems.org/
|
@@ -73,6 +74,7 @@ GEM
|
|
73
74
|
minitest (>= 5.1)
|
74
75
|
tzinfo (~> 2.0)
|
75
76
|
zeitwerk (~> 2.3)
|
77
|
+
benchmark-ips (2.9.1)
|
76
78
|
builder (3.2.4)
|
77
79
|
concurrent-ruby (1.1.8)
|
78
80
|
crass (1.0.6)
|
@@ -161,6 +163,7 @@ GEM
|
|
161
163
|
actionpack (>= 4.0)
|
162
164
|
activesupport (>= 4.0)
|
163
165
|
sprockets (>= 3.0.0)
|
166
|
+
stackprof (0.2.17)
|
164
167
|
thor (1.1.0)
|
165
168
|
typhoeus (1.4.0)
|
166
169
|
ethon (>= 0.9.0)
|
@@ -177,6 +180,7 @@ PLATFORMS
|
|
177
180
|
x86_64-linux
|
178
181
|
|
179
182
|
DEPENDENCIES
|
183
|
+
benchmark-ips
|
180
184
|
dalli (= 2.7.11)
|
181
185
|
faraday
|
182
186
|
iopromise!
|
@@ -184,6 +188,7 @@ DEPENDENCIES
|
|
184
188
|
rails
|
185
189
|
rake (~> 13.0)
|
186
190
|
rspec (~> 3.0)
|
191
|
+
stackprof
|
187
192
|
typhoeus
|
188
193
|
view_component
|
189
194
|
|
data/iopromise.gemspec
CHANGED
data/lib/iopromise.rb
CHANGED
@@ -13,32 +13,31 @@ module IOPromise
|
|
13
13
|
class Error < StandardError; end
|
14
14
|
|
15
15
|
class Base < ::Promise
|
16
|
-
def initialize(*)
|
17
|
-
@instrument_begin = []
|
18
|
-
@instrument_end = []
|
19
|
-
@started_executing = false
|
20
|
-
|
21
|
-
super
|
22
|
-
end
|
23
|
-
|
24
16
|
def instrument(begin_cb = nil, end_cb = nil)
|
25
|
-
raise ::IOPromise::Error.new("Instrumentation called after promise already started executing") if
|
26
|
-
|
27
|
-
|
17
|
+
raise ::IOPromise::Error.new("Instrumentation called after promise already started executing") if started_executing?
|
18
|
+
unless begin_cb.nil?
|
19
|
+
@instrument_begin ||= []
|
20
|
+
@instrument_begin << begin_cb
|
21
|
+
end
|
22
|
+
unless end_cb.nil?
|
23
|
+
@instrument_end ||= []
|
24
|
+
@instrument_end << end_cb
|
25
|
+
end
|
28
26
|
end
|
29
27
|
|
30
28
|
def beginning
|
31
|
-
@instrument_begin
|
29
|
+
@instrument_begin&.each { |cb| cb.call(self) }
|
30
|
+
@instrument_begin&.clear
|
32
31
|
@started_executing = true
|
33
32
|
end
|
34
33
|
|
35
34
|
def started_executing?
|
36
|
-
|
35
|
+
!!@started_executing
|
37
36
|
end
|
38
37
|
|
39
38
|
def notify_completion(value: nil, reason: nil)
|
40
|
-
@instrument_end
|
41
|
-
@instrument_end
|
39
|
+
@instrument_end&.each { |cb| cb.call(self, value: value, reason: reason) }
|
40
|
+
@instrument_end&.clear
|
42
41
|
end
|
43
42
|
|
44
43
|
def fulfill(value)
|
@@ -21,7 +21,7 @@ module IOPromise
|
|
21
21
|
# Returns a promise that resolves to a IOPromise::Dalli::Response with the
|
22
22
|
# value for the given key, or +nil+ if the key is not found.
|
23
23
|
def get(key, options = nil)
|
24
|
-
|
24
|
+
@client.perform(:get, key, options)
|
25
25
|
end
|
26
26
|
|
27
27
|
# Convenience function that attempts to fetch the given key, or set
|
@@ -40,7 +40,7 @@ module IOPromise
|
|
40
40
|
!response.exist? :
|
41
41
|
response.value.nil?
|
42
42
|
if not_found && !block.nil?
|
43
|
-
|
43
|
+
block.call.then do |new_val|
|
44
44
|
# delay the final resolution here until after the add succeeds,
|
45
45
|
# to guarantee errors are caught. we could potentially allow
|
46
46
|
# the add to resolve once it's sent (without confirmation), but
|
@@ -48,7 +48,7 @@ module IOPromise
|
|
48
48
|
add(key, new_val, ttl, options).then { new_val }
|
49
49
|
end
|
50
50
|
else
|
51
|
-
|
51
|
+
response.value
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -56,40 +56,40 @@ module IOPromise
|
|
56
56
|
# Unconditionally sets the +key+ to the +value+ specified.
|
57
57
|
# Returns a promise that resolves to a IOPromise::Dalli::Response.
|
58
58
|
def set(key, value, ttl = nil, options = nil)
|
59
|
-
|
59
|
+
@client.perform(:set, key, value, ttl_or_default(ttl), 0, options)
|
60
60
|
end
|
61
61
|
|
62
62
|
# Conditionally sets the +key+ to the +value+ specified.
|
63
63
|
# Returns a promise that resolves to a IOPromise::Dalli::Response.
|
64
64
|
def add(key, value, ttl = nil, options = nil)
|
65
|
-
|
65
|
+
@client.perform(:add, key, value, ttl_or_default(ttl), options)
|
66
66
|
end
|
67
67
|
|
68
68
|
# Conditionally sets the +key+ to the +value+ specified only
|
69
69
|
# if the key already exists.
|
70
70
|
# Returns a promise that resolves to a IOPromise::Dalli::Response.
|
71
71
|
def replace(key, value, ttl = nil, options = nil)
|
72
|
-
|
72
|
+
@client.perform(:replace, key, value, ttl_or_default(ttl), 0, options)
|
73
73
|
end
|
74
74
|
|
75
75
|
# Deletes the specified key, resolving the promise when complete.
|
76
76
|
def delete(key)
|
77
|
-
|
77
|
+
@client.perform(:delete, key, 0)
|
78
78
|
end
|
79
79
|
|
80
80
|
# Appends a value to the specified key, resolving the promise when complete.
|
81
81
|
# Appending only works for values stored with :raw => true.
|
82
82
|
def append(key, value)
|
83
|
-
|
84
|
-
|
83
|
+
value.then do |resolved_value|
|
84
|
+
@client.perform(:append, key, resolved_value.to_s)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
88
|
# Prepend a value to the specified key, resolving the promise when complete.
|
89
89
|
# Prepending only works for values stored with :raw => true.
|
90
90
|
def prepend(key, value)
|
91
|
-
|
92
|
-
|
91
|
+
value.then do |resolved_value|
|
92
|
+
@client.perform(:prepend, key, resolved_value.to_s)
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -106,7 +106,7 @@ module IOPromise
|
|
106
106
|
# #cas.
|
107
107
|
def incr(key, amt = 1, ttl = nil, default = nil)
|
108
108
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
109
|
-
|
109
|
+
@client.perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
|
110
110
|
end
|
111
111
|
|
112
112
|
##
|
@@ -125,17 +125,13 @@ module IOPromise
|
|
125
125
|
# #cas.
|
126
126
|
def decr(key, amt = 1, ttl = nil, default = nil)
|
127
127
|
raise ArgumentError, "Positive values only: #{amt}" if amt < 0
|
128
|
-
|
128
|
+
@client.perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
|
129
129
|
end
|
130
130
|
|
131
131
|
# TODO: touch, gat, CAS operations
|
132
132
|
|
133
133
|
private
|
134
134
|
|
135
|
-
def execute_as_promise(*args)
|
136
|
-
@client.perform_async(*args)
|
137
|
-
end
|
138
|
-
|
139
135
|
def ttl_or_default(ttl)
|
140
136
|
(ttl || @options[:expires_in]).to_i
|
141
137
|
rescue NoMethodError
|
@@ -3,10 +3,43 @@
|
|
3
3
|
module IOPromise
|
4
4
|
module Dalli
|
5
5
|
class DalliExecutorPool < IOPromise::ExecutorPool::Base
|
6
|
-
def
|
7
|
-
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
8
|
|
9
|
-
|
9
|
+
@iop_monitor = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def dalli_server
|
13
|
+
@connection_pool
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute_continue
|
17
|
+
dalli_server.execute_continue
|
18
|
+
end
|
19
|
+
|
20
|
+
def connected_socket(sock)
|
21
|
+
close_socket
|
22
|
+
|
23
|
+
@iop_monitor = ::IOPromise::ExecutorContext.current.register_observer_io(self, sock, :r)
|
24
|
+
end
|
25
|
+
|
26
|
+
def close_socket
|
27
|
+
unless @iop_monitor.nil?
|
28
|
+
@iop_monitor.close
|
29
|
+
@iop_monitor = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def monitor_ready(monitor, readiness)
|
34
|
+
dalli_server.async_io_ready(monitor.readable?, monitor.writable?)
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_interest(direction, interested)
|
38
|
+
if interested
|
39
|
+
@iop_monitor.add_interest(direction)
|
40
|
+
else
|
41
|
+
@iop_monitor.remove_interest(direction)
|
42
|
+
end
|
10
43
|
end
|
11
44
|
end
|
12
45
|
end
|
@@ -12,16 +12,16 @@ module IOPromise
|
|
12
12
|
super
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
def perform(*)
|
16
|
+
return super unless @async
|
17
|
+
|
18
|
+
begin
|
19
|
+
super
|
20
|
+
rescue => ex
|
21
|
+
# Wrap any connection errors into a promise, this is more forwards-compatible
|
22
|
+
# if we ever attempt to make connecting/server fallback nonblocking too.
|
23
|
+
Promise.new.tap { |p| p.reject(ex) }
|
20
24
|
end
|
21
|
-
rescue => ex
|
22
|
-
# Wrap any connection errors into a promise, this is more forwards-compatible
|
23
|
-
# if we ever attempt to make connecting/server fallback nonblocking too.
|
24
|
-
Promise.new.tap { |p| p.reject(ex) }
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -30,10 +30,14 @@ module IOPromise
|
|
30
30
|
@async = options.delete(:iopromise_async) == true
|
31
31
|
|
32
32
|
if @async
|
33
|
+
@write_buffer = +""
|
34
|
+
@read_buffer = +""
|
33
35
|
async_reset
|
34
36
|
|
35
37
|
@next_opaque_id = 0
|
36
38
|
@pending_ops = {}
|
39
|
+
|
40
|
+
@executor_pool = DalliExecutorPool.for(self)
|
37
41
|
end
|
38
42
|
|
39
43
|
super
|
@@ -51,60 +55,53 @@ module IOPromise
|
|
51
55
|
super
|
52
56
|
end
|
53
57
|
|
54
|
-
def
|
55
|
-
|
56
|
-
@write_offset = 0
|
57
|
-
|
58
|
-
@read_buffer = +""
|
59
|
-
@read_offset = 0
|
60
|
-
end
|
58
|
+
def connect
|
59
|
+
super
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
unless ready_writers.nil? || ready_writers.empty?
|
65
|
-
# we are able to write, so write as much as we can.
|
66
|
-
sock_write_nonblock
|
61
|
+
if async?
|
62
|
+
@executor_pool.connected_socket(@sock)
|
67
63
|
end
|
64
|
+
end
|
68
65
|
|
69
|
-
|
70
|
-
|
66
|
+
def async_reset
|
67
|
+
@write_buffer.clear
|
68
|
+
@write_offset = 0
|
71
69
|
|
72
|
-
|
73
|
-
|
74
|
-
end
|
70
|
+
@read_buffer.clear
|
71
|
+
@read_offset = 0
|
75
72
|
|
76
|
-
|
77
|
-
|
78
|
-
exceptions = [@sock]
|
79
|
-
timeout = nil
|
73
|
+
@executor_pool.close_socket if defined? @executor_pool
|
74
|
+
end
|
80
75
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
op.execute_pool.complete(op)
|
86
|
-
end
|
76
|
+
def async_io_ready(readable, writable)
|
77
|
+
async_sock_write_nonblock if writable
|
78
|
+
async_sock_read_nonblock if readable
|
79
|
+
end
|
87
80
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
81
|
+
# called by ExecutorPool to continue processing for this server
|
82
|
+
def execute_continue
|
83
|
+
timeout = @options[:socket_timeout]
|
84
|
+
@pending_ops.select! do |key, op|
|
85
|
+
if op.timeout?
|
86
|
+
op.reject(Timeout::Error.new)
|
87
|
+
next false # this op is done
|
88
|
+
end
|
93
89
|
|
94
90
|
# let all pending operations know that they are seeing the
|
95
91
|
# select loop. this starts the timer for the operation, because
|
96
92
|
# it guarantees we're now working on it.
|
97
93
|
# this is more accurate than starting the timer when we buffer
|
98
94
|
# the write.
|
99
|
-
|
100
|
-
op.in_select_loop
|
101
|
-
end
|
95
|
+
op.in_select_loop
|
102
96
|
|
103
|
-
|
104
|
-
timeout =
|
97
|
+
remaining = op.timeout_remaining
|
98
|
+
timeout = remaining if remaining < timeout
|
99
|
+
|
100
|
+
true # keep
|
105
101
|
end
|
106
102
|
|
107
|
-
|
103
|
+
@executor_pool.select_timeout = timeout
|
104
|
+
@executor_pool.set_interest(:r, !@pending_ops.empty?)
|
108
105
|
end
|
109
106
|
|
110
107
|
private
|
@@ -115,8 +112,14 @@ module IOPromise
|
|
115
112
|
|
116
113
|
|
117
114
|
def promised_request(key, &block)
|
118
|
-
promise
|
119
|
-
|
115
|
+
promise = ::IOPromise::Dalli::DalliPromise.new(self, key)
|
116
|
+
|
117
|
+
new_id = @next_opaque_id
|
118
|
+
@pending_ops[new_id] = promise
|
119
|
+
@next_opaque_id = (@next_opaque_id + 1) & 0xffff_ffff
|
120
|
+
|
121
|
+
async_buffered_write(block.call(new_id))
|
122
|
+
|
120
123
|
promise
|
121
124
|
end
|
122
125
|
|
@@ -128,12 +131,12 @@ module IOPromise
|
|
128
131
|
end
|
129
132
|
end
|
130
133
|
|
131
|
-
def
|
132
|
-
|
134
|
+
def async_generic_write_op(op, key, value, ttl, cas, options)
|
135
|
+
value.then do |value|
|
133
136
|
(value, flags) = serialize(key, value, options)
|
134
137
|
ttl = sanitize_ttl(ttl)
|
135
138
|
|
136
|
-
|
139
|
+
guard_max_value_with_raise(key, value)
|
137
140
|
|
138
141
|
promised_request(key) do |opaque|
|
139
142
|
[REQUEST, OPCODES[op], key.bytesize, 8, 0, 0, value.bytesize + key.bytesize + 8, opaque, cas, flags, ttl, key, value].pack(FORMAT[op])
|
@@ -143,20 +146,19 @@ module IOPromise
|
|
143
146
|
|
144
147
|
def set(key, value, ttl, cas, options)
|
145
148
|
return super unless async?
|
146
|
-
|
147
|
-
generic_write_op(:set, key, value, ttl, cas, options)
|
149
|
+
async_generic_write_op(:set, key, value, ttl, cas, options)
|
148
150
|
end
|
149
151
|
|
150
152
|
def add(key, value, ttl, options)
|
151
153
|
return super unless async?
|
152
154
|
|
153
|
-
|
155
|
+
async_generic_write_op(:add, key, value, ttl, 0, options)
|
154
156
|
end
|
155
157
|
|
156
158
|
def replace(key, value, ttl, cas, options)
|
157
159
|
return super unless async?
|
158
160
|
|
159
|
-
|
161
|
+
async_generic_write_op(:replace, key, value, ttl, cas, options)
|
160
162
|
end
|
161
163
|
|
162
164
|
def delete(key, cas)
|
@@ -167,7 +169,7 @@ module IOPromise
|
|
167
169
|
end
|
168
170
|
end
|
169
171
|
|
170
|
-
def
|
172
|
+
def async_append_prepend_op(op, key, value)
|
171
173
|
promised_request(key) do |opaque|
|
172
174
|
[REQUEST, OPCODES[op], key.bytesize, 0, 0, 0, value.bytesize + key.bytesize, opaque, 0, key, value].pack(FORMAT[op])
|
173
175
|
end
|
@@ -176,13 +178,13 @@ module IOPromise
|
|
176
178
|
def append(key, value)
|
177
179
|
return super unless async?
|
178
180
|
|
179
|
-
|
181
|
+
async_append_prepend_op(:append, key, value)
|
180
182
|
end
|
181
183
|
|
182
184
|
def prepend(key, value)
|
183
185
|
return super unless async?
|
184
186
|
|
185
|
-
|
187
|
+
async_append_prepend_op(:prepend, key, value)
|
186
188
|
end
|
187
189
|
|
188
190
|
def flush
|
@@ -193,7 +195,7 @@ module IOPromise
|
|
193
195
|
end
|
194
196
|
end
|
195
197
|
|
196
|
-
def
|
198
|
+
def async_decr_incr(opcode, key, count, ttl, default)
|
197
199
|
expiry = default ? sanitize_ttl(ttl) : 0xFFFFFFFF
|
198
200
|
default ||= 0
|
199
201
|
(h, l) = split(count)
|
@@ -206,103 +208,118 @@ module IOPromise
|
|
206
208
|
def decr(key, count, ttl, default)
|
207
209
|
return super unless async?
|
208
210
|
|
209
|
-
|
211
|
+
async_decr_incr :decr, key, count, ttl, default
|
210
212
|
end
|
211
213
|
|
212
214
|
def incr(key, count, ttl, default)
|
213
215
|
return super unless async?
|
214
216
|
|
215
|
-
|
216
|
-
end
|
217
|
-
|
218
|
-
def new_pending(key)
|
219
|
-
promise = ::IOPromise::Dalli::DalliPromise.new(self, key)
|
220
|
-
new_id = @next_opaque_id
|
221
|
-
@pending_ops[new_id] = promise
|
222
|
-
@next_opaque_id = (@next_opaque_id + 1) & 0xffff_ffff
|
223
|
-
[promise, new_id]
|
217
|
+
async_decr_incr :incr, key, count, ttl, default
|
224
218
|
end
|
225
219
|
|
226
|
-
def
|
220
|
+
def async_buffered_write(data)
|
227
221
|
@write_buffer << data
|
228
|
-
|
222
|
+
async_sock_write_nonblock
|
229
223
|
end
|
230
224
|
|
231
|
-
def
|
225
|
+
def async_sock_write_nonblock
|
226
|
+
remaining = @write_buffer.byteslice(@write_offset, @write_buffer.length)
|
232
227
|
begin
|
233
|
-
bytes_written = @sock.write_nonblock(
|
234
|
-
rescue
|
235
|
-
|
228
|
+
bytes_written = @sock.write_nonblock(remaining, exception: false)
|
229
|
+
rescue Errno::EINTR
|
230
|
+
retry
|
236
231
|
end
|
232
|
+
|
233
|
+
return if bytes_written == :wait_writable
|
237
234
|
|
238
235
|
@write_offset += bytes_written
|
239
|
-
|
240
|
-
|
236
|
+
completed = (@write_offset == @write_buffer.length)
|
237
|
+
if completed
|
238
|
+
@write_buffer.clear
|
241
239
|
@write_offset = 0
|
242
240
|
end
|
241
|
+
@executor_pool.set_interest(:w, !completed)
|
243
242
|
rescue SystemCallError, Timeout::Error => e
|
244
243
|
failure!(e)
|
245
244
|
end
|
246
245
|
|
247
246
|
FULL_HEADER = 'CCnCCnNNQ'
|
248
247
|
|
249
|
-
def
|
250
|
-
|
248
|
+
def read_available
|
249
|
+
loop do
|
250
|
+
result = @sock.read_nonblock(8196, exception: false)
|
251
|
+
if result == :wait_readable
|
252
|
+
break
|
253
|
+
elsif result == :wait_writable
|
254
|
+
break
|
255
|
+
elsif result
|
256
|
+
@read_buffer << result
|
257
|
+
else
|
258
|
+
raise Errno::ECONNRESET, "Connection reset: #{safe_options.inspect}"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def async_sock_read_nonblock
|
264
|
+
read_available
|
251
265
|
|
252
266
|
buf = @read_buffer
|
253
267
|
pos = @read_offset
|
254
268
|
|
255
269
|
while buf.bytesize - pos >= 24
|
256
|
-
header = buf.
|
270
|
+
header = buf.byteslice(pos, 24)
|
257
271
|
(magic, opcode, key_length, extra_length, data_type, status, body_length, opaque, cas) = header.unpack(FULL_HEADER)
|
258
272
|
|
259
273
|
if buf.bytesize - pos >= 24 + body_length
|
260
|
-
|
261
|
-
|
262
|
-
flags = buf.slice(pos + 24, 4).unpack1("N")
|
263
|
-
end
|
274
|
+
exists = (status != 1) # Key not found
|
275
|
+
this_pos = pos
|
264
276
|
|
265
|
-
key = buf.
|
266
|
-
value = buf.
|
277
|
+
# key = buf.byteslice(this_pos + 24 + extra_length, key_length)
|
278
|
+
value = buf.byteslice(this_pos + 24 + extra_length + key_length, body_length - key_length - extra_length) if exists
|
267
279
|
|
268
280
|
pos = pos + 24 + body_length
|
269
281
|
|
270
282
|
promise = @pending_ops.delete(opaque)
|
271
283
|
next if promise.nil?
|
272
284
|
|
273
|
-
|
274
|
-
raise Dalli::DalliError, "Response error #{status}: #{Dalli::RESPONSE_CODES[status]}" unless
|
275
|
-
|
276
|
-
exists = (status != 1) # Key not found
|
285
|
+
begin
|
286
|
+
raise Dalli::DalliError, "Response error #{status}: #{Dalli::RESPONSE_CODES[status]}" unless status == 0 || status == 1 || status == 2 || status == 5
|
287
|
+
|
277
288
|
final_value = nil
|
278
289
|
if opcode == OPCODES[:incr] || opcode == OPCODES[:decr]
|
279
290
|
final_value = value.unpack1("Q>")
|
280
291
|
elsif exists
|
292
|
+
flags = if extra_length >= 4
|
293
|
+
buf.byteslice(this_pos + 24, 4).unpack1("N")
|
294
|
+
else
|
295
|
+
0
|
296
|
+
end
|
281
297
|
final_value = deserialize(value, flags)
|
282
298
|
end
|
283
299
|
|
284
|
-
::IOPromise::Dalli::Response.new(
|
300
|
+
response = ::IOPromise::Dalli::Response.new(
|
285
301
|
key: promise.key,
|
286
302
|
value: final_value,
|
287
303
|
exists: exists,
|
288
304
|
stored: !(status == 2 || status == 5), # Key exists or Item not stored
|
289
305
|
cas: cas,
|
290
306
|
)
|
291
|
-
end
|
292
307
|
|
293
|
-
|
294
|
-
|
308
|
+
promise.fulfill(response)
|
309
|
+
rescue => ex
|
310
|
+
promise.reject(ex)
|
311
|
+
end
|
295
312
|
else
|
296
313
|
# not enough data yet, wait for more
|
297
314
|
break
|
298
315
|
end
|
299
316
|
end
|
300
317
|
|
301
|
-
|
302
|
-
|
303
|
-
if @read_offset == @read_buffer.length
|
304
|
-
@read_buffer = +""
|
318
|
+
if pos == @read_buffer.length
|
319
|
+
@read_buffer.clear
|
305
320
|
@read_offset = 0
|
321
|
+
else
|
322
|
+
@read_offset = pos
|
306
323
|
end
|
307
324
|
|
308
325
|
rescue SystemCallError, Timeout::Error, EOFError => e
|
@@ -314,7 +331,6 @@ module IOPromise
|
|
314
331
|
# all pending operations need to be rejected when a failure occurs
|
315
332
|
@pending_ops.each do |op|
|
316
333
|
op.reject(ex)
|
317
|
-
op.execute_pool.complete(op)
|
318
334
|
end
|
319
335
|
@pending_ops = {}
|
320
336
|
end
|
@@ -322,8 +338,8 @@ module IOPromise
|
|
322
338
|
super
|
323
339
|
end
|
324
340
|
|
325
|
-
#
|
326
|
-
def
|
341
|
+
# this is guard_max_value from the master version, rather than using the yield block.
|
342
|
+
def guard_max_value_with_raise(key, value)
|
327
343
|
return if value.bytesize <= @options[:value_max_bytes]
|
328
344
|
|
329
345
|
message = "Value for #{key} over max size: #{@options[:value_max_bytes]} <= #{value.bytesize}"
|