cool.io 1.0.0 → 1.1.0

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