eventmachine 1.0.9.1-java → 1.2.0.1-java

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +2 -2
  4. data/ext/cmain.cpp +77 -5
  5. data/ext/ed.cpp +112 -42
  6. data/ext/ed.h +27 -13
  7. data/ext/em.cpp +105 -163
  8. data/ext/em.h +10 -7
  9. data/ext/eventmachine.h +13 -1
  10. data/ext/extconf.rb +23 -14
  11. data/ext/fastfilereader/extconf.rb +1 -2
  12. data/ext/fastfilereader/rubymain.cpp +6 -6
  13. data/ext/project.h +9 -4
  14. data/ext/rubymain.cpp +155 -36
  15. data/ext/ssl.cpp +157 -13
  16. data/ext/ssl.h +7 -2
  17. data/lib/em/channel.rb +5 -0
  18. data/lib/em/completion.rb +2 -2
  19. data/lib/em/connection.rb +61 -3
  20. data/lib/em/iterator.rb +26 -5
  21. data/lib/em/pool.rb +1 -1
  22. data/lib/em/protocols/line_and_text.rb +1 -1
  23. data/lib/em/pure_ruby.rb +6 -1
  24. data/lib/em/queue.rb +16 -7
  25. data/lib/em/resolver.rb +46 -23
  26. data/lib/em/threaded_resource.rb +2 -2
  27. data/lib/em/version.rb +1 -1
  28. data/lib/eventmachine.rb +59 -42
  29. data/lib/rubyeventmachine.jar +0 -0
  30. data/rakelib/package.rake +23 -1
  31. data/tests/dhparam.pem +13 -0
  32. data/tests/em_test_helper.rb +79 -0
  33. data/tests/test_basic.rb +17 -26
  34. data/tests/test_channel.rb +14 -1
  35. data/tests/test_connection_write.rb +2 -2
  36. data/tests/test_defer.rb +17 -0
  37. data/tests/test_epoll.rb +1 -1
  38. data/tests/test_fork.rb +75 -0
  39. data/tests/test_ipv4.rb +125 -0
  40. data/tests/test_ipv6.rb +131 -0
  41. data/tests/test_iterator.rb +18 -0
  42. data/tests/test_many_fds.rb +1 -1
  43. data/tests/test_queue.rb +14 -0
  44. data/tests/test_resolver.rb +23 -0
  45. data/tests/test_set_sock_opt.rb +2 -0
  46. data/tests/test_ssl_dhparam.rb +83 -0
  47. data/tests/test_ssl_ecdh_curve.rb +79 -0
  48. data/tests/test_ssl_extensions.rb +49 -0
  49. data/tests/test_ssl_methods.rb +19 -0
  50. data/tests/test_ssl_protocols.rb +246 -0
  51. data/tests/test_ssl_verify.rb +44 -0
  52. data/tests/test_system.rb +4 -0
  53. data/tests/test_unbind_reason.rb +5 -1
  54. metadata +101 -20
  55. data/.gitignore +0 -21
  56. data/.travis.yml +0 -22
  57. data/.yardopts +0 -7
  58. data/Gemfile +0 -2
  59. data/Rakefile +0 -20
  60. data/eventmachine.gemspec +0 -38
  61. data/rakelib/cpp.rake_example +0 -77
data/ext/ssl.cpp CHANGED
@@ -120,7 +120,8 @@ static void InitializeDefaultCredentials()
120
120
  SslContext_t::SslContext_t
121
121
  **************************/
122
122
 
123
- SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile):
123
+ SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile, const string &cipherlist, const string &ecdh_curve, const string &dhparam, int ssl_version) :
124
+ bIsServer (is_server),
124
125
  pCtx (NULL),
125
126
  PrivateKey (NULL),
126
127
  Certificate (NULL)
@@ -144,18 +145,47 @@ SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const str
144
145
  InitializeDefaultCredentials();
145
146
  }
146
147
 
