eventmachine 0.12.10 → 1.0.0.beta.1

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 (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