cool.io 1.0.0 → 1.1.0

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 (47) hide show
  1. data/.gitignore +26 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +4 -0
  4. data/{CHANGES → CHANGES.md} +43 -79
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +32 -0
  7. data/{README.markdown → README.md} +0 -4
  8. data/Rakefile +31 -67
  9. data/cool.io.gemspec +25 -133
  10. data/ext/cool.io/extconf.rb +3 -0
  11. data/ext/cool.io/stat_watcher.c +99 -23
  12. data/ext/libev/Changes +24 -0
  13. data/ext/libev/ev.c +78 -32
  14. data/ext/libev/ev.h +11 -8
  15. data/ext/libev/ev_epoll.c +39 -7
  16. data/ext/libev/ev_kqueue.c +5 -5
  17. data/ext/libev/ev_poll.c +5 -5
  18. data/ext/libev/ev_port.c +26 -11
  19. data/ext/libev/ev_select.c +11 -8
  20. data/ext/libev/ev_vars.h +10 -4
  21. data/ext/libev/ev_win32.c +6 -6
  22. data/ext/libev/ev_wrap.h +10 -0
  23. data/lib/cool.io.rb +3 -3
  24. data/lib/cool.io/async_watcher.rb +1 -1
  25. data/lib/cool.io/dns_resolver.rb +14 -12
  26. data/lib/cool.io/dsl.rb +26 -26
  27. data/lib/cool.io/eventmachine.rb +18 -18
  28. data/lib/cool.io/http_client.rb +29 -22
  29. data/lib/cool.io/io.rb +18 -18
  30. data/lib/cool.io/iowatcher.rb +1 -1
  31. data/lib/cool.io/listener.rb +2 -2
  32. data/lib/cool.io/loop.rb +12 -12
  33. data/lib/cool.io/meta.rb +4 -4
  34. data/lib/cool.io/server.rb +3 -3
  35. data/lib/cool.io/socket.rb +36 -28
  36. data/lib/cool.io/timer_watcher.rb +1 -1
  37. data/lib/cool.io/version.rb +5 -0
  38. data/lib/coolio.rb +1 -1
  39. data/spec/stat_watcher_spec.rb +77 -0
  40. metadata +47 -56
  41. data/VERSION +0 -1
  42. data/lib/rev.rb +0 -4
  43. data/spec/possible_tests/schedules_other_threads.rb +0 -48
  44. data/spec/possible_tests/test_on_resolve_failed.rb +0 -9
  45. data/spec/possible_tests/test_resolves.rb +0 -27
  46. data/spec/possible_tests/test_write_during_resolve.rb +0 -27
  47. data/spec/possible_tests/works_straight.rb +0 -71
@@ -40,6 +40,9 @@
40
40
  #define pollidxmax ((loop)->pollidxmax)
41
41
  #define epoll_events ((loop)->epoll_events)
42
42
  #define epoll_eventmax ((loop)->epoll_eventmax)
43
+ #define epoll_eperms ((loop)->epoll_eperms)
44
+ #define epoll_epermcnt ((loop)->epoll_epermcnt)
45
+ #define epoll_epermmax ((loop)->epoll_epermmax)
43
46
  #define kqueue_changes ((loop)->kqueue_changes)
44
47
  #define kqueue_changemax ((loop)->kqueue_changemax)
45
48
  #define kqueue_changecnt ((loop)->kqueue_changecnt)
@@ -82,9 +85,11 @@
82
85
  #define fs_2625 ((loop)->fs_2625)
83
86
  #define fs_hash ((loop)->fs_hash)
84
87
  #define sig_pending ((loop)->sig_pending)
88
+ #define nosigmask ((loop)->nosigmask)
85
89
  #define sigfd ((loop)->sigfd)
86
90
  #define sigfd_w ((loop)->sigfd_w)
87
91
  #define sigfd_set ((loop)->sigfd_set)
92
+ #define origflags ((loop)->origflags)
88
93
  #define loop_count ((loop)->loop_count)
89
94
  #define loop_depth ((loop)->loop_depth)
