hiredis 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,57 +1,11 @@
1
1
  require 'rake'
2
- require 'rake/gempackagetask'
3
2
  require 'rake/testtask'
4
-
5
- gem 'rake-compiler', '~> 0.7.1'
6
3
  require "rake/extensiontask"
7
4
 
8
- $:.unshift File.join(File.dirname(__FILE__), 'lib')
9
- require 'hiredis/version'
10
-
11
- GEM = 'hiredis'
12
- GEM_VERSION = Hiredis::VERSION
13
- AUTHORS = ['Pieter Noordhuis']
14
- EMAIL = "pcnoordhuis@gmail.com"
15
- HOMEPAGE = "http://github.com/pietern/hiredis-rb"
16
- SUMMARY = "Ruby extension that wraps Hiredis (blocking connection and reply parsing)"
17
-
18
- spec = Gem::Specification.new do |s|
19
- s.name = GEM
20
- s.version = GEM_VERSION
21
- s.platform = Gem::Platform::RUBY
22
- s.has_rdoc = true
23
- s.extra_rdoc_files = ["COPYING"]
24
- s.summary = SUMMARY
25
- s.description = s.summary
26
- s.authors = AUTHORS
27
- s.email = EMAIL
28
- s.homepage = HOMEPAGE
29
- s.require_path = 'lib'
30
- s.extensions = FileList["ext/**/extconf.rb"]
31
-
32
- ext_files = Dir.glob("ext/**/*.{rb,c,h}")
33
- lib_files = Dir.glob("lib/**/*.rb")
34
- hiredis_files = Dir.glob("vendor/hiredis/*.{c,h}") -
35
- Dir.glob("vendor/hiredis/example*") +
36
- Dir.glob("vendor/hiredis/COPYING") +
37
- Dir.glob("vendor/hiredis/Makefile")
38
- s.files = %w(COPYING Rakefile) + ext_files + lib_files + hiredis_files
39
-
40
- s.add_runtime_dependency "rake-compiler", "~> 0.7.1"
41
- s.add_runtime_dependency "redis", "~> 2.1.1"
42
- end
43
-
44
- desc "create a gemspec file"
45
- task :gemspec do
46
- File.open("#{GEM}.gemspec", "w") do |file|
47
- file.puts spec.to_ruby
48
- end
49
- end
50
-
51
5
  Rake::ExtensionTask.new('hiredis_ext') do |task|
52
6
  # Pass --with-foo-config args to extconf.rb
53
7
  task.config_options = ARGV[1..-1]
54
- task.lib_dir = File.join(*['lib', 'hiredis'])
8
+ task.lib_dir = File.join(*['lib', 'hiredis', 'ext'])
55
9
  end
56
10
 
57
11
  namespace :hiredis do
@@ -28,24 +28,43 @@ static void parent_context_free(redisParentContext *pc) {
28
28
  free(pc);
29
29
  }
30
30
 
31
+ static void parent_context_raise(redisParentContext *pc) {
32
+ int err;
33
+ char errstr[1024];
34
+
35
+ /* Copy error and free context */
36
+ err = pc->context->err;
37
+ snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
38
+ parent_context_try_free(pc);
39
+
40
+ switch(err) {
41
+ case REDIS_ERR_IO:
42
+ /* Raise native Ruby I/O error */
43
+ rb_sys_fail(0);
44
+ break;
45
+ case REDIS_ERR_EOF:
46
+ /* Raise our own EOF error */
47
+ rb_raise(error_eof,"%s",errstr);
48
+ break;
49
+ default:
50
+ /* Raise something else */
51
+ rb_raise(rb_eRuntimeError,"%s",errstr);
52
+ }
53
+ }
54
+
31
55
  static VALUE connection_parent_context_alloc(VALUE klass) {
32
56
  redisParentContext *pc = malloc(sizeof(*pc));
33
57
  pc->context = NULL;
34
58
  return Data_Wrap_Struct(klass, parent_context_mark, parent_context_free, pc);
35
59
  }
36
60
 
