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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 323b4f0107580d674fa42c4f65644dcae302c822c9d370dd9e3effbf5b4c13eb
4
- data.tar.gz: dc8b3937fea0505e598c0855ccc695b98dc837e221b8959a61acfbf18838c3b5
3
+ metadata.gz: 3fc60525e45ec8cfa8a6cd5ba34bf396b77e4d42f3fd566137f14367a2ba31ae
4
+ data.tar.gz: 1e70b2147a204a220be6b268ae62f67dc06b79065b8ec07636da414264ae626b
5
5
  SHA512:
6
- metadata.gz: 8737b6c9a1e1ee2376f586ceca83a074679d6e0b53e073e07df8817f3f14fe261b606f5398618bcd984f2441b288b89fb9d660f499428d60c93cf35e7b561f7e
7
- data.tar.gz: c82ed8d1da207ce2bfa766b8a1fd88aa4ba803565b749f4c77e0cb90a5b8f9c0289f60111260daf4acc5b7ddaae43b0ddcadd91212201778516b1637f9db2cb6
6
+ metadata.gz: b92e71ab4fa32e5c45f9b4a4fa607844f2558f270f69ddb3a63b1394633a4b3e7eb101f77b06e8bc4efe45ad657e00a7d4bad10f34374356f2dc660db17af525
7
+ data.tar.gz: 4034ef0ae2010b4cea930894d1f7cc05722e6dd0e3c112fe237ce6488441d24d670c098df525046c13d26514ff39ee672c3a26c49f601ddc40b757fbbe691079
data/Gemfile CHANGED
@@ -23,4 +23,8 @@ group :development, :test do
23
23
  # view_component extensions
24
24
  gem "rails"
25
25
  gem "view_component", require: "view_component/engine"
26
+
27
+ # benchmarking
28
+ gem "benchmark-ips"
29
+ gem "stackprof"
26
30
  end
data/Gemfile.lock CHANGED
@@ -8,8 +8,9 @@ GIT
8
8
  PATH
9
9
  remote: .
10
10
  specs:
11
- iopromise (0.1.0)
12
- promise.rb
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
@@ -27,4 +27,5 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_dependency 'promise.rb', '~> 0.7.4'
30
+ spec.add_dependency 'nio4r'
30
31
  end
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 @started_executing
26
- @instrument_begin << begin_cb unless begin_cb.nil?
27
- @instrument_end << end_cb unless end_cb.nil?
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.each { |cb| cb.call(self) }
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
- @started_executing
35
+ !!@started_executing
37
36
  end
38
37
 
39
38
  def notify_completion(value: nil, reason: nil)
40
- @instrument_end.each { |cb| cb.call(self, value: value, reason: reason) }
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
- execute_as_promise(:get, key, options)
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
- Promise.resolve(block.call).then do |new_val|
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
- Promise.resolve(response.value)
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
- execute_as_promise(:set, key, value, ttl_or_default(ttl), 0, options)
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
- execute_as_promise(:add, key, value, ttl_or_default(ttl), options)
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
- execute_as_promise(:replace, key, value, ttl_or_default(ttl), 0, options)
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
- execute_as_promise(:delete, key, 0)
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
- Promise.resolve(value).then do |resolved_value|
84
- execute_as_promise(:append, key, resolved_value.to_s)
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
- Promise.resolve(value).then do |resolved_value|
92
- execute_as_promise(:prepend, key, resolved_value.to_s)
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
- execute_as_promise(:incr, key, amt.to_i, ttl_or_default(ttl), default)
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
- execute_as_promise(:decr, key, amt.to_i, ttl_or_default(ttl), default)
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 execute_continue(ready_readers, ready_writers, ready_exceptions)
7
- dalli_server = @connection_pool
6
+ def initialize(*)
7
+ super
8
8
 