147
- bIsServer = is_server;
148
- pCtx = SSL_CTX_new (is_server ? SSLv23_server_method() : SSLv23_client_method());
148
+ pCtx = SSL_CTX_new (bIsServer ? SSLv23_server_method() : SSLv23_client_method());
149
149
  if (!pCtx)
150
150
  throw std::runtime_error ("no SSL context");
151
151
 
152
152
  SSL_CTX_set_options (pCtx, SSL_OP_ALL);
153
- //SSL_CTX_set_options (pCtx, (SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3));
154
- #ifdef SSL_MODE_RELEASE_BUFFERS
153
+
154
+ #ifdef SSL_CTRL_CLEAR_OPTIONS
155
+ SSL_CTX_clear_options (pCtx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
156
+ # ifdef SSL_OP_NO_TLSv1_1
157
+ SSL_CTX_clear_options (pCtx, SSL_OP_NO_TLSv1_1);
158
+ # endif
159
+ # ifdef SSL_OP_NO_TLSv1_2
160
+ SSL_CTX_clear_options (pCtx, SSL_OP_NO_TLSv1_2);
161
+ # endif
162
+ #endif
163
+
164
+ if (!(ssl_version & EM_PROTO_SSLv2))
165
+ SSL_CTX_set_options (pCtx, SSL_OP_NO_SSLv2);
166
+
167
+ if (!(ssl_version & EM_PROTO_SSLv3))
168
+ SSL_CTX_set_options (pCtx, SSL_OP_NO_SSLv3);
169
+
170
+ if (!(ssl_version & EM_PROTO_TLSv1))
171
+ SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1);
172
+
173
+ #ifdef SSL_OP_NO_TLSv1_1
174
+ if (!(ssl_version & EM_PROTO_TLSv1_1))
175
+ SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1_1);
176
+ #endif
177
+
178
+ #ifdef SSL_OP_NO_TLSv1_2
179
+ if (!(ssl_version & EM_PROTO_TLSv1_2))
180
+ SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1_2);
181
+ #endif
182
+
183
+ #ifdef SSL_MODE_RELEASE_BUFFERS
155
184
  SSL_CTX_set_mode (pCtx, SSL_MODE_RELEASE_BUFFERS);