37
- static VALUE connection_connect(VALUE self, VALUE _host, VALUE _port) {
61
+ static VALUE connection_generic_connect(VALUE self, redisContext *c) {
38
62
  redisParentContext *pc;
39
- redisContext *c;
40
- char *host = StringValuePtr(_host);
41
- int port = NUM2INT(_port);
42
63
  int err;
43
64
  char errstr[1024];
44
65
 
45
66
  Data_Get_Struct(self,redisParentContext,pc);
46
- parent_context_try_free(pc);
47
67
 
48
- c = redisConnect(host,port);
49
68
  if (c->err) {
50
69
  /* Copy error and free context */
51
70
  err = c->err;
@@ -66,6 +85,79 @@ static VALUE connection_connect(VALUE self, VALUE _host, VALUE _port) {
66
85
  return Qnil;
67
86
  }
68
87
 
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;
93
+ }
94
+
95
+ static VALUE connection_connect(int argc, VALUE *argv, VALUE self) {
96
+ redisParentContext *pc;
97
+ redisContext *c;
98
+ VALUE *_host = NULL;
99
+ VALUE *_port = NULL;
100
+ VALUE *_timeout = NULL;
101
+ char *host;
102
+ int port;
103
+ struct timeval timeout;
104
+
105
+ 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
+ }
114
+
115
+ Data_Get_Struct(self,redisParentContext,pc);
116
+ parent_context_try_free(pc);
117
+
118
+ host = StringValuePtr(*_host);
119
+ port = NUM2INT(*_port);
120
+ if (_timeout != NULL) {
121
+ timeout = __timeout_from_robj(*_timeout);
122
+ c = redisConnectWithTimeout(host,port,timeout);
123
+ } else {
124
+ c = redisConnect(host,port);
125
+ }
126
+
127
+ return connection_generic_connect(self,c);
128
+ }
129
+
130
+ static VALUE connection_connect_unix(int argc, VALUE *argv, VALUE self) {
131
+ redisParentContext *pc;
132
+ redisContext *c;
133
+ VALUE *_path = NULL;
134
+ VALUE *_timeout = NULL;
135
+ char *path;
136
+ struct timeval timeout;
137
+
138
+ 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
+ }
146
+
147
+ Data_Get_Struct(self,redisParentContext,pc);
148
+ parent_context_try_free(pc);
149
+
150
+ path = StringValuePtr(*_path);
151
+ if (_timeout != NULL) {
152
+ timeout = __timeout_from_robj(*_timeout);
153
+ c = redisConnectUnixWithTimeout(path,timeout);
154
+ } else {
155
+ c = redisConnectUnix(path);
156
+ }
157
+
158
+ return connection_generic_connect(self,c);
159
+ }
160
+
69
161
  static VALUE connection_is_connected(VALUE self) {
70
162
  redisParentContext *pc;
71
163
  Data_Get_Struct(self,redisParentContext,pc);
@@ -145,33 +237,13 @@ static int __get_reply(redisParentContext *pc, VALUE *reply) {
145
237
  static VALUE connection_read(VALUE self) {
146
238
  redisParentContext *pc;
147
239
  VALUE reply;
148
- int err;
149
- char errstr[1024];
150
240
 
151
241
  Data_Get_Struct(self,redisParentContext,pc);
152
242
  if (!pc->context)
153
243
  rb_raise(rb_eRuntimeError, "not connected");
154
244
 
155
- if (__get_reply(pc,&reply) == -1) {
156
- /* Copy error and free context */
157
- err = pc->context->err;
158
- snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
159
- parent_context_try_free(pc);
160
-
161
- switch(err) {
162
- case REDIS_ERR_IO:
163
- /* Raise native Ruby I/O error */
164
- rb_sys_fail(0);
165
- break;
166
- case REDIS_ERR_EOF:
167
- /* Raise our own EOF error */
168
- rb_raise(error_eof,"%s",errstr);
169
- break;
170
- default:
171
- /* Raise something else */
172
- rb_raise(rb_eRuntimeError,"%s",errstr);
173
- }
174
- }
245
+ if (__get_reply(pc,&reply) == -1)
246
+ parent_context_raise(pc);
175
247
 
176
248
  return reply;
177
249
  }
@@ -181,17 +253,13 @@ static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
181
253
  int s = NUM2INT(usecs)/1000000;
182
254
  int us = NUM2INT(usecs)-(s*1000000);
183
255
  struct timeval timeout = { s, us };
184
- char errstr[1024];
185
256
 
186
257
  Data_Get_Struct(self,redisParentContext,pc);
187
258
  if (!pc->context)
188
259
  rb_raise(rb_eRuntimeError, "not connected");
189
260
 
190
- if (redisSetTimeout(pc->context,timeout) == REDIS_ERR) {
191
- snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
192
- parent_context_try_free(pc);
193
- rb_raise(rb_eRuntimeError,"%s",errstr);
194
- }
261
+ if (redisSetTimeout(pc->context,timeout) == REDIS_ERR)
262
+ parent_context_raise(pc);
195
263
 
196
264
  return usecs;
197
265
  }
@@ -202,7 +270,8 @@ VALUE error_eof;
202
270
  void InitConnection(VALUE mod) {
203
271
  klass_connection = rb_define_class_under(mod, "Connection", rb_cObject);
204
272
  rb_define_alloc_func(klass_connection, connection_parent_context_alloc);
205
- rb_define_method(klass_connection, "connect", connection_connect, 2);
273
+ rb_define_method(klass_connection, "connect", connection_connect, -1);
274
+ rb_define_method(klass_connection, "connect_unix", connection_connect_unix, -1);
206
275
  rb_define_method(klass_connection, "connected?", connection_is_connected, 0);
207
276
  rb_define_method(klass_connection, "disconnect", connection_disconnect, 0);
208
277
  rb_define_method(klass_connection, "timeout=", connection_set_timeout, 1);
@@ -18,4 +18,4 @@ system("cd #{bundled_hiredis_dir} && make static") if File.directory?(bundled_hi
18
18
 
19
19
  need_header('hiredis.h')
20
20
  need_library('hiredis', 'redisReplyReaderCreate')
21
- create_makefile('hiredis/hiredis_ext')
21
+ create_makefile('hiredis/ext/hiredis_ext')
@@ -4,8 +4,10 @@
4
4
  #include "hiredis_ext.h"
5
5
 
6
6
  VALUE mod_hiredis;
7
+ VALUE mod_ext;
7
8
  void Init_hiredis_ext() {
8
9
  mod_hiredis = rb_define_module("Hiredis");
9
- InitReader(mod_hiredis);
10
- InitConnection(mod_hiredis);
10
+ mod_ext = rb_define_module_under(mod_hiredis,"Ext");
11
+ InitReader(mod_ext);
12
+ InitConnection(mod_ext);
11
13
  }
@@ -10,7 +10,6 @@ extern VALUE mod_hiredis;
10
10
  /* Defined in reader.c */
11
11
  extern redisReplyObjectFunctions redisExtReplyObjectFunctions;
12
12
  extern VALUE klass_reader;
13
- extern ID ivar_hiredis_error; /* ivar used to store error reply ("-ERR message") */
14
13
  extern void InitReader(VALUE module);
15
14
 
16
15
  /* Defined in connection.c */
@@ -6,9 +6,6 @@ static VALUE enc_klass;
6
6
  static ID enc_default_external = 0;
7
7
  static ID str_force_encoding = 0;
8
8
 
9
- /* Singleton method to test if the reply contains an error. */
10
- ID ivar_hiredis_error;
11
-
12
9
  /* Add VALUE to parent when the redisReadTask has a parent.
13
10
  * Note that the parent should always be of type T_ARRAY. */
14
11
  static void *tryParentize(const redisReadTask *task, VALUE v) {
@@ -20,10 +17,6 @@ static void *tryParentize(const redisReadTask *task, VALUE v) {
20
17
  return (void*)v;
21
18
  }
22
19
 
23
- static VALUE object_contains_error(VALUE self) {
24
- return Qtrue;
25
- }
26
-
27
20
  static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
28
21
  VALUE v, enc;
29
22
  v = rb_str_new(str,len);
@@ -36,17 +29,6 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
36
29
 
37
30
  if (task->type == REDIS_REPLY_ERROR) {
38
31
  v = rb_funcall(rb_eRuntimeError,rb_intern("new"),1,v);
39
- rb_ivar_set(v,ivar_hiredis_error,v);
40
-
41
- if (task && task->parent != NULL) {
42
- /* Also make the parent respond to this method. Redis currently
43
- * only emits nested multi bulks of depth 2, so we don't need
44
- * to cascade setting this ivar. Make sure to only set the first
45
- * error reply on the parent. */
46
- VALUE parent = (VALUE)task->parent->obj;
47
- if (!rb_ivar_defined(parent,ivar_hiredis_error))
48
- rb_ivar_set(parent,ivar_hiredis_error,v);
49
- }
50
32
  }
51
33
 
52
34
  return tryParentize(task,v);
@@ -93,7 +75,6 @@ static VALUE reader_allocate(VALUE klass) {
93
75
 
94
76
  static VALUE reader_feed(VALUE klass, VALUE str) {
95
77
  void *reader;
96
- unsigned int size;
97
78
 
98
79
  if (TYPE(str) != T_STRING)
99
80
  rb_raise(rb_eTypeError, "not a string");
@@ -122,7 +103,6 @@ void InitReader(VALUE mod) {
122
103
  rb_define_alloc_func(klass_reader, reader_allocate);
123
104
  rb_define_method(klass_reader, "feed", reader_feed, 1);
124
105
  rb_define_method(klass_reader, "gets", reader_gets, 0);
125
- ivar_hiredis_error = rb_intern("@__hiredis_error");
126
106
 
127
107
  /* If the Encoding class is present, #default_external should be used to
128
108
  * determine the encoding for new strings. The "enc_default_external"
@@ -1,10 +1,2 @@
1
- require 'hiredis/version'
2
- require 'hiredis/connection'
3
-
4
- # Make redis-rb use the Hiredis Connection class
5
- class Redis
6
- Connection = ::Hiredis::Connection
7
- end
8
-
9
- # Load redis
10
- require 'redis'
1
+ require "hiredis/version"
2
+ require "hiredis/connection"
@@ -1,17 +1,10 @@
1
- require 'hiredis/hiredis_ext'
2
-
3
1
  module Hiredis
4
- class Connection
5
- # Raise CONNRESET on EOF
6
- alias :_read :read
7
- def read
8
- reply = _read
9
- error = reply.instance_variable_get(:@__hiredis_error)
10
- raise error if error
11
-
12
- reply
13
- rescue EOFError
14
- raise Errno::ECONNRESET
15
- end
2
+ begin
3
+ require "hiredis/ext/connection"
4
+ Connection = Ext::Connection
5
+ rescue LoadError
6
+ warn "WARNING: could not load hiredis extension, using (slower) pure Ruby implementation."
7
+ require "hiredis/ruby/connection"
8
+ Connection = Ruby::Connection
16
9
  end
17
10
  end
@@ -0,0 +1,16 @@
1
+ require "hiredis/ext/hiredis_ext"
2
+ require "hiredis/version"
3
+
4
+ module Hiredis
5
+ module Ext
6
+ class Connection
7
+ # Raise CONNRESET on EOF
8
+ alias :_read :read
9
+ def read
10
+ _read
11
+ rescue EOFError
12
+ raise Errno::ECONNRESET
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,2 @@
1
+ require "hiredis/ext/hiredis_ext"
2
+ require "hiredis/version"
@@ -1 +1,10 @@
1
- require 'hiredis/hiredis_ext'
1
+ module Hiredis
2
+ begin
3
+ require "hiredis/ext/reader"
4
+ Reader = Ext::Reader
5
+ rescue LoadError
6
+ warn "WARNING: could not load hiredis extension, using (slower) pure Ruby implementation."
7
+ require "hiredis/ruby/reader"
8
+ Reader = Ruby::Reader
9
+ end
10
+ end
@@ -0,0 +1,113 @@
1
+ require "socket"
2
+ require "timeout"
3
+ require "hiredis/ruby/reader"
4
+ require "hiredis/version"
5
+
6
+ module Hiredis
7
+ module Ruby
8
+ class Connection
9
+
10
+ def initialize
11
+ @sock = nil
12
+ end
13
+
14
+ def connected?
15
+ !! @sock
16
+ end
17
+
18
+ def connect(host, port, usecs = 0)
19
+ @reader = ::Hiredis::Ruby::Reader.new
20
+
21
+ begin
22
+ 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
26
+ end
27
+ rescue Timeout::Error
28
+ raise Errno::ETIMEDOUT
29
+ 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
36
+ end
37
+ end
38
+ end
39
+
40
+ def connect_unix(path, usecs = 0)
41
+ @reader = ::Hiredis::Ruby::Reader.new
42
+
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
50
+ end
51
+
52
+ def disconnect
53
+ @sock.close
54
+ rescue
55
+ ensure
56
+ @sock = nil
57
+ end
58
+
59
+ def timeout=(usecs)
60
+ raise "not connected" unless connected?
61
+
62
+ secs = Integer(usecs / 1_000_000)
63
+ usecs = Integer(usecs - (secs * 1_000_000)) # 0 - 999_999
64
+
65
+ optval = [secs, usecs].pack("l_2")
66
+
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
72
+ end
73
+
74
+ COMMAND_DELIMITER = "\r\n".freeze
75
+
76
+ def write(args)
77
+ command = []
78
+ command << "*#{args.size}"
79
+ args.each do |arg|
80
+ arg = arg.to_s
81
+ command << "$#{string_size arg}"
82
+ command << arg
83
+ end
84
+
85
+ @sock.syswrite(command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER)
86
+ end
87
+
88
+ def read
89
+ raise "not connected" unless connected?
90
+
91
+ while (reply = @reader.gets) == false
92
+ @reader.feed @sock.sysread(1024)
93
+ end
94
+
95
+ reply
96
+ rescue EOFError
97
+ raise Errno::ECONNRESET
98
+ end
99
+
100
+ protected
101
+
102
+ if "".respond_to?(:bytesize)
103
+ def string_size(string)
104
+ string.to_s.bytesize
105
+ end
106
+ else
107
+ def string_size(string)
108
+ string.to_s.size
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end