9
- dalli_server.execute_continue(ready_readers, ready_writers, ready_exceptions)
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 perform_async(*args)
16
- if @async
17
- perform(*args)
18
- else
19
- raise ArgumentError, "Cannot perform_async when async is not enabled."
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 async_reset
55
- @write_buffer = +""
56
- @write_offset = 0
57
-
58
- @read_buffer = +""
59
- @read_offset = 0
60
- end
58
+ def connect
59
+ super
61
60
 
62
- # called by ExecutorPool to continue processing for this server
63
- def execute_continue(ready_readers, ready_writers, ready_exceptions)
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
- readers_empty = ready_readers.nil? || ready_readers.empty?
70
- exceptions_empty = ready_exceptions.nil? || ready_exceptions.empty?
66
+ def async_reset
67
+ @write_buffer.clear
68
+ @write_offset = 0
71
69
 
72
- if !readers_empty || !exceptions_empty
73
- sock_read_nonblock
74
- end
70
+ @read_buffer.clear
71
+ @read_offset = 0
75
72
 
76
- readers = []
77
- writers = []
78
- exceptions = [@sock]
79
- timeout = nil
73
+ @executor_pool.close_socket if defined? @executor_pool
74
+ end
80
75
 
81
- to_timeout = @pending_ops.select { |key, op| op.timeout? }
82
- to_timeout.each do |key, op|
83
- @pending_ops.delete(key)
84
- op.reject(Timeout::Error.new)
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
- unless @pending_ops.empty?
89
- # wait for writability if we have pending data to write
90
- writers << @sock if @write_buffer.bytesize > @write_offset
91
- # and always call back when there is data available to read
92
- readers << @sock
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
- @pending_ops.each do |_, op|
100
- op.in_select_loop
101
- end
95
+ op.in_select_loop
102
96
 
103
- # mark the amount of time left of the closest to timeout.
104
- timeout = @pending_ops.map { |_, op| op.timeout_remaining }.min
97
+ remaining = op.timeout_remaining
98
+ timeout = remaining if remaining < timeout
99
+
100
+ true # keep
105
101
  end
106
102
 
107
- [readers, writers, exceptions, timeout]
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, opaque = new_pending(key)
119
- buffered_write(block.call(opaque))
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 generic_write_op(op, key, value, ttl, cas, options)
132
- Promise.resolve(value).then do |value|
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
- guard_max_value(key, value)
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
- generic_write_op(:add, key, value, ttl, 0, options)
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
- generic_write_op(:replace, key, value, ttl, cas, options)
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 append_prepend_op(op, key, value)
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
- append_prepend_op(:append, key, value)
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
- append_prepend_op(:prepend, key, value)
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 decr_incr(opcode, key, count, ttl, default)
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
- decr_incr :decr, key, count, ttl, default
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
- decr_incr :incr, key, count, ttl, default
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 buffered_write(data)
220
+ def async_buffered_write(data)
227
221
  @write_buffer << data
228
- sock_write_nonblock
222
+ async_sock_write_nonblock
229
223
  end
230
224
 
231
- def sock_write_nonblock
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(@write_buffer.byteslice(@write_offset..-1))
234
- rescue IO::WaitWritable, Errno::EINTR
235
- return # no room to write immediately
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
- if @write_offset == @write_buffer.length
240
- @write_buffer = +""
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 sock_read_nonblock
250
- @read_buffer << @sock.read_available
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.slice(pos, 24)
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
- flags = 0
261
- if extra_length >= 4
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.slice(pos + 24 + extra_length, key_length)
266
- value = buf.slice(pos + 24 + extra_length + key_length, body_length - key_length - extra_length)
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
- result = Promise.resolve(true).then do # auto capture exceptions below
274
- raise Dalli::DalliError, "Response error #{status}: #{Dalli::RESPONSE_CODES[status]}" unless [0,1,2,5].include?(status)
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
- promise.fulfill(result)
294
- promise.execute_pool.complete(promise)
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
- @read_offset = pos
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
- # FIXME: this is from the master version, rather than using the yield block.
326
- def guard_max_value(key, value)
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}"