rev 0.2.2 → 0.2.3

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.
Files changed (59) hide show
  1. data/CHANGES +11 -0
  2. data/README +10 -3
  3. data/Rakefile +2 -2
  4. data/examples/echo_client.rb +35 -0
  5. data/examples/google.rb +8 -0
  6. data/examples/httpclient.rb +35 -0
  7. data/ext/http11_client/Makefile +149 -0
  8. data/ext/http11_client/http11_client.bundle +0 -0
  9. data/ext/http11_client/http11_client.o +0 -0
  10. data/ext/http11_client/http11_parser.o +0 -0
  11. data/ext/http11_client/mkmf.log +12 -0
  12. data/ext/libev/Changes +114 -1
  13. data/ext/libev/ev.c +212 -97
  14. data/ext/libev/ev.h +13 -7
  15. data/ext/libev/ev_epoll.c +44 -11
  16. data/ext/libev/ev_kqueue.c +2 -2
  17. data/ext/libev/ev_poll.c +3 -1
  18. data/ext/libev/ev_port.c +4 -4
  19. data/ext/libev/ev_select.c +58 -19
  20. data/ext/libev/ev_vars.h +5 -1
  21. data/ext/libev/ev_win32.c +32 -3
  22. data/ext/libev/ev_wrap.h +4 -0
  23. data/ext/libev/test_libev_win32.c +123 -0
  24. data/ext/libev/update_ev_wrap +0 -0
  25. data/ext/rev/Makefile +149 -0
  26. data/ext/rev/ev_wrap.h +8 -0
  27. data/ext/rev/extconf.rb +17 -0
  28. data/ext/rev/libev.c +8 -0
  29. data/ext/rev/libev.o +0 -0
  30. data/ext/rev/mkmf.log +221 -0
  31. data/ext/rev/rev.h +8 -2
  32. data/ext/rev/rev_buffer.c +2 -3
  33. data/ext/rev/rev_buffer.o +0 -0
  34. data/ext/rev/rev_ext.bundle +0 -0
  35. data/ext/rev/rev_ext.c +4 -3
  36. data/ext/rev/rev_ext.o +0 -0
  37. data/ext/rev/rev_io_watcher.c +1 -2
  38. data/ext/rev/rev_io_watcher.o +0 -0
  39. data/ext/rev/rev_loop.c +4 -4
  40. data/ext/rev/rev_loop.o +0 -0
  41. data/ext/rev/rev_ssl.o +0 -0
  42. data/ext/rev/rev_timer_watcher.c +1 -2
  43. data/ext/rev/rev_timer_watcher.o +0 -0
  44. data/ext/rev/rev_utils.c +14 -0
  45. data/ext/rev/rev_utils.o +0 -0
  46. data/ext/rev/rev_watcher.c +7 -6
  47. data/ext/rev/rev_watcher.o +0 -0
  48. data/lib/http11_client.bundle +0 -0
  49. data/lib/rev.rb +1 -1
  50. data/lib/rev/dns_resolver.rb +29 -9
  51. data/lib/rev/io.rb +6 -4
  52. data/lib/rev/listener.rb +5 -1
  53. data/lib/rev/loop.rb +8 -4
  54. data/lib/rev/server.rb +3 -2
  55. data/lib/rev/socket.rb +14 -5
  56. data/lib/rev_ext.bundle +0 -0
  57. data/lib/revem.rb +210 -0
  58. data/rev.gemspec +2 -2
  59. metadata +29 -3
@@ -10,10 +10,16 @@
10
10
  #include "ruby.h"
11
11
  #include "rubyio.h"
12
12
 
13
- #if HAVE_RB_IO_T
14
- #define FPTR_TO_FD(fptr) fptr->fd
13
+ #ifdef GetReadFile
14
+ #define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
15
15
  #else
16
+
17
+ #if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
16
18
  #define FPTR_TO_FD(fptr) fileno(fptr->f)