90
95
  #define userdata ((loop)->userdata)
@@ -132,6 +137,9 @@
132
137
  #undef pollidxmax
133
138
  #undef epoll_events
134
139
  #undef epoll_eventmax
140
+ #undef epoll_eperms
141
+ #undef epoll_epermcnt
142
+ #undef epoll_epermmax
135
143
  #undef kqueue_changes
136
144
  #undef kqueue_changemax
137
145
  #undef kqueue_changecnt
@@ -174,9 +182,11 @@
174
182
  #undef fs_2625
175
183
  #undef fs_hash
176
184
  #undef sig_pending
185
+ #undef nosigmask
177
186
  #undef sigfd
178
187
  #undef sigfd_w
179
188
  #undef sigfd_set
189
+ #undef origflags
180
190
  #undef loop_count
181
191
  #undef loop_depth
182
192
  #undef userdata
@@ -1,12 +1,14 @@
1
1
  #--
2
- # Copyright (C)2007-10 Tony Arcieri
2
+ # Copyright (C)2011 Tony Arcieri
3
3
  # You can redistribute this under the terms of the Ruby license
4
4
  # See file LICENSE for details
5
5
  #++
6
6
 
7
7
  require 'iobuffer'
8
8
 
9
+ require "cool.io/version"
9
10
  require "cool.io_ext"
11
+
10
12
  require "cool.io/loop"
11
13
  require "cool.io/meta"
12
14
  require "cool.io/io"
@@ -21,8 +23,6 @@ require "cool.io/http_client"
21
23
  require "cool.io/dsl"
22
24
 
23
25
  module Coolio
24
- VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip
25
- def self.version; VERSION; end
26
26
  def self.inspect; "Cool.io"; end
27
27
  end
28
28
 
@@ -19,7 +19,7 @@ module Coolio
19
19
  # merely signals an event has occurred for each byte written.
20
20
  @writer.write "\0"
21
21
  end
22
-
22
+
23
23
  # Called whenever a signal is received
24
24
  def on_signal; end
25
25
  event_callback :on_signal
@@ -9,7 +9,7 @@
9
9
  # as best I could with extremely limited knowledge of the DNS format. There's
10
10
  # obviously a ton of stuff it doesn't support (like IPv6 and TCP).
11
11
  #
12
- # If you do know what you're doing with DNS, feel free to improve this!
12
+ # If you do know what you're doing with DNS, feel free to improve this!
13
13
  # A good starting point my be this EventMachine Net::DNS-based asynchronous
14
14
  # resolver:
15
15
  #
@@ -44,10 +44,12 @@ module Coolio
44
44
  # Query /etc/hosts (or the specified hostfile) for the given host
45
45
  def self.hosts(host, hostfile = HOSTS)
46
46
  hosts = {}
47
- File.open(hostfile).each_line do |host_entry|
48
- entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ')
49
- addr = entries.shift
50
- entries.each { |e| hosts[e] ||= addr }
47
+ File.open(hostfile) do |f|
48
+ f.each_line do |host_entry|
49
+ entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ')
50
+ addr = entries.shift
51
+ entries.each { |e| hosts[e] ||= addr }
52
+ end
51
53
  end
52
54
 
53
55
  hosts[host]
@@ -65,10 +67,10 @@ module Coolio
65
67
 
66
68
  @nameservers = nameservers
67
69
  @question = request_question hostname
68
-
70
+
69
71
  @socket = UDPSocket.new
70
72
  @timer = Timeout.new(self)
71
-
73
+
72
74
  super(@socket)
73
75
  end
74
76
 
@@ -110,7 +112,7 @@ module Coolio
110
112
  begin
111
113
  @socket.send request_message, 0
112
114
  rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
113
- end
115
+ end
114
116
  end
115
117
 
116
118
  # Called by the subclass when the DNS response is available
@@ -120,7 +122,7 @@ module Coolio
120
122
  datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
121
123
  rescue Errno::ECONNREFUSED
122
124
  end
123
-
125
+
124
126
  address = response_address datagram rescue nil
