hiredis 0.3.2 → 0.4.1

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.
data/Rakefile CHANGED
@@ -1,28 +1,42 @@
1
- require 'rake'
2
- require 'rake/testtask'
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake/testtask"
3
5
  require "rake/extensiontask"
4
6
 
5
- Rake::ExtensionTask.new('hiredis_ext') do |task|
6
- # Pass --with-foo-config args to extconf.rb
7
- task.config_options = ARGV[1..-1]
8
- task.lib_dir = File.join(*['lib', 'hiredis', 'ext'])
9
- end
7
+ unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
8
+
9
+ Rake::ExtensionTask.new('hiredis_ext') do |task|
10
+ # Pass --with-foo-config args to extconf.rb
11
+ task.config_options = ARGV[1..-1] || []
12
+ task.lib_dir = File.join(*['lib', 'hiredis', 'ext'])
13
+ end
10
14
 
11
- namespace :hiredis do
12
- task :clean do
13
- # Fetch hiredis if not present
14
- if !File.directory?("vendor/hiredis/.git")
15
- system("git submodule update --init")
15
+ namespace :hiredis do
16
+ task :clean do
17
+ # Fetch hiredis if not present
18
+ if !File.directory?("vendor/hiredis/.git")
19
+ system("git submodule update --init")
20
+ end
21
+ system("cd vendor/hiredis && make clean")
16
22
  end
17
- system("cd vendor/hiredis && make clean")
18
23
  end
19
- end
20
24
 
21
- # "rake clean" should also clean bundled hiredis
22
- Rake::Task[:clean].enhance(['hiredis:clean'])
25
+ # "rake clean" should also clean bundled hiredis
26
+ Rake::Task[:clean].enhance(['hiredis:clean'])
27
+
28
+ # Build from scratch
29
+ task :rebuild => [:clean, :compile]
30
+
31
+ else
32
+
33
+ task :rebuild do
34
+ # no-op
35
+ end
36
+
37
+ end
23
38
 
24
- # Build from scratch
25
- task :build => [:clean, :compile]
39
+ task :default => [:rebuild, :test]
26
40
 
27
41
  desc "Run tests"
28
42
  Rake::TestTask.new(:test) do |t|
@@ -4,15 +4,28 @@
4
4
 
5
5
  typedef struct redisParentContext {
6
6
  redisContext *context;
7
+ struct timeval *timeout;
7
8
  } redisParentContext;
8
9
 
