rev 0.2.2 → 0.2.3

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