125
127
  address ? on_success(address) : on_failure
126
128
  detach
@@ -128,7 +130,7 @@ module Coolio
128
130
 
129
131
  def request_question(hostname)
130
132
  raise ArgumentError, "hostname cannot be nil" if hostname.nil?
131
-
133
+
132
134
  # Query name
133
135
  message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0"
134
136
 
@@ -149,7 +151,7 @@ module Coolio
149
151
  qdcount = 1
150
152
 
151
153
  # No answer, authority, or additional records
152
- ancount = nscount = arcount = 0
154
+ ancount = nscount = arcount = 0
153
155
 
154
156
  message << [qdcount, ancount, nscount, arcount].pack('nnnn')
155
157
  message << @question
@@ -210,7 +212,7 @@ module Coolio
210
212
  return @resolver.__send__(:send_request)
211
213
  rescue Errno::EHOSTUNREACH # if the DNS is toast try again after the timeout occurs again
212
214
  return nil
213
- end
215
+ end
214
216
  end
215
217
  @resolver.__send__(:on_timeout)
216
218
  @resolver.detach
@@ -7,112 +7,112 @@
7
7
  module Coolio
8
8
  # A module we stash all the connections defined by the DSL under
9
9
  module Connections; end
10
-
10
+
11
11
  # A DSL for defining Cool.io connection types and servers
12
12
  module DSL
13
13
  # Define all methods on the metaclass
14
14
  module_function
15
-
15
+
16
16
  # Run the default Cool.io event loop
17
17
  def run
18
18
  Cool.io::Loop.default.run
19
19
  end
20
-
20
+
21
21
  # Connect to the given host and port using the given connection class
22
22
  def connect(host, port, connection_name = nil, *initializer_args, &block)
23
23
  if block_given?
24
24
  initializer_args.unshift connection_name if connection_name
25
-
25
+
26
26
  klass = Class.new Cool.io::TCPSocket
27
27
  connection_builder = ConnectionBuilder.new klass
28
- connection_builder.instance_eval &block
28
+ connection_builder.instance_eval(&block)
29
29
  else
30
30
  raise ArgumentError, "no connection name or block given" unless connection_name
31
31
  klass = self[connection_name]
32
32
  end
33
-
33
+
34
34
  client = klass.connect host, port, *initializer_args
35
35
  client.attach Cool.io::Loop.default
36
36
  client
37
37
  end
38
-
38
+
39
39
  # Create a new Cool.io::TCPServer
40
40
  def server(host, port, connection_name = nil, *initializer_args, &block)
41
41
  if block_given?
42
42
  initializer_args.unshift connection_name if connection_name
43
-
43
+
44
44
  klass = Class.new Cool.io::TCPSocket
45
45
  connection_builder = ConnectionBuilder.new klass
46
- connection_builder.instance_eval &block
46
+ connection_builder.instance_eval(&block)
47
47
  else
48
48
  raise ArgumentError, "no connection name or block given" unless connection_name
49
49
  klass = self[connection_name]
50
50
  end
51
-
51
+
52
52
  server = Cool.io::TCPServer.new host, port, klass, *initializer_args
53
53
  server.attach Cool.io::Loop.default
54
54
  server
55
55
  end
56
-
56
+
57
57
  # Create a new Cool.io::TCPSocket class
58
58
  def connection(name, &block)
59
59
  # Camelize class name
60
60
  class_name = name.to_s.split('_').map { |s| s.capitalize }.join
61
-
61
+
62
62
  connection = Class.new Cool.io::TCPSocket
63
63
  connection_builder = ConnectionBuilder.new connection
64
- connection_builder.instance_eval &block
65
-
64
+ connection_builder.instance_eval(&block)
65
+
66
66
  Coolio::Connections.const_set class_name, connection
67
67
  end
68
-
68
+
69
69
  # Look up a connection class by its name
70
70
  def [](connection_name)
71
71
  class_name = connection_name.to_s.split('_').map { |s| s.capitalize }.join
72
-
72
+
73
73
  begin
74
74
  Coolio::Connections.const_get class_name