156
- #endif
185
+ #endif
186
+
187
+ if (bIsServer) {
157
188
 
158
- if (is_server) {
159
189
  // The SSL_CTX calls here do NOT allocate memory.
160
190
  int e;
161
191
  if (privkeyfile.length() > 0)
@@ -171,11 +201,69 @@ SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const str
171
201
  e = SSL_CTX_use_certificate (pCtx, DefaultCertificate);
172
202
  if (e <= 0) ERR_print_errors_fp(stderr);
173
203
  assert (e > 0);
204
+
205
+ if (dhparam.length() > 0) {
206
+ DH *dh;
207
+ BIO *bio;
208
+
209
+ bio = BIO_new_file(dhparam.c_str(), "r");
210
+ if (bio == NULL) {
211
+ char buf [500];
212
+ snprintf (buf, sizeof(buf)-1, "dhparam: BIO_new_file(%s) failed", dhparam.c_str());
213
+ throw std::runtime_error (buf);
214
+ }
215
+
216
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
217
+
218
+ if (dh == NULL) {
219
+ BIO_free(bio);
220
+ char buf [500];
221
+ snprintf (buf, sizeof(buf)-1, "dhparam: PEM_read_bio_DHparams(%s) failed", dhparam.c_str());
222
+ throw new std::runtime_error(buf);
223
+ }
224
+
225
+ SSL_CTX_set_tmp_dh(pCtx, dh);
226
+
227
+ DH_free(dh);
228
+ BIO_free(bio);
229
+ }
230
+
231
+ if (ecdh_curve.length() > 0) {
232
+ #if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
233
+ int nid;
234
+ EC_KEY *ecdh;
235
+
236
+ nid = OBJ_sn2nid((const char *) ecdh_curve.c_str());
237
+ if (nid == 0) {
238
+ char buf [200];
239
+ snprintf (buf, sizeof(buf)-1, "ecdh_curve: Unknown curve name: %s", ecdh_curve.c_str());
240
+ throw std::runtime_error (buf);
241
+ }
242
+
243
+ ecdh = EC_KEY_new_by_curve_name(nid);
244
+ if (ecdh == NULL) {
245
+ char buf [200];
246
+ snprintf (buf, sizeof(buf)-1, "ecdh_curve: Unable to create: %s", ecdh_curve.c_str());
247
+ throw std::runtime_error (buf);
248
+ }
249
+
250
+ SSL_CTX_set_options(pCtx, SSL_OP_SINGLE_ECDH_USE);
251
+
252
+ SSL_CTX_set_tmp_ecdh(pCtx, ecdh);
253
+
254
+ EC_KEY_free(ecdh);
255
+ #else
256
+ throw std::runtime_error ("No openssl ECDH support");
257
+ #endif
258
+ }
174
259
  }
175
260
 
176
- SSL_CTX_set_cipher_list (pCtx, "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH");
261
+ if (cipherlist.length() > 0)
262
+ SSL_CTX_set_cipher_list (pCtx, cipherlist.c_str());
263
+ else
264
+ SSL_CTX_set_cipher_list (pCtx, "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH");
177
265
 
178
- if (is_server) {
266
+ if (bIsServer) {
179
267
  SSL_CTX_sess_set_cache_size (pCtx, 128);
180
268
  SSL_CTX_set_session_id_context (pCtx, (unsigned char*)"eventmachine", 12);
181
269
  }
@@ -216,10 +304,11 @@ SslContext_t::~SslContext_t()
216
304
  SslBox_t::SslBox_t
217
305
  ******************/
218
306
 
219
- SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const uintptr_t binding):
307
+ SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, bool fail_if_no_peer_cert, const string &snihostname, const string &cipherlist, const string &ecdh_curve, const string &dhparam, int ssl_version, const uintptr_t binding):
220
308
  bIsServer (is_server),
221
309
  bHandshakeCompleted (false),
222
310
  bVerifyPeer (verify_peer),
311
+ bFailIfNoPeerCert (fail_if_no_peer_cert),
223
312
  pSSL (NULL),
224
313
  pbioRead (NULL),
225
314
  pbioWrite (NULL)
@@ -228,7 +317,7 @@ SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &cer
228
317
  * a new one every time we come here.
229
318
  */
230
319
 
231
- Context = new SslContext_t (bIsServer, privkeyfile, certchainfile);
320
+ Context = new SslContext_t (bIsServer, privkeyfile, certchainfile, cipherlist, ecdh_curve, dhparam, ssl_version);
232
321
  assert (Context);
233
322
 
234
323
  pbioRead = BIO_new (BIO_s_mem());
@@ -239,13 +328,22 @@ SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &cer
239
328
 
240
329
  pSSL = SSL_new (Context->pCtx);
241
330
  assert (pSSL);
331
+
332
+ if (snihostname.length() > 0) {
333
+ SSL_set_tlsext_host_name (pSSL, snihostname.c_str());
334
+ }
335
+
242
336
  SSL_set_bio (pSSL, pbioRead, pbioWrite);
243
337
 
244
338
  // Store a pointer to the binding signature in the SSL object so we can retrieve it later
245
339
  SSL_set_ex_data(pSSL, 0, (void*) binding);
246
340
 
247
- if (bVerifyPeer)
248
- SSL_set_verify(pSSL, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, ssl_verify_wrapper);
341
+ if (bVerifyPeer) {
342
+ int mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
343
+ if (bFailIfNoPeerCert)
344
+ mode = mode | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
345
+ SSL_set_verify(pSSL, mode, ssl_verify_wrapper);
346
+ }
249
347
 
250
348
  if (!bIsServer)
251
349
  SSL_connect (pSSL);
@@ -437,6 +535,52 @@ X509 *SslBox_t::GetPeerCert()
437
535
  return cert;
