hiredis 0.3.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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
-