75
75
  rescue NameError
76
76
  raise NameError, "No connection type registered for #{connection_name.inspect}"
77
77
  end
78
78
  end
79
-
79
+
80
80
  # Builder for Cool.io::TCPSocket classes
81
81
  class ConnectionBuilder
82
82
  def initialize(klass)
83
83
  @klass = klass
84
84
  end
85
-
85
+
86
86
  # Declare an initialize function
87
87
  def initializer(&action)
88
88
  @klass.send :define_method, :initialize, &action
89
89
  end
90
-
90
+
91
91
  # Declare the on_connect callback
92
92
  def on_connect(&action)
93
93
  @klass.send :define_method, :on_connect, &action
94
94
  end
95
-
95
+
96
96
  # Declare a callback fired if we failed to connect
97
97
  def on_connect_failed(&action)
98
98
  @klass.send :define_method, :on_connect_failed, &action
99
99
  end
100
-
100
+
101
101
  # Declare a callback fired if DNS resolution failed
102
102
  def on_resolve_failed(&action)
103
103
  @klass.send :define_method, :on_resolve_failed, &action
104
104
  end
105
-
105
+
106
106
  # Declare the on_close callback
107
107
  def on_close(&action)
108
108
  @klass.send :define_method, :on_close, &action
109
109
  end
110
-
110
+
111
111
  # Declare the on_read callback
112
112
  def on_read(&action)
113
113
  @klass.send :define_method, :on_read, &action
114
114
  end
115
-
115
+
116
116
  # Declare the on_write_complete callback
117
117
  def on_write_complete(&action)
118
118
  @klass.send :define_method, :on_write_complete, &action
@@ -126,10 +126,10 @@ module Cool
126
126
  module Coolness
127
127
  def cool; Cool::IOThunk; end
128
128
  end
129
-
129
+
130
130
  module IOThunk
131
131
  def self.io; Coolio::DSL; end
132
132
  end
133
133
  end
134
134
 
135
- extend Cool::Coolness
135
+ extend Cool::Coolness
@@ -43,8 +43,8 @@ module EventMachine
43
43
  def add_timer(interval, proc = nil, &block)
44
44
  block ||= proc
45
45
  t = OneShotEMTimer.new(interval, false) # non repeating
46
- t.setup(block)
47
-
46
+ t.setup(block)
47
+
48
48
  # fire 'er off ltodo: do we keep track of these timers in memory?
49
49
  t.attach(Coolio::Loop.default)
50
50
  t
@@ -53,7 +53,7 @@ module EventMachine
53
53
  def cancel_timer(t)
54
54
  # guess there's a case where EM you can say 'cancel' but it's already fired?
55
55
  # kind of odd but it happens
56
- t.detach if t.attached?
56
+ t.detach if t.attached?
57
57
  end
58
58
 
59
59
  def set_comm_inactivity_timeout(*args); end # TODO
@@ -76,7 +76,7 @@ module EventMachine
76
76
  wrapped_child.call_back_to_this(conn) # calls post_init for us
77
77
  yield conn if block_given?
78
78
  end
79
-
79
+
80
80
  # Start a TCP server on the given address and port
81
81
  def start_server(addr, port, handler = Connection, *args, &block)
82
82
  # make sure we're a 'real' class here
@@ -85,11 +85,11 @@ module EventMachine
85
85
  else
86
86
  Class.new( Connection ) {handler and include handler}
87
87
  end
88
-
89
- server = Coolio::TCPServer.new(addr, port, CallsBackToEM, *args) do |wrapped_child|
88
+
89
+ server = Coolio::TCPServer.new(addr, port, CallsBackToEM, *args) do |wrapped_child|
90
90
  conn = klass.new(wrapped_child)
91
91
  conn.heres_your_socket(wrapped_child) # ideally NOT have this :)
92
- wrapped_child.call_back_to_this(conn)
92
+ wrapped_child.call_back_to_this(conn)
93
93
  block.call(conn) if block
94
94
  end
95
95
 
@@ -126,9 +126,9 @@ module EventMachine
126
126
  end
