eventmachine 0.12.10 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +1 -0
  3. data/README +1 -2
  4. data/Rakefile +4 -76
  5. data/docs/DEFERRABLES +183 -70
  6. data/docs/KEYBOARD +15 -11
  7. data/docs/LIGHTWEIGHT_CONCURRENCY +84 -24
  8. data/docs/SMTP +3 -1
  9. data/docs/SPAWNED_PROCESSES +84 -25
  10. data/eventmachine.gemspec +19 -26
  11. data/examples/ex_tick_loop_array.rb +15 -0
  12. data/examples/ex_tick_loop_counter.rb +32 -0
  13. data/ext/binder.cpp +0 -1
  14. data/ext/cmain.cpp +36 -11
  15. data/ext/cplusplus.cpp +1 -1
  16. data/ext/ed.cpp +104 -113
  17. data/ext/ed.h +24 -30
  18. data/ext/em.cpp +347 -248
  19. data/ext/em.h +23 -16
  20. data/ext/eventmachine.h +5 -3
  21. data/ext/extconf.rb +5 -3
  22. data/ext/fastfilereader/extconf.rb +5 -3
  23. data/ext/fastfilereader/mapper.cpp +1 -1
  24. data/ext/kb.cpp +1 -3
  25. data/ext/pipe.cpp +9 -11
  26. data/ext/project.h +12 -4
  27. data/ext/rubymain.cpp +138 -89
  28. data/java/src/com/rubyeventmachine/EmReactor.java +1 -0
  29. data/lib/em/channel.rb +1 -1
  30. data/lib/em/connection.rb +6 -1
  31. data/lib/em/deferrable.rb +16 -2
  32. data/lib/em/iterator.rb +270 -0
  33. data/lib/em/protocols.rb +1 -1
  34. data/lib/em/protocols/httpclient.rb +5 -0
  35. data/lib/em/protocols/line_protocol.rb +28 -0
  36. data/lib/em/protocols/smtpserver.rb +101 -8
  37. data/lib/em/protocols/stomp.rb +1 -1
  38. data/lib/{pr_eventmachine.rb → em/pure_ruby.rb} +1 -11
  39. data/lib/em/queue.rb +1 -0
  40. data/lib/em/streamer.rb +1 -1
  41. data/lib/em/tick_loop.rb +85 -0
  42. data/lib/em/timers.rb +2 -1
  43. data/lib/em/version.rb +1 -1
  44. data/lib/eventmachine.rb +38 -84
  45. data/lib/jeventmachine.rb +1 -0
  46. data/tests/test_attach.rb +13 -3
  47. data/tests/test_basic.rb +60 -95
  48. data/tests/test_channel.rb +3 -2
  49. data/tests/test_defer.rb +14 -12
  50. data/tests/test_deferrable.rb +35 -0
  51. data/tests/test_file_watch.rb +1 -1
  52. data/tests/test_futures.rb +1 -1
  53. data/tests/test_hc.rb +40 -68
  54. data/tests/test_httpclient.rb +15 -6
  55. data/tests/test_httpclient2.rb +3 -2
  56. data/tests/test_inactivity_timeout.rb +3 -3
  57. data/tests/test_ltp.rb +13 -5
  58. data/tests/test_next_tick.rb +1 -1
  59. data/tests/test_pending_connect_timeout.rb +2 -2
  60. data/tests/test_process_watch.rb +36 -34
  61. data/tests/test_proxy_connection.rb +52 -0
  62. data/tests/test_pure.rb +10 -1
  63. data/tests/test_sasl.rb +1 -1
  64. data/tests/test_send_file.rb +16 -7
  65. data/tests/test_servers.rb +1 -1
  66. data/tests/test_tick_loop.rb +59 -0
  67. data/tests/test_timers.rb +13 -15
  68. metadata +45 -17
  69. data/web/whatis +0 -7
@@ -48,6 +48,7 @@ public class EmReactor {
48
48
  public final int EM_SSL_HANDSHAKE_COMPLETED = 108;
49
49
  public final int EM_SSL_VERIFY = 109;
50
50
  public final int EM_PROXY_TARGET_UNBOUND = 110;
51
+ public final int EM_PROXY_COMPLETED = 111;
51
52
 
52
53
  private Selector mySelector;
53
54
  private TreeMap<Long, ArrayList<Long>> Timers;
@@ -35,7 +35,7 @@ module EventMachine
35
35
  # Add items to the channel, which are pushed out to all subscribers.
36
36
  def push(*items)
37
37
  items = items.dup
38
- EM.schedule { @subs.values.each { |s| items.each { |i| s.call i } } }
38
+ EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } }
39
39
  end