19
+ #else
20
+ #define FPTR_TO_FD(fptr) fptr->fd
21
+ #endif
22
+
17
23
  #endif
18
24
 
19
25
  struct Rev_Event
@@ -7,8 +7,7 @@
7
7
  #include "ruby.h"
8
8
  #include "rubyio.h"
9
9
 
10
- #define EV_STANDALONE 1
11
- #include "../libev/ev.h"
10
+ #include "ev_wrap.h"
12
11
 
13
12
  #include "rev.h"
14
13
 
@@ -627,4 +626,4 @@ static int buffer_read_from(struct buffer *buf, int fd)
627
626
  } while(bytes_read == nbytes);
628
627
 
629
628
  return total_bytes_read;
630
- }
629
+ }
Binary file
Binary file
@@ -4,10 +4,11 @@
4
4
  * See LICENSE for details
5
5
  */
6
6
 
7
- #include "ruby.h"
8
7
 
9
- #define EV_STANDALONE 1
10
- #include "../libev/ev.c"
8
+ #include "ruby.h" // allow for an error "OpenFile redeclared" to not exist, somehow.
9
+
10
+ #include "ev_wrap.h"
11
+
11
12
 
12
13
  #include "rev.h"
13
14
 
Binary file
@@ -7,8 +7,7 @@
7
7
  #include "ruby.h"
8
8
  #include "rubyio.h"
9
9
 
10
- #define EV_STANDALONE 1
11
- #include "../libev/ev.h"
10
+ #include "ev_wrap.h"
12
11
 
13
12
  #include "rev.h"
14
13
  #include "rev_watcher.h"
Binary file
@@ -8,8 +8,7 @@
8
8
  #include "ruby.h"
9
9
  #include "rubysig.h"
10
10
 
11
- #define EV_STANDALONE 1
12
- #include "../libev/ev.h"
11
+ #include "ev_wrap.h"
13
12
 
14
13
  #include "rev.h"
15
14
 
@@ -208,7 +207,7 @@ static VALUE Rev_Loop_ev_loop_oneshot_blocking(void *ptr)
208
207
  static void Rev_Loop_ev_loop_oneshot(struct Rev_Loop *loop_data)
209
208
  {
210
209
  /* Use Ruby 1.9's rb_thread_blocking_region call to make a blocking system call */
211
- rb_thread_blocking_region(Rev_Loop_ev_loop_oneshot_blocking, loop_data, RB_UBF_DFL, 0);
210
+ rb_thread_blocking_region(Rev_Loop_ev_loop_oneshot_blocking, loop_data, RUBY_UBF_IO, 0);
212
211
  }
213
212
  #endif
214
213
 
@@ -220,6 +219,7 @@ static void Rev_Loop_ev_loop_oneshot(struct Rev_Loop *loop_data)
220
219
  /* Stub for scheduler's ev_timer callback */
221
220
  static void timer_callback(struct ev_loop *ev_loop, struct ev_timer *timer, int revents)
222
221
  {
222
+ ev_timer_again (ev_loop, timer);
223
223
  }
224
224
 
225
225
  /* Run the event loop, calling rb_thread_schedule every 10ms */
@@ -284,4 +284,4 @@ static void Rev_Loop_dispatch_events(struct Rev_Loop *loop_data)
284
284
  Data_Get_Struct(loop_data->eventbuf[i].watcher, struct Rev_Watcher, watcher_data);
285
285
  watcher_data->dispatch_callback(loop_data->eventbuf[i].watcher, loop_data->eventbuf[i].revents);
286
286
  }
287
- }
287
+ }
Binary file
Binary file
@@ -6,8 +6,7 @@
6
6
 
7
7
  #include "ruby.h"
8
8
 
9
- #define EV_STANDALONE 1
10
- #include "../libev/ev.h"
9
+ #include "ev_wrap.h"
11
10
 
12
11
  #include "rev.h"
13
12
  #include "rev_watcher.h"
@@ -6,7 +6,9 @@
6
6
 