127
127
 
128
128
  def on_connect
129
- # @connection_timer.detach if @connection_timer
129
+ # @connection_timer.detach if @connection_timer
130
130
  # won't need that anymore :) -- with server connecteds we don't have it, anyway
131
-
131
+
132
132
  # TODO should server accepted's call this? They don't currently
133
133
  # [and can't, since on_connect gets called basically in the initializer--needs some code love for that to happen :)
134
134
  @call_back_to_this.connection_completed if @call_back_to_this
@@ -136,10 +136,10 @@ module EventMachine
136
136
 
137
137
  def connection_has_timed_out
138
138
  return if closed?
139
-
139
+
140
140
  # wonder if this works when you're within a half-connected phase.
141
141
  # I think it does. What about TCP state?
142
- close unless closed?
142
+ close unless closed?
143
143
  @call_back_to_this.unbind
144
144
  end
145
145
 
@@ -188,18 +188,18 @@ module EventMachine
188
188
  # initialize *args
189
189
  #end
190
190
  end
191
-
191
+
192
192
  # we will need to call 'their functions' appropriately -- the commented out ones, here
193
- #
193
+ #
194
194
  # Callback fired when connection is created
195
195
  def post_init
196
- # I thought we were 'overriding' EM's existing methods, here.
196
+ # I thought we were 'overriding' EM's existing methods, here.
197
197
  # Huh? Why do we have to define these then?
198
198
  end
199
199
 
200
200
  # Callback fired when connection is closed
201
201
  def unbind; end
202
-
202
+
203
203
  # Callback fired when data is received
204
204
  # def receive_data(data); end
205
205
  def heres_your_socket(instantiated_coolio_socket)
@@ -211,13 +211,13 @@ module EventMachine
211
211
  def send_data(data)
212
212
  @wrapped_coolio.write data
213
213
  end
214
-
214
+
215
215
  # Close the connection, optionally after writing
216
216
  def close_connection(after_writing = false)
217
217
  return close_connection_after_writing if after_writing
218
218
  @wrapped_coolio.close
219
219
  end
220
-
220
+
221
221
  # Close the connection after all data has been written
222
222
  def close_connection_after_writing
223
223
  @wrapped_coolio.output_buffer_size.zero? ? @wrapped_coolio.close : @wrapped_coolio.should_close_after_writing
@@ -231,4 +231,4 @@ module EventMachine
231
231
  end
232
232
 
233
233
  # Shortcut constant
234
- EM = EventMachine
234
+ EM = EventMachine
@@ -29,12 +29,12 @@ module Coolio
29
29
  def content_length
30
30
  Integer(self[HttpClient::CONTENT_LENGTH]) rescue nil
31
31
  end
32
-
32
+
33
33
  # Is the transfer encoding chunked?
34
34
  def chunked_encoding?
35
35
  /chunked/i === self[HttpClient::TRANSFER_ENCODING]
36
36
  end
37
- end
37
+ end
38
38
 
39
39
  class HttpChunkHeader < Hash
40
40
  # When parsing chunked encodings this is set
@@ -56,14 +56,14 @@ module Coolio
56
56
  def escape(s)
57
57
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
58
58
  '%'+$1.unpack('H2'*$1.size).join('%').upcase
59
- }.tr(' ', '+')
59
+ }.tr(' ', '+')
60
60
  end
61
61
 
62
62
  # Unescapes a URI escaped string.
63
63
  def unescape(s)
64
64
  s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
65
65
  [$1.delete('%')].pack('H*')
66
- }
66
+ }
67
67
  end
68
68
 
69
69
  # Map all header keys to a downcased string version
@@ -151,11 +151,11 @@ module Coolio
151
151
  @chunk_header = HttpChunkHeader.new
152
152
  end
153
153
 
154
- # Send an HTTP request and consume the response.
154
+ # Send an HTTP request and consume the response.
155
155
  # Supports the following options:
156
156
  #
157
157
  # head: {Key: Value}