40
40
  alias << push
41
41
 
@@ -36,7 +36,7 @@ module EventMachine
36
36
  allocate.instance_eval do
37
37
  # Store signature
38
38
  @signature = sig
39
- associate_callback_target sig
39
+ # associate_callback_target sig
40
40
 
41
41
  # Call a superclass's #initialize if it has one
42
42
  initialize(*args)
@@ -134,6 +134,11 @@ module EventMachine
134
134
  def proxy_target_unbound
135
135
  end
136
136
 
137
+ # EventMachine::Connection#proxy_completed is called when the reactor finished proxying all
138
+ # of the requested bytes.
139
+ def proxy_completed
140
+ end
141
+
137
142
  # EventMachine::Connection#proxy_incoming_to is called only by user code. It sets up
138
143
  # a low-level proxy relay for all data inbound for this connection, to the connection given
139
144
  # as the argument. This is essentially just a helper method for enable_proxy.
@@ -51,6 +51,13 @@ module EventMachine
51
51
  end
52
52
  end
53
53
 
54
+ # Cancels an outstanding callback to &block if any. Undoes the action of #callback.
55
+ #
56
+ def cancel_callback block
57
+ @callbacks ||= []
58
+ @callbacks.delete block
59
+ end
60
+
54
61
  # Specify a block to be executed if and when the Deferrable object receives
55
62
  # a status of :failed. See #set_deferred_status for more information.
56
63
  #--
@@ -69,6 +76,13 @@ module EventMachine
69
76
  end
70
77
  end
71
78
 
79
+ # Cancels an outstanding errback to &block if any. Undoes the action of #errback.
80
+ #
81
+ def cancel_errback block
82
+ @errbacks ||= []
83
+ @errbacks.delete block
84
+ end
85
+
72
86
  # Sets the "disposition" (status) of the Deferrable object. See also the large set of
73
87
  # sugarings for this method.
74
88
  # Note that if you call this method without arguments,
@@ -150,10 +164,10 @@ module EventMachine
150
164
  # the Timeout expires (passing no arguments to the object's errbacks).
151
165
  # Setting the status at any time prior to a call to the expiration of the timeout
152
166
  # will cause the timer to be cancelled.
153
- def timeout seconds
167
+ def timeout seconds, *args
154
168
  cancel_timeout
155
169
  me = self
156
- @deferred_timeout = EventMachine::Timer.new(seconds) {me.fail}
170
+ @deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)}
157
171
  end
158
172
 
159
173
  # Cancels an outstanding timeout if any. Undoes the action of #timeout.