7
7
  #include "ruby.h"
8
8
 
9
+ #ifdef HAVE_SYS_RESOURCE_H
9
10
  #include <sys/resource.h>
11
+ #endif
10
12
 
11
13
  #ifdef HAVE_SYS_SYSCTL_H
12
14
  #include <sys/param.h>
@@ -80,12 +82,18 @@ static VALUE Rev_Utils_ncpus(VALUE self)
80
82
  */
81
83
  static VALUE Rev_Utils_maxfds(VALUE self)
82
84
  {
85
+ #ifdef HAVE_SYS_RESOURCE_H
83
86
  struct rlimit rlim;
84
87
 
85
88
  if(getrlimit(RLIMIT_NOFILE, &rlim) < 0)
86
89
  rb_sys_fail("getrlimit");
87
90
 
88
91
  return INT2NUM(rlim.rlim_cur);
92
+ #endif
93
+
94
+ #ifndef HAVE_SYS_RESOURCE_H
95
+ rb_raise(rb_eRuntimeError, "operation not supported");
96
+ #endif
89
97
  }
90
98
 
91
99
  /**
@@ -97,6 +105,7 @@ static VALUE Rev_Utils_maxfds(VALUE self)
97
105
  */
98
106
  static VALUE Rev_Utils_setmaxfds(VALUE self, VALUE max)
99
107
  {
108
+ #ifdef HAVE_SYS_RESOURCE_H
100
109
  struct rlimit rlim;
101
110
 
102
111
  rlim.rlim_cur = NUM2INT(max);
@@ -105,4 +114,9 @@ static VALUE Rev_Utils_setmaxfds(VALUE self, VALUE max)
105
114
  rb_sys_fail("setrlimit");
106
115
 
107
116
  return max;
117
+ #endif
118
+
119
+ #ifndef HAVE_SYS_RESOURCE_H
120
+ rb_raise(rb_eRuntimeError, "operation not supported");
121
+ #endif
108
122
  }
Binary file
@@ -6,8 +6,7 @@
6
6
 
7
7
  #include "ruby.h"
8
8
 
9
- #define EV_STANDALONE 1
10
- #include "../libev/ev.h"
9
+ #include "ev_wrap.h"
11
10
 
12
11
  #include "rev.h"
13
12
 
@@ -99,7 +98,8 @@ static VALUE Rev_Watcher_attach(VALUE self, VALUE loop)
99
98
  loop_watchers = rb_iv_get(loop, "@watchers");
100
99
 
101
100
  if(loop_watchers == Qnil) {
102
- loop_watchers = rb_ary_new();
101
+ /* we should never get here */
102
+ loop_watchers = rb_hash_new();
103
103
  rb_iv_set(loop, "@watchers", loop_watchers);
104
104
  }
105
105
 
@@ -108,7 +108,7 @@ static VALUE Rev_Watcher_attach(VALUE self, VALUE loop)
108
108
  * with a loop (and also lets you see within Ruby which watchers are
109
109
  * associated with a given loop), but isn't really necessary for any
110
110
  * other reason */
111
- rb_ary_push(loop_watchers, self);
111
+ rb_hash_aset(loop_watchers, self, Qtrue);
112
112
 
113
113
  active_watchers = rb_iv_get(loop, "@active_watchers");
114
114
  if(active_watchers == Qnil)
@@ -142,8 +142,9 @@ static VALUE Rev_Watcher_detach(VALUE self)
142
142
 
143
143
  /* Remove us from the loop's array of active watchers. This likely
144
144
  * has negative performance and scalability characteristics as this
145
- * isn't an O(1) operation. Hopefully there's a better way... */
146
- rb_ary_delete(loop_watchers, self);
145
+ * isn't an O(1) operation. Hopefully there's a better way...
146
+ * Trying a hash for now... */
147
+ rb_hash_delete(loop_watchers, self);
147
148
 