158
- # Specify an HTTP header, e.g. {'Connection': 'close'}
158
+ # Specify an HTTP header, e.g. {'Connection': 'close'}
159
159
  #
160
160
  # query: {Key: Value}
161
161
  # Specify query string parameters (auto-escaped)
@@ -176,7 +176,7 @@ module Coolio
176
176
  return unless @connected
177
177
  send_request
178
178
  end
179
-
179
+
180
180
  # Enable the HttpClient if it has been disabled
181
181
  def enable
182
182
  super
@@ -195,7 +195,14 @@ module Coolio
195
195
 
196
196
  # Called when the request has completed
197
197
  def on_request_complete
198
- close
198
+ @state == :finished ? close : @state = :finished
199
+ end
200
+
201
+ # called by close
202
+ def on_close
203
+ if @state != :finished and @state == :body
204
+ on_request_complete
205
+ end
199
206
  end
200
207
 
201
208
  # Called when an error occurs dispatching the request
@@ -211,7 +218,7 @@ module Coolio
211
218
  #
212
219
  # Coolio callbacks
213
220
  #
214
-
221
+
215
222
  def on_connect
216
223
  @connected = true
217
224
  send_request if @method and @path
@@ -260,8 +267,8 @@ module Coolio
260
267
 
261
268
  def send_request_body
262
269
  write @options[:body] if @options[:body]
263
- end
264
-
270
+ end
271
+
265
272
  #
266
273
  # Response processing
267
274
  #
@@ -289,21 +296,21 @@ module Coolio
289
296
 
290
297
  def parse_header(header)
291
298
  return false if @data.empty?
292
-
299
+
293
300
  begin
294
301
  @parser_nbytes = @parser.execute(header, @data.to_str, @parser_nbytes)
295
302
  rescue Coolio::HttpClientParserError
296
303
  on_error "invalid HTTP format, parsing fails"
297
304
  @state = :invalid
298
305
  end
299
-
306
+
300
307
  return false unless @parser.finished?
301
308
 
302
309
  # Clear parsed data from the buffer
303
310
  @data.read(@parser_nbytes)
304
311
  @parser.reset
305
312
  @parser_nbytes = 0
306
-
313
+
307
314
  true
308
315
  end
309
316
 
@@ -324,7 +331,7 @@ module Coolio
324
331
  @state = :body
325
332
  @bytes_remaining = @response_header.content_length
326
333
  end
327
-
334
+
328
335
  true
329
336
  end
330
337
 
@@ -334,7 +341,7 @@ module Coolio
334
341
  @bytes_remaining = @chunk_header.chunk_size
335
342
  @chunk_header = HttpChunkHeader.new
336
343
 
337
- @state = @bytes_remaining > 0 ? :chunk_body : :response_footer
344
+ @state = @bytes_remaining > 0 ? :chunk_body : :response_footer
338
345
  true
339
346
  end
340
347
 
@@ -347,8 +354,8 @@ module Coolio
347
354
 
348
355
  on_body_data @data.read(@bytes_remaining)
349
356
  @bytes_remaining = 0
350
-
351
- @state = :chunk_footer
357
+
358
+ @state = :chunk_footer
352
359
  true
353
360
  end
354
361
 
@@ -361,13 +368,13 @@ module Coolio
361
368
  on_error "non-CRLF chunk footer"
362
369
  @state = :invalid
363
370
  end
364
-
371
+
365
372
  true
366
373
  end
367
374
 
368
375
  def process_response_footer
369
376
  return false if @data.size < 2
370
-
377
+
371
378
  if @data.read(2) == CRLF
372
379
  if @data.empty?
373
380
  on_request_complete
@@ -380,7 +387,7 @@ module Coolio
380
387
  on_error "non-CRLF response footer"
381
388
  @state = :invalid
382
389
  end
383
-
390
+
384
391
  false
385
392
  end
386
393
 
@@ -389,7 +396,7 @@ module Coolio
389
396
  on_body_data @data.read
390
397
  return false
391
398
  end
392
-
399
+
393
400
  if @bytes_remaining.zero?
394
401
  on_request_complete
395
402
  @state = :finished