@@ -0,0 +1,270 @@
1
+ module EventMachine
2
+ # A simple iterator for concurrent asynchronous work.
3
+ #
4
+ # Unlike ruby's built-in iterators, the end of the current iteration cycle is signaled manually,
5
+ # instead of happening automatically after the yielded block finishes executing. For example:
6
+ #
7
+ # (0..10).each{ |num| }
8
+ #
9
+ # becomes:
10
+ #
11
+ # EM::Iterator.new(0..10).each{ |num,iter| iter.next }
12
+ #
13
+ # This is especially useful when doing asynchronous work via reactor libraries and
14
+ # functions. For example, given a sync and async http api:
15
+ #
16
+ # response = sync_http_get(url); ...
17
+ # async_http_get(url){ |response| ... }
18
+ #
19
+ # a synchronous iterator such as:
20
+ #
21
+ # responses = urls.map{ |url| sync_http_get(url) }
22
+ # ...
23
+ # puts 'all done!'
24
+ #
25
+ # could be written as:
26
+ #
27
+ # EM::Iterator.new(urls).map(proc{ |url,iter|
28
+ # async_http_get(url){ |res|
29
+ # iter.return(res)
30
+ # }
31
+ # }, proc{ |responses|
32
+ # ...
33
+ # puts 'all done!'
34
+ # })
35
+ #
36
+ # Now, you can take advantage of the asynchronous api to issue requests in parallel. For example,
37
+ # to fetch 10 urls at a time, simply pass in a concurrency of 10:
38
+ #
39
+ # EM::Iterator.new(urls, 10).each do |url,iter|
40
+ # async_http_get(url){ iter.next }
41
+ # end
42
+ #
43
+ class Iterator
44
+ # Create a new parallel async iterator with specified concurrency.
45
+ #
46
+ # i = EM::Iterator.new(1..100, 10)
47
+ #
48
+ # will create an iterator over the range that processes 10 items at a time. Iteration
49
+ # is started via #each, #map or #inject
50
+ #
51
+ def initialize(list, concurrency = 1)
52
+ raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
53
+ @list = list.to_a.dup
54
+ @concurrency = concurrency
55
+
56
+ @started = false
57
+ @ended = false
58
+ end
59
+
60
+ # Change the concurrency of this iterator. Workers will automatically be spawned or destroyed
61
+ # to accomodate the new concurrency level.
62
+ #
63
+ def concurrency=(val)
64
+ old = @concurrency
65
+ @concurrency = val
66
+
67
+ spawn_workers if val > old and @started and !@ended
68
+ end
69
+ attr_reader :concurrency
70
+
71
+ # Iterate over a set of items using the specified block or proc.
72
+ #
73
+ # EM::Iterator.new(1..100).each do |num, iter|
74
+ # puts num
75
+ # iter.next
76
+ # end
77
+ #
78
+ # An optional second proc is invoked after the iteration is complete.
79
+ #
80
+ # EM::Iterator.new(1..100).each(
81
+ # proc{ |num,iter| iter.next },
82
+ # proc{ puts 'all done' }
83
+ # )
84
+ #
85
+ def each(foreach=nil, after=nil, &blk)
86
+ raise ArgumentError, 'proc or block required for iteration' unless foreach ||= blk
87
+ raise RuntimeError, 'cannot iterate over an iterator more than once' if @started or @ended
88
+
89
+ @started = true
90
+ @pending = 0
91
+ @workers = 0
92
+
93
+ all_done = proc{
94
+ after.call if after and @ended and @pending == 0
95
+ }
96
+
97
+ @process_next = proc{
98
+ # p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list]
99
+ unless @ended or @workers > @concurrency
100
+ if @list.empty?
101
+ @ended = true
102
+ @workers -= 1
103
+ all_done.call
104
+ else
105
+ item = @list.shift
106
+ @pending += 1
107
+
108
+ is_done = false
109
+ on_done = proc{
110
+ raise RuntimeError, 'already completed this iteration' if is_done
111
+ is_done = true
112
+
113
+ @pending -= 1
114
+
115
+ if @ended
116
+ all_done.call
117
+ else
118
+ EM.next_tick(@process_next)
119
+ end
120
+ }
121
+ class << on_done
122
+ alias :next :call
123
+ end
124
+
125
+ foreach.call(item, on_done)
126
+ end
127
+ else
128
+ @workers -= 1
129
+ end
130
+ }
131
+
132
+ spawn_workers
133
+
134
+ self
135
+ end
136
+
137
+ # Collect the results of an asynchronous iteration into an array.
138
+ #
139
+ # EM::Iterator.new(%w[ pwd uptime uname date ], 2).map(proc{ |cmd,iter|
140
+ # EM.system(cmd){ |output,status|
141
+ # iter.return(output)
142
+ # }
143
+ # }, proc{ |results|
144
+ # p results
145
+ # })
146
+ #
147
+ def map(foreach, after)
148
+ index = 0
149
+
150
+ inject([], proc{ |results,item,iter|
151
+ i = index
152
+ index += 1
153
+
154
+ is_done = false
155
+ on_done = proc{ |res|
156
+ raise RuntimeError, 'already returned a value for this iteration' if is_done
157
+ is_done = true
158
+
159
+ results[i] = res
160
+ iter.return(results)
161
+ }
162
+ class << on_done
163
+ alias :return :call
164
+ def next
165
+ raise NoMethodError, 'must call #return on a map iterator'
166
+ end
167
+ end
168
+
169
+ foreach.call(item, on_done)
170
+ }, proc{ |results|
171
+ after.call(results)
172
+ })
173
+ end
174
+
175
+ # Inject the results of an asynchronous iteration onto a given object.
176
+ #
177
+ # EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter|
178
+ # EM.system(cmd){ |output,status|
179
+ # hash[cmd] = status.exitstatus == 0 ? output.strip : nil
180
+ # iter.return(hash)
181
+ # }
182
+ # }, proc{ |results|
183
+ # p results
184
+ # })
185
+ #
186
+ def inject(obj, foreach, after)
187
+ each(proc{ |item,iter|
188
+ is_done = false
189
+ on_done = proc{ |res|
190
+ raise RuntimeError, 'already returned a value for this iteration' if is_done
191
+ is_done = true
192
+
193
+ obj = res
194
+ iter.next
195
+ }
196
+ class << on_done
197
+ alias :return :call
198
+ def next
199
+ raise NoMethodError, 'must call #return on an inject iterator'
200
+ end
201
+ end
202
+
203
+ foreach.call(obj, item, on_done)
204
+ }, proc{
205
+ after.call(obj)
206
+ })
207
+ end
208
+
209
+ private
210
+
211
+ # Spawn workers to consume items from the iterator's enumerator based on the current concurrency level.
212
+ #
213
+ def spawn_workers
214
+ EM.next_tick(start_worker = proc{
215
+ if @workers < @concurrency and !@ended
216
+ # p [:spawning_worker, :workers=, @workers, :concurrency=, @concurrency, :ended=, @ended]
217
+ @workers += 1
218
+ @process_next.call
219
+ EM.next_tick(start_worker)
220
+ end
221
+ })
222
+ nil
223
+ end
224
+ end
225
+ end
226
+
227
+ if __FILE__ == $0
228
+ $:.unshift File.join(File.dirname(__FILE__), '..')
229
+ require 'eventmachine'
230
+
231
+ # TODO: real tests
232
+ # TODO: pass in one object instead of two? .each{ |iter| puts iter.current; iter.next }
233
+ # TODO: support iter.pause/resume/stop/break/continue?
234
+ # TODO: create some exceptions instead of using RuntimeError
235
+ # TODO: support proc instead of enumerable? EM::Iterator.new(proc{ return queue.pop })
236
+
237
+ EM.run{
238
+ EM::Iterator.new(1..50).each{ |num,iter| p num; iter.next }
239
+ EM::Iterator.new([1,2,3], 10).each{ |num,iter| p num; iter.next }
240
+
241
+ i = EM::Iterator.new(1..100, 5)
242
+ i.each(proc{|num,iter|
243
+ p num.to_s
244
+ iter.next
245
+ }, proc{
246
+ p :done
247
+ })
248
+ EM.add_timer(0.03){
249
+ i.concurrency = 1
250
+ }
251
+ EM.add_timer(0.04){
252
+ i.concurrency = 3
253
+ }
254
+
255
+ EM::Iterator.new(100..150).map(proc{ |num,iter|
256
+ EM.add_timer(0.01){ iter.return(num) }
257
+ }, proc{ |results|
258
+ p results
259
+ })
260
+
261
+ EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter|
262
+ EM.system(cmd){ |output,status|
263
+ hash[cmd] = status.exitstatus == 0 ? output.strip : nil
264
+ iter.return(hash)
265
+ }
266
+ }, proc{ |results|
267
+ p results
268
+ })
269
+ }
270
+ end
@@ -5,7 +5,7 @@ module EventMachine
5
5
  # - Memcache