9
- static void parent_context_try_free(redisParentContext *pc) {
10
+ static void parent_context_try_free_context(redisParentContext *pc) {
10
11
  if (pc->context) {
11
12
  redisFree(pc->context);
12
13
  pc->context = NULL;
13
14
  }
14
15
  }
15
16
 
17
+ static void parent_context_try_free_timeout(redisParentContext *pc) {
18
+ if (pc->timeout) {
19
+ free(pc->timeout);
20
+ pc->timeout = NULL;
21
+ }
22
+ }
23
+
24
+ static void parent_context_try_free(redisParentContext *pc) {
25
+ parent_context_try_free_context(pc);
26
+ parent_context_try_free_timeout(pc);
27
+ }
28
+
16
29
  static void parent_context_mark(redisParentContext *pc) {
17
30
  VALUE root;
18
31
  if (pc->context && pc->context->reader) {
@@ -43,8 +56,8 @@ static void parent_context_raise(redisParentContext *pc) {
43
56
  rb_sys_fail(0);
44
57
  break;
45
58
  case REDIS_ERR_EOF:
46
- /* Raise our own EOF error */
47
- rb_raise(error_eof,"%s",errstr);
59
+ /* Raise native Ruby EOFError */
60
+ rb_raise(rb_eEOFError,"%s",errstr);
48
61
  break;
49
62
  default:
50
63
  /* Raise something else */
@@ -55,20 +68,56 @@ static void parent_context_raise(redisParentContext *pc) {
55
68
  static VALUE connection_parent_context_alloc(VALUE klass) {
56
69
  redisParentContext *pc = malloc(sizeof(*pc));
57
70
  pc->context = NULL;
71
+ pc->timeout = NULL;
58
72
  return Data_Wrap_Struct(klass, parent_context_mark, parent_context_free, pc);
59
73
  }
60
74
 
61
- static VALUE connection_generic_connect(VALUE self, redisContext *c) {
75
+ static int __wait_readable(int fd, struct timeval *timeout, int *isset) {
76
+ fd_set fds;
77
+ FD_ZERO(&fds);
78
+ FD_SET(fd, &fds);
79
+
80
+ if (rb_thread_select(fd + 1, &fds, NULL, NULL, timeout) < 0) {
81
+ return -1;
82
+ }
83
+
84
+ if (FD_ISSET(fd, &fds) && isset) {
85
+ *isset = 1;
86
+ }
87
+
88
+ return 0;
89
+ }
90
+
91
+ static int __wait_writable(int fd, struct timeval *timeout, int *isset) {
92
+ fd_set fds;
93
+ FD_ZERO(&fds);
94
+ FD_SET(fd, &fds);
95
+
96
+ if (rb_thread_select(fd + 1, NULL, &fds, NULL, timeout) < 0) {
97
+ return -1;
98
+ }
99
+
100
+ if (FD_ISSET(fd, &fds) && isset) {
101
+ *isset = 1;
102
+ }
103
+
104
+ return 0;
105
+ }
106
+
107
+ static VALUE connection_generic_connect(VALUE self, redisContext *c, VALUE arg_timeout) {
62
108
  redisParentContext *pc;
63
- int err;
64
- char errstr[1024];
109
+ struct timeval tv;
110
+ struct timeval *timeout = NULL;
65
111
 
66
112
  Data_Get_Struct(self,redisParentContext,pc);
67
113
 
68
114
  if (c->err) {
115
+ char buf[1024];
116
+ int err;
117
+
69
118
  /* Copy error and free context */
70
119
  err = c->err;
71
- snprintf(errstr,sizeof(errstr),"%s",c->errstr);
120
+ snprintf(buf,sizeof(buf),"%s",c->errstr);
72
121
  redisFree(c);
73
122
 
74
123
  if (err == REDIS_ERR_IO) {
@@ -76,86 +125,107 @@ static VALUE connection_generic_connect(VALUE self, redisContext *c) {
76
125
  rb_sys_fail(0);
77
126
  } else {
78
127
  /* Raise something else */
79
- rb_raise(rb_eRuntimeError,"%s",errstr);
128
+ rb_raise(rb_eRuntimeError,"%s",buf);
80
129
  }
81
130
  }
82
131
 
83
- c->reader->fn = &redisExtReplyObjectFunctions;
132
+ /* Default to context-wide timeout setting */
133
+ if (pc->timeout != NULL) {
134
+ timeout = pc->timeout;
135
+ }
136
+
137
+ /* Override timeout when timeout argument is available */
138
+ if (arg_timeout != Qnil) {
139
+ tv.tv_sec = NUM2INT(arg_timeout) / 1000000;
140
+ tv.tv_usec = NUM2INT(arg_timeout) % 1000000;
141
+ timeout = &tv;
142
+ }
143
+
144
+ /* Wait for socket to become writable */
145
+ int writable = 0;
146
+ if (__wait_writable(c->fd, timeout, &writable) < 0) {
147
+ goto sys_fail;
148
+ }
149
+
150
+ if (!writable) {
151
+ errno = ETIMEDOUT;
152
+ goto sys_fail;
153
+ }
154
+
155
+ /* Check for socket error */
156
+ int optval = 0;
157
+ socklen_t optlen = sizeof(optval);
158
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
159
+ goto sys_fail;
160
+ }
161
+
162
+ if (optval) {
163
+ errno = optval;
164
+ goto sys_fail;
165
+ }
166
+
167
+ parent_context_try_free_context(pc);
84
168
  pc->context = c;
169
+ pc->context->reader->fn = &redisExtReplyObjectFunctions;
85
170
  return Qnil;
86
- }
87
171
 
88
- static struct timeval __timeout_from_robj(VALUE usecs) {
89
- int s = NUM2INT(usecs)/1000000;
90
- int us = NUM2INT(usecs)-(s*1000000);
91
- struct timeval timeout = { s, us };
92
- return timeout;
172
+ sys_fail:
173
+ redisFree(c);
174
+ rb_sys_fail(0);
93
175
  }
94
176
 
95
177
  static VALUE connection_connect(int argc, VALUE *argv, VALUE self) {
96
178
  redisParentContext *pc;
97
179
  redisContext *c;
98
- VALUE *_host = NULL;
99
- VALUE *_port = NULL;
100
- VALUE *_timeout = NULL;
101
- char *host;
102
- int port;
103
- struct timeval timeout;
180
+ VALUE arg_host = Qnil;
181
+ VALUE arg_port = Qnil;
182
+ VALUE arg_timeout = Qnil;
104
183
 
105
184
  if (argc == 2 || argc == 3) {
106
- _host = &argv[0];
107
- _port = &argv[1];
108
- if (argc == 3)
109
- _timeout = &argv[2];
110
- } else {
111
- rb_raise(rb_eArgError, "invalid number of arguments");
112
- return Qnil;
113
- }
185
+ arg_host = argv[0];
186
+ arg_port = argv[1];
114
187
 
115
- Data_Get_Struct(self,redisParentContext,pc);
116
- parent_context_try_free(pc);
188
+ if (argc == 3) {
189
+ arg_timeout = argv[2];
117
190
 
118
- host = StringValuePtr(*_host);
119
- port = NUM2INT(*_port);
120
- if (_timeout != NULL) {
121
- timeout = __timeout_from_robj(*_timeout);
122
- c = redisConnectWithTimeout(host,port,timeout);
191
+ /* Sanity check */
192
+ if (NUM2INT(arg_timeout) <= 0) {
193
+ rb_raise(rb_eArgError, "timeout should be positive");
194
+ }
195
+ }
123
196
  } else {
124
- c = redisConnect(host,port);
197
+ rb_raise(rb_eArgError, "invalid number of arguments");
125
198
  }
126
199
 
127
- return connection_generic_connect(self,c);
200
+ Data_Get_Struct(self,redisParentContext,pc);
201
+ c = redisConnectNonBlock(StringValuePtr(arg_host), NUM2INT(arg_port));
202
+ return connection_generic_connect(self,c,arg_timeout);
128
203
  }
129
204
 
130
205
  static VALUE connection_connect_unix(int argc, VALUE *argv, VALUE self) {
131
206
  redisParentContext *pc;
132
207
  redisContext *c;
133
- VALUE *_path = NULL;
134
- VALUE *_timeout = NULL;
135
- char *path;
136
- struct timeval timeout;
208
+ VALUE arg_path = Qnil;
209
+ VALUE arg_timeout = Qnil;
137
210
 
138
211
  if (argc == 1 || argc == 2) {
139
- _path = &argv[0];
140
- if (argc == 2)
141
- _timeout = &argv[1];
142
- } else {
143
- rb_raise(rb_eArgError, "invalid number of arguments");
144
- return Qnil;
145
- }
212
+ arg_path = argv[0];
146
213
 
147
- Data_Get_Struct(self,redisParentContext,pc);
148
- parent_context_try_free(pc);
214
+ if (argc == 2) {
215
+ arg_timeout = argv[1];
149
216
 
150
- path = StringValuePtr(*_path);
151
- if (_timeout != NULL) {
152
- timeout = __timeout_from_robj(*_timeout);
153
- c = redisConnectUnixWithTimeout(path,timeout);
217
+ /* Sanity check */
218
+ if (NUM2INT(arg_timeout) <= 0) {
219
+ rb_raise(rb_eArgError, "timeout should be positive");
220
+ }
221
+ }
154
222
  } else {
155
- c = redisConnectUnix(path);
223
+ rb_raise(rb_eArgError, "invalid number of arguments");
156
224
  }
157
225
 
158
- return connection_generic_connect(self,c);
226
+ Data_Get_Struct(self,redisParentContext,pc);
227
+ c = redisConnectUnixNonBlock(StringValuePtr(arg_path));
228
+ return connection_generic_connect(self,c,arg_timeout);
159
229
  }
160
230
 
161
231
  static VALUE connection_is_connected(VALUE self) {
@@ -210,31 +280,113 @@ static VALUE connection_write(VALUE self, VALUE command) {
210
280
  return Qnil;
211
281
  }
212
282
 
283
+ static VALUE connection_flush(VALUE self) {
284
+ redisParentContext *pc;
285
+ redisContext *c;
286
+ int wdone = 0;
287
+
288
+ Data_Get_Struct(self,redisParentContext,pc);
289
+ if (!pc->context)
290
+ rb_raise(rb_eRuntimeError, "not connected");
291
+
292
+ c = pc->context;
293
+ while (!wdone) {
294
+ errno = 0;
295
+
296
+ if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
297
+ /* Socket error */
298
+ parent_context_raise(pc);
299
+ }
300
+
301
+ if (errno == EAGAIN) {
302
+ int writable = 0;
303
+
304
+ if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
305
+ rb_sys_fail(0);
306
+ }
307
+
308
+ if (!writable) {
309
+ errno = EAGAIN;
310
+ rb_sys_fail(0);
311
+ }
312
+ }
313
+ }
314
+
315
+ return Qnil;
316
+ }
317
+
213
318
  static int __get_reply(redisParentContext *pc, VALUE *reply) {
214
319
  redisContext *c = pc->context;
215
320
  int wdone = 0;
216
321
  void *aux = NULL;
217
322
 
218
323
  /* Try to read pending replies */
219
- if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
324
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
325
+ /* Protocol error */
220
326
  return -1;
327
+ }
221
328
 
222
329
  if (aux == NULL) {
223
- do { /* Write until done */
224
- if (redisBufferWrite(c,&wdone) == REDIS_ERR)
330
+ /* Write until the write buffer is drained */
331
+ while (!wdone) {
332
+ errno = 0;
333
+
334
+ if (redisBufferWrite(c, &wdone) == REDIS_ERR) {
335
+ /* Socket error */
225
336
  return -1;
226
- } while (!wdone);
227
- do { /* Read until there is a reply */
228
- rb_thread_wait_fd(c->fd);
229
- if (redisBufferRead(c) == REDIS_ERR)
337
+ }
338
+
339
+ if (errno == EAGAIN) {
340
+ int writable = 0;
341
+
342
+ if (__wait_writable(c->fd, pc->timeout, &writable) < 0) {
343
+ rb_sys_fail(0);
344
+ }
345
+
346
+ if (!writable) {
347
+ errno = EAGAIN;
348
+ rb_sys_fail(0);
349
+ }
350
+ }
351
+ }
352
+
353
+ /* Read until there is a full reply */
354
+ while (aux == NULL) {
355
+ errno = 0;
356
+
357
+ if (redisBufferRead(c) == REDIS_ERR) {
358
+ /* Socket error */
230
359
  return -1;
231
- if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
360
+ }
361
+
362
+ if (errno == EAGAIN) {
363
+ int readable = 0;
364
+
365
+ if (__wait_readable(c->fd, pc->timeout, &readable) < 0) {
366
+ rb_sys_fail(0);
367
+ }
368
+
369
+ if (!readable) {
370
+ errno = EAGAIN;
371
+ rb_sys_fail(0);
372
+ }
373
+
374
+ /* Retry */
375
+ continue;
376
+ }
377
+
378
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) {
379
+ /* Protocol error */
232
380
  return -1;
233
- } while (aux == NULL);
381
+ }
382
+ }
234
383
  }
235
384
 
236
385
  /* Set reply object */
237
- if (reply != NULL) *reply = (VALUE)aux;
386
+ if (reply != NULL) {
387
+ *reply = (VALUE)aux;
388
+ }
389
+
238
390
  return 0;
239
391
  }
240
392
 
@@ -254,23 +406,41 @@ static VALUE connection_read(VALUE self) {
254
406
 
255
407
  static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
256
408
  redisParentContext *pc;
257
- int s = NUM2INT(usecs)/1000000;
258
- int us = NUM2INT(usecs)-(s*1000000);
259
- struct timeval timeout = { s, us };
409
+ struct timeval *ptr;
260
410
 
261
411
  Data_Get_Struct(self,redisParentContext,pc);
262
- if (!pc->context)
263
- rb_raise(rb_eRuntimeError, "not connected");
264
412
 
265
- if (redisSetTimeout(pc->context,timeout) == REDIS_ERR)
266
- parent_context_raise(pc);
413
+ if (NUM2INT(usecs) < 0) {
414
+ rb_raise(rb_eArgError, "timeout cannot be negative");
415
+ } else {
416
+ parent_context_try_free_timeout(pc);
417
+
418
+ /* A timeout equal to zero means not to time out. This translates to a
419
+ * NULL timeout for select(2). Only allocate and populate the timeout
420
+ * when it is a positive integer. */
421
+ if (NUM2INT(usecs) > 0) {
422
+ ptr = malloc(sizeof(*ptr));
423
+ ptr->tv_sec = NUM2INT(usecs) / 1000000;
424
+ ptr->tv_usec = NUM2INT(usecs) % 1000000;
425
+ pc->timeout = ptr;
426
+ }
427
+ }
267
428
 
268
- return usecs;
429
+ return Qnil;
269
430
  }
270
431
 
432
+ static VALUE connection_fileno(VALUE self) {
433
+ redisParentContext *pc;
434
+
435
+ Data_Get_Struct(self,redisParentContext,pc);
436
+
437
+ if (!pc->context)
438
+ rb_raise(rb_eRuntimeError, "not connected");
439
+
440
+ return INT2NUM(pc->context->fd);
441
+ }
271
442
 
272
443
  VALUE klass_connection;
273
- VALUE error_eof;
274
444
  void InitConnection(VALUE mod) {
275
445
  klass_connection = rb_define_class_under(mod, "Connection", rb_cObject);
276
446
  rb_define_alloc_func(klass_connection, connection_parent_context_alloc);
@@ -279,7 +449,8 @@ void InitConnection(VALUE mod) {
279
449
  rb_define_method(klass_connection, "connected?", connection_is_connected, 0);
280
450
  rb_define_method(klass_connection, "disconnect", connection_disconnect, 0);
281
451
  rb_define_method(klass_connection, "timeout=", connection_set_timeout, 1);
452
+ rb_define_method(klass_connection, "fileno", connection_fileno, 0);
282
453
  rb_define_method(klass_connection, "write", connection_write, 1);
454
+ rb_define_method(klass_connection, "flush", connection_flush, 0);
283
455
  rb_define_method(klass_connection, "read", connection_read, 0);
284
- error_eof = rb_define_class_under(klass_connection, "EOFError", rb_eStandardError);
285
456
  }
@@ -1,16 +1,29 @@
1
1
  require "hiredis/ext/hiredis_ext"
2
2
  require "hiredis/version"
3
+ require "socket"
3
4
 
4
5
  module Hiredis
5
6
  module Ext
6
7
  class Connection
7
- # Raise CONNRESET on EOF
8
+ alias :_disconnect :disconnect
9
+
10
+ def disconnect
11
+ _disconnect
12
+ ensure
13
+ @sock = nil
14
+ end
15
+
8
16
  alias :_read :read
17
+
9
18
  def read
10
19
  _read
11
- rescue EOFError
20
+ rescue ::EOFError
12
21
  raise Errno::ECONNRESET
13
22
  end
23
+
24
+ def sock
25
+ @sock ||= Socket.for_fd(fileno)
26
+ end
14
27
  end
15
28
  end
16
29
  end
@@ -1,5 +1,4 @@
1
1
  require "socket"
2
- require "timeout"
3
2
  require "hiredis/ruby/reader"
4
3
  require "hiredis/version"
5
4
 
@@ -7,46 +6,211 @@ module Hiredis
7
6
  module Ruby
8
7
  class Connection
9
8
 
10
- def initialize
11
- @sock = nil
12
- end
9
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
10
+
11
+ def self.errno_to_class
12
+ Errno::Mapping
13
+ end
14
+
15
+ else
16
+
17
+ def self.errno_to_class
18
+ @mapping ||= Hash[Errno.constants.map do |name|
19
+ klass = Errno.const_get(name)
20
+ [klass.const_get("Errno"), klass]
21
+ end]
22
+ end
13
23
 
14
- def connected?
15
- !! @sock
16
24
  end
17
25
 
18
- def connect(host, port, usecs = 0)
19
- @reader = ::Hiredis::Ruby::Reader.new
26
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
27
+
28
+ require "timeout"
29
+
30
+ def _connect(host, port, timeout)
31
+ sock = nil
32
+
33
+ begin
34
+ Timeout.timeout(timeout) do
35
+ sock = TCPSocket.new(host, port)
36
+ end
37
+ rescue SocketError => se
38
+ raise se.message
39
+ rescue Timeout::Error
40
+ raise Errno::ETIMEDOUT
41
+ end
42
+
43
+ sock
44
+ end
45
+
46
+ def _connect_unix(path, timeout)
47
+ sock = nil
20
48
 
21
- begin
22
49
  begin
23
- Timeout.timeout(usecs.to_f / 1_000_000) do
24
- @sock = TCPSocket.new(host, port)
25
- @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
50
+ Timeout.timeout(timeout) do
51
+ sock = UNIXSocket.new(path)
26
52
  end
53
+ rescue SocketError => se
54
+ raise se.message
27
55
  rescue Timeout::Error
28
56
  raise Errno::ETIMEDOUT
29
57
  end
30
- rescue SocketError => error
31
- # Raise RuntimeError when host cannot be resolved
32
- if error.message.start_with?("getaddrinfo:")
33
- raise error.message
34
- else
35
- raise error
58
+
59
+ sock
60
+ end
61
+
62
+ def _write(sock, data, timeout)
63
+ begin
64
+ Timeout.timeout(timeout) do
65
+ sock.write(data)
66
+ end
67
+ rescue Timeout::Error
68
+ raise Errno::EAGAIN
69
+ end
70
+ end
71
+
72
+ else
73
+
74
+ def _connect(host, port, timeout)
75
+ error = nil
76
+ sock = nil
77
+
78
+ # Resolve address
79
+ begin
80
+ addrinfo = Socket.getaddrinfo(host, port, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
81
+ rescue SocketError => se
82
+ raise se.message
83
+ end
84
+
85
+ addrinfo.each do |_, port, name, addr, af|
86
+ begin
87
+ sockaddr = Socket.pack_sockaddr_in(port, addr)
88
+ sock = _connect_sockaddr(af, sockaddr, timeout)
89
+ rescue => aux
90
+ case aux
91
+ when Errno::EAFNOSUPPORT, Errno::ECONNREFUSED
92
+ error = aux
93
+ next
94
+ else
95
+ # Re-raise
96
+ raise
97
+ end
98
+ else
99
+ # No errors, awesome!
100
+ break
101
+ end
102
+ end
103
+
104
+ unless sock
105
+ # Re-raise last error since the last try obviously failed
106
+ raise error if error
107
+
108
+ # This code path should not happen: getaddrinfo should always return
109
+ # at least one record, which should either succeed or fail and leave
110
+ # and error to raise.
111
+ raise
112
+ end
113
+
114
+ sock
115
+ end
116
+
117
+ def _connect_unix(path, timeout)
118
+ sockaddr = Socket.pack_sockaddr_un(path)
119
+ _connect_sockaddr(Socket::AF_UNIX, sockaddr, timeout)
120
+ end
121
+
122
+ def _write(sock, data, timeout)
123
+ data.force_encoding("binary") if data.respond_to?(:force_encoding)
124
+
125
+ begin
126
+ nwritten = @sock.write_nonblock(data)
127
+
128
+ while nwritten < string_size(data)
129
+ data = data[nwritten..-1]
130
+ nwritten = @sock.write_nonblock(data)
131
+ end
132
+ rescue Errno::EAGAIN
133
+ if IO.select([], [@sock], [], timeout)
134
+ # Writable, try again
135
+ retry
136
+ else
137
+ # Timed out, raise
138
+ raise Errno::EAGAIN
139
+ end
140
+ end
141
+ end
142
+
143
+ def _connect_sockaddr(af, sockaddr, timeout)
144
+ sock = Socket.new(af, Socket::SOCK_STREAM, 0)
145
+
146
+ begin
147
+ sock.connect_nonblock(sockaddr)
148
+ rescue Errno::EINPROGRESS
149
+ if IO.select(nil, [sock], nil, timeout)
150
+ # Writable, check for errors
151
+ optval = sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR)
152
+ errno = optval.unpack("i").first
153
+
154
+ # Raise socket error if there is any
155
+ raise self.class.errno_to_class[errno] if errno > 0
156
+ else
157
+ # Timeout (TODO: replace with own Timeout class)
158
+ raise Errno::ETIMEDOUT
159
+ end
36
160
  end
161
+
162
+ sock
163
+ rescue
164
+ sock.close if sock
165
+
166
+ # Re-raise
167
+ raise
37
168
  end
169
+
170
+ private :_connect_sockaddr
171
+
172
+ end
173
+
174
+ attr_reader :sock
175
+
176
+ def initialize
177
+ @sock = nil
178
+ @timeout = nil
179
+ end
180
+
181
+ def connected?
182
+ !! @sock
183
+ end
184
+
185
+ def connect(host, port, usecs = nil)
186
+ # Temporarily override timeout on #connect
187
+ timeout = usecs ? (usecs / 1_000_000.0) : @timeout
188
+
189
+ # Optionally disconnect current socket
190
+ disconnect if connected?
191
+
192
+ sock = _connect(host, port, timeout)
193
+ sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
194
+
195
+ @reader = ::Hiredis::Ruby::Reader.new
196
+ @sock = sock
197
+
198
+ nil
38
199
  end
39
200
 
40
201
  def connect_unix(path, usecs = 0)
202
+ # Temporarily override timeout on #connect
203
+ timeout = usecs ? (usecs / 1_000_000.0) : @timeout
204
+
205
+ # Optionally disconnect current socket
206
+ disconnect if connected?
207
+
208
+ sock = _connect_unix(path, timeout)
209
+
41
210
  @reader = ::Hiredis::Ruby::Reader.new
211
+ @sock = sock
42
212
 
43
- begin
44
- Timeout.timeout(usecs.to_f / 1_000_000) do
45
- @sock = UNIXSocket.new(path)
46
- end
47
- rescue Timeout::Error
48
- raise Errno::ETIMEDOUT
49
- end
213
+ nil
50
214
  end
51
215
 
52
216
  def disconnect
@@ -57,18 +221,21 @@ module Hiredis
57
221
  end
58
222
 
59
223
  def timeout=(usecs)
60
- raise "not connected" unless connected?
224
+ raise ArgumentError.new("timeout cannot be negative") if usecs < 0
61
225
 
62
- secs = Integer(usecs / 1_000_000)
63
- usecs = Integer(usecs - (secs * 1_000_000)) # 0 - 999_999
226
+ if usecs == 0
227
+ @timeout = nil
228
+ else
229
+ @timeout = usecs / 1_000_000.0
230
+ end
64
231
 
65
- optval = [secs, usecs].pack("l_2")
232
+ nil
233
+ end
66
234
 
67
- begin
68
- @sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
69
- @sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
70
- rescue Errno::ENOPROTOOPT
71
- end
235
+ def fileno
236
+ raise "not connected" unless connected?
237
+
238
+ @sock.fileno
72
239
  end
73
240
 
74
241
  COMMAND_DELIMITER = "\r\n".freeze
@@ -82,18 +249,36 @@ module Hiredis
82
249
  command << arg
83
250
  end
84
251
 
85
- @sock.syswrite(command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER)
252
+ data = command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
253
+
254
+ _write(@sock, data, @timeout)
255
+
256
+ nil
257
+ end
258
+
259
+ # No-op for now..
260
+ def flush
86
261
  end
87
262
 
88
263
  def read
89
264
  raise "not connected" unless connected?
90
265
 
91
266
  while (reply = @reader.gets) == false
92
- @reader.feed @sock.sysread(1024)
267
+ begin
268
+ @reader.feed @sock.read_nonblock(1024)
269
+ rescue Errno::EAGAIN
270
+ if IO.select([@sock], [], [], @timeout)
271
+ # Readable, try again
272
+ retry
273
+ else
274
+ # Timed out, raise
275
+ raise Errno::EAGAIN
276
+ end
277
+ end
93
278
  end
94
279
 
95
280
  reply
96
- rescue EOFError
281
+ rescue ::EOFError
97
282
  raise Errno::ECONNRESET
98
283
  end
99
284
 
@@ -1,3 +1,3 @@
1
1
  module Hiredis
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,39 +1,35 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: hiredis
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
4
5
  prerelease:
5
- version: 0.3.2
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Pieter Noordhuis
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-05-17 00:00:00 +02:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2011-10-26 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: rake-compiler
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70311128546620 !ruby/object:Gem::Requirement
20
17
  none: false
21
- requirements:
18
+ requirements:
22
19
  - - ~>
23
- - !ruby/object:Gem::Version
20
+ - !ruby/object:Gem::Version
24
21
  version: 0.7.1
25
22
  type: :development
26
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: *70311128546620
27
25
  description: Ruby extension that wraps Hiredis (blocking connection and reply parsing)
28
- email:
26
+ email:
29
27
  - pcnoordhuis@gmail.com
30
28
  executables: []
31
-
32
- extensions:
29
+ extensions:
33
30
  - ext/hiredis_ext/extconf.rb
34
31
  extra_rdoc_files: []
35
-
36
- files:
32
+ files:
37
33
  - ext/hiredis_ext/extconf.rb
38
34
  - ext/hiredis_ext/connection.c
39
35
  - ext/hiredis_ext/hiredis_ext.c
@@ -63,33 +59,28 @@ files:
63
59
  - lib/hiredis.rb
64
60
  - COPYING
65
61
  - Rakefile
66
- has_rdoc: true
67
62
  homepage: http://github.com/pietern/hiredis-rb
68
63
  licenses: []
69
-
70
64
  post_install_message:
71
65
  rdoc_options: []
72
-
73
- require_paths:
66
+ require_paths:
74
67
  - lib
75
- required_ruby_version: !ruby/object:Gem::Requirement
68
+ required_ruby_version: !ruby/object:Gem::Requirement
76
69
  none: false
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: "0"
81
- required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
75
  none: false
83
- requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: "0"
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
87
80
  requirements: []
88
-
89
81
  rubyforge_project:
90
- rubygems_version: 1.6.2
82
+ rubygems_version: 1.8.10
91
83
  signing_key:
92
84
  specification_version: 3
93
85
  summary: Ruby extension that wraps Hiredis (blocking connection and reply parsing)
94
86
  test_files: []
95
-