438
536
  }
439
537
 
538
+ /**********************
539
+ SslBox_t::GetCipherBits
540
+ **********************/
541
+
542
+ int SslBox_t::GetCipherBits()
543
+ {
544
+ int bits = -1;
545
+ if (pSSL)
546
+ SSL_get_cipher_bits(pSSL, &bits);
547
+ return bits;
548
+ }
549
+
550
+ /**********************
551
+ SslBox_t::GetCipherName
552
+ **********************/
553
+
554
+ const char *SslBox_t::GetCipherName()
555
+ {
556
+ if (pSSL)
557
+ return SSL_get_cipher_name(pSSL);
558
+ return NULL;
559
+ }
560
+
561
+ /**********************
562
+ SslBox_t::GetCipherProtocol
563
+ **********************/
564
+
565
+ const char *SslBox_t::GetCipherProtocol()
566
+ {
567
+ if (pSSL)
568
+ return SSL_get_cipher_version(pSSL);
569
+ return NULL;
570
+ }
571
+
572
+ /**********************
573
+ SslBox_t::GetSNIHostname
574
+ **********************/
575
+
576
+ const char *SslBox_t::GetSNIHostname()
577
+ {
578
+ #ifdef TLSEXT_NAMETYPE_host_name
579
+ if (pSSL)
580
+ return SSL_get_servername (pSSL, TLSEXT_NAMETYPE_host_name);
581
+ #endif
582
+ return NULL;
583
+ }
440
584
 