6
6
  # - SmtpClient and SmtpServer
7
7
  # - SASLauth and SASLauthclient
8
- # - LineAndTextProtocol and LineText2
8
+ # - LineProtocol, LineAndTextProtocol and LineText2
9
9
  # - HeaderAndContentProtocol
10
10
  # - Postgres3
11
11
  # - ObjectProtocol
@@ -142,6 +142,11 @@ module EventMachine
142
142
  req << "Cookie: #{args[:cookie]}"
143
143
  end
144
144
 
145
+ # Allow custom HTTP headers, e.g. SOAPAction
146
+ args[:custom_headers].each do |k,v|
147
+ req << "#{k}: #{v}"
148
+ end if args[:custom_headers]
149
+
145
150
  # Basic-auth stanza contributed by Matt Murphy.
146
151
  if args[:basic_auth]
147
152
  basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
@@ -0,0 +1,28 @@
1
+ module EventMachine
2
+ module Protocols
3
+ # LineProtocol will parse out newline terminated strings from a receive_data stream
4
+ #
5
+ # module Server
6
+ # include EM::P::LineProtocol
7
+ #
8
+ # def receive_line(line)
9
+ # send_data("you said: #{line}")
10
+ # end
11
+ # end
12
+ #
13
+ module LineProtocol
14
+ def receive_data data # :nodoc:
15
+ (@buf ||= '') << data
16
+
17
+ while line = @buf.slice!(/(.*)\r?\n/)
18
+ receive_line(line)
19
+ end
20
+ end
21
+
22
+ # Invoked with lines received over the network
23
+ def receive_line(line)
24
+ # stub
25
+ end
26
+ end
27
+ end
28
+ end
@@ -32,6 +32,86 @@ module EventMachine
32
32
  # and data to user code. User code is responsible for doing the right things with the
33
33
  # data in order to get complete and correct SMTP server behavior.
34
34
  #