148
149
  if(watcher_data->enabled) {
149
150
  rb_iv_set(
Binary file
Binary file
data/lib/rev.rb CHANGED
@@ -24,6 +24,6 @@ require File.dirname(__FILE__) + '/rev/server'
24
24
  require File.dirname(__FILE__) + '/rev/http_client'
25
25
 
26
26
  module Rev
27
- Rev::VERSION = '0.2.2' unless defined? Rev::VERSION
27
+ Rev::VERSION = '0.2.3' unless defined? Rev::VERSION
28
28
  def self.version() VERSION end
29
29
  end
@@ -11,7 +11,6 @@
11
11
  #
12
12
  # If you do know what you're doing with DNS, feel free to improve this!
13
13
  #++
14
-
15
14
  module Rev
16
15
  # A non-blocking DNS resolver. It provides interfaces for querying both
17
16
  # /etc/hosts and nameserves listed in /etc/resolv.conf, or nameservers of
@@ -25,12 +24,16 @@ module Rev
25
24
  # automatically detach themselves from the event loop and cannot be used
26
25
  # again.
27
26
  class DNSResolver < IOWatcher
27
+ #--
28
+ # TODO check if it's caching right
28
29
  RESOLV_CONF = '/etc/resolv.conf'
29
30
  HOSTS = '/etc/hosts'
30
31
  DNS_PORT = 53
31
32
  DATAGRAM_SIZE = 512
32
33
  TIMEOUT = 3 # Retry timeout for each datagram sent
33
34
  RETRIES = 4 # Number of retries to attempt
35
+ # so currently total is 12s before it will err due to timeouts
36
+ # if it errs due to inability to reach the DNS server [Errno::EHOSTUNREACH], same
34
37
 
35
38
  # Query /etc/hosts (or the specified hostfile) for the given host
36
39
  def self.hosts(host, hostfile = HOSTS)
@@ -50,8 +53,8 @@ module Rev
50
53
  # use nameservers listed in /etc/resolv.conf
51
54
  def initialize(hostname, *nameservers)
52
55
  if nameservers.empty?
53
- nameservers = File.read(RESOLV_CONF).scan(/^\s*nameserver\s+([0-9.:]+)/).flatten
54
- raise RuntimeError, "no nameservers found in #{RESOLV_CONF}" if nameservers.empty?
56
+ nameservers = File.read(RESOLV_CONF).scan(/^\s*nameserver\s+([0-9.:]+)/).flatten # TODO could optimize this to just read once
57
+ raise RuntimeError, "no nameservers found in #{RESOLV_CONF}" if nameservers.empty? # TODO just call resolve_failed, not raise [also handle Errno::ENOENT)]
55
58
  end
56
59
 
57
60
  @nameservers = nameservers
@@ -84,8 +87,10 @@ module Rev
84
87
  def on_failure; end
85
88
  event_callback :on_failure
86
89
 
87
- # Called if we don't receive a response, defaults to on_failure
88
- alias_method :on_timeout, :on_failure
90
+ # Called if we don't receive a response, defaults to calling on_failure
91
+ def on_timeout
92
+ on_failure
93
+ end
89
94
 
90
95
  #########
91
96
  protected
@@ -93,13 +98,23 @@ module Rev
93
98
 
94
99
  # Send a request to the DNS server
95
100
  def send_request
101
+ nameserver = @nameservers.shift
102
+ @nameservers << nameserver # rotate them
96
103
  @socket.connect @nameservers.first, DNS_PORT
97
- @socket.send request_message, 0
104
+ begin
105
+ @socket.send request_message, 0
106
+ rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
107
+
108
+ end
98
109
  end
99
110
 
100
111
  # Called by the subclass when the DNS response is available
101
112
  def on_readable
102
- datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
113
+ datagram = nil
114
+ begin
115
+ datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
116
+ rescue Errno::ECONNREFUSED
117
+ end
103
118
  address = response_address datagram rescue nil
104
119
  address ? on_success(address) : on_failure
105
120
  detach
@@ -184,8 +199,13 @@ module Rev
184
199
 
185
200
  def on_timer
186
201
  @attempts += 1
187
- return @resolver.__send__(:send_request) if @attempts <= RETRIES
188
-
202
+ if @attempts <= RETRIES
203
+ begin
204
+ return @resolver.__send__(:send_request)
205
+ rescue Errno::EHOSTUNREACH # if the DNS is toast try again after the timeout occurs again
206
+ return nil
207
+ end
208
+ end
189
209
  @resolver.__send__(:on_timeout)
190
210
  @resolver.detach
191
211
  end
@@ -21,7 +21,7 @@ module Rev
21
21
 
22
22
  def initialize(io)
23
23
  @_io = io
24
- @_write_buffer = Rev::Buffer.new
24
+ @_write_buffer ||= Rev::Buffer.new
25
25
  @_read_watcher = Watcher.new(io, self, :r)
26
26
  @_write_watcher = Watcher.new(io, self, :w)
27
27
  end
@@ -31,10 +31,10 @@ module Rev
31
31
  #
32
32
 
33
33
  # Attach to the event loop
34
- def attach(loop); @_read_watcher.attach loop; self; end
34
+ def attach(loop); @_read_watcher.attach loop; schedule_write if !@_write_buffer.empty?; self; end
35
35
 
36
36
  # Detach from the event loop
37
- def detach; @_read_watcher.detach; self; end
37
+ def detach; @_read_watcher.detach; self; end # TODO should these detect write buffers, as well?
38
38
 
39
39
  # Enable the watcher
40
40
  def enable; @_read_watcher.enable; self; end
@@ -128,6 +128,8 @@ module Rev
128
128
 
129
129
  # Schedule a write to be performed when the IO object becomes writable
130
130
  def schedule_write
131
+ return unless @_io # this would mean 'we are still pre DNS here'
132
+ return unless attached? # this would mean 'currently unattached' -- ie still pre DNS, or just plain not attached, which is ok
131
133
  begin
132
134
  enable_write_watcher
133
135
  rescue IOError
@@ -162,4 +164,4 @@ module Rev
162
164
  def on_writable; @rev_io.__send__(:on_writable); end
163
165
  end
164
166
  end
165
- end
167
+ end
@@ -32,7 +32,11 @@ module Rev
32
32
 
33
33
  # Rev callback for handling new connections
34
34
  def on_readable
35
- on_connection @listen_socket.accept_nonblock
35
+ begin
36
+ on_connection @listen_socket.accept_nonblock
37
+ rescue Errno::EAGAIN
38
+ STDERR.puts "warning: listener socket spuriously readable"
39
+ end
36
40
  end
37
41
  end
38
42
 
@@ -15,10 +15,8 @@ end
15
15
 
16
16
  module Rev
17
17
  class Loop
18
- attr_reader :watchers
19
-
20
18
  # In Ruby 1.9 we want a Rev::Loop per thread, but Ruby 1.8 is unithreaded
21
- if RUBY_VERSION.gsub('.', '').to_i >= 190
19
+ if RUBY_VERSION >= "1.9.0"
22
20
  # Retrieve the default event loop for the current thread
23
21
  def self.default
24
22
  Thread.current._rev_loop
@@ -49,7 +47,7 @@ module Rev
49
47
  # :port (Solaris 10)
50
48
  #
51
49
  def initialize(options = {})
52
- @watchers = []
50
+ @watchers = {}
53
51
  @active_watchers = 0
54
52
 
55
53
  flags = 0
@@ -97,6 +95,7 @@ module Rev
97
95
  while @running and not @active_watchers.zero?
98
96
  run_once
99
97
  end
98
+ @running = false
100
99
  end
101
100
 
102
101
  # Stop the event loop if it's running
@@ -110,6 +109,11 @@ module Rev
110
109
  @active_watchers > 0
111
110
  end
112
111
 
112
+ # All watchers attached to the current loop
113
+ def watchers
114
+ @watchers.keys
115
+ end
116
+
113
117
  #######
114
118
  private
115
119
  #######
@@ -39,7 +39,8 @@ module Rev
39
39
  end
40
40
 
41
41
  # TCP server class. Listens on the specified host and port and creates
42
- # new connection objects of the given class.
42
+ # new connection objects of the given class. This is the most common server class.
43
+ # Note that the new connection objects will be bound by default to the same event loop that the server is attached to.
43
44
  class TCPServer < Server
44
45
  def initialize(host, port, klass = TCPSocket, *args, &block)
45
46
  listen_socket = ::TCPServer.new(host, port)
@@ -55,4 +56,4 @@ module Rev
55
56
  super(::UNIXServer.new(*args), klass, *args, &block)
56
57
  end
57
58
  end
58
- end
59
+ end
@@ -10,6 +10,7 @@ require 'resolv'
10
10
  module Rev
11
11
  class Socket < IO
12
12
  def self.connect(socket, *args)
13
+
13
14
  new(socket, *args).instance_eval do
14
15
  @_connector = Connector.new(self, socket)
15
16
  self
@@ -38,8 +39,10 @@ module Rev
38
39
  event_callback :on_connect_failed
39
40
 
40
41
  # Called if a hostname failed to resolve when connecting
41
- # Defaults to being aliased to on_connect_failed
42
- alias_method :on_resolve_failed, :on_connect_failed
42
+ # Defaults to calling on_connect_failed
43
+ def on_resolve_failed
44
+ on_connect_failed
45
+ end
43
46
 
44
47
  #########
45
48
  protected
@@ -58,6 +61,9 @@ module Rev
58
61
  if connect_successful?
59
62
  @rev_socket.instance_eval { @_connector = nil }
60
63
  @rev_socket.attach(evl)
64
+ @ruby_socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, [1].pack("l"))
65
+ @ruby_socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
66
+
61
67
  @rev_socket.__send__(:on_connect)
62
68
  else
63
69
  @rev_socket.instance_eval { @_failed = true }
@@ -93,6 +99,8 @@ module Rev
93
99
  end
94
100
 
95
101
  # Perform a non-blocking connect to the given host and port
102
+ # see examples/echo_client.rb
103
+ # addr is a string, can be an IP address or a hostname.
96
104
  def self.connect(addr, port, *args)
97
105
  family = nil
98
106
 
@@ -103,11 +111,11 @@ module Rev
103
111
  end
104
112
 
105
113
  if family
106
- return super(TCPConnectSocket.new(family, addr, port), *args)
114
+ return super(TCPConnectSocket.new(family, addr, port), *args) # this creates a 'real' write buffer so we're ok there with regards to already having a write buffer from the get go
107
115
  end
108
116
 
109
117
  if host = Rev::DNSResolver.hosts(addr)
110
- return connect(host, port, *args)
118
+ return connect(host, port, *args) # calls this same function
111
119
  end
112
120
 
113
121
  precreate(addr, port, *args)
@@ -115,6 +123,7 @@ module Rev
115
123
 
116
124
  # Called by precreate during asyncronous DNS resolution
117
125
  def preinitialize(addr, port, *args)
126
+ @_write_buffer = Rev::Buffer.new # allow for writing BEFORE the DNS has resolved
118
127
  @remote_host, @remote_addr, @remote_port = addr, addr, port
119
128
  @_resolver = TCPConnectResolver.new(self, addr, port, *args)
120
129
  end
@@ -141,7 +150,7 @@ module Rev
141
150
 
142
151
  class TCPConnectSocket < ::Socket
143
152
  def initialize(family, addr, port, host = addr)
144
- @host, @addr, @port = host, addr, port
153
+ @host, addr, @port = host, addr, port
145
154
  @address_family = nil
146
155
 
147
156
  @socket = super(family, ::Socket::SOCK_STREAM, 0)