441
585
  /******************
442
586
  ssl_verify_wrapper
data/ext/ssl.h CHANGED
@@ -33,7 +33,7 @@ class SslContext_t
33
33
  class SslContext_t
34
34
  {
35
35
  public:
36
- SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile);
36
+ SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile, const string &cipherlist, const string &ecdh_curve, const string &dhparam, int ssl_version);
37
37
  virtual ~SslContext_t();
38
38
 
39
39
  private:
@@ -61,7 +61,7 @@ class SslBox_t
61
61
  class SslBox_t
62
62
  {
63
63
  public:
64
- SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const uintptr_t binding);
64
+ SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, bool fail_if_no_peer_cert, const string &snihostname, const string &cipherlist, const string &ecdh_curve, const string &dhparam, int ssl_version, const uintptr_t binding);
65
65
  virtual ~SslBox_t();
66
66
 
67
67
  int PutPlaintext (const char*, int);
@@ -73,6 +73,10 @@ class SslBox_t
73
73
  bool IsHandshakeCompleted() {return bHandshakeCompleted;}
74
74
 
75
75
  X509 *GetPeerCert();
76
+ int GetCipherBits();
77
+ const char *GetCipherName();
78
+ const char *GetCipherProtocol();
79
+ const char *GetSNIHostname();
76
80
 
77
81
  void Shutdown();
78
82
 
@@ -82,6 +86,7 @@ class SslBox_t
82
86
  bool bIsServer;
83
87
  bool bHandshakeCompleted;
84
88
  bool bVerifyPeer;
89
+ bool bFailIfNoPeerCert;
85
90
  SSL *pSSL;
86
91
  BIO *pbioRead;
87
92
  BIO *pbioWrite;
data/lib/em/channel.rb CHANGED
@@ -17,6 +17,11 @@ module EventMachine
17
17
  @uid = 0
18
18
  end
19
19
 
20
+ # Return the number of current subscribers.
21
+ def num_subscribers
22
+ return @subs.size
23
+ end
24
+
20
25
  # Takes any arguments suitable for EM::Callback() and returns a subscriber
21
26
  # id for use when unsubscribing.
22
27
  #
data/lib/em/completion.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # = EM::Completion
2
2
  #
3
3
  # A completion is a callback container for various states of completion. In
4
- # it's most basic form it has a start state and a finish state.
4
+ # its most basic form it has a start state and a finish state.
5
5
  #
6
6
  # This implementation includes some hold-back from the EM::Deferrable
7
7
  # interface in order to be compatible - but it has a much cleaner
@@ -50,7 +50,7 @@
50
50
  # @completion.fail :unknown, line
51
51
  # end
52
52
  # end
53
- #
53
+ #
54
54
  # def unbind
55
55
  # @completion.fail :disconnected unless @completion.completed?
56
56
  # end
data/lib/em/connection.rb CHANGED
@@ -376,10 +376,21 @@ module EventMachine
376
376
  #
377
377
  # @option args [String] :private_key_file (nil) local path of a readable file that must contain a private key in the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail).
378
378
  #
379
- # @option args [String] :verify_peer (false) indicates whether a server should request a certificate from a peer, to be verified by user code.
379
+ # @option args [Boolean] :verify_peer (false) indicates whether a server should request a certificate from a peer, to be verified by user code.
380
380
  # If true, the {#ssl_verify_peer} callback on the {EventMachine::Connection} object is called with each certificate
381
381
  # in the certificate chain provided by the peer. See documentation on {#ssl_verify_peer} for how to use this.
382
382
  #
383
+ # @option args [Boolean] :fail_if_no_peer_cert (false) Used in conjunction with verify_peer. If set the SSL handshake will be terminated if the peer does not provide a certificate.
384
+ #
385
+ #
386
+ # @option args [String] :cipher_list ("ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH") indicates the available SSL cipher values. Default value is "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH". Check the format of the OpenSSL cipher string at http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT.
387
+ #
388
+ # @option args [String] :ecdh_curve (nil) The curve for ECDHE ciphers. See available ciphers with 'openssl ecparam -list_curves'
389
+ #
390
+ # @option args [String] :dhparam (nil) The local path of a file containing DH parameters for EDH ciphers in [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail) See: 'openssl dhparam'
391
+ #
392
+ # @option args [Array] :ssl_version (TLSv1 TLSv1_1 TLSv1_2) indicates the allowed SSL/TLS versions. Possible values are: {SSLv2}, {SSLv3}, {TLSv1}, {TLSv1_1}, {TLSv1_2}.
393
+ #
383
394
  # @example Using TLS with EventMachine
384
395
  #
385
396
  # require 'rubygems'
@@ -404,7 +415,15 @@ module EventMachine
404
415
  #
405
416
  # @see #ssl_verify_peer
406
417
  def start_tls args={}
407
- priv_key, cert_chain, verify_peer = args.values_at(:private_key_file, :cert_chain_file, :verify_peer)
418
+ priv_key = args[:private_key_file]
419
+ cert_chain = args[:cert_chain_file]
420
+ verify_peer = args[:verify_peer]
421
+ sni_hostname = args[:sni_hostname]
422
+ cipher_list = args[:cipher_list]
423
+ ssl_version = args[:ssl_version]
424
+ ecdh_curve = args[:ecdh_curve]
425
+ dhparam = args[:dhparam]
426
+ fail_if_no_peer_cert = args[:fail_if_no_peer_cert]
408
427
 
409
428
  [priv_key, cert_chain].each do |file|
410
429
  next if file.nil? or file.empty?
@@ -412,7 +431,31 @@ module EventMachine
412
431
  "Could not find #{file} for start_tls" unless File.exist? file
413
432
  end
414
433
 
415
- EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer)
434
+ protocols_bitmask = 0
435
+ if ssl_version.nil?
436
+ protocols_bitmask |= EventMachine::EM_PROTO_TLSv1
437
+ protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1
438
+ protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2
439
+ else
440
+ [ssl_version].flatten.each do |p|
441
+ case p.to_s.downcase
442
+ when 'sslv2'
443
+ protocols_bitmask |= EventMachine::EM_PROTO_SSLv2
444
+ when 'sslv3'
445
+ protocols_bitmask |= EventMachine::EM_PROTO_SSLv3
446
+ when 'tlsv1'
447
+ protocols_bitmask |= EventMachine::EM_PROTO_TLSv1
448
+ when 'tlsv1_1'
449
+ protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1
450
+ when 'tlsv1_2'
451
+ protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2
452
+ else
453
+ raise("Unrecognized SSL/TLS Protocol: #{p}")
454
+ end
455
+ end
456
+ end
457
+
458
+ EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer, fail_if_no_peer_cert, sni_hostname || '', cipher_list || '', ecdh_curve || '', dhparam || '', protocols_bitmask)
416
459
  EventMachine::start_tls @signature
417
460
  end
418
461
 
@@ -488,6 +531,21 @@ module EventMachine
488
531
  EventMachine::get_peer_cert @signature
489
532
  end
490
533
 
534
+ def get_cipher_bits
535
+ EventMachine::get_cipher_bits @signature
536
+ end
537
+
538
+ def get_cipher_name
539
+ EventMachine::get_cipher_name @signature
540
+ end
541
+
542
+ def get_cipher_protocol
543
+ EventMachine::get_cipher_protocol @signature
544
+ end
545
+
546
+ def get_sni_hostname
547
+ EventMachine::get_sni_hostname @signature
548
+ end
491
549
 
492
550
  # Sends UDP messages.
493
551
  #
data/lib/em/iterator.rb CHANGED
@@ -41,6 +41,7 @@ module EventMachine
41
41
  # end
42
42
  #
43
43
  class Iterator
44
+ Stop = "EM::Stop"
44
45
  # Create a new parallel async iterator with specified concurrency.
45
46
  #
46
47
  # i = EM::Iterator.new(1..100, 10)
@@ -48,10 +49,21 @@ module EventMachine
48
49
  # will create an iterator over the range that processes 10 items at a time. Iteration
49
50
  # is started via #each, #map or #inject
50
51
  #
52
+ # The list may either be an array-like object, or a proc that returns a new object
53
+ # to be processed each time it is called. If a proc is used, it must return
54
+ # EventMachine::Iterator::Stop to signal the end of the iterations.
55
+ #
51
56
  def initialize(list, concurrency = 1)
52
- raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
53
57
  raise ArgumentError, 'concurrency must be bigger than zero' unless (concurrency > 0)
54
- @list = list.to_a.dup
58
+ if list.respond_to?(:call)
59
+ @list = nil
60
+ @list_proc = list
61
+ elsif list.respond_to?(:to_a)
62
+ @list = list.to_a.dup
63
+ @list_proc = nil
64
+ else
65
+ raise ArgumentError, 'argument must be a proc or an array'
66
+ end
55
67
  @concurrency = concurrency
56
68
 
57
69
  @started = false
@@ -98,12 +110,12 @@ module EventMachine
98
110
  @process_next = proc{
99
111
  # p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list]
100
112
  unless @ended or @workers > @concurrency
101
- if @list.empty?
113
+ item = next_item()
114
+ if item.equal?(Stop)
102
115
  @ended = true
103
116
  @workers -= 1
104
117
  all_done.call
105
118
  else
106
- item = @list.shift
107
119
  @pending += 1
108
120
 
109
121
  is_done = false
@@ -222,10 +234,19 @@ module EventMachine
222
234
  })
223
235
  nil
224
236
  end
237
+
238
+ # Return the next item from @list or @list_proc.
239
+ # Once items have run out, will return EM::Iterator::Stop. Procs must supply this themselves
240
+ def next_item
241
+ if @list_proc
242
+ @list_proc.call
243
+ else
244
+ @list.empty? ? Stop : @list.shift
245
+ end
246
+ end
225
247
  end
226
248
  end
227
249
 
228
250
  # TODO: pass in one object instead of two? .each{ |iter| puts iter.current; iter.next }
229
251
  # TODO: support iter.pause/resume/stop/break/continue?
230
252
  # TODO: create some exceptions instead of using RuntimeError
231
- # TODO: support proc instead of enumerable? EM::Iterator.new(proc{ return queue.pop })