35
+ # Simple SMTP server example:
36
+ #
37
+ # class EmailServer < EM::P::SmtpServer
38
+ # def receive_plain_auth(user, pass)
39
+ # true
40
+ # end
41
+ #
42
+ # def get_server_domain
43
+ # "mock.smtp.server.local"
44
+ # end
45
+ #
46
+ # def get_server_greeting
47
+ # "mock smtp server greets you with impunity"
48
+ # end
49
+ #
50
+ # def receive_sender(sender)
51
+ # current.sender = sender
52
+ # true
53
+ # end
54
+ #
55
+ # def receive_recipient(recipient)
56
+ # current.recipient = recipient
57
+ # true
58
+ # end
59
+ #
60
+ # def receive_message
61
+ # current.received = true
62
+ # current.completed_at = Time.now
63
+ #
64
+ # p [:received_email, current]
65
+ # @current = OpenStruct.new
66
+ # true
67
+ # end
68
+ #
69
+ # def receive_ehlo_domain(domain)
70
+ # @ehlo_domain = domain
71
+ # true
72
+ # end
73
+ #
74
+ # def receive_data_command
75
+ # current.data = ""
76
+ # true
77
+ # end
78
+ #
79
+ # def receive_data_chunk(data)
80
+ # current.data << data.join("\n")
81
+ # true
82
+ # end
83
+ #
84
+ # def receive_transaction
85
+ # if @ehlo_domain
86
+ # current.ehlo_domain = @ehlo_domain
87
+ # @ehlo_domain = nil
88
+ # end
89
+ # true
90
+ # end
91
+ #
92
+ # def current
93
+ # @current ||= OpenStruct.new
94
+ # end
95
+ #
96
+ # def self.start(host = 'localhost', port = 1025)
97
+ # require 'ostruct'
98
+ # @server = EM.start_server host, port, self
99
+ # end
100
+ #
101
+ # def self.stop
102
+ # if @server
103
+ # EM.stop_server @server
104
+ # @server = nil
105
+ # end
106
+ # end
107
+ #
108
+ # def self.running?
109
+ # !!@server
110
+ # end
111
+ # end
112
+ #
113
+ # EM.run{ EmailServer.start }
114
+ #
35
115
  #--
36
116
  # Useful paragraphs in RFC-2821:
37
117
  # 4.3.2: Concise list of command-reply sequences, in essence a text representation
@@ -114,6 +194,7 @@ module EventMachine
114
194
  @@parms[:verbose] and $>.puts ">>> #{ln}"
115
195
 
116
196
  return process_data_line(ln) if @state.include?(:data)
197
+ return process_auth_line(ln) if @state.include?(:auth_incomplete)
117
198
 
118
199
  case ln
119
200
  when EhloRegex
@@ -216,7 +297,7 @@ module EventMachine
216
297
  send_data "250-STARTTLS\r\n"
217
298
  end
218
299
  if @@parms[:auth]
219
- send_data "250-AUTH PLAIN LOGIN\r\n"
300
+ send_data "250-AUTH PLAIN\r\n"
220
301
  end
221
302
  send_data "250-NO-SOLICITING\r\n"
222
303
  # TODO, size needs to be configurable.
@@ -259,14 +340,14 @@ module EventMachine
259
340
  def process_auth str
260
341
  if @state.include?(:auth)
261
342
  send_data "503 auth already issued\r\n"
262
- elsif str =~ /\APLAIN\s+/i
263
- plain = ($'.dup).unpack("m").first # Base64::decode64($'.dup)
264
- discard,user,psw = plain.split("\000")
265
- if receive_plain_auth user,psw
266
- send_data "235 authentication ok\r\n"
267
- @state << :auth
343
+ elsif str =~ /\APLAIN\s?/i
344
+ if $'.length == 0
345
+ # we got a partial response, so let the client know to send the rest
346
+ @state << :auth_incomplete
347
+ send_data("334 \r\n")
268
348
  else
269
- send_data "535 invalid authentication\r\n"
349
+ # we got the initial response, so go ahead & process it
350
+ process_auth_line($')
270
351
  end
271
352
  #elsif str =~ /\ALOGIN\s+/i
272
353
  else
@@ -274,6 +355,18 @@ module EventMachine
274
355
  end
275
356
  end
276
357
 
358
+ def process_auth_line(line)
359
+ plain = line.unpack("m").first
360
+ discard,user,psw = plain.split("\000")
361
+ if receive_plain_auth user,psw
362
+ send_data "235 authentication ok\r\n"
363
+ @state << :auth
364
+ else
365
+ send_data "535 invalid authentication\r\n"
366
+ end
367
+ @state.delete :auth_incomplete
368
+ end
369
+
277
370
  #--
278
371
  # Unusually, we can deal with a Deferrable returned from the user application.
279
372
  # This was added to deal with a special case in a particular application, but