From 5571dc766e2143e39762ff0b47c41d1c2e154f4c Mon Sep 17 00:00:00 2001 From: Benjamin Sanders Date: Sat, 11 Oct 2025 10:34:24 -0400 Subject: Vendor bundled gems for deployment --- .../3.4.0/gems/eventmachine-1.2.7/CHANGELOG.md | 179 ++ .../bundle/ruby/3.4.0/gems/eventmachine-1.2.7/GNU | 281 +++ .../ruby/3.4.0/gems/eventmachine-1.2.7/LICENSE | 60 + .../ruby/3.4.0/gems/eventmachine-1.2.7/README.md | 110 + .../docs/DocumentationGuidesIndex.md | 27 + .../gems/eventmachine-1.2.7/docs/GettingStarted.md | 521 +++++ .../gems/eventmachine-1.2.7/docs/old/ChangeLog | 211 ++ .../gems/eventmachine-1.2.7/docs/old/DEFERRABLES | 246 ++ .../3.4.0/gems/eventmachine-1.2.7/docs/old/EPOLL | 141 ++ .../3.4.0/gems/eventmachine-1.2.7/docs/old/INSTALL | 13 + .../gems/eventmachine-1.2.7/docs/old/KEYBOARD | 42 + .../3.4.0/gems/eventmachine-1.2.7/docs/old/LEGAL | 25 + .../docs/old/LIGHTWEIGHT_CONCURRENCY | 130 ++ .../gems/eventmachine-1.2.7/docs/old/PURE_RUBY | 75 + .../gems/eventmachine-1.2.7/docs/old/RELEASE_NOTES | 94 + .../3.4.0/gems/eventmachine-1.2.7/docs/old/SMTP | 4 + .../eventmachine-1.2.7/docs/old/SPAWNED_PROCESSES | 148 ++ .../3.4.0/gems/eventmachine-1.2.7/docs/old/TODO | 8 + .../getting_started/01_eventmachine_echo_server.rb | 18 + ...ine_echo_server_that_recognizes_exit_command.rb | 22 + .../getting_started/03_simple_chat_server.rb | 149 ++ .../04_simple_chat_server_step_one.rb | 27 + .../05_simple_chat_server_step_two.rb | 43 + .../06_simple_chat_server_step_three.rb | 98 + .../07_simple_chat_server_step_four.rb | 121 + .../08_simple_chat_server_step_five.rb | 141 ++ .../eventmachine-1.2.7/examples/old/ex_channel.rb | 43 + .../eventmachine-1.2.7/examples/old/ex_queue.rb | 2 + .../examples/old/ex_tick_loop_array.rb | 15 + .../examples/old/ex_tick_loop_counter.rb | 32 + .../gems/eventmachine-1.2.7/examples/old/helper.rb | 2 + .../3.4.0/gems/eventmachine-1.2.7/ext/Makefile | 273 +++ .../3.4.0/gems/eventmachine-1.2.7/ext/binder.cpp | 124 + .../3.4.0/gems/eventmachine-1.2.7/ext/binder.h | 52 + .../3.4.0/gems/eventmachine-1.2.7/ext/cmain.cpp | 988 ++++++++ .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.cpp | 2096 +++++++++++++++++ .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.h | 457 ++++ .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.cpp | 2396 ++++++++++++++++++++ .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.h | 308 +++ .../gems/eventmachine-1.2.7/ext/eventmachine.h | 143 ++ .../3.4.0/gems/eventmachine-1.2.7/ext/extconf.rb | 270 +++ .../eventmachine-1.2.7/ext/fastfilereader/Makefile | 273 +++ .../ext/fastfilereader/extconf.rb | 109 + .../ext/fastfilereader/mapper.cpp | 214 ++ .../eventmachine-1.2.7/ext/fastfilereader/mapper.h | 59 + .../ext/fastfilereader/rubymain.cpp | 126 + .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/kb.cpp | 79 + .../3.4.0/gems/eventmachine-1.2.7/ext/page.cpp | 107 + .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.h | 51 + .../3.4.0/gems/eventmachine-1.2.7/ext/pipe.cpp | 354 +++ .../3.4.0/gems/eventmachine-1.2.7/ext/project.h | 174 ++ .../3.4.0/gems/eventmachine-1.2.7/ext/rubymain.cpp | 1515 +++++++++++++ .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.cpp | 619 +++++ .../ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.h | 103 + .../3.4.0/gems/eventmachine-1.2.7/java/.classpath | 8 + .../3.4.0/gems/eventmachine-1.2.7/java/.project | 17 + .../java/src/com/rubyeventmachine/EmReactor.java | 613 +++++ .../com/rubyeventmachine/EmReactorException.java | 40 + .../src/com/rubyeventmachine/EventableChannel.java | 72 + .../rubyeventmachine/EventableDatagramChannel.java | 201 ++ .../rubyeventmachine/EventableSocketChannel.java | 415 ++++ .../3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb | 59 + .../gems/eventmachine-1.2.7/lib/em/callback.rb | 58 + .../gems/eventmachine-1.2.7/lib/em/channel.rb | 69 + .../gems/eventmachine-1.2.7/lib/em/completion.rb | 304 +++ .../gems/eventmachine-1.2.7/lib/em/connection.rb | 770 +++++++ .../gems/eventmachine-1.2.7/lib/em/deferrable.rb | 210 ++ .../eventmachine-1.2.7/lib/em/deferrable/pool.rb | 2 + .../gems/eventmachine-1.2.7/lib/em/file_watch.rb | 73 + .../3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb | 61 + .../gems/eventmachine-1.2.7/lib/em/iterator.rb | 252 ++ .../gems/eventmachine-1.2.7/lib/em/messages.rb | 66 + .../3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb | 151 ++ .../eventmachine-1.2.7/lib/em/process_watch.rb | 45 + .../gems/eventmachine-1.2.7/lib/em/processes.rb | 123 + .../gems/eventmachine-1.2.7/lib/em/protocols.rb | 37 + .../lib/em/protocols/header_and_content.rb | 138 ++ .../lib/em/protocols/httpclient.rb | 300 +++ .../lib/em/protocols/httpclient2.rb | 600 +++++ .../lib/em/protocols/line_and_text.rb | 125 + .../lib/em/protocols/line_protocol.rb | 29 + .../lib/em/protocols/linetext2.rb | 179 ++ .../lib/em/protocols/memcache.rb | 331 +++ .../lib/em/protocols/object_protocol.rb | 46 + .../lib/em/protocols/postgres3.rb | 246 ++ .../lib/em/protocols/saslauth.rb | 175 ++ .../lib/em/protocols/smtpclient.rb | 394 ++++ .../lib/em/protocols/smtpserver.rb | 666 ++++++ .../eventmachine-1.2.7/lib/em/protocols/socks4.rb | 66 + .../eventmachine-1.2.7/lib/em/protocols/stomp.rb | 205 ++ .../eventmachine-1.2.7/lib/em/protocols/tcptest.rb | 54 + .../gems/eventmachine-1.2.7/lib/em/pure_ruby.rb | 1284 +++++++++++ .../3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb | 80 + .../gems/eventmachine-1.2.7/lib/em/resolver.rb | 232 ++ .../gems/eventmachine-1.2.7/lib/em/spawnable.rb | 84 + .../gems/eventmachine-1.2.7/lib/em/streamer.rb | 118 + .../eventmachine-1.2.7/lib/em/threaded_resource.rb | 90 + .../gems/eventmachine-1.2.7/lib/em/tick_loop.rb | 85 + .../3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb | 61 + .../gems/eventmachine-1.2.7/lib/em/version.rb | 3 + .../gems/eventmachine-1.2.7/lib/eventmachine.rb | 1601 +++++++++++++ .../lib/fastfilereaderext.bundle | Bin 0 -> 57016 bytes .../gems/eventmachine-1.2.7/lib/jeventmachine.rb | 316 +++ .../eventmachine-1.2.7/lib/rubyeventmachine.bundle | Bin 0 -> 524152 bytes .../gems/eventmachine-1.2.7/rakelib/package.rake | 120 + .../gems/eventmachine-1.2.7/rakelib/test.rake | 8 + .../gems/eventmachine-1.2.7/rakelib/test_pure.rake | 13 + .../3.4.0/gems/eventmachine-1.2.7/tests/client.crt | 31 + .../3.4.0/gems/eventmachine-1.2.7/tests/client.key | 51 + .../gems/eventmachine-1.2.7/tests/dhparam.pem | 13 + .../eventmachine-1.2.7/tests/em_test_helper.rb | 154 ++ .../gems/eventmachine-1.2.7/tests/test_attach.rb | 151 ++ .../gems/eventmachine-1.2.7/tests/test_basic.rb | 321 +++ .../gems/eventmachine-1.2.7/tests/test_channel.rb | 75 + .../eventmachine-1.2.7/tests/test_completion.rb | 178 ++ .../tests/test_connection_count.rb | 83 + .../tests/test_connection_write.rb | 35 + .../gems/eventmachine-1.2.7/tests/test_defer.rb | 35 + .../eventmachine-1.2.7/tests/test_deferrable.rb | 35 + .../gems/eventmachine-1.2.7/tests/test_epoll.rb | 142 ++ .../eventmachine-1.2.7/tests/test_error_handler.rb | 38 + .../gems/eventmachine-1.2.7/tests/test_exc.rb | 43 + .../eventmachine-1.2.7/tests/test_file_watch.rb | 85 + .../gems/eventmachine-1.2.7/tests/test_fork.rb | 75 + .../gems/eventmachine-1.2.7/tests/test_futures.rb | 170 ++ .../eventmachine-1.2.7/tests/test_handler_check.rb | 35 + .../3.4.0/gems/eventmachine-1.2.7/tests/test_hc.rb | 155 ++ .../eventmachine-1.2.7/tests/test_httpclient.rb | 238 ++ .../eventmachine-1.2.7/tests/test_httpclient2.rb | 128 ++ .../tests/test_idle_connection.rb | 31 + .../tests/test_inactivity_timeout.rb | 54 + .../gems/eventmachine-1.2.7/tests/test_ipv4.rb | 95 + .../gems/eventmachine-1.2.7/tests/test_ipv6.rb | 107 + .../gems/eventmachine-1.2.7/tests/test_iterator.rb | 118 + .../3.4.0/gems/eventmachine-1.2.7/tests/test_kb.rb | 28 + .../eventmachine-1.2.7/tests/test_line_protocol.rb | 33 + .../gems/eventmachine-1.2.7/tests/test_ltp.rb | 155 ++ .../gems/eventmachine-1.2.7/tests/test_ltp2.rb | 332 +++ .../gems/eventmachine-1.2.7/tests/test_many_fds.rb | 22 + .../eventmachine-1.2.7/tests/test_next_tick.rb | 104 + .../tests/test_object_protocol.rb | 36 + .../gems/eventmachine-1.2.7/tests/test_pause.rb | 107 + .../tests/test_pending_connect_timeout.rb | 52 + .../gems/eventmachine-1.2.7/tests/test_pool.rb | 196 ++ .../eventmachine-1.2.7/tests/test_process_watch.rb | 50 + .../eventmachine-1.2.7/tests/test_processes.rb | 128 ++ .../tests/test_proxy_connection.rb | 180 ++ .../gems/eventmachine-1.2.7/tests/test_pure.rb | 139 ++ .../gems/eventmachine-1.2.7/tests/test_queue.rb | 64 + .../gems/eventmachine-1.2.7/tests/test_resolver.rb | 105 + .../gems/eventmachine-1.2.7/tests/test_running.rb | 14 + .../gems/eventmachine-1.2.7/tests/test_sasl.rb | 47 + .../eventmachine-1.2.7/tests/test_send_file.rb | 217 ++ .../gems/eventmachine-1.2.7/tests/test_servers.rb | 33 + .../tests/test_shutdown_hooks.rb | 23 + .../eventmachine-1.2.7/tests/test_smtpclient.rb | 75 + .../eventmachine-1.2.7/tests/test_smtpserver.rb | 57 + .../gems/eventmachine-1.2.7/tests/test_sock_opt.rb | 54 + .../gems/eventmachine-1.2.7/tests/test_spawn.rb | 293 +++ .../gems/eventmachine-1.2.7/tests/test_ssl_args.rb | 78 + .../eventmachine-1.2.7/tests/test_ssl_dhparam.rb | 84 + .../tests/test_ssl_ecdh_curve.rb | 80 + .../tests/test_ssl_extensions.rb | 49 + .../eventmachine-1.2.7/tests/test_ssl_methods.rb | 65 + .../eventmachine-1.2.7/tests/test_ssl_protocols.rb | 246 ++ .../eventmachine-1.2.7/tests/test_ssl_verify.rb | 128 ++ .../gems/eventmachine-1.2.7/tests/test_stomp.rb | 38 + .../gems/eventmachine-1.2.7/tests/test_system.rb | 46 + .../tests/test_threaded_resource.rb | 63 + .../eventmachine-1.2.7/tests/test_tick_loop.rb | 59 + .../gems/eventmachine-1.2.7/tests/test_timers.rb | 130 ++ .../3.4.0/gems/eventmachine-1.2.7/tests/test_ud.rb | 8 + .../eventmachine-1.2.7/tests/test_unbind_reason.rb | 40 + 173 files changed, 31547 insertions(+) create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/CHANGELOG.md create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/GNU create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/LICENSE create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/README.md create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/DocumentationGuidesIndex.md create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/GettingStarted.md create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/ChangeLog create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/DEFERRABLES create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/EPOLL create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/INSTALL create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/KEYBOARD create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LEGAL create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LIGHTWEIGHT_CONCURRENCY create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/PURE_RUBY create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/RELEASE_NOTES create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SMTP create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SPAWNED_PROCESSES create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/TODO create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/01_eventmachine_echo_server.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/03_simple_chat_server.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/04_simple_chat_server_step_one.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/05_simple_chat_server_step_two.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/06_simple_chat_server_step_three.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/07_simple_chat_server_step_four.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/08_simple_chat_server_step_five.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_channel.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_queue.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_array.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_counter.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/helper.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/Makefile create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/cmain.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/eventmachine.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/extconf.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/Makefile create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/extconf.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/rubymain.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/kb.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/pipe.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/project.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/rubymain.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.cpp create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.h create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.classpath create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.project create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactor.java create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactorException.java create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableChannel.java create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableDatagramChannel.java create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableSocketChannel.java create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/eventmachine.rb create mode 100755 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/fastfilereaderext.bundle create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/jeventmachine.rb create mode 100755 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/rubyeventmachine.bundle create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/package.rake create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test.rake create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test_pure.rake create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.crt create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.key create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/dhparam.pem create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/em_test_helper.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_attach.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_basic.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_channel.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_completion.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_count.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_write.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_defer.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_deferrable.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_epoll.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_error_handler.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_exc.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_file_watch.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_fork.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_futures.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_handler_check.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_hc.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient2.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_idle_connection.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_inactivity_timeout.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv4.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv6.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_iterator.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_kb.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_line_protocol.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp2.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_many_fds.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_next_tick.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_object_protocol.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pause.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pending_connect_timeout.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pool.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_process_watch.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_processes.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_proxy_connection.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pure.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_queue.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_resolver.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_running.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sasl.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_send_file.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_servers.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_shutdown_hooks.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpclient.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpserver.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sock_opt.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_spawn.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_args.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_dhparam.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_ecdh_curve.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_extensions.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_methods.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_protocols.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_verify.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_stomp.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_system.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_threaded_resource.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_tick_loop.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_timers.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ud.rb create mode 100644 vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_unbind_reason.rb (limited to 'vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7') diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/CHANGELOG.md b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/CHANGELOG.md new file mode 100644 index 0000000..cd42c39 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/CHANGELOG.md @@ -0,0 +1,179 @@ +# Changelog + +## 1.2.7 (May 12, 2018) +* Fix segfault on large numbers of connections [#843] + +## 1.2.6 (April 30, 2018) +* *Fix segfault when an Exception is raised from unbind callback (for real this time!)* +* Fix race condition while initializing the machine [#756] +* Fix for newer compilers where bind() and std::bind() conflict [#830, #831] +* Be verbose about SSL connection errors [#807] +* Avoid explicitly calling class methods when in class scope +* Java: Add EM_PROTO_SSL/TLS definitions [#773, #791] +* Java: return zero when sending data to a closed connection [#475, #804] +* Pure Ruby: Connection::error? calls report_connection_error_status [#801] + +## 1.2.5 (July 27, 2017) +* Java: Use long for larger values in oneshot timer intervals [#784, #794] + +## 1.2.4 (July 27, 2017) +* Java: Add EM_PROTO_SSL/TLS definitions [#773, #791] +* Fix IPv6 UDP get_peername [#788] +* Allow for larger values in oneshot timer intervals [#784, #793] +* Update extconf.rb to allow MinGW builds with OpenSSL 1.1.0 [#785] + +## 1.2.3 (February 22, 2017) +* Pure Ruby: Add get_sockname [#308, #772] +* Fix segfault when an Exception is raised from unbind callback [#765, #766] +* Allow destructors to throw when compiling in >= C++11 [#767] + +## 1.2.2 (January 23, 2017) +* Java: Fix Fixnum deprecated warning in Ruby 2.4+ [#759] +* Fix uncaught C++ exception in file watcher and raise InvalidSignature [#512, #757] +* Fix connection count off-by-one for epoll and kqueue [#750] +* Fix uninitialized variable warning in EM::P::HttpClient [#749] +* Fix missing initial value for EventableDescriptor NextHeartbeat [#748] +* Fix hostname resolution on Solaris, Ilumos, SmartOS, et al [#745, #746] +* Improve reliability of tests, reduce public Internet accesses in tests [#656, #666, #749] + +## 1.2.1 (November 15, 2016) +* Throw strerror(errno) when getsockname or getpeername fail [#683] +* Use a single concrete implementation of getpeername/getsockname, the rest pure virtuals [#683] +* Use gai_strerror to get the failure string from getaddrinfo [#744] +* Fix deregistering descriptor when using KQUEUE [#728] +* Fix to enable to work an example code in EM::Pool [#731] +* LineText2: Add regular expression delimiter support [#706] +* Pure Ruby: EM rescue ECONNREFUSED on initial TCP connect [#741] +* Pure Ruby: EM SSL (working start_tls) [#712] +* Pure Ruby: EM fixes [#707] +* Java: Use Acceptors to get peer and sock names if not present in Connections [#743] + +## 1.2.0.1 (March 15, 2016) +* Fix crash when accepting IPv6 connections due to struct sockaddr_in [#698, #699] + +## 1.2.0 (March 15, 2016) +* Integrate work from the EventMachine-LE 1.1.x versions [#570] +* Add start_tls options :ecdh_curve, :dhparam, :fail_if_no_peer_cert [#195, #275, #399, #665] +* Add start_tls option :ssl_version for choosing SSL/TLS versions and ciphers [#359, #348, #603, #654] +* Add start_tls option :sni_hostname to be passed to TLS params [#593] +* Add method EM::Channel#num_subscribers to get the number of subscribers to a channel [#640] +* Add support for proc-sources in EM::Iterator [#639] +* Factor out method cleanup_machine to cleanup code from EM.run [#650] +* Replace Exception class with StandardError [#637] +* Close socket on close_connection even after close_connection_after_writing [#694] +* Allow reusing of datagram socket/setting bind device [#662] +* Handle deferred exceptions in reactor thread [#486] +* Reimplement Queue to avoid shift/push performance problem [#311] +* Windows: Switch from gethostbyname to getaddrinfo, support IPv6 addresses [#303, #630] +* Windows: Use rake-compiler-dock to cross-compile gems [#627] +* Windows: Add AppVeyor configuration for Windows CI testing [#578] +* Windows: Bump rake-compiler to version 0.9.x [#542] +* Fix compilation on AIX (w/ XLC) [#693] +* Fix build on OpenBSD [#690] +* Fix OpenSSL compile issue on AIX 7.1 [#678] +* Fix EventMachine.fork_reactor keeps the threadpool of the original process [#425] +* Fix to prevent event machine from stopping when a raise is done in an unbind [#327] + +## 1.0.9.1 (January 14, 2016) +* Fix EPROTO not defined on Windows [#676] +* Fix missing cast to struct sockaddr * [#671] +* Fix bug in OpenSSL path detection [#675] + +## 1.0.9 (January 13, 2016) +* Try more ways to detect OpenSSL [#602, #643, #661, #663, #668, #669] +* Use WSAGetLastError in pipe.cpp same as ed.cpp [#659] +* Test compiler flags with the C++ compiler and add them to CXXFLAGS [#634, #651] +* Restore silent-fail on unsupported EM.epoll and EM.kqueue [#638, #649] +* getDescriptorByFileno deprecated in JRuby 1.7.x, removed in JRuby 9000 [#642, #648] +* Add -Wno-address always-true because on Windows rb_fd_select [#578] +* Remove the WITHOUT_SSL constant [#578] +* Fix SSL error when the server replies a TLS Alert to our ClientHello [#544, #653] +* Use WSAStringToAddress in lieu of inet_pton for IPv6 address detection on Windows [#595, #632] +* Fix nasty TCP/IPv6 bug [#595, #632] +* Use select_large_fdset on Solaris [#611, #625] +* Detect the Solaris Studio compiler [#611, #625] +* Throw a message with strerror included [#136, #621] + +## 1.0.8 (August 6, 2015) +* fix kqueue assertion failed, postpone ArmKqueueWriter until all events are processed [#51, #176, #372, #401, #619] +* fix Rubinius GC, crank the machine from Ruby space when running Rubinius [#201, #202, #617] +* test to show that LineText2 preserves whitespace and newlines [#32, #622] +* bump up compiler warnings and resolve them [#616] +* fix Windows x64 use uintptr_t instead of unsigned long for binding pointers [#612, #615] +* fix linetext2 unroll tail recursion to avoid stack level too deep [#609] +* fix for compilation with SSL on windows [#601] +* open file descriptors and sockets with O_CLOEXEC where possible [#298, #488, #591] +* fix SmtpClient: send second EHLO after STARTTLS. [#589] +* fix nul-terminated strings in C, use StringValueCStr instead of StringValuePtr + +## 1.0.7 (February 10, 2015) +* fix delay in kqueue/epoll reactor shutdown when timers exist [#587] +* fix memory leak introduced in v1.0.5 [#586] +* expose EM.set_simultaneous_accept_count [#420] +* fix busy loop when EM.run and EM.next_tick are invoked from exception handler [#452] + +## 1.0.6 (February 3, 2015) +* add support for Rubinius Process::Status [#568] +* small bugfixes for SmtpServer [#449] +* update buftok.rb [#547] +* fix assertion on Write() [#525] +* work around mkmf.rb bug preventing gem installation [#574] +* add pause/resume support to jruby reactor [#556] +* fix pure ruby reactor to use 127.0.0.1 instead of localhost [#439] +* fix compilation under macruby [#243] +* add chunked encoding to http client [#111] +* fix errors on win32 when dealing with pipes [1ea45498] [#105] + +## 1.0.5 (February 2, 2015) +* use monotonic clocks on Linux, OS X, Solaris, and Windows [#563] +* use the rb_fd_* API to get autosized fd_sets [#502] +* add basic tests that the DNS resolver isn't leaking timers [#571] +* update to test-unit 2.x and improve various unit tests [#551] +* remove EventMachine_t::Popen code marked by ifdef OBSOLETE [#551] +* ruby 2.0 may fail at Queue.pop, so rescue and complain to $stderr [#551] +* set file handle to INVALID_HANDLE_VALUE after closing the file [#565] +* use `defined?` instead of rescuing NameError for flow control [#535] +* fix closing files and sockets on Windows [#564] +* fix file uploads in Windows [#562] +* catch failure to fork [#539] +* use chunks for SSL write [#545] + +## 1.0.4 (December 19, 2014) +* add starttls_options to smtp server [#552] +* fix closesocket on windows [#497] +* fix build on ruby 2.2 [#503] +* fix build error on ruby 1.9 [#508] +* fix timer leak during dns resolution [#489] +* add concurrency validation to EM::Iterator [#468] +* add get_file_descriptor to get fd for a signature [#467] +* add EM.attach_server and EM.attach_socket_server [#465, #466] +* calling pause from receive_data takes effect immediately [#464] +* reactor_running? returns false after fork [#455] +* fix infinite loop on double close [edc4d0e6, #441, #445] +* fix compilation issue on llvm [#433] +* fix socket error codes on win32 [ff811a81] +* fix EM.stop latency when timers exist [8b613d05, #426] +* fix infinite loop when system time changes [1427a2c80, #428] +* fix crash when callin attach/detach in the same tick [#427] +* fix compilation issue on solaris [#416] + +## 1.0.3 (March 8, 2013) +* EM.system was broken in 1.0.2 release [#413] + +## 1.0.2 (March 8, 2013) +* binary win32 gems now include fastfilereader shim [#222] +* fix long-standing connection timeout issues [27fdd5b, igrigorik/em-http-request#222] +* http and line protocol cleanups [#193, #151] +* reactor return value cleanup [#225] +* fix double require from gemspec [#284] +* fix smtp server reset behavior [#351] +* fix EM.system argument handling [#322] +* ruby 1.9 compat in smtp server and stomp protocols [#349, #315] +* fix pause from post_init [#380] + +## 1.0.1 (February 27, 2013) +* use rb_wait_for_single_fd() on ruby 2.0 to fix rb_thread_select() deprecation [#363] +* fix epoll/kqueue mode in ruby 2.0 by removing calls to rb_enable_interrupt() [#248, #389] +* fix memory leak when verifying ssl cerificates [#403] +* fix initial connection delay [#393, #374] +* fix build on windows [#371] diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/GNU b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/GNU new file mode 100644 index 0000000..3b70c5b --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/GNU @@ -0,0 +1,281 @@ +. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + License is intended to guarantee your freedom to share and change free + software--to make sure the software is free for all its users. This + General Public License applies to most of the Free Software + Foundation's software and to any other program whose authors commit to + using it. (Some other Free Software Foundation software is covered by + the GNU Lesser General Public License instead.) You can apply it to + your programs, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if you + distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether + gratis or for a fee, you must give the recipients all the rights that + you have. You must make sure that they, too, receive or can get the + source code. And you must show them these terms so they know their + rights. + + We protect your rights with two steps: (1) copyright the software, and + (2) offer you this license which gives you legal permission to copy, + distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain + that everyone understands that there is no warranty for this free + software. If the software is modified by someone else and passed on, we + want its recipients to know that what they have is not the original, so + that any problems introduced by others will not reflect on the original + authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that redistributors of a free + program will individually obtain patent licenses, in effect making the + program proprietary. To prevent this, we have made it clear that any + patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and + modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains + a notice placed by the copyright holder saying it may be distributed + under the terms of this General Public License. The "Program", below, + refers to any such program or work, and a "work based on the Program" + means either the Program or any derivative work under copyright law: + that is to say, a work containing the Program or a portion of it, + either verbatim or with modifications and/or translated into another + language. (Hereinafter, translation is included without limitation in + the term "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running the Program is not restricted, and the output from the Program + is covered only if its contents constitute a work based on the + Program (independent of having been made by running the Program). + Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an appropriate + copyright notice and disclaimer of warranty; keep intact all the + notices that refer to this License and to the absence of any warranty; + and give any other recipients of the Program a copy of this License + along with the Program. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion + of it, thus forming a work based on the Program, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Program, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Program, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Program. + + In addition, mere aggregation of another work not based on the Program + with the Program (or with a work based on the Program) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the terms of + Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + + The source code for a work means the preferred form of the work for + making modifications to it. For an executable work, complete source + code means all the source code for all modules it contains, plus any + associated interface definition files, plus the scripts used to + control compilation and installation of the executable. However, as a + special exception, the source code distributed need not include + anything that is normally distributed (in either source or binary + form) with the major components (compiler, kernel, and so on) of the + operating system on which the executable runs, unless that component + itself accompanies the executable. + + If distribution of executable or object code is made by offering + access to copy from a designated place, then offering equivalent + access to copy the source code from the same place counts as + distribution of the source code, even though third parties are not + compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt + otherwise to copy, modify, sublicense or distribute the Program is + void, and will automatically terminate your rights under this License. + However, parties who have received copies, or rights, from you under + this License will not have their licenses terminated so long as such + parties remain in full compliance. + + 5. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Program or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Program (or any work based on the + Program), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from the + original licensor to copy, distribute or modify the Program subject to + these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 7. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Program at all. For example, if a patent + license would not permit royalty-free redistribution of the Program by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Program. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system, which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Program under this License + may add an explicit geographical distribution limitation excluding + those countries, so that distribution is permitted only in or among + countries not thus excluded. In such case, this License incorporates + the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions + of the General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the Program + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and conditions + either of that version or of any later version published by the Free + Software Foundation. If the Program does not specify a version number of + this License, you may choose any version ever published by the Free Software + Foundation. + + 10. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to the author + to ask for permission. For software which is copyrighted by the Free + Software Foundation, write to the Free Software Foundation; we sometimes + make exceptions for this. Our decision will be guided by the two goals + of preserving the free status of all derivatives of our free software and + of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE + PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING + OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/LICENSE b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/LICENSE new file mode 100644 index 0000000..fbe8c83 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/LICENSE @@ -0,0 +1,60 @@ +EventMachine is copyrighted free software owned by Francis Cianfrocca +(blackhedd ... gmail.com). The Owner of this software permits you to +redistribute and/or modify the software under either the terms of the GPL +version 2 (see the file GPL), or the conditions below ("Ruby License"): + + 1. You may make and give away verbatim copies of the source form of this + software without restriction, provided that you retain ALL of the + original copyright notices and associated disclaimers. + + 2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b) use the modified software only within your corporation or + organization. + + c) give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the Owner. + + 3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a) distribute the binaries and library files of the software, + together with instructions (in a manual page or equivalent) + on where to get the original distribution. + + b) accompany the distribution with the machine-readable source of + the software. + + c) give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the Owner. + + 4. You may modify and include parts of the software into any other + software (possibly commercial), provided you comply with the terms in + Sections 1, 2, and 3 above. But some files in the distribution + are not written by the Owner, so they may be made available to you + under different terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + + 5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whoever generated them, + and may be sold commercially, and may be aggregated with this + software. + + 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/README.md b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/README.md new file mode 100644 index 0000000..4b17cb4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/README.md @@ -0,0 +1,110 @@ +# About EventMachine [![Build Status](https://travis-ci.org/eventmachine/eventmachine.svg?branch=master)](https://travis-ci.org/eventmachine/eventmachine) [![Code Climate Maintainability](https://api.codeclimate.com/v1/badges/e9b0603462905d5b9118/maintainability)](https://codeclimate.com/github/eventmachine/eventmachine/maintainability) + + +## What is EventMachine ## + +EventMachine is an event-driven I/O and lightweight concurrency library for Ruby. +It provides event-driven I/O using the [Reactor pattern](http://en.wikipedia.org/wiki/Reactor_pattern), +much like [JBoss Netty](http://www.jboss.org/netty), [Apache MINA](http://mina.apache.org/), +Python's [Twisted](http://twistedmatrix.com), [Node.js](http://nodejs.org), libevent and libev. + +EventMachine is designed to simultaneously meet two key needs: + + * Extremely high scalability, performance and stability for the most demanding production environments. + * An API that eliminates the complexities of high-performance threaded network programming, + allowing engineers to concentrate on their application logic. + +This unique combination makes EventMachine a premier choice for designers of critical networked +applications, including Web servers and proxies, email and IM production systems, authentication/authorization +processors, and many more. + +EventMachine has been around since the early 2000s and is a mature and battle-tested library. + + +## What EventMachine is good for? ## + + * Scalable event-driven servers. Examples: [Thin](http://code.macournoyer.com/thin/) or [Goliath](https://github.com/postrank-labs/goliath/). + * Scalable asynchronous clients for various protocols, RESTful APIs and so on. Examples: [em-http-request](https://github.com/igrigorik/em-http-request) or [amqp gem](https://github.com/ruby-amqp/amqp). + * Efficient network proxies with custom logic. Examples: [Proxymachine](https://github.com/mojombo/proxymachine/). + * File and network monitoring tools. Examples: [eventmachine-tail](https://github.com/jordansissel/eventmachine-tail) and [logstash](https://github.com/logstash/logstash). + + + +## What platforms are supported by EventMachine? ## + +EventMachine supports Ruby 1.8.7 through 2.6, REE, JRuby and **works well on Windows** as well +as many operating systems from the Unix family (Linux, Mac OS X, BSD flavors). + + + +## Install the gem ## + +Install it with [RubyGems](https://rubygems.org/) + + gem install eventmachine + +or add this to your Gemfile if you use [Bundler](http://gembundler.com/): + + gem "eventmachine" + + + +## Getting started ## + +For an introduction to EventMachine, check out: + + * [blog post about EventMachine by Ilya Grigorik](http://www.igvita.com/2008/05/27/ruby-eventmachine-the-speed-demon/). + * [EventMachine Introductions by Dan Sinclair](http://everburning.com/news/eventmachine-introductions.html). + + +### Server example: Echo server ### + +Here's a fully-functional echo server written with EventMachine: + +```ruby + require 'eventmachine' + + module EchoServer + def post_init + puts "-- someone connected to the echo server!" + end + + def receive_data data + send_data ">>>you sent: #{data}" + close_connection if data =~ /quit/i + end + + def unbind + puts "-- someone disconnected from the echo server!" + end +end + +# Note that this will block current thread. +EventMachine.run { + EventMachine.start_server "127.0.0.1", 8081, EchoServer +} +``` + + +## EventMachine documentation ## + +Currently we only have [reference documentation](http://rdoc.info/github/eventmachine/eventmachine/frames) and a [wiki](https://github.com/eventmachine/eventmachine/wiki). + + +## Community and where to get help ## + + * Join the [mailing list](http://groups.google.com/group/eventmachine) (Google Group) + * Join IRC channel #eventmachine on irc.freenode.net + + +## License and copyright ## + +EventMachine is copyrighted free software made available under the terms +of either the GPL or Ruby's License. + +Copyright: (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. + + +## Alternatives ## + +If you are unhappy with EventMachine and want to use Ruby, check out [Celluloid](https://celluloid.io/). diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/DocumentationGuidesIndex.md b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/DocumentationGuidesIndex.md new file mode 100644 index 0000000..b8ce5a2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/DocumentationGuidesIndex.md @@ -0,0 +1,27 @@ +# EventMachine documentation guides # + +Welcome to the documentation guides for [EventMachine](http://github.com/eventmachine/eventmachine), +a fast and simple event-processing library for Ruby programs (à la JBoss Netty, Twisted, Node.js +and so on). + +## Guide list ## + + * {file:docs/GettingStarted.md Getting started with EventMachine} + * {file:docs/EventDrivenServers.md Writing event-driven servers} + * {file:docs/EventDrivenClients.md Writing event-driven clients} + * {file:docs/ConnectionFailureAndRecovery.md Connection Failure and Recovery} + * {file:docs/TLS.md TLS (aka SSL)} + * {file:docs/Ecosystem.md EventMachine ecosystem}: Thin, Goliath, em-http-request, em-websockets, Proxymachine and beyond + * {file:docs/BlockingEventLoop.md On blocking the event loop: why it is harmful for performance and how to avoid it} + * {file:docs/LightweightConcurrency.md Lightweight concurrency with EventMachine} + * {file:docs/Deferrables.md Deferrables} + * {file:docs/ModernKernelInputOutputAPIs.md Brief introduction to epoll, kqueue, select} + * {file:docs/WorkingWithOtherIOSources.md Working with other IO sources such as the keyboard} + + +## Tell us what you think! ## + +Please take a moment and tell us what you think about this guide on the [EventMachine mailing list](http://bit.ly/jW3cR3) +or in the #eventmachine channel on irc.freenode.net: what was unclear? What wasn't covered? +Maybe you don't like the guide style or the grammar and spelling are incorrect? Reader feedback is +key to making documentation better. diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/GettingStarted.md b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/GettingStarted.md new file mode 100644 index 0000000..63acbb7 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/GettingStarted.md @@ -0,0 +1,521 @@ +# @title Getting Started with Ruby EventMachine +# @markup markdown +# @author Michael S. Klishin, Dan Sinclair + +# Getting started with Ruby EventMachine # + + +## About this guide ## + +This guide is a quick tutorial that helps you to get started with EventMachine for writing event-driven +servers, clients and using it as a lightweight concurrency library. +It should take about 20 minutes to read and study the provided code examples. This guide covers + + * Installing EventMachine via [Rubygems](http://rubygems.org) and [Bundler](http://gembundler.com). + * Building an Echo server, the "Hello, world"-like code example of network servers. + * Building a simple chat, both server and client. + * Building a very small asynchronous Websockets client. + + +## Covered versions ## + +This guide covers EventMachine v0.12.10 and 1.0 (including betas). + + +## Level ## + +This guide assumes you are comfortable (but not necessary a guru) with the command line. On Microsoft Windows™, +we recommend you to use [JRuby](http://jruby.org) when running these examples. + + +## Installing EventMachine ## + +### Make sure you have Ruby installed ### + +This guide assumes you have one of the supported Ruby implementations installed: + + * Ruby 1.8.7 + * Ruby 1.9.2 + * [JRuby](http://jruby.org) (we recommend 1.6) + * [Rubinius](http://rubini.us) 1.2 or higher + * [Ruby Enterprise Edition](http://www.rubyenterpriseedition.com) + +EventMachine works on Microsoft Windows™. + + +### With Rubygems ### + +To install the EventMachine gem do + + gem install eventmachine + + +### With Bundler ### + + gem "eventmachine" + + +### Verifying your installation ### + +Lets verify your installation with this quick IRB session: + + irb -rubygems + + ruby-1.9.2-p180 :001 > require "eventmachine" + => true + ruby-1.9.2-p180 :002 > EventMachine::VERSION + => "1.0.0.beta.3" + + +## An Echo Server Example ## + +Lets begin with the classic "Hello, world"-like example, an echo server. The echo server responds clients with the +same data that was provided. First, here's the code: + +{include:file:examples/guides/getting\_started/01\_eventmachine\_echo_server.rb} + + +When run, the server binds to port 10000. We can connect using Telnet and verify it's working: + + telnet localhost 10000 + +On my machine the output looks like: + + ~ telnet localhost 10000 + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + +Let's send something to our server. Type in "Hello, EventMachine" and hit Enter. The server will respond with +the same string: + + ~ telnet localhost 10000 + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + Hello, EventMachine + # (here we hit Enter) + Hello, EventMachine + # (this ^^^ is our echo server reply) + +It works! Congratulations, you now can tell your Node.js-loving friends that you "have done some event-driven programming, too". +Oh, and to stop Telnet, hit Control + Shift + ] and then Control + C. + +Lets walk this example line by line and see what's going on. These lines + + require 'rubygems' # or use Bundler.setup + require 'eventmachine' + +probably look familiar: you use [RubyGems](http://rubygems.org) (or [Bundler](http://gembundler.com/)) for dependencies and then require EventMachine gem. Boring. + +Next: + + class EchoServer < EventMachine::Connection + def receive_data(data) + send_data(data) + end + end + +Is the implementation of our echo server. We define a class that inherits from {EventMachine::Connection} +and a handler (aka callback) for one event: when we receive data from a client. + +EventMachine handles the connection setup, receiving data and passing it to our handler, {EventMachine::Connection#receive_data}. + +Then we implement our protocol logic, which in the case of Echo is pretty trivial: we send back whatever we receive. +To do so, we're using {EventMachine::Connection#send_data}. + +Lets modify the example to recognize `exit` command: + +{include:file:examples/guides/getting\_started/02\_eventmachine\_echo_server\_that\_recognizes\_exit\_command.rb} + +Our `receive\_data` changed slightly and now looks like this: + + def receive_data(data) + if data.strip =~ /exit$/i + EventMachine.stop_event_loop + else + send_data(data) + end + end + +Because incoming data has trailing newline character, we strip it off before matching it against a simple regular +expression. If the data ends in `exit`, we stop EventMachine event loop with {EventMachine.stop_event_loop}. This unblocks +main thread and it finishes execution, and our little program exits as the result. + +To summarize this first example: + + * Subclass {EventMachine::Connection} and override {EventMachine::Connection#send_data} to handle incoming data. + * Use {EventMachine.run} to start EventMachine event loop and then bind echo server with {EventMachine.start_server}. + * To stop the event loop, use {EventMachine.stop_event_loop} (aliased as {EventMachine.stop}) + +Lets move on to a slightly more sophisticated example that will introduce several more features and methods +EventMachine has to offer. + + +## A Simple Chat Server Example ## + +Next we will write a simple chat. Initially clients will still use telnet to connect, but then we will add little +client application that will serve as a proxy between telnet and the chat server. This example is certainly longer +(~ 150 lines with whitespace and comments) so instead of looking at the final version and going through it line by line, +we will instead begin with a very simple version that only keeps track of connected clients and then add features +as we go. + +To set some expectations about our example: + + * It will keep track of connected clients + * It will support a couple of commands, à la IRC + * It will support direct messages using Twitter-like @usernames + * It won't use MongoDB, fibers or distributed map/reduce for anything but will be totally [Web Scale™](http://bit.ly/webscaletm) nonetheless. Maybe even [ROFLscale](http://bit.ly/roflscalevideo). + +### Step one: detecting connections and disconnectons ### + +First step looks like this: + +{include:file:examples/guides/getting\_started/04\_simple\_chat\_server\_step\_one.rb} + +We see familiar {EventMachine.run} and {EventMachine.start_server}, but also {EventMachine::Connection#post_init} and {EventMachine::Connection#unbind} we haven't +met yet. We don't use them in this code, so when are they run? Like {EventMachine::Connection#receive_data}, these methods are callbacks. EventMachine calls them +when certain events happen: + + * {EventMachine#post_init} is called by the event loop immediately after the network connection has been established. + In the chat server example case, this is when a new client connects. + * {EventMachine#unbind} is called when client disconnects, connection is closed or is lost (because of a network issue, for example). + +All our chat server does so far is logging connections or disconnections. What we want it to do next is to keep track of connected clients. + + +### Step two: keep track of connected clients ### + +Next iteration of the code looks like this: + +{include:file:examples/guides/getting\_started/05\_simple\_chat\_server\_step\_two.rb} + +While the code we added is very straightforward, we have to clarify one this first: subclasses of {EventMachine::Connection} are instantiated by +EventMachine for every new connected peer. So for 10 connected chat clients, there will be 10 separate `SimpleChatServer` instances in our +server process. Like any other objects, they can be stored in a collection, can provide public API other objects use, can instantiate or inject +dependencies and in general live a happy life all Ruby objects live until garbage collection happens. + +In the example above we use a @@class_variable to keep track of connected clients. In Ruby, @@class variables are accessible from instance +methods so we can add new connections to the list from `SimpleChatServer#post_init` and remove them in `SimpleChatServer#unbind`. We can also +filter connections by some criteria, as `SimpleChatServer#other_peers demonstrates`. + +So, we keep track of connections but how do we identify them? For a chat app, it's pretty common to use usernames for that. Lets ask our clients +to enter usernames when they connect. + + +### Step three: adding usernames ## + +To add usernames, we need to add a few things: + + * We need to invite newly connected clients to enter their username. + * A reader (getter) method on our {EventMachine::Connection} subclass. + * An idea of connection state (keeping track of whether a particular participant had entered username before). + +Here is one way to do it: + +{include:file:examples/guides/getting\_started/06\_simple\_chat\_server\_step\_three.rb} + +This is quite an update so lets take a look at each method individually. First, `SimpleChatServer#post_init`: + + def post_init + @username = nil + puts "A client has connected..." + ask_username + end + +To keep track of username we ask chat participants for, we add @username instance variable to our connection class. Connection +instances are just Ruby objects associated with a particular connected peer, so using @ivars is very natural. To make username +value accessible to other objects, we added a reader method that was not shown on the snippet above. + +Lets dig into `SimpleChatServer#ask_username`: + + def ask_username + self.send_line("[info] Enter your username:") + end # ask_username + + # ... + + def send_line(line) + self.send_data("#{line}\n") + end # send_line(line) + +Nothing new here, we are using {EventMachine::Connection#send_data} which we have seen before. + + +In `SimpleChatServer#receive_data` we now have to check if the username was entered or we need +to ask for it: + + def receive_data(data) + if entered_username? + handle_chat_message(data.strip) + else + handle_username(data.strip) + end + end + + # ... + + def entered_username? + !@username.nil? && !@username.empty? + end # entered_username? + +Finally, handler of chat messages is not yet implemented: + + def handle_chat_message(msg) + raise NotImplementedError + end + +Lets try this example out using Telnet: + + ~ telnet localhost 10000 + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + [info] Enter your username: + antares_ + [info] Ohai, antares_ + +and the server output: + + A client has connected... + antares_ has joined + +This version requires you to remember how to terminate your Telnet session (Ctrl + Shift + ], then Ctrl + C). +It is annoying, so why don't we add the same `exit` command to our chat server? + + +### Step four: adding exit command and delivering chat messages #### + +{include:file:examples/guides/getting\_started/07\_simple\_chat\_server\_step\_four.rb} + +TBD + +Lets test-drive this version. Client A: + + ~ telnet localhost 10000 + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + [info] Enter your username: + michael + [info] Ohai, michael + Hi everyone + michael: Hi everyone + joe has joined the room + # here ^^^ client B connects, lets greet him + hi joe + michael: hi joe + joe: hey michael + # ^^^ client B replies + exit + # ^^^ out command in action + Connection closed by foreign host. + +Client B: + + ~ telnet localhost 10000 + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + [info] Enter your username: + joe + [info] Ohai, joe + michael: hi joe + # ^^^ client A greets us, lets reply + hey michael + joe: hey michael + exit + # ^^^ out command in action + Connection closed by foreign host. + +And finally, the server output: + + A client has connected... + michael has joined + A client has connected... + _antares has joined + [info] _antares has left + [info] michael has left + +Our little char server now supports usernames, sending messages and the `exit` command. Next up, private (aka direct) messages. + + +### Step five: adding direct messages and one more command ### + +To add direct messages, we come up with a simple convention: private messages begin with @username and may have optional colon before +message text, like this: + + @joe: hey, how do you like eventmachine? + +This convention makes parsing of messages simple so that we can concentrate on delivering them to a particular client connection. +Remember when we added `username` reader on our connection class? That tiny change makes this step possible: when a new direct +message comes in, we extract username and message text and then find then connection for @username in question: + + # + # Message handling + # + + def handle_chat_message(msg) + if command?(msg) + self.handle_command(msg) + else + if direct_message?(msg) + self.handle_direct_message(msg) + else + self.announce(msg, "#{@username}:") + end + end + end # handle_chat_message(msg) + + def direct_message?(input) + input =~ DM_REGEXP + end # direct_message?(input) + + def handle_direct_message(input) + username, message = parse_direct_message(input) + + if connection = @@connected_clients.find { |c| c.username == username } + puts "[dm] @#{@username} => @#{username}" + connection.send_line("[dm] @#{@username}: #{message}") + else + send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}" + end + end # handle_direct_message(input) + + def parse_direct_message(input) + return [$1, $2] if input =~ DM_REGEXP + end # parse_direct_message(input) + +This snippet demonstrates how one connection instance can obtain another connection instance and send data to it. +This is a very powerful feature, consider just a few use cases: + + * Peer-to-peer protocols + * Content-aware routing + * Efficient streaming with optional filtering + +Less common use cases include extending C++ core of EventMachine to provide access to hardware that streams events that +can be re-broadcasted to any interested parties connected via TCP, UDP or something like AMQP or WebSockets. With this, +sky is the limit. Actually, EventMachine has several features for efficient proxying data between connections. +We will not cover them in this guide. + +One last feature that we are going to add to our chat server is the `status` command that tells you current server time and how many people +are there in the chat room: + + # + # Commands handling + # + + def command?(input) + input =~ /(exit|status)$/i + end # command?(input) + + def handle_command(cmd) + case cmd + when /exit$/i then self.close_connection + when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room") + end + end # handle_command(cmd) + +Hopefully this piece of code is easy to follow. Try adding a few more commands, for example, the `whoishere` command that lists people +currently in the chat room. + +In the end, our chat server looks like this: + +{include:file:examples/guides/getting\_started/08\_simple\_chat\_server\_step\_five.rb} + +We are almost done with the server but there are some closing thoughts. + + +### Step six: final version ### + +Just in case, here is the final version of the chat server code we have built: + +{include:file:examples/guides/getting\_started/03\_simple\_chat\_server.rb} + + +### Step seven: future directions and some closing thoughts ### + +The chat server is just about 150 lines of Ruby including empty lines and comments, yet it has a few features most of chat server +examples never add. We did not, however, implement many other features that popular IRC clients like [Colloquy](http://colloquy.info) have: + + * Chat moderation + * Multiple rooms + * Connection timeout detection + +How would one go about implementing them? We thought it is worth discussing what else EventMachine has to offer and what ecosystem projects +one can use to build a really feature-rich Web-based IRC chat client. + +With multiple rooms it's more or less straightforward, just add one more hash and a bunch of commands and use the information about which rooms participant +is in when you are delivering messages. There is nothing in EventMachine itself that can make the job much easier for developer. + +To implement chat moderation feature you may want to do a few things: + + * Work with client IP addresses. Maybe we want to consider everyone who connects from certain IPs a moderator. + * Access persistent data about usernames of moderators and their credentials. + +Does EventMachine have anything to offer here? It does. To obtain peer IP address, take a look at {EventMachine::Connection#get_peername}. The name of this method is +a little bit misleading and originates from low-level socket programming APIs. + +#### A whirlwind tour of the EventMachine ecosystem #### + +To work with data stores you can use several database drivers that ship with EventMachine itself, however, quite often there are some 3rd party projects in +the EventMachine ecosystem that have more features, are faster or just better maintained. So we figured it will be helpful to provide a few pointers +to some of those projects: + + * For MySQL, check out [em-mysql](https://github.com/eventmachine/em-mysql) project. + * For PostgreSQL, have a look at Mike Perham's [EventMachine-based PostgreSQL driver](https://github.com/mperham/em_postgresql). + * For Redis, there is a young but already popular [em-hiredis](https://github.com/mloughran/em-hiredis) library that combines EventMachine's non-blocking I/O with + extreme performance of the official Redis C client, [hiredis](https://github.com/antirez/hiredis). + * For MongoDB, see [em-mongo](https://github.com/bcg/em-mongo) + * For Cassandra, Mike Perham [added transport agnosticism feature](http://www.mikeperham.com/2010/02/09/cassandra-and-eventmachine/) to the [cassandra gem](https://rubygems.org/gems/cassandra). + +[Riak](http://www.basho.com/products_riak_overview.php) and CouchDB talk HTTP so it's possible to use [em-http-request](https://github.com/igrigorik/em-http-request). +If you are aware of EventMachine-based non-blocking drivers for these databases, as well as for HBase, let us know on the [EventMachine mailing list](http://groups.google.com/group/eventmachine). +Also, EventMachine supports TLS (aka SSL) and works well on [JRuby](http://jruby.org) and Windows. + +Learn more in our {file:docs/Ecosystem.md EventMachine ecosystem} and {file:docs/TLS.md TLS (aka SSL)} guides. + + +#### Connection loss detection #### + +Finally, connection loss detection. When our chat participant closes her laptop lid, how do we know that she is no longer active? The answer is, when EventMachine +detects TCP connectin closure, it calls {EventMachine::Connection#unbind}. Version 1.0.beta3 and later also pass an optional argument to that method. The argument +indicates what error (if any) caused the connection to be closed. + +Learn more in our {file:docs/ConnectionFailureAndRecovery.md Connection Failure and Recovery} guide. + + +#### What the Chat Server Example doesn't demonstrate #### + +This chat server also leaves out something production quality clients and servers must take care of: buffering. We intentionally did not include any buffering in +our chat server example: it would only distract you from learning what you really came here to learn: how to use EventMachine to build blazing fast asynchronous +networking programs quickly. However, {EventMachine::Connection#receive_data} does not offer any guarantees that you will be receiving "whole messages" all the time, +largely because the underlying transport (UDP or TCP) does not offer such guarantees. Many protocols, for example, AMQP, mandate that large content chunks are +split into smaller _frames_ of certain size. This means that [amq-client](https://github.com/ruby-amqp/amq-client) library, for instance, that has EventMachine-based driver, +has to deal with figuring out when exactly we received "the whole message". To do so, it uses buffering and employs various checks to detect _frame boundaries_. +So **don't be deceived by the simplicity of this chat example**: it intentionally leaves framing out, but real world protocols usually require it. + + + +## A (Proxying) Chat Client Example ## + +TBD + + +## Wrapping up ## + +This tutorial ends here. Congratulations! You have learned quite a bit about EventMachine. + + +## What to read next ## + +The documentation is organized as a {file:docs/DocumentationGuidesIndex.md number of guides}, covering all kinds of +topics. TBD + + +## Tell us what you think! ## + +Please take a moment and tell us what you think about this guide on the [EventMachine mailing list](http://bit.ly/jW3cR3) +or in the #eventmachine channel on irc.freenode.net: what was unclear? What wasn't covered? +Maybe you don't like the guide style or the grammar and spelling are incorrect? Reader feedback is +key to making documentation better. diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/ChangeLog b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/ChangeLog new file mode 100644 index 0000000..c7a6c48 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/ChangeLog @@ -0,0 +1,211 @@ +01Oct06: Replaced EventMachine#open_datagram_server with a version that can + take a Class or a Module, instead of just a Module. Thanks to Tobias + Gustafsson for pointing out the missing case. +04Oct06: Supported subsecond timer resolutions, per request by Jason Roelofs. +05Oct06: Added EventMachine#set_quantum, which sets the timer resolution. +15Nov06: Added Connection#set_comm_inactivity_timeout. +15Nov06: Checked in a Line-and-Text Protocol Handler. +18Nov06: Checked in a Header-and-Body Protocol Handler. +22Nov06: Changed EventMachine#reconnect: no longer excepts when called on an + already-connected handler. +28Nov06: Supported a binary-unix gem. +19Dec06: Added EventMachine#set_effective_user. +05Jan07: Upped max outstanding timers to 1000. +15May07: Applied Solaris patches from Brett Eisenberg +22May07: Cleaned up the license text in all the source files. +22May07: Released version 0.7.2 + +23May07: Per suggestion from Bill Kelly, fixed a bug with the initialization + of the network libraries under Windows. The goal is to enable EM to + be used without Ruby. +28May07: Applied patch from Bill Kelly, refactors the declarations of + event names to make EM easier to use from C programs without Ruby. +31May07: Added a preliminary implementation of EventMachine#popen. +01Jun07: Added EM, a "pseudo-alias" for EventMachine. +01Jun07: Added EM#next_tick. +01Jun07: Added EM::Connection#get_outbound_data_size +05Jun07: Removed the code which loads a pure-Ruby EM library in case the + compiled extension is unavailable. Suggested by Moshe Litvin. +06Jun07: Preliminary epoll implementation. +12Jun07: Added an evented popen implementation that, like Ruby's, is + full-duplex and makes the subprocess PID available to the caller. +06Jul07: Performance-tweaked the callback dispatcher in eventmachine.rb. +10Jul07: Released version 0.8.0. +12Jul07: Applied patches from Tim Pease to fix Solaris build problems. +15Jul07: Created a new provisional source branch, experiments/jruby-1. + This is a preliminary implementation of the EM reactor in Java, + suitable for use with JRuby. +17Jul07: Added EventMachine#stop_server, per request from Kirk Haines, + and associated unit tests. +22Jul07: Added EventMachine#stream_file_data. This is a very fast and scalable + way of sending data from static files over network connections. It + has separate implementations for small files and large file, and + has tunings to minimize memory consumption. +26Jul07: Added some patches by Kirk Haines to improve the behavior of + EM::Connection#send_file_data_to_connection. +26Jul07: Added a C++ module for directly integrating EM into C++ programs + with no Ruby dependencies. Needs example code. +29Jul07: Added EventMachine::Protocols::LineText2. +29Jul07: Added EventMachine::Protocols::Stomp. +30Jul07: Added sys/stat.h to project.h to fix compilation bug on Darwin. +13Aug07: Added EventMachine#reactor_running? +15Aug07: Added parameters for EventMachine::Connection:start_tls that can be + used to specify client-side private keys and certificates. +17Aug07: Added EventMachine#run_block, a sugaring for a common use case. +24Aug07: Added a preliminary keyboard handler. Needs docs and testing on + windows. +26Aug07: Created EventMachine::Spawnable, an implementation of Erlang-like + processes. +27Aug07: Silenced some -w warnings, requested by James Edward Gray II. +30Aug07: Added cookies to EM::HttpClient#request. +04Sep07: Added an initial implementation of an evented SMTP client. +04Sep07: Added an initial implementation of an evented SMTP server. +10Sep07: Changed EM#spawn to run spawned blocks in the context of the + SpawnedProcess object, not of whatever was the active object at the + time of the spawn. +14Sep07: Heartbeats weren't working with EPOLL. Noticed by Brian Candler. +15Sep07: Added some features, tests and documents to Deferrable. +16Sep07: Added [:content] parameter to EM::Protocols::SmtpClient#send. +16Sep07: Bumped version to 0.9.0 in anticipation of a release. +18Sep07: Released version 0.9.0. +19Sep07: Added #receive_reset to EM::Protocols::SmtpServer. +19Sep07: User overrides of EM::Protocols::SmtpServer#receive_recipient can now + return a Deferrable. Also fixed bug: SmtpClient now raises a protocol + error if none of its RCPT TO: commands are accepted by the server. +26Sep07: Fixed missing keyboard support for Windows. +03Oct07: Added a default handler for RuntimeErrors emitted from user-written + code. Suggested by Brian Candler. +19Oct07: Set the SO_BROADCAST option automatically on all UDP sockets. +10Nov07: Forced integer conversion of send_datagram's port parameter. +Suggested by Matthieu Riou. +12Nov07: Added saslauth.rb, a protocol module to replace the Cyrus SASL +daemons saslauthd and pwcheck. +15Nov07: Fixed bug reported by Mark Zvillius. We were failing to dispatch + zero-length datagrams under certain conditions. +19Nov07: Added EventMachine#set_max_timers. Requested by Matthieu Riou and + others. +19Nov07: Fixed bug with EM::Connection#start_tls. Was not working with server + connections. Reported by Michael S. Fischer. +26Nov07: Supported a hack for EventMachine#popen so it can return an exit + status from subprocesses. Requested by Michael S. Fischer. +30Nov07: Changed Pipe descriptors so that the child-side of the socketpair is + NOT set nonblocking. Suggested by Duane Johnson. +05Dec07: Re-enabled the pure-Ruby implementation. +06Dec07: Released Version 0.10.0. +13Dec07: Added EM::DeferrableChildProcess +24Dec07: Added a SASL client for simple password authentication. +27Dec07: Removed the hookable error handler. No one was using it and it significantly + degraded performance. +30Dec07: Implemented Kqueue support for OSX and BSD. +04Jan08: Fixed bug in epoll ("Bad file descriptor"), patch supplied by Chris + Heath. +04Jan08: Fixed bug reported by Michael S. Fischer. We were terminating + SSL connections that sent data before the handshake was complete. +08Jan08: Added an OpenBSD branch for extconf.rb, contributed by Guillaume + Sellier. +19Jan08: Added EM::Connection::get_sockname per request by Michael Fischer. +19Jan08: Supported IPv6 addresses. +30Apr08: Set the NODELAY option on sockets that we connect to other servers. + Omission noted by Roger Pack. +14May08: Generated a 0.12 release. +15May08: Supported EM#get_sockname for acceptors (TCP server sockets). + Requested by Roger Pack. +15May08; Accepted a patch from Dan Aquino that allows the interval of a + PeriodicTimer to be changed on the fly. +15Jun08: Supported nested calls to EM#run. Many people contributed ideas to + this, notably raggi and tmm1. +20Jul08: Accepted patch from tmm1 for EM#fork_reactor. +28Jul08: Added a Postgres3 implementation, written by FCianfrocca. +14Aug08: Added a patch by Mike Murphy to support basic auth in the http +client. +28Aug08: Added a patch by tmm1 to fix a longstanding problem with Java +data-sends. +13Sep08: Added LineText2#set_binary_mode, a back-compatibility alias. +13Sep08: Modified the load order of protocol libraries in eventmachine.rb + to permit a modification of HeaderAndContentProtocol. +13Sep08: Modified HeaderAndContent to use LineText2, which is less buggy + than LineAndTextProtocol. This change may be reversed if we can fix + the bugs in buftok. +13Sep08: Improved the password handling in the Postgres protocol handler. +15Sep08: Added attach/detach, contributed by Aman Gupta (tmm1) and Riham Aldakkak, + to support working with file descriptors not created in the reactor. +16Sep08: Added an optional version string to the HTTP client. This is a hack + that allows a client to specify a version 1.0 request, which + keeps the server from sending a chunked response. The right way to + solve this, of course, is to support chunked responses. +23Sep08: ChangeLog Summary for Merge of branches/raggi +Most notable work and patches by Aman Gupta, Roger Pack, and James Tucker. +Patches / Tickets also submitted by: Jeremy Evans, aanand, darix, mmmurf, +danielaquino, macournoyer. + - Moved docs into docs/ dir + - Major refactor of rakefile, added generic rakefile helpers in tasks + - Added example CPP build rakefile in tasks/cpp.rake + - Moved rake tests out to tasks/tests.rake + - Added svn ignores where appropriate + - Fixed jruby build on older java platforms + - Gem now builds from Rakefile rather than directly via extconf + - Gem unified for jruby, C++ and pure ruby. + - Correction for pure C++ build, removing ruby dependency + - Fix for CYGWIN builds on ipv6 + - Major refactor for extconf.rb + - Working mingw builds + - extconf optionally uses pkg_config over manual configuration + - extconf builds for 1.9 on any system that has 1.9 + - extconf no longer links pthread explicitly + - looks for kqueue on all *nix systems + - better error output on std::runtime_error, now says where it came from + - Fixed some tests on jruby + - Added test for general send_data flaw, required for a bugfix in jruby build + - Added timeout to epoll tests + - Added fixes for java reactor ruby api + - Small addition of some docs in httpclient.rb and httpcli2.rb + - Some refactor and fixes in smtpserver.rb + - Added parenthesis where possible to avoid excess ruby warnings + - Refactor of $eventmachine_library logic for accuracy and maintenance, jruby + - EM::start_server now supports unix sockets + - EM::connect now supports unix sockets + - EM::defer @threadqueue now handled more gracefully + - Added better messages on exceptions raised + - Fix edge case in timer fires + - Explicitly require buftok.rb + - Add protocols to autoload, rather than require them all immediately + - Fix a bug in pr_eventmachine for outbound_q + - Refactors to take some of the use of defer out of tests. + - Fixes in EM.defer under start/stop conditions. Reduced scope of threads. +23Sep08: Added patch from tmm1 to avoid popen errors on exit. +30Sep08: Added File.exists? checks in the args for start_tls, as suggested by + Brian Lopez (brianmario). +10Nov08: ruby 1.9 compatibility enhancements +28Nov08: Allow for older ruby builds where RARRAY_LEN is not defined +03Dec08: allow passing arguments to popen handlers +13Jan09: SSL support for httpclient2 (David Smalley) +22Jan09: Fixed errors on OSX with the kqueue reactor, fixed errors in the pure + ruby reactor. Added EM.current_time. Added EM.epoll? and EM.kqueue? +27Jan09: Reactor errors are now raised as ruby RuntimeErrors. +28Jan09: Documentation patch from alloy +29Jan09: (Late sign-off) Use a longer timeout for connect_server (Ilya + Grigorik) +07Feb09: Fix signal handling issues with threads+epoll +07Feb09: Use rb_thread_schedule in the epoll reactor +07Feb09: Use TRAP_BEG/END and rb_thread_schedule in kqueue reactor +08Feb09: Added fastfilereader from swiftiply +08Feb09: 1.9 fix for rb_trap_immediate +08Feb09: Enable rb_thread_blocking_region for 1.9.0 and 1.9.1 +10Feb09: Support win32 builds for fastfilereader +10Feb09: Added a new event to indicate completion of SSL handshake on TCP + connections +10Feb09: Working get_peer_cert method. Returns the certificate as a Ruby + String in PEM format. (Jake Douglas) +10Feb09: Added EM.get_max_timers +11Feb09: Fix compile options for sun compiler (Alasdairrr) +11Feb09: get_status returns a Process::Status object +12Feb09: Add EM::Protocols::Memcache with simple get/set functionality +19Feb09: Add catch-all EM.error_handler +20Feb09: Support miniunit (1.9) +20Feb09: Return success on content-length = 0 instead of start waiting forever + (Ugo Riboni) +25Feb09: Allow next_tick to be used to pre-schedule reactor operations before + EM.run +26Feb09: Added EM.get_connection_count +01Mar09: Switch back to extconf for compiling gem extensions +01Mar09: fixed a small bug with basic auth (mmmurf) diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/DEFERRABLES b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/DEFERRABLES new file mode 100644 index 0000000..6e8856c --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/DEFERRABLES @@ -0,0 +1,246 @@ +EventMachine (EM) adds two different formalisms for lightweight concurrency +to the Ruby programmer's toolbox: spawned processes and deferrables. This +note will show you how to use deferrables. For more information, see the +separate document LIGHTWEIGHT_CONCURRENCY. + +=== What are Deferrables? + +EventMachine's Deferrable borrows heavily from the "deferred" object in +Python's "Twisted" event-handling framework. Here's a minimal example that +illustrates Deferrable: + + require 'eventmachine' + + class MyClass + include EM::Deferrable + + def print_value x + puts "MyClass instance received #{x}" + end + end + + EM.run { + df = MyClass.new + df.callback {|x| + df.print_value(x) + EM.stop + } + + EM::Timer.new(2) { + df.set_deferred_status :succeeded, 100 + } + } + + +This program will spin for two seconds, print out the string "MyClass +instance received 100" and then exit. The Deferrable pattern relies on +an unusual metaphor that may be unfamiliar to you, unless you've used +Python's Twisted. You may need to read the following material through +more than once before you get the idea. + +EventMachine::Deferrable is simply a Ruby Module that you can include +in your own classes. (There also is a class named +EventMachine::DefaultDeferrable for when you want to create one without +including it in code of your own.) + +An object that includes EventMachine::Deferrable is like any other Ruby +object: it can be created whenever you want, returned from your functions, +or passed as an argument to other functions. + +The Deferrable pattern allows you to specify any number of Ruby code +blocks (callbacks or errbacks) that will be executed at some future time +when the status of the Deferrable object changes. + +How might that be useful? Well, imagine that you're implementing an HTTP +server, but you need to make a call to some other server in order to fulfill +a client request. + +When you receive a request from one of your clients, you can create and +return a Deferrable object. Some other section of your program can add a +callback to the Deferrable that will cause the client's request to be +fulfilled. Simultaneously, you initiate an event-driven or threaded client +request to some different server. And then your EM program will continue to +process other events and service other client requests. + +When your client request to the other server completes some time later, you +will call the #set_deferred_status method on the Deferrable object, passing +either a success or failure status, and an arbitrary number of parameters +(which might include the data you received from the other server). + +At that point, the status of the Deferrable object becomes known, and its +callback or errback methods are immediately executed. Callbacks and errbacks +are code blocks that are attached to Deferrable objects at any time through +the methods #callback and #errback. + +The deep beauty of this pattern is that it decouples the disposition of one +operation (such as a client request to an outboard server) from the +subsequent operations that depend on that disposition (which may include +responding to a different client or any other operation). + +The code which invokes the deferred operation (that will eventually result +in a success or failure status together with associated data) is completely +separate from the code which depends on that status and data. This achieves +one of the primary goals for which threading is typically used in +sophisticated applications, with none of the nondeterminacy or debugging +difficulties of threads. + +As soon as the deferred status of a Deferrable becomes known by way of a call +to #set_deferred_status, the Deferrable will IMMEDIATELY execute all of its +callbacks or errbacks in the order in which they were added to the Deferrable. + +Callbacks and errbacks can be added to a Deferrable object at any time, not +just when the object is created. They can even be added after the status of +the object has been determined! (In this case, they will be executed +immediately when they are added.) + +A call to Deferrable#set_deferred_status takes :succeeded or :failed as its +first argument. (This determines whether the object will call its callbacks +or its errbacks.) #set_deferred_status also takes zero or more additional +parameters, that will in turn be passed as parameters to the callbacks or +errbacks. + +In general, you can only call #set_deferred_status ONCE on a Deferrable +object. A call to #set_deferred_status will not return until all of the +associated callbacks or errbacks have been called. If you add callbacks or +errbacks AFTER making a call to #set_deferred_status, those additional +callbacks or errbacks will execute IMMEDIATELY. Any given callback or +errback will be executed AT MOST once. + +It's possible to call #set_deferred_status AGAIN, during the execution a +callback or errback. This makes it possible to change the parameters which +will be sent to the callbacks or errbacks farther down the chain, enabling +some extremely elegant use-cases. You can transform the data returned from +a deferred operation in arbitrary ways as needed by subsequent users, without +changing any of the code that generated the original data. + +A call to #set_deferred_status will not return until all of the associated +callbacks or errbacks have been called. If you add callbacks or errbacks +AFTER making a call to #set_deferred_status, those additional callbacks or +errbacks will execute IMMEDIATELY. + +Let's look at some more sample code. It turns out that many of the internal +protocol implementations in the EventMachine package rely on Deferrable. One +of these is EM::Protocols::HttpClient. + +To make an evented HTTP request, use the module function +EM::Protocols::HttpClient#request, which returns a Deferrable object. +Here's how: + + require 'eventmachine' + + EM.run { + df = EM::Protocols::HttpClient.request( :host=>"www.example.com", + :request=>"/index.html" ) + + df.callback {|response| + puts "Succeeded: #{response[:content]}" + EM.stop + } + + df.errback {|response| + puts "ERROR: #{response[:status]}" + EM.stop + } + } + +(See the documentation of EventMachine::Protocols::HttpClient for information +on the object returned by #request.) + +In this code, we make a call to HttpClient#request, which immediately returns +a Deferrable object. In the background, an HTTP client request is being made +to www.example.com, although your code will continue to run concurrently. + +At some future point, the HTTP client request will complete, and the code in +EM::Protocols::HttpClient will process either a valid HTTP response (including +returned content), or an error. + +At that point, EM::Protocols::HttpClient will call +EM::Deferrable#set_deferred_status on the Deferrable object that was returned +to your program, as the return value from EM::Protocols::HttpClient.request. +You don't have to do anything to make this happen. All you have to do is tell +the Deferrable what to do in case of either success, failure, or both. + +In our code sample, we set one callback and one errback. The former will be +called if the HTTP call succeeds, and the latter if it fails. (For +simplicity, we have both of them calling EM#stop to end the program, although +real programs would be very unlikely to do this.) + +Setting callbacks and errbacks is optional. They are handlers to defined +events in the lifecycle of the Deferrable event. It's not an error if you +fail to set either a callback, an errback, or both. But of course your +program will then fail to receive those notifications. + +If through some bug it turns out that #set_deferred_status is never called +on a Deferrable object, then that object's callbacks or errbacks will NEVER +be called. It's also possible to set a timeout on a Deferrable. If the +timeout elapses before any other call to #set_deferred_status, the Deferrable +object will behave as is you had called set_deferred_status(:failed) on it. + + +Now let's modify the example to illustrate some additional points: + + require 'eventmachine' + + EM.run { + df = EM::Protocols::HttpClient.request( :host=>"www.example.com", + :request=>"/index.html" ) + + df.callback {|response| + df.set_deferred_status :succeeded, response[:content] + } + + df.callback {|string| + puts "Succeeded: #{string}" + EM.stop + } + + df.errback {|response| + puts "ERROR: #{response[:status]}" + EM.stop + } + } + + +Just for the sake of illustration, we've now set two callbacks instead of +one. If the deferrable operation (the HTTP client-request) succeeds, then +both of the callbacks will be executed in order. + +But notice that we've also made our own call to #set_deferred_status in the +first callback. This isn't required, because the HttpClient implementation +already made a call to #set_deferred_status. (Otherwise, of course, the +callback would not be executing.) + +But we used #set_deferred_status in the first callback in order to change the +parameters that will be sent to subsequent callbacks in the chain. In this +way, you can construct powerful sequences of layered functionality. If you +want, you can even change the status of the Deferrable from :succeeded to +:failed, which would abort the chain of callback calls, and invoke the chain +of errbacks instead. + +Now of course it's somewhat trivial to define two callbacks in the same +method, even with the parameter-changing effect we just described. It would +be much more interesting to pass the Deferrable to some other function (for +example, a function defined in another module or a different gem), that would +in turn add callbacks and/or errbacks of its own. That would illustrate the +true power of the Deferrable pattern: to isolate the HTTP client-request +from other functions that use the data that it returns without caring where +those data came from. + +Remember that you can add a callback or an errback to a Deferrable at any +point in time, regardless of whether the status of the deferred operation is +known (more precisely, regardless of when #set_deferred_status is called on +the object). Even hours or days later. + +When you add a callback or errback to a Deferrable object on which +#set_deferred_status has not yet been called, the callback/errback is queued +up for future execution, inside the Deferrable object. When you add a +callback or errback to a Deferrable on which #set_deferred_status has +already been called, the callback/errback will be executed immediately. +Your code doesn't have to worry about the ordering, and there are no timing +issues, as there would be with a threaded approach. + +For more information on Deferrables and their typical usage patterns, look +in the EM unit tests. There are also quite a few sugarings (including +EM::Deferrable#future) that make typical Deferrable usages syntactically +easier to work with. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/EPOLL b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/EPOLL new file mode 100644 index 0000000..13cea8f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/EPOLL @@ -0,0 +1,141 @@ +EventMachine now supports epoll, bringing large increases in performance and scalability to Ruby programs. + +Epoll(7) is a alternative mechanism for multiplexed I/O that is available in Linux 2.6 kernels. +It features significantly greater performance than the standard select(2) mechanism, when used in +applications that require very large numbers of open I/O descriptors. + +EventMachine has always used select(2) because its behavior is well standardized and broadly supported. +But select becomes unreasonably slow when a program has a +very large number of file descriptors or sockets. Ruby's version of select hardcodes a limit +of 1024 descriptors per process, but heavily loaded processes will start to show performance +degradation even after only a few hundred descriptors are in use. + +Epoll is an extended version of the poll(2) call, and it solves the problems with select. Programs +based on epoll can easily scale past Ruby's 1024-descriptor limit, potentially to tens of thousands +of connectors, with no significant impact on performance. + +(Another alternative which is very similar to epoll in principle is kqueue, supplied on BSD and its +variants.) + + + +This note shows you how to use epoll in your programs. + +=== Compiling EventMachine to use epoll. + +You don't have to do anything to get epoll support in EventMachine. +When you compile EventMachine on a platform that supports epoll, EM will +automatically generate a Makefile that includes epoll. (At this writing, this will only work +on Linux 2.6 kernels.) If you compile EM on a platform without epoll, then epoll support will +be omitted from the Makefile, and EM will work just as it always has. + +=== Using epoll in your programs. + +First, you need to tell EventMachine to use epoll instead of select (but see below, as this requirement +will be removed in a future EventMachine version). Second, you need to prepare your program to use +more than 1024 descriptors, an operation that generally requires superuser privileges. Third, you will probably +want your process to drop the superuser privileges after you increase your process's descriptor limit. + +=== Using EventMachine#epoll + +Call the method EventMachine#epoll anytime before you call EventMachine#run, and your program will +automatically use epoll, if available. It's safe to call EventMachine#epoll on any platform because +it compiles to a no-op on platforms that don't support epoll. + + require 'rubygems' + require 'eventmachine' + + EM.epoll + EM.run { + ... + } + + +EventMachine#epoll was included in this initial release only to avoid changing the behavior of existing +programs. However, it's expected that a future release of EM will convert EventMachine#epoll to a no-op, +and run epoll by default on platforms that support it. + +=== Using EventMachine#set_descriptor_table_size + +In Linux (as in every Unix-like platform), every process has a internal table that determines the maximum +number of file and socket descriptors you may have open at any given time. The size of this table is +generally fixed at 1024, although it may be increased within certain system-defined hard and soft limits. + +If you want your EventMachine program to support more than 1024 total descriptors, you must use +EventMachine#set_descriptor_table_size, as follows: + + require 'rubygems' + require 'eventmachine' + + new_size = EM.set_descriptor_table_size( 60000 ) + $>.puts "New descriptor-table size is #{new_size}" + + EM.run { + ... + } + +If successful, this example will increase the maximum number of descriptors that epoll can use to 60,000. +Call EventMachine#set_descriptor_table_size without an argument at any time to find out the current +size of the descriptor table. + +Using EventMachine#set_descriptor_table_size ONLY affects the number of descriptors that can be used +by epoll. It has no useful effect on platforms that don't support epoll, and it does NOT increase the +number of descriptors that Ruby's own I/O functions can use. + +#set_descriptor_table_size can fail if your process is not running as superuser, or if you try to set a +table size that exceeds the hard limits imposed by your system. In the latter case, try a smaller number. + + +=== Using EventMachine#set_effective_user + +In general, you must run your program with elevated or superuser privileges if you want to increase +your descriptor-table size beyond 1024 descriptors. This is easy enough to verify. Try running the +sample program given above, that increases the descriptor limit to 60,000. You will probably find that +the table size will not be increased if you don't run your program as root or with elevated privileges. + +But of course network servers, especially long-running ones, should not run with elevated privileges. +You will want to drop superuser privileges as soon as possible after initialization. To do this, +use EventMachine#set_effective_user: + + require 'rubygems' + require 'eventmachine' + + # (Here, program is running as superuser) + + EM.set_descriptor_table_size( 60000 ) + EM.set_effective_user( "nobody" ) + # (Here, program is running as nobody) + + EM.run { + ... + } + +Of course, you will need to replace "nobody" in the example with the name of an unprivileged user +that is valid on your system. What if you want to drop privileges after opening a server socket +on a privileged (low-numbered) port? Easy, just call #set_effective_user after opening your sockets: + + require 'rubygems' + require 'eventmachine' + + # (Here, program is running as superuser) + + EM.set_descriptor_table_size( 60000 ) + + EM.run { + EM.start_server( "0.0.0.0", 80, MyHttpServer ) + EM.start_server( "0.0.0.0", 443, MyEncryptedHttpServer ) + + EM.set_effective_user( "nobody" ) + # (Here, program is running as nobody) + + ... + } + + +Because EventMachine#set_effective_user is used to enforce security +requirements, it has no nonfatal errors. If you try to set a nonexistent or invalid effective user, +#set_effective_user will abort your program, rather than continue to run with elevated privileges. + +EventMachine#set_effective_user is a silent no-op on platforms that don't support it, such as Windows. + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/INSTALL b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/INSTALL new file mode 100644 index 0000000..dee2e42 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/INSTALL @@ -0,0 +1,13 @@ +If you have obtained an EventMachine source-tarball (.tar.gz): +unzip and untar the tarball, and enter the directory that is +created. In that directory, say: +ruby setup.rb +(You may need to be root to execute this command.) + +To create documentation for EventMachine, simply type: +rake rdoc +in the distro directory. Rdocs will be created in subdirectory rdoc. + +If you have obtained a gem version of EventMachine, install it in the +usual way (gem install eventmachine). You may need superuser privileges +to execute this command. diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/KEYBOARD b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/KEYBOARD new file mode 100644 index 0000000..6c699e4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/KEYBOARD @@ -0,0 +1,42 @@ +EventMachine (EM) can respond to keyboard events. This gives your event-driven +programs the ability to respond to input from local users. + +Programming EM to handle keyboard input in Ruby is simplicity itself. Just use +EventMachine#open_keyboard, and supply the name of a Ruby module or class that +will receive the input: + + require 'rubygems' + require 'eventmachine' + + module MyKeyboardHandler + def receive_data keystrokes + puts "I received the following data from the keyboard: #{keystrokes}" + end + end + + EM.run { + EM.open_keyboard(MyKeyboardHandler) + } + +If you want EM to send line-buffered keyboard input to your program, just +include the LineText2 protocol module in your handler class or module: + + require 'rubygems' + require 'eventmachine' + + module MyKeyboardHandler + include EM::Protocols::LineText2 + def receive_line data + puts "I received the following line from the keyboard: #{data}" + end + end + + EM.run { + EM.open_keyboard(MyKeyboardHandler) + } + +As we said, simplicity itself. You can call EventMachine#open_keyboard at any +time while the EM reactor loop is running. In other words, the method +invocation may appear anywhere in an EventMachine#run block, or in any code +invoked in the #run block. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LEGAL b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LEGAL new file mode 100644 index 0000000..ee01825 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LEGAL @@ -0,0 +1,25 @@ +LEGAL NOTICE INFORMATION +------------------------ + +EventMachine is Copyright (C) 2006-07 by Francis Cianfrocca. + +EventMachine is copyrighted software owned by Francis Cianfrocca +(blackhedd ... gmail.com). You may redistribute and/or modify this +software as long as you comply with either the terms of the GPL +(see the file GPL), or Ruby's license (see the file COPYING). + +Your use of all the files in this distribution is controlled by these +license terms, except for those files specifically mentioned below: + + + +setup.rb + This file is Copyright (C) 2000-2005 by Minero Aoki + You can distribute/modify this file under the terms of + the GNU LGPL, Lesser General Public License version 2.1. + + +lib/em/buftok.rb + This file is Copyright (C) 2007 by Tony Arcieri. This file is + covered by the terms of Ruby's License (see the file COPYING). + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LIGHTWEIGHT_CONCURRENCY b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LIGHTWEIGHT_CONCURRENCY new file mode 100644 index 0000000..3c2cfa0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/LIGHTWEIGHT_CONCURRENCY @@ -0,0 +1,130 @@ +EventMachine (EM) adds two different formalisms for lightweight concurrency to +the Ruby programmer's toolbox: spawned processes and deferrables. This note +will show you how to use them. + + +=== What is Lightweight Concurrency? + +We use the term "Lightweight Concurrency" (LC) to refer to concurrency +mechanisms that are lighter than Ruby threads. By "lighter," we mean: less +resource-intensive in one or more dimensions, usually including memory and +CPU usage. In general, you turn to LC in the hope of improving the +performance and scalability of your programs. + +In addition to the two EventMachine mechanisms we will discuss here, Ruby +has at least one other LC construct: Fibers, which are currently under +development in Ruby 1.9. + +The technical feature that makes all of these LC mechanisms different from +standard Ruby threads is that they are not scheduled automatically. + +When you create and run Ruby threads, you can assume (within certain +constraints) that your threads will all be scheduled fairly by Ruby's runtime. +Ruby itself is responsible for giving each of your threads its own share of +the total runtime. + +But with LC, your program is responsible for causing different execution +paths to run. In effect, your program has to act as a "thread scheduler." +Scheduled entities in LC run to completion and are never preempted. The +runtime system has far less work to do since it has no need to interrupt +threads or to schedule them fairly. This is what makes LC lighter and faster. + +You'll learn exactly how LC scheduling works in practice as we work through +specific examples. + + +=== EventMachine Lightweight Concurrency + +Recall that EM provides a reactor loop that must be running in order for +your programs to perform event-driven logic. An EM program typically has a +structure like this: + + require 'eventmachine' + + # your initializations + + EM.run { + # perform event-driven I/O here, including network clients, + # servers, timers, and thread-pool operations. + } + + # your cleanup + # end of the program + + +EventMachine#run executes the reactor loop, which causes your code to be +called as events of interest to your program occur. The block you pass to +EventMachine#run is executed right after the reactor loop starts, and is +the right place to start socket acceptors, etc. + +Because the reactor loop runs constantly in an EM program (until it is +stopped by a call to EventMachine#stop), it has the ability to schedule +blocks of code for asynchronous execution. Unlike a pre-emptive thread +scheduler, it's NOT able to interrupt code blocks while they execute. But +the scheduling capability it does have is enough to enable lightweight +concurrency. + + +For information on Spawned Processes, see the separate document +SPAWNED_PROCESSES. + +For information on Deferrables, see the separate document DEFERRABLES. + + +=== [SIDEBAR]: I Heard That EventMachine Doesn't Work With Ruby Threads. + +This is incorrect. EM is fully interoperable with all versions of Ruby +threads, and has been since its earliest releases. + +It's very true that EM encourages an "evented" (non-threaded) programming +style. The specific benefits of event-driven programming are far better +performance and scalability for well-written programs, and far easier +debugging. + +The benefit of using threads for similar applications is a possibly more +intuitive programming model, as well as the fact that threads are already +familiar to most programmers. Also, bugs in threaded programs often fail +to show up until programs go into production. These factors create the +illusion that threaded programs are easier to write. + +However, some operations that occur frequently in professional-caliber +applications simply can't be done without threads. (The classic example +is making calls to database client-libraries that block on network I/O +until they complete.) + +EventMachine not only allows the use of Ruby threads in these cases, but +it even provides a built-in thread-pool object to make them easier to +work with. + +You may have heard a persistent criticism that evented I/O is fundamentally +incompatible with Ruby threads. It is true that some well-publicized attempts +to incorporate event-handling libraries into Ruby were not successful. But +EventMachine was designed from the ground up with Ruby compatibility in mind, +so EM never suffered from the problems that defeated the earlier attempts. + + +=== [SIDEBAR]: I Heard That EventMachine Doesn't Work Very Well On Windows. + +This too is incorrect. EventMachine is an extension written in C++ and Java, +and therefore it requires compilation. Many Windows computers (and some Unix +computers, especially in production environments) don't have a build stack. +Attempting to install EventMachine on a machine without a compiler usually +produces a confusing error. + +In addition, Ruby has a much-debated issue with Windows compiler versions. +Ruby on Windows works best with Visual Studio 6, a compiler version that is +long out-of-print, no longer supported by Microsoft, and difficult to obtain. +(This problem is not specific to EventMachine.) + +Shortly after EventMachine was first released, the compiler issues led to +criticism that EM was incompatible with Windows. Since that time, every +EventMachine release has been supplied in a precompiled binary form for +Windows users, that does not require you to compile the code yourself. EM +binary Gems for Windows are compiled using Visual Studio 6. + +EventMachine does supply some advanced features (such as Linux EPOLL support, +reduced-privilege operation, UNIX-domain sockets, etc.) that have no +meaningful implementation on Windows. Apart from these special cases, all EM +functionality (including lightweight concurrency) works perfectly well on +Windows. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/PURE_RUBY b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/PURE_RUBY new file mode 100644 index 0000000..157d59e --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/PURE_RUBY @@ -0,0 +1,75 @@ +EventMachine is supplied in three alternative versions. + +1) A version that includes a Ruby extension written in C++. This version requires compilation; +2) A version for JRuby that contains a precompiled JAR file written in Java; +3) A pure Ruby version that has no external dependencies and can run in any Ruby environment. + +The Java version of EventMachine is packaged in a distinct manner and must be installed using a +special procedure. This version is described fully in a different document, and not considered +further here. + +The C++ and pure-Ruby versions, however, are shipped in the same distribution. You use the same +files (either tarball or Ruby gem) to install both of these versions. + +If you intend to use the C++ version, you must successfully compile EventMachine after you install it. +(The gem installation attempts to perform this step automatically.) + +If you choose not to compile the EventMachine C++ extension, or if your compilation fails for any +reason, you still have a fully-functional installation of the pure-Ruby version of EM. + +However, for technical reasons, a default EM installation (whether or not the compilation succeeds) +will always assume that the compiled ("extension") implementation should be used. + +If you want your EM program to use the pure Ruby version, you must specifically request it. There +are two ways to do this: by setting either a Ruby global variable, or an environment string. + +The following code will invoke the pure-Ruby implementation of EM: + + $eventmachine_library = :pure_ruby + require 'eventmachine' + + EM.library_type #=> "pure_ruby" + +Notice that this requires a code change and is not the preferred way to select pure Ruby, unless +for some reason you are absolutely sure you will never want the compiled implementation. + +Setting the following environment string has the same effect: + + export EVENTMACHINE_LIBRARY="pure_ruby" + +This technique gives you the flexibility to select either version at runtime with no code changes. + +Support + +The EventMachine development team has committed to support precisely the same APIs for all the +various implementations of EM. + +This means that you can expect any EM program to behave identically, whether you use pure Ruby, +the compiled C++ extension, or JRuby. Deviations from this behavior are to be considered bugs +and should be reported as such. + +There is a small number of exceptions to this rule, which arise from underlying platform +distinctions. Notably, EM#epoll is a silent no-op in the pure Ruby implementation. + + +When Should You Use the Pure-Ruby Implementation of EM? + + +Use the pure Ruby implementation of EM when you must support a platform for which no C++ compiler +is available, or on which the standard EM C++ code can't be compiled. + +Keep in mind that you don't need a C++ compiler in order to deploy EM applications that rely on +the compiled version, so long as appropriate C++ runtime libraries are available on the target platform. + +In extreme cases, you may find that you can develop software with the compiled EM version, but are +not allowed to install required runtime libraries on the deployment system(s). This would be another +case in which the pure Ruby implementation can be useful. + +In general you should avoid the pure Ruby version of EM when performance and scalability are important. +EM in pure Ruby will necessarily run slower than the compiled version. Depending on your application +this may or may not be a key issue. + +Also, since EPOLL is not supported in pure Ruby, your applications will be affected by Ruby's built-in +limit of 1024 file and socket descriptors that may be open in a single process. For maximum scalability +and performance, always use EPOLL if possible. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/RELEASE_NOTES b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/RELEASE_NOTES new file mode 100644 index 0000000..6110820 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/RELEASE_NOTES @@ -0,0 +1,94 @@ +RUBY/EventMachine RELEASE NOTES + +-------------------------------------------------- +Version: 0.9.0, released xxXXX07 +Added Erlang-like distributed-computing features + +-------------------------------------------------- +Version: 0.8.0, released 23Jun07 +Added an epoll implementation for Linux 2.6 kernels. +Added evented #popen. + +-------------------------------------------------- +Version: 0.7.3, released 22May07 +Added a large variety of small features. See the ChangeLog. + +-------------------------------------------------- +Version: 0.7.1, released xxNov06 +Added protocol handlers for line-oriented protocols. +Various bug fixes. + +-------------------------------------------------- +Version: 0.7.0, released 20Nov06 +Added a fix in em.cpp/ConnectToServer to fix a fatal exception that +occurred in FreeBSD when connecting successfully to a remote server. + +-------------------------------------------------- +Version: 0.6.0, released xxJul06 +Added deferred operations, suggested by Don Stocks, amillionhitpoints@yahoo.com. + +-------------------------------------------------- +Version: 0.5.4, released xxJun06 +Added get_peername support for streams and datagrams. + +-------------------------------------------------- +Version: 0.5.3, released 17May06 +Fixed bugs in extconf.rb, thanks to Daniel Harple, dharple@generalconsumption.org. +Added proper setup.rb and rake tasks, thanks to Austin Ziegler. +Fixed a handful of reported problems with builds on various platforms. + +-------------------------------------------------- +Version: 0.5.2, released 05May06 +Made several nonvisible improvements to the Windows +implementation. +Added an exception-handling patch contributed by Jeff Rose, jeff@rosejn.net. +Added a dir-config patch contributed anonymously. +Supported builds on Solaris. + +-------------------------------------------------- +Version: 0.5.1, released 05May06 +Made it possible to pass a Class rather than a Module +to a protocol handler. +Added Windows port. + +-------------------------------------------------- +Version: 0.5.0, released 30Apr06 +Added a preliminary SSL/TLS extension. This will probably +change over the next few releases. + +-------------------------------------------------- +Version: 0.4.5, released 29Apr06 +Changed ext files so the ruby.h is installed after unistd.h +otherwise it doesn't compile on gcc 4.1 + +-------------------------------------------------- +Version: 0.4.2, released 19Apr06 +Changed the Ruby-glue so the extension will play nicer +in the sandbox with Ruby threads. +Added an EventMachine::run_without_threads API to +switch off the thread-awareness for better performance +in programs that do not spin any Ruby threads. + +-------------------------------------------------- +Version: 0.4.1, released 15Apr06 +Reworked the shared-object interface to make it easier to +use EventMachine from languages other than Ruby. + +-------------------------------------------------- +Version: 0.3.2, released 12Apr06 +Added support for a user-supplied block in EventMachine#connect. + +-------------------------------------------------- +Version: 0.3.1, released 11Apr06 +Fixed bug that prevented EventMachine from being run multiple +times in a single process. + +-------------------------------------------------- +Version: 0.3.0, released 10Apr06 +Added method EventHandler::Connection::post_init + +-------------------------------------------------- +Version: 0.2.0, released 10Apr06 +Added method EventHandler::stop + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SMTP b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SMTP new file mode 100644 index 0000000..92bf311 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SMTP @@ -0,0 +1,4 @@ +This note details the usage of EventMachine's built-in support for SMTP. EM +supports both client and server connections, which will be described in +separate sections. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SPAWNED_PROCESSES b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SPAWNED_PROCESSES new file mode 100644 index 0000000..ee68e3e --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/SPAWNED_PROCESSES @@ -0,0 +1,148 @@ +EventMachine (EM) adds two different formalisms for lightweight concurrency +to the Ruby programmer's toolbox: spawned processes and deferrables. This +note will show you how to use spawned processes. For more information, see +the separate document LIGHTWEIGHT_CONCURRENCY. + + +=== What are Spawned Processes? + +Spawned Processes in EventMachine are inspired directly by the "processes" +found in the Erlang programming language. EM deliberately borrows much (but +not all) of Erlang's terminology. However, EM's spawned processes differ from +Erlang's in ways that reflect not only Ruby style, but also the fact that +Ruby is not a functional language like Erlang. + +Let's proceed with a complete, working code sample that we will analyze line +by line. Here's an EM implementation of the "ping-pong" program that also +appears in the Erlang tutorial: + + + require 'eventmachine' + + EM.run { + pong = EM.spawn {|x, ping| + puts "Pong received #{x}" + ping.notify( x-1 ) + } + + ping = EM.spawn {|x| + if x > 0 + puts "Pinging #{x}" + pong.notify x, self + else + EM.stop + end + } + + ping.notify 3 + } + +If you run this program, you'll see the following output: + + Pinging 3 + Pong received 3 + Pinging 2 + Pong received 2 + Pinging 1 + Pong received 1 + +Let's take it step by step. + +EventMachine#spawn works very much like the built-in function spawn in +Erlang. It returns a reference to a Ruby object of class +EventMachine::SpawnedProcess, which is actually a schedulable entity. In +Erlang, the value returned from spawn is called a "process identifier" or +"pid." But we'll refer to the Ruby object returned from EM#spawn simply as a +"spawned process." + +You pass a Ruby block with zero or more parameters to EventMachine#spawn. +Like all Ruby blocks, this one is a closure, so it can refer to variables +defined in the local context when you call EM#spawn. + +However, the code block passed to EM#spawn does NOT execute immediately by +default. Rather, it will execute only when the Spawned Object is "notified." +In Erlang, this process is called "message passing," and is done with the +operator !, but in Ruby it's done simply by calling the #notify method of a +spawned-process object. The parameters you pass to #notify must match those +defined in the block that was originally passed to EM#spawn. + +When you call the #notify method of a spawned-process object, EM's reactor +core will execute the code block originally passed to EM#spawn, at some point +in the future. (#notify itself merely adds a notification to the object's +message queue and ALWAYS returns immediately.) + +When a SpawnedProcess object executes a notification, it does so in the +context of the SpawnedProcess object itself. The notified code block can see +local context from the point at which EM#spawn was called. However, the value +of "self" inside the notified code block is a reference to the SpawnedProcesss +object itself. + +An EM spawned process is nothing more than a Ruby object with a message +queue attached to it. You can have any number of spawned processes in your +program without compromising scalability. You can notify a spawned process +any number of times, and each notification will cause a "message" to be +placed in the queue of the spawned process. Spawned processes with non-empty +message queues are scheduled for execution automatically by the EM reactor. +Spawned processes with no visible references are garbage-collected like any +other Ruby object. + +Back to our code sample: + + pong = EM.spawn {|x, ping| + puts "Pong received #{x}" + ping.notify( x-1 ) + } + +This simply creates a spawned process and assigns it to the local variable +pong. You can see that the spawned code block takes a numeric parameter and a +reference to another spawned process. When pong is notified, it expects to +receive arguments corresponding to these two parameters. It simply prints out +the number it receives as the first argument. Then it notifies the spawned +process referenced by the second argument, passing it the first argument +minus 1. + +And then the block ends, which is crucial because otherwise nothing else +can run. (Remember that in LC, scheduled entities run to completion and are +never preempted.) + +On to the next bit of the code sample: + + ping = EM.spawn {|x| + if x > 0 + puts "Pinging #{x}" + pong.notify x, self + else + EM.stop + end + } + +Here, we're spawning a process that takes a single (numeric) parameter. If +the parameter is greater than zero, the block writes it to the console. It +then notifies the spawned process referenced by the pong local variable, +passing as arguments its number argument, and a reference to itself. The +latter reference, as you saw above, is used by pong to send a return +notification. + +If the ping process receives a zero value, it will stop the reactor loop and +end the program. + +Now we've created a pair of spawned processes, but nothing else has happened. +If we stop now, the program will spin in the EM reactor loop, doing nothing +at all. Our spawned processes will never be scheduled for execution. + +But look at the next line in the code sample: + + ping.notify 3 + +This line gets the ping-pong ball rolling. We call ping's #notify method, +passing the argument 3. This causes a message to be sent to the ping spawned +process. The message contains the single argument, and it causes the EM +reactor to schedule the ping process. And this in turn results in the +execution of the Ruby code block passed to EM#spawn when ping was created. +Everything else proceeds as a result of the messages that are subsequently +passed to each other by the spawned processes. + +[TODO, present the outbound network i/o use case, and clarify that spawned +processes are interleaved with normal i/o operations and don't interfere +with them at all. Also, blame Erlang for the confusing term "process"] + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/TODO b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/TODO new file mode 100644 index 0000000..686a0d5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/docs/old/TODO @@ -0,0 +1,8 @@ +TODO List: + +12Aug06: Noticed by Don Stocks. A TCP connect-request that results +in a failed DNS resolution fires a fatal error back to user code. +Uuuuuugly. We should probably cause an unbind event to get fired +instead, and add some parameterization so the caller can detect +the nature of the failure. + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/01_eventmachine_echo_server.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/01_eventmachine_echo_server.rb new file mode 100644 index 0000000..51c5c7d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/01_eventmachine_echo_server.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class EchoServer < EM::Connection + def receive_data(data) + send_data(data) + end +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, EchoServer) +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb new file mode 100644 index 0000000..4cfff19 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class EchoServer < EM::Connection + def receive_data(data) + if data.strip =~ /exit$/i + EventMachine.stop + else + send_data(data) + end + end +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, EchoServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/03_simple_chat_server.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/03_simple_chat_server.rb new file mode 100644 index 0000000..3352551 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/03_simple_chat_server.rb @@ -0,0 +1,149 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class SimpleChatServer < EM::Connection + + @@connected_clients = Array.new + DM_REGEXP = /^@([a-zA-Z0-9]+)\s*:?\s*(.+)/.freeze + + attr_reader :username + + + # + # EventMachine handlers + # + + def post_init + @username = nil + + puts "A client has connected..." + ask_username + end + + def unbind + @@connected_clients.delete(self) + puts "[info] #{@username} has left" if entered_username? + end + + def receive_data(data) + if entered_username? + handle_chat_message(data.strip) + else + handle_username(data.strip) + end + end + + + # + # Username handling + # + + def entered_username? + !@username.nil? && !@username.empty? + end # entered_username? + + def handle_username(input) + if input.empty? + send_line("Blank usernames are not allowed. Try again.") + ask_username + else + @username = input + @@connected_clients.push(self) + self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") } + puts "#{@username} has joined" + + self.send_line("[info] Ohai, #{@username}") + end + end # handle_username(input) + + def ask_username + self.send_line("[info] Enter your username:") + end # ask_username + + + # + # Message handling + # + + def handle_chat_message(msg) + if command?(msg) + self.handle_command(msg) + else + if direct_message?(msg) + self.handle_direct_message(msg) + else + self.announce(msg, "#{@username}:") + end + end + end # handle_chat_message(msg) + + def direct_message?(input) + input =~ DM_REGEXP + end # direct_message?(input) + + def handle_direct_message(input) + username, message = parse_direct_message(input) + + if connection = @@connected_clients.find { |c| c.username == username } + puts "[dm] @#{@username} => @#{username}" + connection.send_line("[dm] @#{@username}: #{message}") + else + send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}" + end + end # handle_direct_message(input) + + def parse_direct_message(input) + return [$1, $2] if input =~ DM_REGEXP + end # parse_direct_message(input) + + + # + # Commands handling + # + + def command?(input) + input =~ /(exit|status)$/i + end # command?(input) + + def handle_command(cmd) + case cmd + when /exit$/i then self.close_connection + when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room") + end + end # handle_command(cmd) + + + # + # Helpers + # + + def announce(msg = nil, prefix = "[chat server]") + @@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty? + end # announce(msg) + + def number_of_connected_clients + @@connected_clients.size + end # number_of_connected_clients + + def other_peers + @@connected_clients.reject { |c| self == c } + end # other_peers + + def send_line(line) + self.send_data("#{line}\n") + end # send_line(line) + + def usernames + @@connected_clients.map { |c| c.username } + end # usernames +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/04_simple_chat_server_step_one.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/04_simple_chat_server_step_one.rb new file mode 100644 index 0000000..bb283a7 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/04_simple_chat_server_step_one.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class SimpleChatServer < EM::Connection + + # + # EventMachine handlers + # + + def post_init + puts "A client has connected..." + end + + def unbind + puts "A client has left..." + end +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/05_simple_chat_server_step_two.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/05_simple_chat_server_step_two.rb new file mode 100644 index 0000000..1361c5d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/05_simple_chat_server_step_two.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class SimpleChatServer < EM::Connection + + @@connected_clients = Array.new + + + # + # EventMachine handlers + # + + def post_init + @@connected_clients.push(self) + puts "A client has connected..." + end + + def unbind + @@connected_clients.delete(self) + puts "A client has left..." + end + + + + + # + # Helpers + # + + def other_peers + @@connected_clients.reject { |c| self == c } + end # other_peers +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/06_simple_chat_server_step_three.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/06_simple_chat_server_step_three.rb new file mode 100644 index 0000000..d9b85e2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/06_simple_chat_server_step_three.rb @@ -0,0 +1,98 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class SimpleChatServer < EM::Connection + + @@connected_clients = Array.new + + + attr_reader :username + + + # + # EventMachine handlers + # + + def post_init + @username = nil + + puts "A client has connected..." + ask_username + end + + def unbind + @@connected_clients.delete(self) + puts "A client has left..." + end + + def receive_data(data) + if entered_username? + handle_chat_message(data.strip) + else + handle_username(data.strip) + end + end + + + + + # + # Username handling + # + + def entered_username? + !@username.nil? && !@username.empty? + end # entered_username? + + def handle_username(input) + if input.empty? + send_line("Blank usernames are not allowed. Try again.") + ask_username + else + @username = input + @@connected_clients.push(self) + self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") } + puts "#{@username} has joined" + + self.send_line("[info] Ohai, #{@username}") + end + end # handle_username(input) + + def ask_username + self.send_line("[info] Enter your username:") + end # ask_username + + + + # + # Message handling + # + + def handle_chat_message(msg) + raise NotImplementedError + end + + + + # + # Helpers + # + + def other_peers + @@connected_clients.reject { |c| self == c } + end # other_peers + + def send_line(line) + self.send_data("#{line}\n") + end # send_line(line) +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/07_simple_chat_server_step_four.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/07_simple_chat_server_step_four.rb new file mode 100644 index 0000000..d4948af --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/07_simple_chat_server_step_four.rb @@ -0,0 +1,121 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class SimpleChatServer < EM::Connection + + @@connected_clients = Array.new + + + attr_reader :username + + + # + # EventMachine handlers + # + + def post_init + @username = nil + + puts "A client has connected..." + ask_username + end + + def unbind + @@connected_clients.delete(self) + puts "[info] #{@username} has left" if entered_username? + end + + def receive_data(data) + if entered_username? + handle_chat_message(data.strip) + else + handle_username(data.strip) + end + end + + + + + # + # Username handling + # + + def entered_username? + !@username.nil? && !@username.empty? + end # entered_username? + + def handle_username(input) + if input.empty? + send_line("Blank usernames are not allowed. Try again.") + ask_username + else + @username = input + @@connected_clients.push(self) + self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") } + puts "#{@username} has joined" + + self.send_line("[info] Ohai, #{@username}") + end + end # handle_username(input) + + def ask_username + self.send_line("[info] Enter your username:") + end # ask_username + + + + # + # Message handling + # + + def handle_chat_message(msg) + if command?(msg) + self.handle_command(msg) + else + self.announce(msg, "#{@username}:") + end + end + + + # + # Commands handling + # + + def command?(input) + input =~ /exit$/i + end # command?(input) + + def handle_command(cmd) + case cmd + when /exit$/i then self.close_connection + end + end # handle_command(cmd) + + + + # + # Helpers + # + + def announce(msg = nil, prefix = "[chat server]") + @@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty? + end # announce(msg) + + def other_peers + @@connected_clients.reject { |c| self == c } + end # other_peers + + def send_line(line) + self.send_data("#{line}\n") + end # send_line(line) +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/08_simple_chat_server_step_five.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/08_simple_chat_server_step_five.rb new file mode 100644 index 0000000..03da66b --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/guides/getting_started/08_simple_chat_server_step_five.rb @@ -0,0 +1,141 @@ +#!/usr/bin/env ruby + +require 'rubygems' # or use Bundler.setup +require 'eventmachine' + +class SimpleChatServer < EM::Connection + + @@connected_clients = Array.new + DM_REGEXP = /^@([a-zA-Z0-9]+)\s*:?\s+(.+)/.freeze + + attr_reader :username + + + # + # EventMachine handlers + # + + def post_init + @username = nil + + puts "A client has connected..." + ask_username + end + + def unbind + @@connected_clients.delete(self) + puts "[info] #{@username} has left" if entered_username? + end + + def receive_data(data) + if entered_username? + handle_chat_message(data.strip) + else + handle_username(data.strip) + end + end + + + # + # Username handling + # + + def entered_username? + !@username.nil? && !@username.empty? + end # entered_username? + + def handle_username(input) + if input.empty? + send_line("Blank usernames are not allowed. Try again.") + ask_username + else + @username = input + @@connected_clients.push(self) + self.other_peers.each { |c| c.send_data("#{@username} has joined the room\n") } + puts "#{@username} has joined" + + self.send_line("[info] Ohai, #{@username}") + end + end # handle_username(input) + + def ask_username + self.send_line("[info] Enter your username:") + end # ask_username + + + # + # Message handling + # + + def handle_chat_message(msg) + if command?(msg) + self.handle_command(msg) + else + if direct_message?(msg) + self.handle_direct_message(msg) + else + self.announce(msg, "#{@username}:") + end + end + end # handle_chat_message(msg) + + def direct_message?(input) + input =~ DM_REGEXP + end # direct_message?(input) + + def handle_direct_message(input) + username, message = parse_direct_message(input) + + if connection = @@connected_clients.find { |c| c.username == username } + puts "[dm] @#{@username} => @#{username}" + connection.send_line("[dm] @#{@username}: #{message}") + else + send_line "@#{username} is not in the room. Here's who is: #{usernames.join(', ')}" + end + end # handle_direct_message(input) + + def parse_direct_message(input) + return [$1, $2] if input =~ DM_REGEXP + end # parse_direct_message(input) + + + # + # Commands handling + # + + def command?(input) + input =~ /(exit|status)$/i + end # command?(input) + + def handle_command(cmd) + case cmd + when /exit$/i then self.close_connection + when /status$/i then self.send_line("[chat server] It's #{Time.now.strftime('%H:%M')} and there are #{self.number_of_connected_clients} people in the room") + end + end # handle_command(cmd) + + + # + # Helpers + # + + def announce(msg = nil, prefix = "[chat server]") + @@connected_clients.each { |c| c.send_line("#{prefix} #{msg}") } unless msg.empty? + end # announce(msg) + + def other_peers + @@connected_clients.reject { |c| self == c } + end # other_peers + + def send_line(line) + self.send_data("#{line}\n") + end # send_line(line) +end + +EventMachine.run do + # hit Control + C to stop + Signal.trap("INT") { EventMachine.stop } + Signal.trap("TERM") { EventMachine.stop } + + EventMachine.start_server("0.0.0.0", 10000, SimpleChatServer) +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_channel.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_channel.rb new file mode 100644 index 0000000..16e8d08 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_channel.rb @@ -0,0 +1,43 @@ +require File.dirname(__FILE__) + '/helper' + +EM.run do + + # Create a channel to push data to, this could be stocks... + RandChannel = EM::Channel.new + + # The server simply subscribes client connections to the channel on connect, + # and unsubscribes them on disconnect. + class Server < EM::Connection + def self.start(host = '127.0.0.1', port = 8000) + EM.start_server(host, port, self) + end + + def post_init + @sid = RandChannel.subscribe { |m| send_data "#{m.inspect}\n" } + end + + def unbind + RandChannel.unsubscribe @sid + end + end + Server.start + + # Two client connections, that just print what they receive. + 2.times do + EM.connect('127.0.0.1', 8000) do |c| + c.extend EM::P::LineText2 + def c.receive_line(line) + puts "Subscriber: #{signature} got #{line}" + end + EM.add_timer(2) { c.close_connection } + end + end + + # This part of the example is more fake, but imagine sleep was in fact a + # long running calculation to achieve the value. + 40.times do + EM.defer lambda { v = sleep(rand * 2); RandChannel << [Time.now, v] } + end + + EM.add_timer(5) { EM.stop } +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_queue.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_queue.rb new file mode 100644 index 0000000..761ea76 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_queue.rb @@ -0,0 +1,2 @@ +require File.dirname(__FILE__) + '/helper' + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_array.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_array.rb new file mode 100644 index 0000000..81b0ae3 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_array.rb @@ -0,0 +1,15 @@ +require File.dirname(__FILE__) + '/helper' + +EM.run do + array = (1..100).to_a + + tickloop = EM.tick_loop do + if array.empty? + :stop + else + puts array.shift + end + end + + tickloop.on_stop { EM.stop } +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_counter.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_counter.rb new file mode 100644 index 0000000..58e51ff --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/ex_tick_loop_counter.rb @@ -0,0 +1,32 @@ +require File.dirname(__FILE__) + '/helper' + +class TickCounter + attr_reader :start_time, :count + + def initialize + reset + @tick_loop = EM.tick_loop(method(:tick)) + end + + def reset + @count = 0 + @start_time = EM.current_time + end + + def tick + @count += 1 + end + + def rate + @count / (EM.current_time - @start_time) + end +end + +period = 5 +EM.run do + counter = TickCounter.new + EM.add_periodic_timer(period) do + puts "Ticks per second: #{counter.rate} (mean of last #{period}s)" + counter.reset + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/helper.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/helper.rb new file mode 100644 index 0000000..835ded2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/examples/old/helper.rb @@ -0,0 +1,2 @@ +$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') +require 'eventmachine' \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/Makefile b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/Makefile new file mode 100644 index 0000000..2221700 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/Makefile @@ -0,0 +1,273 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +V0 = $(V:0=) +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /Users/ben/.rubies/ruby-3.4.1/include/ruby-3.4.0 +hdrdir = $(topdir) +arch_hdrdir = /Users/ben/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/Users/ben/.rubies/ruby-3.4.1 +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(sitelibdir)/$(sitearch) +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +modular_gc_dir = $(DESTDIR) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(SDKROOT)$(prefix)/include +runstatedir = $(localstatedir)/run +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = clang +CXX = clang++ -std=gnu++11 +LIBRUBY = $(LIBRUBY_A) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(hardenflags) -fdeclspec $(optflags) $(debugflags) $(warnflags) +cxxflags = +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef +cppflags = +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -pipe $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -DHAVE_OPENSSL_SSL_H -DHAVE_OPENSSL_ERR_H -DWITH_SSL -DBUILD_FOR_RUBY -DHAVE_RB_THREAD_CALL_WITHOUT_GVL -DHAVE_RB_THREAD_FD_SELECT -DHAVE_TYPE_RB_FDSET_T -DHAVE_RB_WAIT_FOR_SINGLE_FD -DHAVE_RB_TIME_NEW -DHAVE_WRITEV -DOS_UNIX -DHAVE_SYS_EVENT_H -DHAVE_SYS_QUEUE_H -DHAVE_KQUEUE -DOS_DARWIN -DHAVE_CLOCK_GETTIME -DHAVE_CONST_CLOCK_MONOTONIC_RAW -DHAVE_CONST_CLOCK_MONOTONIC -DHAVE_MAKE_PAIR -I/opt/homebrew/opt/openssl@3/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/gdbm/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG) +ldflags = -L. -fstack-protector-strong -L/opt/homebrew/Cellar/readline/8.2.13/lib -L/opt/homebrew/Cellar/libyaml/0.2.5/lib -L/opt/homebrew/Cellar/gdbm/1.24/lib +dldflags = -L/opt/homebrew/Cellar/readline/8.2.13/lib -L/opt/homebrew/Cellar/libyaml/0.2.5/lib -L/opt/homebrew/Cellar/gdbm/1.24/lib -Wl,-undefined,dynamic_lookup -bundle_loader '$(BUILTRUBY)' +ARCH_FLAG = -arch arm64 +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CXX) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +POSTLINK = dsymutil $@ 2>/dev/null; { test -z '$(RUBY_CODESIGN)' || codesign -s '$(RUBY_CODESIGN)' $@; } +AR = ar +LD = ld +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby.3.4 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = arm64-darwin24 +sitearch = $(arch) +ruby_version = 3.4.0 +ruby = $(bindir)/$(RUBY_BASE_NAME) +RUBY = $(ruby) +BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = rm -fr +RMDIRS = rmdir -p +MAKEDIRS = mkdir -p +INSTALL = /usr/bin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(libdir) /opt/homebrew/opt/openssl@3/lib /opt/homebrew/opt/readline/lib /opt/homebrew/opt/libyaml/lib /opt/homebrew/opt/gdbm/lib +LIBPATH = -L. -L$(libdir) -L/opt/homebrew/opt/openssl@3/lib -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/libyaml/lib -L/opt/homebrew/opt/gdbm/lib +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = +LOCAL_LIBS = +LIBS = -lcrypto -lssl -lpthread +ORIG_SRCS = binder.cpp cmain.cpp ed.cpp em.cpp kb.cpp page.cpp pipe.cpp rubymain.cpp ssl.cpp +SRCS = $(ORIG_SRCS) +OBJS = binder.o cmain.o ed.o em.o kb.o page.o pipe.o rubymain.o ssl.o +HDRS = $(srcdir)/binder.h $(srcdir)/ed.h $(srcdir)/em.h $(srcdir)/eventmachine.h $(srcdir)/page.h $(srcdir)/project.h $(srcdir)/ssl.h +LOCAL_HDRS = +TARGET = rubyeventmachine +TARGET_NAME = rubyeventmachine +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(sitehdrdir)$(target_prefix) +ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) $(TARGET_SO:=.dSYM) +CLEANOBJS = $(OBJS) *.bak +TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP) + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TARGET_SO_DIR_TIMESTAMP): + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object $(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHAREDXX) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.cpp new file mode 100644 index 0000000..5b90876 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.cpp @@ -0,0 +1,124 @@ +/***************************************************************************** + +$Id$ + +File: binder.cpp +Date: 07Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#include "project.h" + +#define DEV_URANDOM "/dev/urandom" + + +std::map Bindable_t::BindingBag; + + +/******************************** +STATIC Bindable_t::CreateBinding +********************************/ + +uintptr_t Bindable_t::CreateBinding() +{ + static uintptr_t num = 0; + while(BindingBag[++num]) {} + return num; +} + +#if 0 +string Bindable_t::CreateBinding() +{ + static int index = 0; + static string seed; + + if ((index >= 1000000) || (seed.length() == 0)) { + #ifdef OS_UNIX + int fd = open (DEV_URANDOM, O_RDONLY); + if (fd < 0) + throw std::runtime_error ("No entropy device"); + + unsigned char u[16]; + size_t r = read (fd, u, sizeof(u)); + if (r < sizeof(u)) + throw std::runtime_error ("Unable to read entropy device"); + + unsigned char *u1 = (unsigned char*)u; + char u2 [sizeof(u) * 2 + 1]; + + for (size_t i=0; i < sizeof(u); i++) + sprintf (u2 + (i * 2), "%02x", u1[i]); + + seed = string (u2); + #endif + + + #ifdef OS_WIN32 + UUID uuid; + UuidCreate (&uuid); + unsigned char *uuidstring = NULL; + UuidToString (&uuid, &uuidstring); + if (!uuidstring) + throw std::runtime_error ("Unable to read uuid"); + seed = string ((const char*)uuidstring); + + RpcStringFree (&uuidstring); + #endif + + index = 0; + + + } + + stringstream ss; + ss << seed << (++index); + return ss.str(); +} +#endif + +/***************************** +STATIC: Bindable_t::GetObject +*****************************/ + +Bindable_t *Bindable_t::GetObject (const uintptr_t binding) +{ + std::map::const_iterator i = BindingBag.find (binding); + if (i != BindingBag.end()) + return i->second; + else + return NULL; +} + + +/********************** +Bindable_t::Bindable_t +**********************/ + +Bindable_t::Bindable_t() +{ + Binding = Bindable_t::CreateBinding(); + BindingBag [Binding] = this; +} + + + +/*********************** +Bindable_t::~Bindable_t +***********************/ + +Bindable_t::~Bindable_t() NO_EXCEPT_FALSE +{ + BindingBag.erase (Binding); +} + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.h new file mode 100644 index 0000000..dd32c8d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/binder.h @@ -0,0 +1,52 @@ +/***************************************************************************** + +$Id$ + +File: binder.h +Date: 07Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#ifndef __ObjectBindings__H_ +#define __ObjectBindings__H_ + + +#if __cplusplus >= 201103L +#define NO_EXCEPT_FALSE noexcept(false) +#else +#define NO_EXCEPT_FALSE +#endif + +class Bindable_t +{ + public: + static uintptr_t CreateBinding(); + static Bindable_t *GetObject (const uintptr_t); + static std::map BindingBag; + + public: + Bindable_t(); + virtual ~Bindable_t() NO_EXCEPT_FALSE; + + const uintptr_t GetBinding() {return Binding;} + + private: + uintptr_t Binding; +}; + + + + + +#endif // __ObjectBindings__H_ + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/cmain.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/cmain.cpp new file mode 100644 index 0000000..f58c2cd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/cmain.cpp @@ -0,0 +1,988 @@ +/***************************************************************************** + +$Id$ + +File: cmain.cpp +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#include "project.h" + +/* 21Sep09: ruby 1.9 defines macros for common i/o functions that point to rb_w32_* implementations. + We need to undef the stat to fix a build failure in evma_send_file_data_to_connection. + See http://groups.google.com/group/eventmachine/browse_thread/thread/fc60d9bb738ffc71 +*/ +#if defined(BUILD_FOR_RUBY) && defined(OS_WIN32) +#undef stat +#undef fstat +#endif + +static EventMachine_t *EventMachine; +static Poller_t Poller = Poller_Default; + +extern "C" void ensure_eventmachine (const char *caller = "unknown caller") +{ + if (!EventMachine) { + const int err_size = 128; + char err_string[err_size]; + snprintf (err_string, err_size, "eventmachine not initialized: %s", caller); + #ifdef BUILD_FOR_RUBY + rb_raise(rb_eRuntimeError, "%s", err_string); + #else + throw std::runtime_error (err_string); + #endif + } +} + +/*********************** +evma_initialize_library +***********************/ + +extern "C" void evma_initialize_library (EMCallback cb) +{ + if (EventMachine) + #ifdef BUILD_FOR_RUBY + rb_raise(rb_eRuntimeError, "eventmachine already initialized: evma_initialize_library"); + #else + throw std::runtime_error ("eventmachine already initialized: evma_initialize_library"); + #endif + + EventMachine = new EventMachine_t (cb, Poller); +} + + +/******************** +evma_release_library +********************/ + +extern "C" void evma_release_library() +{ + ensure_eventmachine("evma_release_library"); + delete EventMachine; + EventMachine = NULL; +} + + +/********************* +evma_run_machine_once +*********************/ + +extern "C" bool evma_run_machine_once() +{ + ensure_eventmachine("evma_run_machine_once"); + return EventMachine->RunOnce(); +} + + +/**************** +evma_run_machine +****************/ + +extern "C" void evma_run_machine() +{ + ensure_eventmachine("evma_run_machine"); + EventMachine->Run(); +} + + +/************************** +evma_install_oneshot_timer +**************************/ + +extern "C" const uintptr_t evma_install_oneshot_timer (uint64_t milliseconds) +{ + ensure_eventmachine("evma_install_oneshot_timer"); + return EventMachine->InstallOneshotTimer (milliseconds); +} + + +/********************** +evma_connect_to_server +**********************/ + +extern "C" const uintptr_t evma_connect_to_server (const char *bind_addr, int bind_port, const char *server, int port) +{ + ensure_eventmachine("evma_connect_to_server"); + return EventMachine->ConnectToServer (bind_addr, bind_port, server, port); +} + +/*************************** +evma_connect_to_unix_server +***************************/ + +extern "C" const uintptr_t evma_connect_to_unix_server (const char *server) +{ + ensure_eventmachine("evma_connect_to_unix_server"); + return EventMachine->ConnectToUnixServer (server); +} + +/************** +evma_attach_fd +**************/ + +extern "C" const uintptr_t evma_attach_fd (int file_descriptor, int watch_mode) +{ + ensure_eventmachine("evma_attach_fd"); + return EventMachine->AttachFD (file_descriptor, watch_mode ? true : false); +} + +/************** +evma_detach_fd +**************/ + +extern "C" int evma_detach_fd (const uintptr_t binding) +{ + ensure_eventmachine("evma_detach_fd"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return EventMachine->DetachFD (ed); + else + #ifdef BUILD_FOR_RUBY + rb_raise(rb_eRuntimeError, "invalid binding to detach"); + #else + throw std::runtime_error ("invalid binding to detach"); + #endif + return -1; +} + +/************************ +evma_get_file_descriptor +************************/ + +extern "C" int evma_get_file_descriptor (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_file_descriptor"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->GetSocket(); + else + #ifdef BUILD_FOR_RUBY + rb_raise(rb_eRuntimeError, "invalid binding to get_fd"); + #else + throw std::runtime_error ("invalid binding to get_fd"); + #endif + return -1; +} + +/*********************** +evma_is_notify_readable +***********************/ + +extern "C" int evma_is_notify_readable (const uintptr_t binding) +{ + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + return cd->IsNotifyReadable() ? 1 : 0; + return -1; +} + +/************************ +evma_set_notify_readable +************************/ + +extern "C" void evma_set_notify_readable (const uintptr_t binding, int mode) +{ + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + cd->SetNotifyReadable (mode ? true : false); +} + +/*********************** +evma_is_notify_writable +***********************/ + +extern "C" int evma_is_notify_writable (const uintptr_t binding) +{ + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + return cd->IsNotifyWritable() ? 1 : 0; + return -1; +} + +/************************ +evma_set_notify_writable +************************/ + +extern "C" void evma_set_notify_writable (const uintptr_t binding, int mode) +{ + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + cd->SetNotifyWritable (mode ? true : false); +} + +/********** +evma_pause +**********/ + +extern "C" int evma_pause (const uintptr_t binding) +{ + EventableDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + return cd->Pause() ? 1 : 0; + + return 0; +} + +/*********** +evma_resume +***********/ + +extern "C" int evma_resume (const uintptr_t binding) +{ + EventableDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + return cd->Resume() ? 1 : 0; + + return 0; +} + +/************** +evma_is_paused +**************/ + +extern "C" int evma_is_paused (const uintptr_t binding) +{ + EventableDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + return cd->IsPaused() ? 1 : 0; + + return 0; +} + +/************************ +evma_num_close_scheduled +************************/ + +extern "C" int evma_num_close_scheduled () +{ + ensure_eventmachine("evma_num_close_scheduled"); + return EventMachine->NumCloseScheduled; +} + +/********************** +evma_create_tcp_server +**********************/ + +extern "C" const uintptr_t evma_create_tcp_server (const char *address, int port) +{ + ensure_eventmachine("evma_create_tcp_server"); + return EventMachine->CreateTcpServer (address, port); +} + +/****************************** +evma_create_unix_domain_server +******************************/ + +extern "C" const uintptr_t evma_create_unix_domain_server (const char *filename) +{ + ensure_eventmachine("evma_create_unix_domain_server"); + return EventMachine->CreateUnixDomainServer (filename); +} + +/*********************** +evma_attach_sd +************************/ + +extern "C" const uintptr_t evma_attach_sd (int sd) +{ + ensure_eventmachine("evma_attach_sd"); + return EventMachine->AttachSD (sd); +} + +/************************* +evma_open_datagram_socket +*************************/ + +extern "C" const uintptr_t evma_open_datagram_socket (const char *address, int port) +{ + ensure_eventmachine("evma_open_datagram_socket"); + return EventMachine->OpenDatagramSocket (address, port); +} + +/****************** +evma_open_keyboard +******************/ + +extern "C" const uintptr_t evma_open_keyboard() +{ + ensure_eventmachine("evma_open_keyboard"); + return EventMachine->OpenKeyboard(); +} + +/******************* +evma_watch_filename +*******************/ + +extern "C" const uintptr_t evma_watch_filename (const char *fname) +{ + ensure_eventmachine("evma_watch_filename"); + return EventMachine->WatchFile(fname); +} + +/********************* +evma_unwatch_filename +*********************/ + +extern "C" void evma_unwatch_filename (const uintptr_t sig) +{ + ensure_eventmachine("evma_unwatch_file"); + EventMachine->UnwatchFile(sig); +} + +/************** +evma_watch_pid +**************/ + +extern "C" const uintptr_t evma_watch_pid (int pid) +{ + ensure_eventmachine("evma_watch_pid"); + return EventMachine->WatchPid(pid); +} + +/**************** +evma_unwatch_pid +****************/ + +extern "C" void evma_unwatch_pid (const uintptr_t sig) +{ + ensure_eventmachine("evma_unwatch_pid"); + EventMachine->UnwatchPid(sig); +} + +/**************************** +evma_send_data_to_connection +****************************/ + +extern "C" int evma_send_data_to_connection (const uintptr_t binding, const char *data, int data_length) +{ + ensure_eventmachine("evma_send_data_to_connection"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->SendOutboundData(data, data_length); + return -1; +} + +/****************** +evma_send_datagram +******************/ + +extern "C" int evma_send_datagram (const uintptr_t binding, const char *data, int data_length, const char *address, int port) +{ + ensure_eventmachine("evma_send_datagram"); + DatagramDescriptor *dd = dynamic_cast (Bindable_t::GetObject (binding)); + if (dd) + return dd->SendOutboundDatagram(data, data_length, address, port); + return -1; +} + + +/********************* +evma_close_connection +*********************/ + +extern "C" void evma_close_connection (const uintptr_t binding, int after_writing) +{ + ensure_eventmachine("evma_close_connection"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + ed->ScheduleClose (after_writing ? true : false); +} + +/*********************************** +evma_report_connection_error_status +***********************************/ + +extern "C" int evma_report_connection_error_status (const uintptr_t binding) +{ + ensure_eventmachine("evma_report_connection_error_status"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->ReportErrorStatus(); + return -1; +} + +/******************** +evma_stop_tcp_server +********************/ + +extern "C" void evma_stop_tcp_server (const uintptr_t binding) +{ + ensure_eventmachine("evma_stop_tcp_server"); + AcceptorDescriptor::StopAcceptor (binding); +} + + +/***************** +evma_stop_machine +*****************/ + +extern "C" void evma_stop_machine() +{ + ensure_eventmachine("evma_stop_machine"); + EventMachine->ScheduleHalt(); +} + +/***************** +evma_stopping +*****************/ + +extern "C" bool evma_stopping() +{ + ensure_eventmachine("evma_stopping"); + return EventMachine->Stopping(); +} + +/************** +evma_start_tls +**************/ + +extern "C" void evma_start_tls (const uintptr_t binding) +{ + ensure_eventmachine("evma_start_tls"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + ed->StartTls(); +} + +/****************** +evma_set_tls_parms +******************/ + +extern "C" void evma_set_tls_parms (const uintptr_t binding, const char *privatekey_filename, const char *certchain_filename, int verify_peer, int fail_if_no_peer_cert, const char *sni_hostname, const char *cipherlist, const char *ecdh_curve, const char *dhparam, int ssl_version) +{ + ensure_eventmachine("evma_set_tls_parms"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + ed->SetTlsParms (privatekey_filename, certchain_filename, (verify_peer == 1 ? true : false), (fail_if_no_peer_cert == 1 ? true : false), sni_hostname, cipherlist, ecdh_curve, dhparam, ssl_version); +} + +/****************** +evma_get_peer_cert +******************/ + +#ifdef WITH_SSL +extern "C" X509 *evma_get_peer_cert (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_peer_cert"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->GetPeerCert(); + return NULL; +} +#endif + +/****************** +evma_get_cipher_bits +******************/ + +#ifdef WITH_SSL +extern "C" int evma_get_cipher_bits (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_cipher_bits"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->GetCipherBits(); + return -1; +} +#endif + +/****************** +evma_get_cipher_name +******************/ + +#ifdef WITH_SSL +extern "C" const char *evma_get_cipher_name (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_cipher_name"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->GetCipherName(); + return NULL; +} +#endif + +/****************** +evma_get_cipher_protocol +******************/ + +#ifdef WITH_SSL +extern "C" const char *evma_get_cipher_protocol (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_cipher_protocol"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->GetCipherProtocol(); + return NULL; +} +#endif + +/****************** +evma_get_sni_hostname +******************/ + +#ifdef WITH_SSL +extern "C" const char *evma_get_sni_hostname (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_sni_hostname"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) + return ed->GetSNIHostname(); + return NULL; +} +#endif + +/******************** +evma_accept_ssl_peer +********************/ + +#ifdef WITH_SSL +extern "C" void evma_accept_ssl_peer (const uintptr_t binding) +{ + ensure_eventmachine("evma_accept_ssl_peer"); + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + cd->AcceptSslPeer(); +} +#endif + +/***************** +evma_get_peername +*****************/ + +extern "C" int evma_get_peername (const uintptr_t binding, struct sockaddr *sa, socklen_t *len) +{ + ensure_eventmachine("evma_get_peername"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) { + return ed->GetPeername (sa, len) ? 1 : 0; + } + else + return 0; +} + +/***************** +evma_get_sockname +*****************/ + +extern "C" int evma_get_sockname (const uintptr_t binding, struct sockaddr *sa, socklen_t *len) +{ + ensure_eventmachine("evma_get_sockname"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) { + return ed->GetSockname (sa, len) ? 1 : 0; + } + else + return 0; +} + +/*********************** +evma_get_subprocess_pid +***********************/ + +#ifdef OS_UNIX +extern "C" int evma_get_subprocess_pid (const uintptr_t binding, pid_t *pid) +{ + ensure_eventmachine("evma_get_subprocess_pid"); + PipeDescriptor *pd = dynamic_cast (Bindable_t::GetObject (binding)); + if (pd) { + return pd->GetSubprocessPid (pid) ? 1 : 0; + } + else if (pid && EventMachine->SubprocessPid) { + *pid = EventMachine->SubprocessPid; + return 1; + } + else + return 0; +} +#else +extern "C" int evma_get_subprocess_pid (const uintptr_t binding UNUSED, pid_t *pid UNUSED) +{ + return 0; +} +#endif + +/************************** +evma_get_subprocess_status +**************************/ + +extern "C" int evma_get_subprocess_status (const uintptr_t binding UNUSED, int *status) +{ + ensure_eventmachine("evma_get_subprocess_status"); + if (status) { + *status = EventMachine->SubprocessExitStatus; + return 1; + } + else + return 0; +} + +/************************* +evma_get_connection_count +*************************/ + +extern "C" int evma_get_connection_count() +{ + ensure_eventmachine("evma_get_connection_count"); + return EventMachine->GetConnectionCount(); +} + +/********************* +evma_signal_loopbreak +*********************/ + +extern "C" void evma_signal_loopbreak() +{ + ensure_eventmachine("evma_signal_loopbreak"); + EventMachine->SignalLoopBreaker(); +} + + + +/******************************** +evma_get_comm_inactivity_timeout +********************************/ + +extern "C" float evma_get_comm_inactivity_timeout (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_comm_inactivity_timeout"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) { + return ((float)ed->GetCommInactivityTimeout() / 1000); + } + else + return 0.0; //Perhaps this should be an exception. Access to an unknown binding. +} + +/******************************** +evma_set_comm_inactivity_timeout +********************************/ + +extern "C" int evma_set_comm_inactivity_timeout (const uintptr_t binding, float value) +{ + ensure_eventmachine("evma_set_comm_inactivity_timeout"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) { + return ed->SetCommInactivityTimeout ((uint64_t)(value * 1000)); + } + else + return 0; //Perhaps this should be an exception. Access to an unknown binding. +} + + +/******************************** +evma_get_pending_connect_timeout +********************************/ + +extern "C" float evma_get_pending_connect_timeout (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_pending_connect_timeout"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) { + return ((float)ed->GetPendingConnectTimeout() / 1000); + } + else + return 0.0; +} + + +/******************************** +evma_set_pending_connect_timeout +********************************/ + +extern "C" int evma_set_pending_connect_timeout (const uintptr_t binding, float value) +{ + ensure_eventmachine("evma_set_pending_connect_timeout"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + if (ed) { + return ed->SetPendingConnectTimeout ((uint64_t)(value * 1000)); + } + else + return 0; +} + + +/********************** +evma_set_timer_quantum +**********************/ + +extern "C" void evma_set_timer_quantum (int interval) +{ + ensure_eventmachine("evma_set_timer_quantum"); + EventMachine->SetTimerQuantum (interval); +} + + +/************************ +evma_get_max_timer_count +************************/ + +extern "C" int evma_get_max_timer_count() +{ + return EventMachine_t::GetMaxTimerCount(); +} + +/************************ +evma_set_max_timer_count +************************/ + +extern "C" void evma_set_max_timer_count (int ct) +{ + // This may only be called if the reactor is not running. + + if (EventMachine) + #ifdef BUILD_FOR_RUBY + rb_raise(rb_eRuntimeError, "eventmachine already initialized: evma_set_max_timer_count"); + #else + throw std::runtime_error ("eventmachine already initialized: evma_set_max_timer_count"); + #endif + EventMachine_t::SetMaxTimerCount (ct); +} + +/****************** +evma_get/set_simultaneous_accept_count +******************/ + +extern "C" void evma_set_simultaneous_accept_count (int count) +{ + EventMachine_t::SetSimultaneousAcceptCount(count); +} + +extern "C" int evma_get_simultaneous_accept_count() +{ + return EventMachine_t::GetSimultaneousAcceptCount(); +} + + +/****************** +evma_setuid_string +******************/ + +extern "C" void evma_setuid_string (const char *username) +{ + // We do NOT need to be running an EM instance because this method is static. + EventMachine_t::SetuidString (username); +} + + +/********** +evma_popen +**********/ + +extern "C" const uintptr_t evma_popen (char * const*cmd_strings) +{ + ensure_eventmachine("evma_popen"); + return EventMachine->Socketpair (cmd_strings); +} + + +/*************************** +evma_get_outbound_data_size +***************************/ + +extern "C" int evma_get_outbound_data_size (const uintptr_t binding) +{ + ensure_eventmachine("evma_get_outbound_data_size"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); + return ed ? ed->GetOutboundDataSize() : 0; +} + + +/************** +evma_set_epoll +**************/ + +extern "C" void evma_set_epoll (int use) +{ + if (use) + Poller = Poller_Epoll; + else + Poller = Poller_Default; +} + +/*************** +evma_set_kqueue +***************/ + +extern "C" void evma_set_kqueue (int use) +{ + if (use) + Poller = Poller_Kqueue; + else + Poller = Poller_Default; +} + + +/********************** +evma_set_rlimit_nofile +**********************/ + +extern "C" int evma_set_rlimit_nofile (int nofiles) +{ + return EventMachine_t::SetRlimitNofile (nofiles); +} + + +/********************************* +evma_send_file_data_to_connection +*********************************/ + +extern "C" int evma_send_file_data_to_connection (const uintptr_t binding, const char *filename) +{ + /* This is a sugaring over send_data_to_connection that reads a file into a + * locally-allocated buffer, and sends the file data to the remote peer. + * Return the number of bytes written to the caller. + * TODO, needs to impose a limit on the file size. This is intended only for + * small files. (I don't know, maybe 8K or less.) For larger files, use interleaved + * I/O to avoid slowing the rest of the system down. + * TODO: we should return a code rather than barf, in case of file-not-found. + * TODO, does this compile on Windows? + * TODO, given that we want this to work only with small files, how about allocating + * the buffer on the stack rather than the heap? + * + * Modified 25Jul07. This now returns -1 on file-too-large; 0 for success, and a positive + * errno in case of other errors. + * + * Contributed by Kirk Haines. + */ + + char data[32*1024]; + int r; + + ensure_eventmachine("evma_send_file_data_to_connection"); + +#if defined(OS_WIN32) + int Fd = open (filename, O_RDONLY|O_BINARY); +#else + int Fd = open (filename, O_RDONLY); +#endif + if (Fd < 0) + return errno; + // From here on, all early returns MUST close Fd. + + struct stat st; + if (fstat (Fd, &st)) { + int e = errno; + close (Fd); + return e; + } + + off_t filesize = st.st_size; + if (filesize <= 0) { + close (Fd); + return 0; + } + else if (filesize > (off_t) sizeof(data)) { + close (Fd); + return -1; + } + + r = read (Fd, data, filesize); + if (r != filesize) { + int e = errno; + close (Fd); + return e; + } + evma_send_data_to_connection (binding, data, r); + close (Fd); + + return 0; +} + + +/**************** +evma_start_proxy +*****************/ + +extern "C" void evma_start_proxy (const uintptr_t from, const uintptr_t to, const unsigned long bufsize, const unsigned long length) +{ + ensure_eventmachine("evma_start_proxy"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (from)); + if (ed) + ed->StartProxy(to, bufsize, length); +} + + +/*************** +evma_stop_proxy +****************/ + +extern "C" void evma_stop_proxy (const uintptr_t from) +{ + ensure_eventmachine("evma_stop_proxy"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (from)); + if (ed) + ed->StopProxy(); +} + +/****************** +evma_proxied_bytes +*******************/ + +extern "C" unsigned long evma_proxied_bytes (const uintptr_t from) +{ + ensure_eventmachine("evma_proxied_bytes"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (from)); + if (ed) + return ed->GetProxiedBytes(); + else + return 0; +} + + +/*************************** +evma_get_last_activity_time +****************************/ + +extern "C" uint64_t evma_get_last_activity_time(const uintptr_t from) +{ + ensure_eventmachine("evma_get_last_activity_time"); + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (from)); + if (ed) + return ed->GetLastActivity(); + else + return 0; +} + + +/*************************** +evma_get_heartbeat_interval +****************************/ + +extern "C" float evma_get_heartbeat_interval() +{ + ensure_eventmachine("evma_get_heartbeat_interval"); + return EventMachine->GetHeartbeatInterval(); +} + + +/*************************** +evma_set_heartbeat_interval +****************************/ + +extern "C" int evma_set_heartbeat_interval(float interval) +{ + ensure_eventmachine("evma_set_heartbeat_interval"); + return EventMachine->SetHeartbeatInterval(interval); +} + + +/************************** +evma_get_current_loop_time +***************************/ + +extern "C" uint64_t evma_get_current_loop_time() +{ + ensure_eventmachine("evma_get_current_loop_time"); + return EventMachine->GetCurrentLoopTime(); +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.cpp new file mode 100644 index 0000000..a469dff --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.cpp @@ -0,0 +1,2096 @@ +/***************************************************************************** + +$Id$ + +File: ed.cpp +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#include "project.h" + + + +/******************** +SetSocketNonblocking +********************/ + +bool SetSocketNonblocking (SOCKET sd) +{ + #ifdef OS_UNIX + int val = fcntl (sd, F_GETFL, 0); + return (fcntl (sd, F_SETFL, val | O_NONBLOCK) != SOCKET_ERROR) ? true : false; + #endif + + #ifdef OS_WIN32 + #ifdef BUILD_FOR_RUBY + // 14Jun09 Ruby provides its own wrappers for ioctlsocket. On 1.8 this is a simple wrapper, + // however, 1.9 keeps its own state about the socket. + // NOTE: F_GETFL is not supported + return (fcntl (sd, F_SETFL, O_NONBLOCK) == 0) ? true : false; + #else + unsigned long one = 1; + return (ioctlsocket (sd, FIONBIO, &one) == 0) ? true : false; + #endif + #endif +} + +/************ +SetFdCloexec +************/ + +#ifdef OS_UNIX +bool SetFdCloexec (int fd) +{ + int flags = fcntl(fd, F_GETFD, 0); + assert (flags >= 0); + flags |= FD_CLOEXEC; + return (fcntl(fd, F_SETFD, FD_CLOEXEC) == 0) ? true : false; +} +#else +bool SetFdCloexec (int fd UNUSED) +{ + return true; +} +#endif + +/**************************************** +EventableDescriptor::EventableDescriptor +****************************************/ + +EventableDescriptor::EventableDescriptor (SOCKET sd, EventMachine_t *em): + bCloseNow (false), + bCloseAfterWriting (false), + MySocket (sd), + bAttached (false), + bWatchOnly (false), + EventCallback (NULL), + bCallbackUnbind (true), + UnbindReasonCode (0), + ProxyTarget(NULL), + ProxiedFrom(NULL), + ProxiedBytes(0), + MaxOutboundBufSize(0), + MyEventMachine (em), + PendingConnectTimeout(20000000), + InactivityTimeout (0), + NextHeartbeat (0), + bPaused (false) +{ + /* There are three ways to close a socket, all of which should + * automatically signal to the event machine that this object + * should be removed from the polling scheduler. + * First is a hard close, intended for bad errors or possible + * security violations. It immediately closes the connection + * and puts this object into an error state. + * Second is to set bCloseNow, which will cause the event machine + * to delete this object (and thus close the connection in our + * destructor) the next chance it gets. bCloseNow also inhibits + * the writing of new data on the socket (but not necessarily + * the reading of new data). + * The third way is to set bCloseAfterWriting, which inhibits + * the writing of new data and converts to bCloseNow as soon + * as everything in the outbound queue has been written. + * bCloseAfterWriting is really for use only by protocol handlers + * (for example, HTTP writes an HTML page and then closes the + * connection). All of the error states we generate internally + * cause an immediate close to be scheduled, which may have the + * effect of discarding outbound data. + */ + + if (sd == INVALID_SOCKET) + throw std::runtime_error ("bad eventable descriptor"); + if (MyEventMachine == NULL) + throw std::runtime_error ("bad em in eventable descriptor"); + CreatedAt = MyEventMachine->GetCurrentLoopTime(); + LastActivity = MyEventMachine->GetCurrentLoopTime(); + + #ifdef HAVE_EPOLL + EpollEvent.events = 0; + EpollEvent.data.ptr = this; + #endif +} + + +/***************************************** +EventableDescriptor::~EventableDescriptor +*****************************************/ + +EventableDescriptor::~EventableDescriptor() NO_EXCEPT_FALSE +{ + if (NextHeartbeat) + MyEventMachine->ClearHeartbeat(NextHeartbeat, this); + if (EventCallback && bCallbackUnbind) + (*EventCallback)(GetBinding(), EM_CONNECTION_UNBOUND, NULL, UnbindReasonCode); + if (ProxiedFrom) { + (*EventCallback)(ProxiedFrom->GetBinding(), EM_PROXY_TARGET_UNBOUND, NULL, 0); + ProxiedFrom->StopProxy(); + } + MyEventMachine->NumCloseScheduled--; + StopProxy(); + Close(); +} + + +/************************************* +EventableDescriptor::SetEventCallback +*************************************/ + +void EventableDescriptor::SetEventCallback (EMCallback cb) +{ + EventCallback = cb; +} + + +/************************** +EventableDescriptor::Close +**************************/ + +void EventableDescriptor::Close() +{ + /* EventMachine relies on the fact that when close(fd) + * is called that the fd is removed from any + * epoll event queues. + * + * However, this is not *always* the behavior of close(fd) + * + * See man 4 epoll Q6/A6 and then consider what happens + * when using pipes with eventmachine. + * (As is often done when communicating with a subprocess) + * + * The pipes end up looking like: + * + * ls -l /proc//fd + * ... + * lr-x------ 1 root root 64 2011-08-19 21:31 3 -> pipe:[940970] + * l-wx------ 1 root root 64 2011-08-19 21:31 4 -> pipe:[940970] + * + * This meets the critera from man 4 epoll Q6/A4 for not + * removing fds from epoll event queues until all fds + * that reference the underlying file have been removed. + * + * If the EventableDescriptor associated with fd 3 is deleted, + * its dtor will call EventableDescriptor::Close(), + * which will call ::close(int fd). + * + * However, unless the EventableDescriptor associated with fd 4 is + * also deleted before the next call to epoll_wait, events may fire + * for fd 3 that were registered with an already deleted + * EventableDescriptor. + * + * Therefore, it is necessary to notify EventMachine that + * the fd associated with this EventableDescriptor is + * closing. + * + * EventMachine also never closes fds for STDIN, STDOUT and + * STDERR (0, 1 & 2) + */ + + // Close the socket right now. Intended for emergencies. + if (MySocket != INVALID_SOCKET) { + MyEventMachine->Deregister (this); + + // Do not close STDIN, STDOUT, STDERR + if (MySocket > 2 && !bAttached) { + shutdown (MySocket, 1); + close (MySocket); + } + + MySocket = INVALID_SOCKET; + } +} + + +/********************************* +EventableDescriptor::ShouldDelete +*********************************/ + +bool EventableDescriptor::ShouldDelete() +{ + /* For use by a socket manager, which needs to know if this object + * should be removed from scheduling events and deleted. + * Has an immediate close been scheduled, or are we already closed? + * If either of these are the case, return true. In theory, the manager will + * then delete us, which in turn will make sure the socket is closed. + * Note, if bCloseAfterWriting is true, we check a virtual method to see + * if there is outbound data to write, and only request a close if there is none. + */ + + return ((MySocket == INVALID_SOCKET) || bCloseNow || (bCloseAfterWriting && (GetOutboundDataSize() <= 0))); +} + + +/********************************** +EventableDescriptor::ScheduleClose +**********************************/ + +void EventableDescriptor::ScheduleClose (bool after_writing) +{ + if (IsCloseScheduled()) { + if (!after_writing) { + // If closing has become more urgent, then upgrade the scheduled + // after_writing close to one NOW. + bCloseNow = true; + } + return; + } + MyEventMachine->NumCloseScheduled++; + // KEEP THIS SYNCHRONIZED WITH ::IsCloseScheduled. + if (after_writing) + bCloseAfterWriting = true; + else + bCloseNow = true; +} + + +/************************************* +EventableDescriptor::IsCloseScheduled +*************************************/ + +bool EventableDescriptor::IsCloseScheduled() +{ + // KEEP THIS SYNCHRONIZED WITH ::ScheduleClose. + return (bCloseNow || bCloseAfterWriting); +} + + +/******************************* +EventableDescriptor::StartProxy +*******************************/ + +void EventableDescriptor::StartProxy(const uintptr_t to, const unsigned long bufsize, const unsigned long length) +{ + EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (to)); + if (ed) { + StopProxy(); + ProxyTarget = ed; + BytesToProxy = length; + ProxiedBytes = 0; + ed->SetProxiedFrom(this, bufsize); + return; + } + throw std::runtime_error ("Tried to proxy to an invalid descriptor"); +} + + +/****************************** +EventableDescriptor::StopProxy +******************************/ + +void EventableDescriptor::StopProxy() +{ + if (ProxyTarget) { + ProxyTarget->SetProxiedFrom(NULL, 0); + ProxyTarget = NULL; + } +} + + +/*********************************** +EventableDescriptor::SetProxiedFrom +***********************************/ + +void EventableDescriptor::SetProxiedFrom(EventableDescriptor *from, const unsigned long bufsize) +{ + if (from != NULL && ProxiedFrom != NULL) + throw std::runtime_error ("Tried to proxy to a busy target"); + + ProxiedFrom = from; + MaxOutboundBufSize = bufsize; +} + + +/******************************************** +EventableDescriptor::_GenericInboundDispatch +********************************************/ + +void EventableDescriptor::_GenericInboundDispatch(const char *buf, unsigned long size) +{ + assert(EventCallback); + + if (ProxyTarget) { + if (BytesToProxy > 0) { + unsigned long proxied = std::min(BytesToProxy, size); + ProxyTarget->SendOutboundData(buf, proxied); + ProxiedBytes += (unsigned long) proxied; + BytesToProxy -= proxied; + if (BytesToProxy == 0) { + StopProxy(); + (*EventCallback)(GetBinding(), EM_PROXY_COMPLETED, NULL, 0); + if (proxied < size) { + (*EventCallback)(GetBinding(), EM_CONNECTION_READ, buf + proxied, size - proxied); + } + } + } else { + ProxyTarget->SendOutboundData(buf, size); + ProxiedBytes += size; + } + } else { + (*EventCallback)(GetBinding(), EM_CONNECTION_READ, buf, size); + } +} + + +/********************************* +EventableDescriptor::_GenericGetPeername +*********************************/ + +bool EventableDescriptor::_GenericGetPeername (struct sockaddr *s, socklen_t *len) +{ + if (!s) + return false; + + int gp = getpeername (GetSocket(), s, len); + if (gp == -1) { + char buf[200]; + snprintf (buf, sizeof(buf)-1, "unable to get peer name: %s", strerror(errno)); + throw std::runtime_error (buf); + } + + return true; +} + +/********************************* +EventableDescriptor::_GenericGetSockname +*********************************/ + +bool EventableDescriptor::_GenericGetSockname (struct sockaddr *s, socklen_t *len) +{ + if (!s) + return false; + + int gp = getsockname (GetSocket(), s, len); + if (gp == -1) { + char buf[200]; + snprintf (buf, sizeof(buf)-1, "unable to get sock name: %s", strerror(errno)); + throw std::runtime_error (buf); + } + + return true; +} + + +/********************************************* +EventableDescriptor::GetPendingConnectTimeout +*********************************************/ + +uint64_t EventableDescriptor::GetPendingConnectTimeout() +{ + return PendingConnectTimeout / 1000; +} + + +/********************************************* +EventableDescriptor::SetPendingConnectTimeout +*********************************************/ + +int EventableDescriptor::SetPendingConnectTimeout (uint64_t value) +{ + if (value > 0) { + PendingConnectTimeout = value * 1000; + MyEventMachine->QueueHeartbeat(this); + return 1; + } + return 0; +} + + +/************************************* +EventableDescriptor::GetNextHeartbeat +*************************************/ + +uint64_t EventableDescriptor::GetNextHeartbeat() +{ + if (NextHeartbeat) + MyEventMachine->ClearHeartbeat(NextHeartbeat, this); + + NextHeartbeat = 0; + + if (!ShouldDelete()) { + uint64_t time_til_next = InactivityTimeout; + if (IsConnectPending()) { + if (time_til_next == 0 || PendingConnectTimeout < time_til_next) + time_til_next = PendingConnectTimeout; + } + if (time_til_next == 0) + return 0; + NextHeartbeat = time_til_next + MyEventMachine->GetRealTime(); + } + + return NextHeartbeat; +} + + +/****************************************** +ConnectionDescriptor::ConnectionDescriptor +******************************************/ + +ConnectionDescriptor::ConnectionDescriptor (SOCKET sd, EventMachine_t *em): + EventableDescriptor (sd, em), + bConnectPending (false), + bNotifyReadable (false), + bNotifyWritable (false), + bReadAttemptedAfterClose (false), + bWriteAttemptedAfterClose (false), + OutboundDataSize (0), + #ifdef WITH_SSL + SslBox (NULL), + bHandshakeSignaled (false), + bSslVerifyPeer (false), + bSslPeerAccepted(false), + #endif + #ifdef HAVE_KQUEUE + bGotExtraKqueueEvent(false), + #endif + bIsServer (false) +{ + // 22Jan09: Moved ArmKqueueWriter into SetConnectPending() to fix assertion failure in _WriteOutboundData() + // 5May09: Moved EPOLLOUT into SetConnectPending() so it doesn't happen for attached read pipes +} + + +/******************************************* +ConnectionDescriptor::~ConnectionDescriptor +*******************************************/ + +ConnectionDescriptor::~ConnectionDescriptor() +{ + // Run down any stranded outbound data. + for (size_t i=0; i < OutboundPages.size(); i++) + OutboundPages[i].Free(); + + #ifdef WITH_SSL + if (SslBox) + delete SslBox; + #endif +} + + +/*********************************** +ConnectionDescriptor::_UpdateEvents +************************************/ + +void ConnectionDescriptor::_UpdateEvents() +{ + _UpdateEvents(true, true); +} + +void ConnectionDescriptor::_UpdateEvents(bool read, bool write) +{ + if (MySocket == INVALID_SOCKET) + return; + + if (!read && !write) + return; + + #ifdef HAVE_EPOLL + unsigned int old = EpollEvent.events; + + if (read) { + if (SelectForRead()) + EpollEvent.events |= EPOLLIN; + else + EpollEvent.events &= ~EPOLLIN; + } + + if (write) { + if (SelectForWrite()) + EpollEvent.events |= EPOLLOUT; + else + EpollEvent.events &= ~EPOLLOUT; + } + + if (old != EpollEvent.events) + MyEventMachine->Modify (this); + #endif + + #ifdef HAVE_KQUEUE + if (read && SelectForRead()) + MyEventMachine->ArmKqueueReader (this); + bKqueueArmWrite = SelectForWrite(); + if (write && bKqueueArmWrite) + MyEventMachine->Modify (this); + #endif +} + +/*************************************** +ConnectionDescriptor::SetConnectPending +****************************************/ + +void ConnectionDescriptor::SetConnectPending(bool f) +{ + bConnectPending = f; + MyEventMachine->QueueHeartbeat(this); + _UpdateEvents(); +} + + +/********************************** +ConnectionDescriptor::SetAttached +***********************************/ + +void ConnectionDescriptor::SetAttached(bool state) +{ + bAttached = state; +} + + +/********************************** +ConnectionDescriptor::SetWatchOnly +***********************************/ + +void ConnectionDescriptor::SetWatchOnly(bool watching) +{ + bWatchOnly = watching; + _UpdateEvents(); +} + + +/********************************* +ConnectionDescriptor::HandleError +*********************************/ + +void ConnectionDescriptor::HandleError() +{ + if (bWatchOnly) { + // An EPOLLHUP | EPOLLIN condition will call Read() before HandleError(), in which case the + // socket is already detached and invalid, so we don't need to do anything. + if (MySocket == INVALID_SOCKET) return; + + // HandleError() is called on WatchOnly descriptors by the epoll reactor + // when it gets a EPOLLERR | EPOLLHUP. Usually this would show up as a readable and + // writable event on other reactors, so we have to fire those events ourselves. + if (bNotifyReadable) Read(); + if (bNotifyWritable) Write(); + } else { + ScheduleClose (false); + } +} + + +/*********************************** +ConnectionDescriptor::ScheduleClose +***********************************/ + +void ConnectionDescriptor::ScheduleClose (bool after_writing) +{ + if (bWatchOnly) + throw std::runtime_error ("cannot close 'watch only' connections"); + + EventableDescriptor::ScheduleClose(after_writing); +} + + +/*************************************** +ConnectionDescriptor::SetNotifyReadable +****************************************/ + +void ConnectionDescriptor::SetNotifyReadable(bool readable) +{ + if (!bWatchOnly) + throw std::runtime_error ("notify_readable must be on 'watch only' connections"); + + bNotifyReadable = readable; + _UpdateEvents(true, false); +} + + +/*************************************** +ConnectionDescriptor::SetNotifyWritable +****************************************/ + +void ConnectionDescriptor::SetNotifyWritable(bool writable) +{ + if (!bWatchOnly) + throw std::runtime_error ("notify_writable must be on 'watch only' connections"); + + bNotifyWritable = writable; + _UpdateEvents(false, true); +} + + +/************************************** +ConnectionDescriptor::SendOutboundData +**************************************/ + +int ConnectionDescriptor::SendOutboundData (const char *data, unsigned long length) +{ + if (bWatchOnly) + throw std::runtime_error ("cannot send data on a 'watch only' connection"); + + if (ProxiedFrom && MaxOutboundBufSize && (unsigned int)(GetOutboundDataSize() + length) > MaxOutboundBufSize) + ProxiedFrom->Pause(); + + #ifdef WITH_SSL + if (SslBox) { + if (length > 0) { + unsigned long writed = 0; + char *p = (char*)data; + + while (writed < length) { + int to_write = SSLBOX_INPUT_CHUNKSIZE; + int remaining = length - writed; + + if (remaining < SSLBOX_INPUT_CHUNKSIZE) + to_write = remaining; + + int w = SslBox->PutPlaintext (p, to_write); + if (w < 0) { + ScheduleClose (false); + }else + _DispatchCiphertext(); + + p += to_write; + writed += to_write; + } + } + // TODO: What's the correct return value? + return 1; // That's a wild guess, almost certainly wrong. + } + else + #endif + return _SendRawOutboundData (data, length); +} + + + +/****************************************** +ConnectionDescriptor::_SendRawOutboundData +******************************************/ + +int ConnectionDescriptor::_SendRawOutboundData (const char *data, unsigned long length) +{ + /* This internal method is called to schedule bytes that + * will be sent out to the remote peer. + * It's not directly accessed by the caller, who hits ::SendOutboundData, + * which may or may not filter or encrypt the caller's data before + * sending it here. + */ + + // Highly naive and incomplete implementation. + // There's no throttle for runaways (which should abort only this connection + // and not the whole process), and no coalescing of small pages. + // (Well, not so bad, small pages are coalesced in ::Write) + + if (IsCloseScheduled()) + return 0; + // 25Mar10: Ignore 0 length packets as they are not meaningful in TCP (as opposed to UDP) + // and can cause the assert(nbytes>0) to fail when OutboundPages has a bunch of 0 length pages. + if (length == 0) + return 0; + + if (!data && (length > 0)) + throw std::runtime_error ("bad outbound data"); + char *buffer = (char *) malloc (length + 1); + if (!buffer) + throw std::runtime_error ("no allocation for outbound data"); + + memcpy (buffer, data, length); + buffer [length] = 0; + OutboundPages.push_back (OutboundPage (buffer, length)); + OutboundDataSize += length; + + _UpdateEvents(false, true); + + return length; +} + + + +/*********************************** +ConnectionDescriptor::SelectForRead +***********************************/ + +bool ConnectionDescriptor::SelectForRead() +{ + /* A connection descriptor is always scheduled for read, + * UNLESS it's in a pending-connect state. + * On Linux, unlike Unix, a nonblocking socket on which + * connect has been called, does NOT necessarily select + * both readable and writable in case of error. + * The socket will select writable when the disposition + * of the connect is known. On the other hand, a socket + * which successfully connects and selects writable may + * indeed have some data available on it, so it will + * select readable in that case, violating expectations! + * So we will not poll for readability until the socket + * is known to be in a connected state. + */ + + if (bPaused) + return false; + else if (bConnectPending) + return false; + else if (bWatchOnly) + return bNotifyReadable ? true : false; + else + return true; +} + + +/************************************ +ConnectionDescriptor::SelectForWrite +************************************/ + +bool ConnectionDescriptor::SelectForWrite() +{ + /* Cf the notes under SelectForRead. + * In a pending-connect state, we ALWAYS select for writable. + * In a normal state, we only select for writable when we + * have outgoing data to send. + */ + + if (bPaused) + return false; + else if (bConnectPending) + return true; + else if (bWatchOnly) + return bNotifyWritable ? true : false; + else + return (GetOutboundDataSize() > 0); +} + +/*************************** +ConnectionDescriptor::Pause +***************************/ + +bool ConnectionDescriptor::Pause() +{ + if (bWatchOnly) + throw std::runtime_error ("cannot pause/resume 'watch only' connections, set notify readable/writable instead"); + + bool old = bPaused; + bPaused = true; + _UpdateEvents(); + return old == false; +} + +/**************************** +ConnectionDescriptor::Resume +****************************/ + +bool ConnectionDescriptor::Resume() +{ + if (bWatchOnly) + throw std::runtime_error ("cannot pause/resume 'watch only' connections, set notify readable/writable instead"); + + bool old = bPaused; + bPaused = false; + _UpdateEvents(); + return old == true; +} + +/************************** +ConnectionDescriptor::Read +**************************/ + +void ConnectionDescriptor::Read() +{ + /* Read and dispatch data on a socket that has selected readable. + * It's theoretically possible to get and dispatch incoming data on + * a socket that has already been scheduled for closing or close-after-writing. + * In those cases, we'll leave it up the to protocol handler to "do the + * right thing" (which probably means to ignore the incoming data). + * + * 22Aug06: Chris Ochs reports that on FreeBSD, it's possible to come + * here with the socket already closed, after the process receives + * a ctrl-C signal (not sure if that's TERM or INT on BSD). The application + * was one in which network connections were doing a lot of interleaved reads + * and writes. + * Since we always write before reading (in order to keep the outbound queues + * as light as possible), I think what happened is that an interrupt caused + * the socket to be closed in ConnectionDescriptor::Write. We'll then + * come here in the same pass through the main event loop, and won't get + * cleaned up until immediately after. + * We originally asserted that the socket was valid when we got here. + * To deal properly with the possibility that we are closed when we get here, + * I removed the assert. HOWEVER, the potential for an infinite loop scares me, + * so even though this is really clunky, I added a flag to assert that we never + * come here more than once after being closed. (FCianfrocca) + */ + + SOCKET sd = GetSocket(); + //assert (sd != INVALID_SOCKET); (original, removed 22Aug06) + if (sd == INVALID_SOCKET) { + assert (!bReadAttemptedAfterClose); + bReadAttemptedAfterClose = true; + return; + } + + if (bWatchOnly) { + if (bNotifyReadable && EventCallback) + (*EventCallback)(GetBinding(), EM_CONNECTION_NOTIFY_READABLE, NULL, 0); + return; + } + + LastActivity = MyEventMachine->GetCurrentLoopTime(); + + int total_bytes_read = 0; + char readbuffer [16 * 1024 + 1]; + + for (int i=0; i < 10; i++) { + // Don't read just one buffer and then move on. This is faster + // if there is a lot of incoming. + // But don't read indefinitely. Give other sockets a chance to run. + // NOTICE, we're reading one less than the buffer size. + // That's so we can put a guard byte at the end of what we send + // to user code. + + + int r = read (sd, readbuffer, sizeof(readbuffer) - 1); +#ifdef OS_WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + //cerr << ""; + + if (r > 0) { + total_bytes_read += r; + + // Add a null-terminator at the the end of the buffer + // that we will send to the callback. + // DO NOT EVER CHANGE THIS. We want to explicitly allow users + // to be able to depend on this behavior, so they will have + // the option to do some things faster. Additionally it's + // a security guard against buffer overflows. + readbuffer [r] = 0; + _DispatchInboundData (readbuffer, r); + if (bPaused) + break; + } + else if (r == 0) { + break; + } + else { + #ifdef OS_UNIX + if ((e != EINPROGRESS) && (e != EWOULDBLOCK) && (e != EAGAIN) && (e != EINTR)) { + #endif + #ifdef OS_WIN32 + if ((e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK)) { + #endif + // 26Mar11: Previously, all read errors were assumed to be EWOULDBLOCK and ignored. + // Now, instead, we call Close() on errors like ECONNRESET and ENOTCONN. + UnbindReasonCode = e; + Close(); + break; + } else { + // Basically a would-block, meaning we've read everything there is to read. + break; + } + } + + } + + + if (total_bytes_read == 0) { + // If we read no data on a socket that selected readable, + // it generally means the other end closed the connection gracefully. + ScheduleClose (false); + //bCloseNow = true; + } + +} + + + +/****************************************** +ConnectionDescriptor::_DispatchInboundData +******************************************/ + +#ifdef WITH_SSL +void ConnectionDescriptor::_DispatchInboundData (const char *buffer, unsigned long size) +{ + if (SslBox) { + SslBox->PutCiphertext (buffer, size); + + int s; + char B [2048]; + while ((s = SslBox->GetPlaintext (B, sizeof(B) - 1)) > 0) { + _CheckHandshakeStatus(); + B [s] = 0; + _GenericInboundDispatch(B, s); + } + + // If our SSL handshake had a problem, shut down the connection. + if (s == -2) { + #ifndef EPROTO // OpenBSD does not have EPROTO + #define EPROTO EINTR + #endif + #ifdef OS_UNIX + UnbindReasonCode = EPROTO; + #endif + #ifdef OS_WIN32 + UnbindReasonCode = WSAECONNABORTED; + #endif + ScheduleClose(false); + return; + } + + _CheckHandshakeStatus(); + _DispatchCiphertext(); + } + else { + _GenericInboundDispatch(buffer, size); + } +} +#else +void ConnectionDescriptor::_DispatchInboundData (const char *buffer, unsigned long size) +{ + _GenericInboundDispatch(buffer, size); +} +#endif + + + +/******************************************* +ConnectionDescriptor::_CheckHandshakeStatus +*******************************************/ + +void ConnectionDescriptor::_CheckHandshakeStatus() +{ + #ifdef WITH_SSL + if (SslBox && (!bHandshakeSignaled) && SslBox->IsHandshakeCompleted()) { + bHandshakeSignaled = true; + if (EventCallback) + (*EventCallback)(GetBinding(), EM_SSL_HANDSHAKE_COMPLETED, NULL, 0); + } + #endif +} + + + +/*************************** +ConnectionDescriptor::Write +***************************/ + +void ConnectionDescriptor::Write() +{ + /* A socket which is in a pending-connect state will select + * writable when the disposition of the connect is known. + * At that point, check to be sure there are no errors, + * and if none, then promote the socket out of the pending + * state. + * TODO: I haven't figured out how Windows signals errors on + * unconnected sockets. Maybe it does the untraditional but + * logical thing and makes the socket selectable for error. + * If so, it's unsupported here for the time being, and connect + * errors will have to be caught by the timeout mechanism. + */ + + if (bConnectPending) { + int error; + socklen_t len; + len = sizeof(error); + #ifdef OS_UNIX + int o = getsockopt (GetSocket(), SOL_SOCKET, SO_ERROR, &error, &len); + #endif + #ifdef OS_WIN32 + int o = getsockopt (GetSocket(), SOL_SOCKET, SO_ERROR, (char*)&error, &len); + #endif + if ((o == 0) && (error == 0)) { + if (EventCallback) + (*EventCallback)(GetBinding(), EM_CONNECTION_COMPLETED, "", 0); + + // 5May09: Moved epoll/kqueue read/write arming into SetConnectPending, so it can be called + // from EventMachine_t::AttachFD as well. + SetConnectPending (false); + } + else { + if (o == 0) + UnbindReasonCode = error; + ScheduleClose (false); + //bCloseNow = true; + } + } + else { + + if (bNotifyWritable) { + if (EventCallback) + (*EventCallback)(GetBinding(), EM_CONNECTION_NOTIFY_WRITABLE, NULL, 0); + + _UpdateEvents(false, true); + return; + } + + assert(!bWatchOnly); + + /* 5May09: Kqueue bugs on OSX cause one extra writable event to fire even though we're using + EV_ONESHOT. We ignore this extra event once, but only the first time. If it happens again, + we should fall through to the assert(nbytes>0) failure to catch any EM bugs which might cause + ::Write to be called in a busy-loop. + */ + #ifdef HAVE_KQUEUE + if (MyEventMachine->GetPoller() == Poller_Kqueue) { + if (OutboundDataSize == 0 && !bGotExtraKqueueEvent) { + bGotExtraKqueueEvent = true; + return; + } else if (OutboundDataSize > 0) { + bGotExtraKqueueEvent = false; + } + } + #endif + + _WriteOutboundData(); + } +} + + +/**************************************** +ConnectionDescriptor::_WriteOutboundData +****************************************/ + +void ConnectionDescriptor::_WriteOutboundData() +{ + /* This is a helper function called by ::Write. + * It's possible for a socket to select writable and then no longer + * be writable by the time we get around to writing. The kernel might + * have used up its available output buffers between the select call + * and when we get here. So this condition is not an error. + * + * 20Jul07, added the same kind of protection against an invalid socket + * that is at the top of ::Read. Not entirely how this could happen in + * real life (connection-reset from the remote peer, perhaps?), but I'm + * doing it to address some reports of crashing under heavy loads. + */ + + SOCKET sd = GetSocket(); + //assert (sd != INVALID_SOCKET); + if (sd == INVALID_SOCKET) { + assert (!bWriteAttemptedAfterClose); + bWriteAttemptedAfterClose = true; + return; + } + + LastActivity = MyEventMachine->GetCurrentLoopTime(); + size_t nbytes = 0; + + #ifdef HAVE_WRITEV + int iovcnt = OutboundPages.size(); + // Max of 16 outbound pages at a time + if (iovcnt > 16) iovcnt = 16; + + iovec iov[16]; + + for(int i = 0; i < iovcnt; i++){ + OutboundPage *op = &(OutboundPages[i]); + #ifdef CC_SUNWspro + // TODO: The void * cast works fine on Solaris 11, but + // I don't know at what point that changed from older Solaris. + iov[i].iov_base = (char *)(op->Buffer + op->Offset); + #else + iov[i].iov_base = (void *)(op->Buffer + op->Offset); + #endif + iov[i].iov_len = op->Length - op->Offset; + + nbytes += iov[i].iov_len; + } + #else + char output_buffer [16 * 1024]; + + while ((OutboundPages.size() > 0) && (nbytes < sizeof(output_buffer))) { + OutboundPage *op = &(OutboundPages[0]); + if ((nbytes + op->Length - op->Offset) < sizeof (output_buffer)) { + memcpy (output_buffer + nbytes, op->Buffer + op->Offset, op->Length - op->Offset); + nbytes += (op->Length - op->Offset); + op->Free(); + OutboundPages.pop_front(); + } + else { + int len = sizeof(output_buffer) - nbytes; + memcpy (output_buffer + nbytes, op->Buffer + op->Offset, len); + op->Offset += len; + nbytes += len; + } + } + #endif + + // We should never have gotten here if there were no data to write, + // so assert that as a sanity check. + // Don't bother to make sure nbytes is less than output_buffer because + // if it were we probably would have crashed already. + assert (nbytes > 0); + + assert (GetSocket() != INVALID_SOCKET); + #ifdef HAVE_WRITEV + int bytes_written = writev (GetSocket(), iov, iovcnt); + #else + int bytes_written = write (GetSocket(), output_buffer, nbytes); + #endif + + bool err = false; +#ifdef OS_WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + if (bytes_written < 0) { + err = true; + bytes_written = 0; + } + + assert (bytes_written >= 0); + OutboundDataSize -= bytes_written; + + if (ProxiedFrom && MaxOutboundBufSize && (unsigned int)GetOutboundDataSize() < MaxOutboundBufSize && ProxiedFrom->IsPaused()) + ProxiedFrom->Resume(); + + #ifdef HAVE_WRITEV + if (!err) { + unsigned int sent = bytes_written; + std::deque::iterator op = OutboundPages.begin(); + + for (int i = 0; i < iovcnt; i++) { + if (iov[i].iov_len <= sent) { + // Sent this page in full, free it. + op->Free(); + OutboundPages.pop_front(); + + sent -= iov[i].iov_len; + } else { + // Sent part (or none) of this page, increment offset to send the remainder + op->Offset += sent; + break; + } + + // Shouldn't be possible run out of pages before the loop ends + assert(op != OutboundPages.end()); + *op++; + } + } + #else + if ((size_t)bytes_written < nbytes) { + int len = nbytes - bytes_written; + char *buffer = (char*) malloc (len + 1); + if (!buffer) + throw std::runtime_error ("bad alloc throwing back data"); + memcpy (buffer, output_buffer + bytes_written, len); + buffer [len] = 0; + OutboundPages.push_front (OutboundPage (buffer, len)); + } + #endif + + _UpdateEvents(false, true); + + if (err) { + #ifdef OS_UNIX + if ((e != EINPROGRESS) && (e != EWOULDBLOCK) && (e != EINTR)) { + #endif + #ifdef OS_WIN32 + if ((e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK)) { + #endif + UnbindReasonCode = e; + Close(); + } + } +} + + +/*************************************** +ConnectionDescriptor::ReportErrorStatus +***************************************/ + +int ConnectionDescriptor::ReportErrorStatus() +{ + if (MySocket == INVALID_SOCKET) { + return -1; + } + + int error; + socklen_t len; + len = sizeof(error); + #ifdef OS_UNIX + int o = getsockopt (GetSocket(), SOL_SOCKET, SO_ERROR, &error, &len); + #endif + #ifdef OS_WIN32 + int o = getsockopt (GetSocket(), SOL_SOCKET, SO_ERROR, (char*)&error, &len); + #endif + if ((o == 0) && (error == 0)) + return 0; + else if (o == 0) + return error; + else + return -1; +} + + +/****************************** +ConnectionDescriptor::StartTls +******************************/ + +#ifdef WITH_SSL +void ConnectionDescriptor::StartTls() +{ + if (SslBox) + throw std::runtime_error ("SSL/TLS already running on connection"); + + SslBox = new SslBox_t (bIsServer, PrivateKeyFilename, CertChainFilename, bSslVerifyPeer, bSslFailIfNoPeerCert, SniHostName, CipherList, EcdhCurve, DhParam, Protocols, GetBinding()); + _DispatchCiphertext(); + +} +#else +void ConnectionDescriptor::StartTls() +{ + throw std::runtime_error ("Encryption not available on this event-machine"); +} +#endif + + +/********************************* +ConnectionDescriptor::SetTlsParms +*********************************/ + +#ifdef WITH_SSL +void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer, bool fail_if_no_peer_cert, const char *sni_hostname, const char *cipherlist, const char *ecdh_curve, const char *dhparam, int protocols) +{ + if (SslBox) + throw std::runtime_error ("call SetTlsParms before calling StartTls"); + if (privkey_filename && *privkey_filename) + PrivateKeyFilename = privkey_filename; + if (certchain_filename && *certchain_filename) + CertChainFilename = certchain_filename; + bSslVerifyPeer = verify_peer; + bSslFailIfNoPeerCert = fail_if_no_peer_cert; + + if (sni_hostname && *sni_hostname) + SniHostName = sni_hostname; + if (cipherlist && *cipherlist) + CipherList = cipherlist; + if (ecdh_curve && *ecdh_curve) + EcdhCurve = ecdh_curve; + if (dhparam && *dhparam) + DhParam = dhparam; + + Protocols = protocols; +} +#else +void ConnectionDescriptor::SetTlsParms (const char *privkey_filename UNUSED, const char *certchain_filename UNUSED, bool verify_peer UNUSED, bool fail_if_no_peer_cert UNUSED, const char *sni_hostname UNUSED, const char *cipherlist UNUSED, const char *ecdh_curve UNUSED, const char *dhparam UNUSED, int protocols UNUSED) +{ + throw std::runtime_error ("Encryption not available on this event-machine"); +} +#endif + + +/********************************* +ConnectionDescriptor::GetPeerCert +*********************************/ + +#ifdef WITH_SSL +X509 *ConnectionDescriptor::GetPeerCert() +{ + if (!SslBox) + throw std::runtime_error ("SSL/TLS not running on this connection"); + return SslBox->GetPeerCert(); +} +#endif + + +/********************************* +ConnectionDescriptor::GetCipherBits +*********************************/ + +#ifdef WITH_SSL +int ConnectionDescriptor::GetCipherBits() +{ + if (!SslBox) + throw std::runtime_error ("SSL/TLS not running on this connection"); + return SslBox->GetCipherBits(); +} +#endif + + +/********************************* +ConnectionDescriptor::GetCipherName +*********************************/ + +#ifdef WITH_SSL +const char *ConnectionDescriptor::GetCipherName() +{ + if (!SslBox) + throw std::runtime_error ("SSL/TLS not running on this connection"); + return SslBox->GetCipherName(); +} +#endif + + +/********************************* +ConnectionDescriptor::GetCipherProtocol +*********************************/ + +#ifdef WITH_SSL +const char *ConnectionDescriptor::GetCipherProtocol() +{ + if (!SslBox) + throw std::runtime_error ("SSL/TLS not running on this connection"); + return SslBox->GetCipherProtocol(); +} +#endif + + +/********************************* +ConnectionDescriptor::GetSNIHostname +*********************************/ + +#ifdef WITH_SSL +const char *ConnectionDescriptor::GetSNIHostname() +{ + if (!SslBox) + throw std::runtime_error ("SSL/TLS not running on this connection"); + return SslBox->GetSNIHostname(); +} +#endif + + +/*********************************** +ConnectionDescriptor::VerifySslPeer +***********************************/ + +#ifdef WITH_SSL +bool ConnectionDescriptor::VerifySslPeer(const char *cert) +{ + bSslPeerAccepted = false; + + if (EventCallback) + (*EventCallback)(GetBinding(), EM_SSL_VERIFY, cert, strlen(cert)); + + return bSslPeerAccepted; +} +#endif + + +/*********************************** +ConnectionDescriptor::AcceptSslPeer +***********************************/ + +#ifdef WITH_SSL +void ConnectionDescriptor::AcceptSslPeer() +{ + bSslPeerAccepted = true; +} +#endif + + +/***************************************** +ConnectionDescriptor::_DispatchCiphertext +*****************************************/ + +#ifdef WITH_SSL +void ConnectionDescriptor::_DispatchCiphertext() +{ + assert (SslBox); + + + char BigBuf [SSLBOX_OUTPUT_CHUNKSIZE]; + bool did_work; + + do { + did_work = false; + + // try to drain ciphertext + while (SslBox->CanGetCiphertext()) { + int r = SslBox->GetCiphertext (BigBuf, sizeof(BigBuf)); + assert (r > 0); + _SendRawOutboundData (BigBuf, r); + did_work = true; + } + + // Pump the SslBox, in case it has queued outgoing plaintext + // This will return >0 if data was written, + // 0 if no data was written, and <0 if there was a fatal error. + bool pump; + do { + pump = false; + int w = SslBox->PutPlaintext (NULL, 0); + if (w > 0) { + did_work = true; + pump = true; + } + else if (w < 0) + ScheduleClose (false); + } while (pump); + + // try to put plaintext. INCOMPLETE, doesn't belong here? + // In SendOutboundData, we're spooling plaintext directly + // into SslBox. That may be wrong, we may need to buffer it + // up here! + /* + const char *ptr; + int ptr_length; + while (OutboundPlaintext.GetPage (&ptr, &ptr_length)) { + assert (ptr && (ptr_length > 0)); + int w = SslMachine.PutPlaintext (ptr, ptr_length); + if (w > 0) { + OutboundPlaintext.DiscardBytes (w); + did_work = true; + } + else + break; + } + */ + + } while (did_work); + +} +#endif + + + +/******************************* +ConnectionDescriptor::Heartbeat +*******************************/ + +void ConnectionDescriptor::Heartbeat() +{ + /* Only allow a certain amount of time to go by while waiting + * for a pending connect. If it expires, then kill the socket. + * For a connected socket, close it if its inactivity timer + * has expired. + */ + + if (bConnectPending) { + if ((MyEventMachine->GetCurrentLoopTime() - CreatedAt) >= PendingConnectTimeout) { + UnbindReasonCode = ETIMEDOUT; + ScheduleClose (false); + //bCloseNow = true; + } + } + else { + if (InactivityTimeout && ((MyEventMachine->GetCurrentLoopTime() - LastActivity) >= InactivityTimeout)) { + UnbindReasonCode = ETIMEDOUT; + ScheduleClose (false); + //bCloseNow = true; + } + } +} + + +/**************************************** +LoopbreakDescriptor::LoopbreakDescriptor +****************************************/ + +LoopbreakDescriptor::LoopbreakDescriptor (SOCKET sd, EventMachine_t *parent_em): + EventableDescriptor (sd, parent_em) +{ + /* This is really bad and ugly. Change someday if possible. + * We have to know about an event-machine (probably the one that owns us), + * so we can pass newly-created connections to it. + */ + + bCallbackUnbind = false; + + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + #endif + #ifdef HAVE_KQUEUE + MyEventMachine->ArmKqueueReader (this); + #endif +} + + + + +/************************* +LoopbreakDescriptor::Read +*************************/ + +void LoopbreakDescriptor::Read() +{ + // TODO, refactor, this code is probably in the wrong place. + assert (MyEventMachine); + MyEventMachine->_ReadLoopBreaker(); +} + + +/************************** +LoopbreakDescriptor::Write +**************************/ + +void LoopbreakDescriptor::Write() +{ + // Why are we here? + throw std::runtime_error ("bad code path in loopbreak"); +} + +/************************************** +AcceptorDescriptor::AcceptorDescriptor +**************************************/ + +AcceptorDescriptor::AcceptorDescriptor (SOCKET sd, EventMachine_t *parent_em): + EventableDescriptor (sd, parent_em) +{ + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + #endif + #ifdef HAVE_KQUEUE + MyEventMachine->ArmKqueueReader (this); + #endif +} + + +/*************************************** +AcceptorDescriptor::~AcceptorDescriptor +***************************************/ + +AcceptorDescriptor::~AcceptorDescriptor() +{ +} + +/**************************************** +STATIC: AcceptorDescriptor::StopAcceptor +****************************************/ + +void AcceptorDescriptor::StopAcceptor (const uintptr_t binding) +{ + // TODO: This is something of a hack, or at least it's a static method of the wrong class. + AcceptorDescriptor *ad = dynamic_cast (Bindable_t::GetObject (binding)); + if (ad) + ad->ScheduleClose (false); + else + throw std::runtime_error ("failed to close nonexistent acceptor"); +} + + +/************************ +AcceptorDescriptor::Read +************************/ + +void AcceptorDescriptor::Read() +{ + /* Accept up to a certain number of sockets on the listening connection. + * Don't try to accept all that are present, because this would allow a DoS attack + * in which no data were ever read or written. We should accept more than one, + * if available, to keep the partially accepted sockets from backing up in the kernel. + */ + + /* Make sure we use non-blocking i/o on the acceptor socket, since we're selecting it + * for readability. According to Stevens UNP, it's possible for an acceptor to select readable + * and then block when we call accept. For example, the other end resets the connection after + * the socket selects readable and before we call accept. The kernel will remove the dead + * socket from the accept queue. If the accept queue is now empty, accept will block. + */ + + + struct sockaddr_in6 pin; + socklen_t addrlen = sizeof (pin); + int accept_count = EventMachine_t::GetSimultaneousAcceptCount(); + + for (int i=0; i < accept_count; i++) { +#if defined(HAVE_CONST_SOCK_CLOEXEC) && defined(HAVE_ACCEPT4) + SOCKET sd = accept4 (GetSocket(), (struct sockaddr*)&pin, &addrlen, SOCK_CLOEXEC); + if (sd == INVALID_SOCKET) { + // We may be running in a kernel where + // SOCK_CLOEXEC is not supported - fall back: + sd = accept (GetSocket(), (struct sockaddr*)&pin, &addrlen); + } +#else + SOCKET sd = accept (GetSocket(), (struct sockaddr*)&pin, &addrlen); +#endif + if (sd == INVALID_SOCKET) { + // This breaks the loop when we've accepted everything on the kernel queue, + // up to 10 new connections. But what if the *first* accept fails? + // Does that mean anything serious is happening, beyond the situation + // described in the note above? + break; + } + + // Set the newly-accepted socket non-blocking and to close on exec. + // On Windows, this may fail because, weirdly, Windows inherits the non-blocking + // attribute that we applied to the acceptor socket into the accepted one. + if (!SetFdCloexec(sd) || !SetSocketNonblocking (sd)) { + //int val = fcntl (sd, F_GETFL, 0); + //if (fcntl (sd, F_SETFL, val | O_NONBLOCK) == -1) { + shutdown (sd, 1); + close (sd); + continue; + } + + // Disable slow-start (Nagle algorithm). Eventually make this configurable. + int one = 1; + setsockopt (sd, IPPROTO_TCP, TCP_NODELAY, (char*) &one, sizeof(one)); + + + ConnectionDescriptor *cd = new ConnectionDescriptor (sd, MyEventMachine); + if (!cd) + throw std::runtime_error ("no newly accepted connection"); + cd->SetServerMode(); + if (EventCallback) { + (*EventCallback) (GetBinding(), EM_CONNECTION_ACCEPTED, NULL, cd->GetBinding()); + } + #ifdef HAVE_EPOLL + cd->GetEpollEvent()->events = 0; + if (cd->SelectForRead()) + cd->GetEpollEvent()->events |= EPOLLIN; + if (cd->SelectForWrite()) + cd->GetEpollEvent()->events |= EPOLLOUT; + #endif + assert (MyEventMachine); + MyEventMachine->Add (cd); + #ifdef HAVE_KQUEUE + bKqueueArmWrite = cd->SelectForWrite(); + if (bKqueueArmWrite) + MyEventMachine->Modify (cd); + if (cd->SelectForRead()) + MyEventMachine->ArmKqueueReader (cd); + #endif + } + +} + + +/************************* +AcceptorDescriptor::Write +*************************/ + +void AcceptorDescriptor::Write() +{ + // Why are we here? + throw std::runtime_error ("bad code path in acceptor"); +} + + +/***************************** +AcceptorDescriptor::Heartbeat +*****************************/ + +void AcceptorDescriptor::Heartbeat() +{ + // No-op +} + + +/************************************** +DatagramDescriptor::DatagramDescriptor +**************************************/ + +DatagramDescriptor::DatagramDescriptor (SOCKET sd, EventMachine_t *parent_em): + EventableDescriptor (sd, parent_em), + OutboundDataSize (0) +{ + memset (&ReturnAddress, 0, sizeof(ReturnAddress)); + + /* Provisionally added 19Oct07. All datagram sockets support broadcasting. + * Until now, sending to a broadcast address would give EACCES (permission denied) + * on systems like Linux and BSD that require the SO_BROADCAST socket-option in order + * to accept a packet to a broadcast address. Solaris doesn't require it. I think + * Windows DOES require it but I'm not sure. + * + * Ruby does NOT do what we're doing here. In Ruby, you have to explicitly set SO_BROADCAST + * on a UDP socket in order to enable broadcasting. The reason for requiring the option + * in the first place is so that applications don't send broadcast datagrams by mistake. + * I imagine that could happen if a user of an application typed in an address that happened + * to be a broadcast address on that particular subnet. + * + * This is provisional because someone may eventually come up with a good reason not to + * do it for all UDP sockets. If that happens, then we'll need to add a usercode-level API + * to set the socket option, just like Ruby does. AND WE'LL ALSO BREAK CODE THAT DOESN'T + * EXPLICITLY SET THE OPTION. + */ + + int oval = 1; + setsockopt (GetSocket(), SOL_SOCKET, SO_BROADCAST, (char*)&oval, sizeof(oval)); + + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + #endif + #ifdef HAVE_KQUEUE + MyEventMachine->ArmKqueueReader (this); + #endif +} + + +/*************************************** +DatagramDescriptor::~DatagramDescriptor +***************************************/ + +DatagramDescriptor::~DatagramDescriptor() +{ + // Run down any stranded outbound data. + for (size_t i=0; i < OutboundPages.size(); i++) + OutboundPages[i].Free(); +} + + +/***************************** +DatagramDescriptor::Heartbeat +*****************************/ + +void DatagramDescriptor::Heartbeat() +{ + // Close it if its inactivity timer has expired. + + if (InactivityTimeout && ((MyEventMachine->GetCurrentLoopTime() - LastActivity) >= InactivityTimeout)) + ScheduleClose (false); + //bCloseNow = true; +} + + +/************************ +DatagramDescriptor::Read +************************/ + +void DatagramDescriptor::Read() +{ + SOCKET sd = GetSocket(); + assert (sd != INVALID_SOCKET); + LastActivity = MyEventMachine->GetCurrentLoopTime(); + + // This is an extremely large read buffer. + // In many cases you wouldn't expect to get any more than 4K. + char readbuffer [16 * 1024]; + + for (int i=0; i < 10; i++) { + // Don't read just one buffer and then move on. This is faster + // if there is a lot of incoming. + // But don't read indefinitely. Give other sockets a chance to run. + // NOTICE, we're reading one less than the buffer size. + // That's so we can put a guard byte at the end of what we send + // to user code. + + struct sockaddr_in6 sin; + socklen_t slen = sizeof (sin); + memset (&sin, 0, slen); + + int r = recvfrom (sd, readbuffer, sizeof(readbuffer) - 1, 0, (struct sockaddr*)&sin, &slen); + //cerr << ""; + + // In UDP, a zero-length packet is perfectly legal. + if (r >= 0) { + + // Add a null-terminator at the the end of the buffer + // that we will send to the callback. + // DO NOT EVER CHANGE THIS. We want to explicitly allow users + // to be able to depend on this behavior, so they will have + // the option to do some things faster. Additionally it's + // a security guard against buffer overflows. + readbuffer [r] = 0; + + + // Set up a "temporary" return address so that callers can "reply" to us + // from within the callback we are about to invoke. That means that ordinary + // calls to "send_data_to_connection" (which is of course misnamed in this + // case) will result in packets being sent back to the same place that sent + // us this one. + // There is a different call (evma_send_datagram) for cases where the caller + // actually wants to send a packet somewhere else. + + memset (&ReturnAddress, 0, sizeof(ReturnAddress)); + memcpy (&ReturnAddress, &sin, slen); + + _GenericInboundDispatch(readbuffer, r); + + } + else { + // Basically a would-block, meaning we've read everything there is to read. + break; + } + + } + + +} + + +/************************* +DatagramDescriptor::Write +*************************/ + +void DatagramDescriptor::Write() +{ + /* It's possible for a socket to select writable and then no longer + * be writable by the time we get around to writing. The kernel might + * have used up its available output buffers between the select call + * and when we get here. So this condition is not an error. + * This code is very reminiscent of ConnectionDescriptor::_WriteOutboundData, + * but differs in the that the outbound data pages (received from the + * user) are _message-structured._ That is, we send each of them out + * one message at a time. + * TODO, we are currently suppressing the EMSGSIZE error!!! + */ + + SOCKET sd = GetSocket(); + assert (sd != INVALID_SOCKET); + LastActivity = MyEventMachine->GetCurrentLoopTime(); + + assert (OutboundPages.size() > 0); + + // Send out up to 10 packets, then cycle the machine. + for (int i = 0; i < 10; i++) { + if (OutboundPages.size() <= 0) + break; + OutboundPage *op = &(OutboundPages[0]); + + // The nasty cast to (char*) is needed because Windows is brain-dead. + int s = sendto (sd, (char*)op->Buffer, op->Length, 0, (struct sockaddr*)&(op->From), + (op->From.sin6_family == AF_INET6 ? sizeof (struct sockaddr_in6) : sizeof (struct sockaddr_in))); +#ifdef OS_WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + + OutboundDataSize -= op->Length; + op->Free(); + OutboundPages.pop_front(); + + if (s == SOCKET_ERROR) { + #ifdef OS_UNIX + if ((e != EINPROGRESS) && (e != EWOULDBLOCK) && (e != EINTR)) { + #endif + #ifdef OS_WIN32 + if ((e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK)) { + #endif + UnbindReasonCode = e; + Close(); + break; + } + } + } + + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + if (SelectForWrite()) + EpollEvent.events |= EPOLLOUT; + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + #ifdef HAVE_KQUEUE + bKqueueArmWrite = SelectForWrite(); + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif +} + + +/********************************** +DatagramDescriptor::SelectForWrite +**********************************/ + +bool DatagramDescriptor::SelectForWrite() +{ + /* Changed 15Nov07, per bug report by Mark Zvillius. + * The outbound data size will be zero if there are zero-length outbound packets, + * so we now select writable in case the outbound page buffer is not empty. + * Note that the superclass ShouldDelete method still checks for outbound data size, + * which may be wrong. + */ + //return (GetOutboundDataSize() > 0); (Original) + return (OutboundPages.size() > 0); +} + + +/************************************ +DatagramDescriptor::SendOutboundData +************************************/ + +int DatagramDescriptor::SendOutboundData (const char *data, unsigned long length) +{ + // This is almost an exact clone of ConnectionDescriptor::_SendRawOutboundData. + // That means most of it could be factored to a common ancestor. Note that + // empty datagrams are meaningful, which isn't the case for TCP streams. + + if (IsCloseScheduled()) + return 0; + + if (!data && (length > 0)) + throw std::runtime_error ("bad outbound data"); + char *buffer = (char *) malloc (length + 1); + if (!buffer) + throw std::runtime_error ("no allocation for outbound data"); + memcpy (buffer, data, length); + buffer [length] = 0; + OutboundPages.push_back (OutboundPage (buffer, length, ReturnAddress)); + OutboundDataSize += length; + + #ifdef HAVE_EPOLL + EpollEvent.events = (EPOLLIN | EPOLLOUT); + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + #ifdef HAVE_KQUEUE + bKqueueArmWrite = true; + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + + return length; +} + + +/**************************************** +DatagramDescriptor::SendOutboundDatagram +****************************************/ + +int DatagramDescriptor::SendOutboundDatagram (const char *data, unsigned long length, const char *address, int port) +{ + // This is an exact clone of ConnectionDescriptor::SendOutboundData. + // That means it needs to move to a common ancestor. + // TODO: Refactor this so there's no overlap with SendOutboundData. + + if (IsCloseScheduled()) + //if (bCloseNow || bCloseAfterWriting) + return 0; + + if (!address || !*address || !port) + return 0; + + struct sockaddr_in6 addr_here; + size_t addr_here_len = sizeof addr_here; + if (0 != EventMachine_t::name2address (address, port, SOCK_DGRAM, (struct sockaddr *)&addr_here, &addr_here_len)) + return -1; + + if (!data && (length > 0)) + throw std::runtime_error ("bad outbound data"); + char *buffer = (char *) malloc (length + 1); + if (!buffer) + throw std::runtime_error ("no allocation for outbound data"); + memcpy (buffer, data, length); + buffer [length] = 0; + OutboundPages.push_back (OutboundPage (buffer, length, addr_here)); + OutboundDataSize += length; + + #ifdef HAVE_EPOLL + EpollEvent.events = (EPOLLIN | EPOLLOUT); + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + #ifdef HAVE_KQUEUE + bKqueueArmWrite = true; + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + + return length; +} + + +/********************************************** +ConnectionDescriptor::GetCommInactivityTimeout +**********************************************/ + +uint64_t ConnectionDescriptor::GetCommInactivityTimeout() +{ + return InactivityTimeout / 1000; +} + + +/********************************************** +ConnectionDescriptor::SetCommInactivityTimeout +**********************************************/ + +int ConnectionDescriptor::SetCommInactivityTimeout (uint64_t value) +{ + InactivityTimeout = value * 1000; + MyEventMachine->QueueHeartbeat(this); + return 1; +} + +/******************************* +DatagramDescriptor::GetPeername +*******************************/ + +bool DatagramDescriptor::GetPeername (struct sockaddr *s, socklen_t *len) +{ + bool ok = false; + if (s) { + *len = sizeof(ReturnAddress); + memset (s, 0, sizeof(ReturnAddress)); + memcpy (s, &ReturnAddress, sizeof(ReturnAddress)); + ok = true; + } + return ok; +} + + +/******************************************** +DatagramDescriptor::GetCommInactivityTimeout +********************************************/ + +uint64_t DatagramDescriptor::GetCommInactivityTimeout() +{ + return InactivityTimeout / 1000; +} + +/******************************************** +DatagramDescriptor::SetCommInactivityTimeout +********************************************/ + +int DatagramDescriptor::SetCommInactivityTimeout (uint64_t value) +{ + if (value > 0) { + InactivityTimeout = value * 1000; + MyEventMachine->QueueHeartbeat(this); + return 1; + } + return 0; +} + + +/************************************ +InotifyDescriptor::InotifyDescriptor +*************************************/ + +InotifyDescriptor::InotifyDescriptor (EventMachine_t *em): + EventableDescriptor(0, em) +{ + bCallbackUnbind = false; + + #ifndef HAVE_INOTIFY + throw std::runtime_error("no inotify support on this system"); + #else + + int fd = inotify_init(); + if (fd == -1) { + char buf[200]; + snprintf (buf, sizeof(buf)-1, "unable to create inotify descriptor: %s", strerror(errno)); + throw std::runtime_error (buf); + } + + MySocket = fd; + SetSocketNonblocking(MySocket); + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + #endif + + #endif +} + + +/************************************* +InotifyDescriptor::~InotifyDescriptor +**************************************/ + +InotifyDescriptor::~InotifyDescriptor() +{ + close(MySocket); + MySocket = INVALID_SOCKET; +} + +/*********************** +InotifyDescriptor::Read +************************/ + +void InotifyDescriptor::Read() +{ + assert (MyEventMachine); + MyEventMachine->_ReadInotifyEvents(); +} + + +/************************ +InotifyDescriptor::Write +*************************/ + +void InotifyDescriptor::Write() +{ + throw std::runtime_error("bad code path in inotify"); +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.h new file mode 100644 index 0000000..4d7f7d4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ed.h @@ -0,0 +1,457 @@ +/***************************************************************************** + +$Id$ + +File: ed.h +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#ifndef __EventableDescriptor__H_ +#define __EventableDescriptor__H_ + + +class EventMachine_t; // forward reference +#ifdef WITH_SSL +class SslBox_t; // forward reference +#endif + +bool SetSocketNonblocking (SOCKET); +bool SetFdCloexec (int); + +/************************* +class EventableDescriptor +*************************/ + +class EventableDescriptor: public Bindable_t +{ + public: + EventableDescriptor (SOCKET, EventMachine_t*); + virtual ~EventableDescriptor() NO_EXCEPT_FALSE; + + SOCKET GetSocket() {return MySocket;} + void SetSocketInvalid() { MySocket = INVALID_SOCKET; } + void Close(); + + virtual void Read() = 0; + virtual void Write() = 0; + virtual void Heartbeat() = 0; + + // These methods tell us whether the descriptor + // should be selected or polled for read/write. + virtual bool SelectForRead() = 0; + virtual bool SelectForWrite() = 0; + + // are we scheduled for a close, or in an error state, or already closed? + bool ShouldDelete(); + // Do we have any data to write? This is used by ShouldDelete. + virtual int GetOutboundDataSize() {return 0;} + virtual bool IsWatchOnly(){ return bWatchOnly; } + + virtual void ScheduleClose (bool after_writing); + bool IsCloseScheduled(); + virtual void HandleError(){ ScheduleClose (false); } + + void SetEventCallback (EMCallback); + + virtual bool GetPeername (struct sockaddr*, socklen_t*) = 0; + virtual bool GetSockname (struct sockaddr*, socklen_t*) = 0; + virtual bool GetSubprocessPid (pid_t*) {return false;} + + virtual void StartTls() {} + virtual void SetTlsParms (const char *, const char *, bool, bool, const char *, const char *, const char *, const char *, int) {} + + #ifdef WITH_SSL + virtual X509 *GetPeerCert() {return NULL;} + virtual int GetCipherBits() {return -1;} + virtual const char *GetCipherName() {return NULL;} + virtual const char *GetCipherProtocol() {return NULL;} + virtual const char *GetSNIHostname() {return NULL;} + #endif + + virtual uint64_t GetCommInactivityTimeout() {return 0;} + virtual int SetCommInactivityTimeout (uint64_t) {return 0;} + uint64_t GetPendingConnectTimeout(); + int SetPendingConnectTimeout (uint64_t value); + uint64_t GetLastActivity() { return LastActivity; } + + #ifdef HAVE_EPOLL + struct epoll_event *GetEpollEvent() { return &EpollEvent; } + #endif + + #ifdef HAVE_KQUEUE + bool GetKqueueArmWrite() { return bKqueueArmWrite; } + #endif + + virtual void StartProxy(const uintptr_t, const unsigned long, const unsigned long); + virtual void StopProxy(); + virtual unsigned long GetProxiedBytes(){ return ProxiedBytes; }; + virtual void SetProxiedFrom(EventableDescriptor*, const unsigned long); + virtual int SendOutboundData(const char*,unsigned long){ return -1; } + virtual bool IsPaused(){ return bPaused; } + virtual bool Pause(){ bPaused = true; return bPaused; } + virtual bool Resume(){ bPaused = false; return bPaused; } + + void SetUnbindReasonCode(int code){ UnbindReasonCode = code; } + virtual int ReportErrorStatus(){ return 0; } + virtual bool IsConnectPending(){ return false; } + virtual uint64_t GetNextHeartbeat(); + + private: + bool bCloseNow; + bool bCloseAfterWriting; + + protected: + SOCKET MySocket; + bool bAttached; + bool bWatchOnly; + + EMCallback EventCallback; + void _GenericInboundDispatch (const char *buffer, unsigned long size); + bool _GenericGetPeername (struct sockaddr*, socklen_t*); + bool _GenericGetSockname (struct sockaddr*, socklen_t*); + + uint64_t CreatedAt; + bool bCallbackUnbind; + int UnbindReasonCode; + + unsigned long BytesToProxy; + EventableDescriptor *ProxyTarget; + EventableDescriptor *ProxiedFrom; + unsigned long ProxiedBytes; + + unsigned long MaxOutboundBufSize; + + #ifdef HAVE_EPOLL + struct epoll_event EpollEvent; + #endif + + #ifdef HAVE_KQUEUE + bool bKqueueArmWrite; + #endif + + EventMachine_t *MyEventMachine; + uint64_t PendingConnectTimeout; + uint64_t InactivityTimeout; + uint64_t LastActivity; + uint64_t NextHeartbeat; + bool bPaused; +}; + + + +/************************* +class LoopbreakDescriptor +*************************/ + +class LoopbreakDescriptor: public EventableDescriptor +{ + public: + LoopbreakDescriptor (SOCKET, EventMachine_t*); + virtual ~LoopbreakDescriptor() {} + + virtual void Read(); + virtual void Write(); + virtual void Heartbeat() {} + + virtual bool SelectForRead() {return true;} + virtual bool SelectForWrite() {return false;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); } + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); } +}; + + +/************************** +class ConnectionDescriptor +**************************/ + +class ConnectionDescriptor: public EventableDescriptor +{ + public: + ConnectionDescriptor (SOCKET, EventMachine_t*); + virtual ~ConnectionDescriptor(); + + int SendOutboundData (const char*, unsigned long); + + void SetConnectPending (bool f); + virtual void ScheduleClose (bool after_writing); + virtual void HandleError(); + + void SetNotifyReadable (bool); + void SetNotifyWritable (bool); + void SetAttached (bool); + void SetWatchOnly (bool); + + bool Pause(); + bool Resume(); + + bool IsNotifyReadable(){ return bNotifyReadable; } + bool IsNotifyWritable(){ return bNotifyWritable; } + + virtual void Read(); + virtual void Write(); + virtual void Heartbeat(); + + virtual bool SelectForRead(); + virtual bool SelectForWrite(); + + // Do we have any data to write? This is used by ShouldDelete. + virtual int GetOutboundDataSize() {return OutboundDataSize;} + + virtual void StartTls(); + virtual void SetTlsParms (const char *, const char *, bool, bool, const char *, const char *, const char *, const char *, int); + + #ifdef WITH_SSL + virtual X509 *GetPeerCert(); + virtual int GetCipherBits(); + virtual const char *GetCipherName(); + virtual const char *GetCipherProtocol(); + virtual const char *GetSNIHostname(); + virtual bool VerifySslPeer(const char*); + virtual void AcceptSslPeer(); + #endif + + void SetServerMode() {bIsServer = true;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); } + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); } + + virtual uint64_t GetCommInactivityTimeout(); + virtual int SetCommInactivityTimeout (uint64_t value); + + virtual int ReportErrorStatus(); + virtual bool IsConnectPending(){ return bConnectPending; } + + protected: + struct OutboundPage { + OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {} + void Free() {if (Buffer) free (const_cast(Buffer)); } + const char *Buffer; + int Length; + int Offset; + }; + + protected: + bool bConnectPending; + + bool bNotifyReadable; + bool bNotifyWritable; + + bool bReadAttemptedAfterClose; + bool bWriteAttemptedAfterClose; + + std::deque OutboundPages; + int OutboundDataSize; + + #ifdef WITH_SSL + SslBox_t *SslBox; + std::string CertChainFilename; + std::string PrivateKeyFilename; + std::string CipherList; + std::string EcdhCurve; + std::string DhParam; + int Protocols; + bool bHandshakeSignaled; + bool bSslVerifyPeer; + bool bSslFailIfNoPeerCert; + std::string SniHostName; + bool bSslPeerAccepted; + #endif + + #ifdef HAVE_KQUEUE + bool bGotExtraKqueueEvent; + #endif + + bool bIsServer; + + private: + void _UpdateEvents(); + void _UpdateEvents(bool, bool); + void _WriteOutboundData(); + void _DispatchInboundData (const char *buffer, unsigned long size); + void _DispatchCiphertext(); + int _SendRawOutboundData (const char *buffer, unsigned long size); + void _CheckHandshakeStatus(); + +}; + + +/************************ +class DatagramDescriptor +************************/ + +class DatagramDescriptor: public EventableDescriptor +{ + public: + DatagramDescriptor (SOCKET, EventMachine_t*); + virtual ~DatagramDescriptor(); + + virtual void Read(); + virtual void Write(); + virtual void Heartbeat(); + + virtual bool SelectForRead() {return true;} + virtual bool SelectForWrite(); + + int SendOutboundData (const char*, unsigned long); + int SendOutboundDatagram (const char*, unsigned long, const char*, int); + + // Do we have any data to write? This is used by ShouldDelete. + virtual int GetOutboundDataSize() {return OutboundDataSize;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len); + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); }; + + virtual uint64_t GetCommInactivityTimeout(); + virtual int SetCommInactivityTimeout (uint64_t value); + + protected: + struct OutboundPage { + OutboundPage (const char *b, int l, struct sockaddr_in6 f, int o=0): Buffer(b), Length(l), Offset(o), From(f) {} + void Free() {if (Buffer) free (const_cast(Buffer)); } + const char *Buffer; + int Length; + int Offset; + struct sockaddr_in6 From; + }; + + std::deque OutboundPages; + int OutboundDataSize; + + struct sockaddr_in6 ReturnAddress; +}; + + +/************************ +class AcceptorDescriptor +************************/ + +class AcceptorDescriptor: public EventableDescriptor +{ + public: + AcceptorDescriptor (SOCKET, EventMachine_t*); + virtual ~AcceptorDescriptor(); + + virtual void Read(); + virtual void Write(); + virtual void Heartbeat(); + + virtual bool SelectForRead() {return true;} + virtual bool SelectForWrite() {return false;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); } + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); }; + + static void StopAcceptor (const uintptr_t binding); +}; + +/******************** +class PipeDescriptor +********************/ + +#ifdef OS_UNIX +class PipeDescriptor: public EventableDescriptor +{ + public: + PipeDescriptor (SOCKET, pid_t, EventMachine_t*); + virtual ~PipeDescriptor() NO_EXCEPT_FALSE; + + virtual void Read(); + virtual void Write(); + virtual void Heartbeat(); + + virtual bool SelectForRead(); + virtual bool SelectForWrite(); + + int SendOutboundData (const char*, unsigned long); + virtual int GetOutboundDataSize() {return OutboundDataSize;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); } + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); } + + virtual bool GetSubprocessPid (pid_t*); + + protected: + struct OutboundPage { + OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {} + void Free() {if (Buffer) free (const_cast(Buffer)); } + const char *Buffer; + int Length; + int Offset; + }; + + protected: + bool bReadAttemptedAfterClose; + + std::deque OutboundPages; + int OutboundDataSize; + + pid_t SubprocessPid; + + private: + void _DispatchInboundData (const char *buffer, int size); +}; +#endif // OS_UNIX + + +/************************ +class KeyboardDescriptor +************************/ + +class KeyboardDescriptor: public EventableDescriptor +{ + public: + KeyboardDescriptor (EventMachine_t*); + virtual ~KeyboardDescriptor(); + + virtual void Read(); + virtual void Write(); + virtual void Heartbeat(); + + virtual bool SelectForRead() {return true;} + virtual bool SelectForWrite() {return false;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return _GenericGetPeername (s, len); } + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return _GenericGetSockname (s, len); } + + protected: + bool bReadAttemptedAfterClose; + + private: + void _DispatchInboundData (const char *buffer, int size); +}; + + +/*********************** +class InotifyDescriptor +************************/ + +class InotifyDescriptor: public EventableDescriptor +{ + public: + InotifyDescriptor (EventMachine_t*); + virtual ~InotifyDescriptor(); + + void Read(); + void Write(); + + virtual void Heartbeat() {} + virtual bool SelectForRead() {return true;} + virtual bool SelectForWrite() {return false;} + + virtual bool GetPeername (struct sockaddr* s, socklen_t* len) { return false; } + virtual bool GetSockname (struct sockaddr* s, socklen_t* len) { return false; } +}; + +#endif // __EventableDescriptor__H_ diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.cpp new file mode 100644 index 0000000..2907485 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.cpp @@ -0,0 +1,2396 @@ +/***************************************************************************** + +$Id$ + +File: em.cpp +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +// THIS ENTIRE FILE WILL EVENTUALLY BE FOR UNIX BUILDS ONLY. +//#ifdef OS_UNIX + +#include "project.h" + +/* The numer of max outstanding timers was once a const enum defined in em.h. + * Now we define it here so that users can change its value if necessary. + */ +static unsigned int MaxOutstandingTimers = 100000; + +/* The number of accept() done at once in a single tick when the acceptor + * socket becomes readable. + */ +static unsigned int SimultaneousAcceptCount = 10; + +/* Internal helper to create a socket with SOCK_CLOEXEC set, and fall + * back to fcntl'ing it if the headers/runtime don't support it. + */ +SOCKET EmSocket (int domain, int type, int protocol) +{ + SOCKET sd; +#ifdef HAVE_SOCKET_CLOEXEC + sd = socket (domain, type | SOCK_CLOEXEC, protocol); + if (sd == INVALID_SOCKET) { + sd = socket (domain, type, protocol); + if (sd < 0) { + return sd; + } + SetFdCloexec(sd); + } +#else + sd = socket (domain, type, protocol); + if (sd == INVALID_SOCKET) { + return sd; + } + SetFdCloexec(sd); +#endif + return sd; +} + + +/*************************************** +STATIC EventMachine_t::GetMaxTimerCount +***************************************/ + +int EventMachine_t::GetMaxTimerCount() +{ + return MaxOutstandingTimers; +} + + +/*************************************** +STATIC EventMachine_t::SetMaxTimerCount +***************************************/ + +void EventMachine_t::SetMaxTimerCount (int count) +{ + /* Allow a user to increase the maximum number of outstanding timers. + * If this gets "too high" (a metric that is of course platform dependent), + * bad things will happen like performance problems and possible overuse + * of memory. + * The actual timer mechanism is very efficient so it's hard to know what + * the practical max, but 100,000 shouldn't be too problematical. + */ + if (count < 100) + count = 100; + MaxOutstandingTimers = count; +} + +int EventMachine_t::GetSimultaneousAcceptCount() +{ + return SimultaneousAcceptCount; +} + +void EventMachine_t::SetSimultaneousAcceptCount (int count) +{ + if (count < 1) + count = 1; + SimultaneousAcceptCount = count; +} + + +/****************************** +EventMachine_t::EventMachine_t +******************************/ + +EventMachine_t::EventMachine_t (EMCallback event_callback, Poller_t poller): + NumCloseScheduled (0), + HeartbeatInterval(2000000), + EventCallback (event_callback), + LoopBreakerReader (INVALID_SOCKET), + LoopBreakerWriter (INVALID_SOCKET), + bTerminateSignalReceived (false), + Poller (poller), + epfd (-1), + kqfd (-1) + #ifdef HAVE_INOTIFY + , inotify (NULL) + #endif +{ + // Default time-slice is just smaller than one hundred mills. + Quantum.tv_sec = 0; + Quantum.tv_usec = 90000; + + // Override the requested poller back to default if needed. + #if !defined(HAVE_EPOLL) && !defined(HAVE_KQUEUE) + Poller = Poller_Default; + #endif + + /* Initialize monotonic timekeeping on OS X before the first call to GetRealTime */ + #ifdef OS_DARWIN + (void) mach_timebase_info(&mach_timebase); + #endif + + #ifdef OS_WIN32 + TickCountTickover = 0; + LastTickCount = 0; + #endif + + // Make sure the current loop time is sane, in case we do any initializations of + // objects before we start running. + _UpdateTime(); + + /* We initialize the network library here (only on Windows of course) + * and initialize "loop breakers." Our destructor also does some network-level + * cleanup. There's thus an implicit assumption that any given instance of EventMachine_t + * will only call ::Run once. Is that a good assumption? Should we move some of these + * inits and de-inits into ::Run? + */ + #ifdef OS_WIN32 + WSADATA w; + WSAStartup (MAKEWORD (1, 1), &w); + #endif + + _InitializeLoopBreaker(); + SelectData = new SelectData_t(); +} + + +/******************************* +EventMachine_t::~EventMachine_t +*******************************/ + +EventMachine_t::~EventMachine_t() +{ + // Run down descriptors + size_t i; + for (i = 0; i < NewDescriptors.size(); i++) + delete NewDescriptors[i]; + for (i = 0; i < Descriptors.size(); i++) + delete Descriptors[i]; + + close (LoopBreakerReader); + close (LoopBreakerWriter); + + // Remove any file watch descriptors + while(!Files.empty()) { + std::map::iterator f = Files.begin(); + UnwatchFile (f->first); + } + + if (epfd != -1) + close (epfd); + if (kqfd != -1) + close (kqfd); + + delete SelectData; +} + + +/**************************** +EventMachine_t::ScheduleHalt +****************************/ + +void EventMachine_t::ScheduleHalt() +{ + /* This is how we stop the machine. + * This can be called by clients. Signal handlers will probably + * set the global flag. + * For now this means there can only be one EventMachine ever running at a time. + * + * IMPORTANT: keep this light, fast, and async-safe. Don't do anything frisky in here, + * because it may be called from signal handlers invoked from code that we don't + * control. At this writing (20Sep06), EM does NOT install any signal handlers of + * its own. + * + * We need a FAQ. And one of the questions is: how do I stop EM when Ctrl-C happens? + * The answer is to call evma_stop_machine, which calls here, from a SIGINT handler. + */ + bTerminateSignalReceived = true; + + /* Signal the loopbreaker so we break out of long-running select/epoll/kqueue and + * notice the halt boolean is set. Signalling the loopbreaker also uses a single + * signal-safe syscall. + */ + SignalLoopBreaker(); +} + +bool EventMachine_t::Stopping() +{ + return bTerminateSignalReceived; +} + +/******************************* +EventMachine_t::SetTimerQuantum +*******************************/ + +void EventMachine_t::SetTimerQuantum (int interval) +{ + /* We get a timer-quantum expressed in milliseconds. + */ + + if ((interval < 5) || (interval > 5*60*1000)) + throw std::runtime_error ("invalid timer-quantum"); + + Quantum.tv_sec = interval / 1000; + Quantum.tv_usec = (interval % 1000) * 1000; +} + + +/************************************* +(STATIC) EventMachine_t::SetuidString +*************************************/ + +#ifdef OS_UNIX +void EventMachine_t::SetuidString (const char *username) +{ + /* This method takes a caller-supplied username and tries to setuid + * to that user. There is no meaningful implementation (and no error) + * on Windows. On Unix, a failure to setuid the caller-supplied string + * causes a fatal abort, because presumably the program is calling here + * in order to fulfill a security requirement. If we fail silently, + * the user may continue to run with too much privilege. + * + * TODO, we need to decide on and document a way of generating C++ level errors + * that can be wrapped in documented Ruby exceptions, so users can catch + * and handle them. And distinguish it from errors that we WON'T let the Ruby + * user catch (like security-violations and resource-overallocation). + * A setuid failure here would be in the latter category. + */ + + if (!username || !*username) + throw std::runtime_error ("setuid_string failed: no username specified"); + + errno = 0; + struct passwd *p = getpwnam (username); + if (!p) { + if (errno) { + char buf[200]; + snprintf (buf, sizeof(buf)-1, "setuid_string failed: %s", strerror(errno)); + throw std::runtime_error (buf); + } else { + throw std::runtime_error ("setuid_string failed: unknown username"); + } + } + + if (setuid (p->pw_uid) != 0) + throw std::runtime_error ("setuid_string failed: no setuid"); + + // Success. +} +#else +void EventMachine_t::SetuidString (const char *username UNUSED) { } +#endif + +/**************************************** +(STATIC) EventMachine_t::SetRlimitNofile +****************************************/ + +#ifdef OS_UNIX +int EventMachine_t::SetRlimitNofile (int nofiles) +{ + struct rlimit rlim; + getrlimit (RLIMIT_NOFILE, &rlim); + if (nofiles >= 0) { + rlim.rlim_cur = nofiles; + if ((unsigned int)nofiles > rlim.rlim_max) + rlim.rlim_max = nofiles; + setrlimit (RLIMIT_NOFILE, &rlim); + // ignore the error return, for now at least. + // TODO, emit an error message someday when we have proper debug levels. + } + getrlimit (RLIMIT_NOFILE, &rlim); + return rlim.rlim_cur; +} +#else +int EventMachine_t::SetRlimitNofile (int nofiles UNUSED) { return 0; } +#endif + +/********************************* +EventMachine_t::SignalLoopBreaker +*********************************/ + +void EventMachine_t::SignalLoopBreaker() +{ + #ifdef OS_UNIX + (void)write (LoopBreakerWriter, "", 1); + #endif + #ifdef OS_WIN32 + sendto (LoopBreakerReader, "", 0, 0, (struct sockaddr*)&(LoopBreakerTarget), sizeof(LoopBreakerTarget)); + #endif +} + + +/************************************** +EventMachine_t::_InitializeLoopBreaker +**************************************/ + +void EventMachine_t::_InitializeLoopBreaker() +{ + /* A "loop-breaker" is a socket-descriptor that we can write to in order + * to break the main select loop. Primarily useful for things running on + * threads other than the main EM thread, so they can trigger processing + * of events that arise exogenously to the EM. + * Keep the loop-breaker pipe out of the main descriptor set, otherwise + * its events will get passed on to user code. + */ + + #ifdef OS_UNIX + int fd[2]; +#if defined (HAVE_CLOEXEC) && defined (HAVE_PIPE2) + int pipestatus = pipe2(fd, O_CLOEXEC); + if (pipestatus < 0) { + if (pipe(fd)) + throw std::runtime_error (strerror(errno)); + } +#else + if (pipe (fd)) + throw std::runtime_error (strerror(errno)); +#endif + if (!SetFdCloexec(fd[0]) || !SetFdCloexec(fd[1])) + throw std::runtime_error (strerror(errno)); + + LoopBreakerWriter = fd[1]; + LoopBreakerReader = fd[0]; + + /* 16Jan11: Make sure the pipe is non-blocking, so more than 65k loopbreaks + * in one tick do not fill up the pipe and block the process on write() */ + SetSocketNonblocking (LoopBreakerWriter); + #endif + + #ifdef OS_WIN32 + SOCKET sd = EmSocket (AF_INET, SOCK_DGRAM, 0); + if (sd == INVALID_SOCKET) + throw std::runtime_error ("no loop breaker socket"); + SetSocketNonblocking (sd); + + memset (&LoopBreakerTarget, 0, sizeof(LoopBreakerTarget)); + LoopBreakerTarget.sin_family = AF_INET; + LoopBreakerTarget.sin_addr.s_addr = inet_addr ("127.0.0.1"); + + srand ((int)time(NULL)); + int i; + for (i=0; i < 100; i++) { + int r = (rand() % 10000) + 20000; + LoopBreakerTarget.sin_port = htons (r); + if (bind (sd, (struct sockaddr*)&LoopBreakerTarget, sizeof(LoopBreakerTarget)) == 0) + break; + } + + if (i == 100) + throw std::runtime_error ("no loop breaker"); + LoopBreakerReader = sd; + #endif + + #ifdef HAVE_EPOLL + if (Poller == Poller_Epoll) { + epfd = epoll_create (MaxEpollDescriptors); + if (epfd == -1) { + char buf[200]; + snprintf (buf, sizeof(buf)-1, "unable to create epoll descriptor: %s", strerror(errno)); + throw std::runtime_error (buf); + } + int cloexec = fcntl (epfd, F_GETFD, 0); + assert (cloexec >= 0); + cloexec |= FD_CLOEXEC; + fcntl (epfd, F_SETFD, cloexec); + + assert (LoopBreakerReader >= 0); + LoopbreakDescriptor *ld = new LoopbreakDescriptor (LoopBreakerReader, this); + assert (ld); + Add (ld); + } + #endif + + #ifdef HAVE_KQUEUE + if (Poller == Poller_Kqueue) { + kqfd = kqueue(); + if (kqfd == -1) { + char buf[200]; + snprintf (buf, sizeof(buf)-1, "unable to create kqueue descriptor: %s", strerror(errno)); + throw std::runtime_error (buf); + } + // cloexec not needed. By definition, kqueues are not carried across forks. + + assert (LoopBreakerReader >= 0); + LoopbreakDescriptor *ld = new LoopbreakDescriptor (LoopBreakerReader, this); + assert (ld); + Add (ld); + } + #endif +} + +/*************************** +EventMachine_t::_UpdateTime +***************************/ + +void EventMachine_t::_UpdateTime() +{ + MyCurrentLoopTime = GetRealTime(); +} + +/*************************** +EventMachine_t::GetRealTime +***************************/ + +// Two great writeups of cross-platform monotonic time are at: +// http://www.python.org/dev/peps/pep-0418 +// http://nadeausoftware.com/articles/2012/04/c_c_tip_how_measure_elapsed_real_time_benchmarking +// Uncomment the #pragma messages to confirm which compile-time option was used +uint64_t EventMachine_t::GetRealTime() +{ + uint64_t current_time; + + #if defined(HAVE_CONST_CLOCK_MONOTONIC_RAW) + // #pragma message "GetRealTime: clock_gettime CLOCK_MONOTONIC_RAW" + // Linux 2.6.28 and above + struct timespec tv; + clock_gettime (CLOCK_MONOTONIC_RAW, &tv); + current_time = (((uint64_t)(tv.tv_sec)) * 1000000LL) + ((uint64_t)((tv.tv_nsec)/1000)); + + #elif defined(HAVE_CONST_CLOCK_MONOTONIC) + // #pragma message "GetRealTime: clock_gettime CLOCK_MONOTONIC" + // Linux, FreeBSD 5.0 and above, Solaris 8 and above, OpenBSD, NetBSD, DragonflyBSD + struct timespec tv; + clock_gettime (CLOCK_MONOTONIC, &tv); + current_time = (((uint64_t)(tv.tv_sec)) * 1000000LL) + ((uint64_t)((tv.tv_nsec)/1000)); + + #elif defined(HAVE_GETHRTIME) + // #pragma message "GetRealTime: gethrtime" + // Solaris and HP-UX + current_time = (uint64_t)gethrtime() / 1000; + + #elif defined(OS_DARWIN) + // #pragma message "GetRealTime: mach_absolute_time" + // Mac OS X + // https://developer.apple.com/library/mac/qa/qa1398/_index.html + current_time = mach_absolute_time() * mach_timebase.numer / mach_timebase.denom / 1000; + + #elif defined(OS_UNIX) + // #pragma message "GetRealTime: gettimeofday" + // Unix fallback + struct timeval tv; + gettimeofday (&tv, NULL); + current_time = (((uint64_t)(tv.tv_sec)) * 1000000LL) + ((uint64_t)(tv.tv_usec)); + + #elif defined(OS_WIN32) + // #pragma message "GetRealTime: GetTickCount" + // Future improvement: use GetTickCount64 in Windows Vista / Server 2008 + unsigned tick = GetTickCount(); + if (tick < LastTickCount) + TickCountTickover += 1; + LastTickCount = tick; + current_time = ((uint64_t)TickCountTickover << 32) + (uint64_t)tick; + current_time *= 1000; // convert to microseconds + + #else + // #pragma message "GetRealTime: time" + // Universal fallback + current_time = (uint64_t)time(NULL) * 1000000LL; + #endif + + return current_time; +} + +/*********************************** +EventMachine_t::_DispatchHeartbeats +***********************************/ + +void EventMachine_t::_DispatchHeartbeats() +{ + // Store the first processed heartbeat descriptor and bail out if + // we see it again. This fixes an infinite loop in case the system time + // is changed out from underneath MyCurrentLoopTime. + const EventableDescriptor *head = NULL; + + while (true) { + std::multimap::iterator i = Heartbeats.begin(); + if (i == Heartbeats.end()) + break; + if (i->first > MyCurrentLoopTime) + break; + + EventableDescriptor *ed = i->second; + if (ed == head) + break; + + ed->Heartbeat(); + QueueHeartbeat(ed); + + if (head == NULL) + head = ed; + } +} + +/****************************** +EventMachine_t::QueueHeartbeat +******************************/ + +void EventMachine_t::QueueHeartbeat(EventableDescriptor *ed) +{ + uint64_t heartbeat = ed->GetNextHeartbeat(); + + if (heartbeat) { + #ifndef HAVE_MAKE_PAIR + Heartbeats.insert (std::multimap::value_type (heartbeat, ed)); + #else + Heartbeats.insert (std::make_pair (heartbeat, ed)); + #endif + } +} + +/****************************** +EventMachine_t::ClearHeartbeat +******************************/ + +void EventMachine_t::ClearHeartbeat(uint64_t key, EventableDescriptor* ed) +{ + std::multimap::iterator it; + std::pair::iterator,std::multimap::iterator> ret; + ret = Heartbeats.equal_range (key); + for (it = ret.first; it != ret.second; ++it) { + if (it->second == ed) { + Heartbeats.erase (it); + break; + } + } +} + +/******************* +EventMachine_t::Run +*******************/ + +void EventMachine_t::Run() +{ + while (RunOnce()) ; +} + +/*********************** +EventMachine_t::RunOnce +***********************/ + +bool EventMachine_t::RunOnce() +{ + _UpdateTime(); + _RunTimers(); + + /* _Add must precede _Modify because the same descriptor might + * be on both lists during the same pass through the machine, + * and to modify a descriptor before adding it would fail. + */ + _AddNewDescriptors(); + _ModifyDescriptors(); + + switch (Poller) { + case Poller_Epoll: + _RunEpollOnce(); + break; + case Poller_Kqueue: + _RunKqueueOnce(); + break; + case Poller_Default: + _RunSelectOnce(); + break; + } + + _DispatchHeartbeats(); + _CleanupSockets(); + + if (bTerminateSignalReceived) + return false; + + return true; +} + + +/***************************** +EventMachine_t::_RunEpollOnce +*****************************/ + +void EventMachine_t::_RunEpollOnce() +{ + #ifdef HAVE_EPOLL + assert (epfd != -1); + int s; + + timeval tv = _TimeTilNextEvent(); + + #ifdef BUILD_FOR_RUBY + int ret = 0; + + #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD + if ((ret = rb_wait_for_single_fd(epfd, RB_WAITFD_IN|RB_WAITFD_PRI, &tv)) < 1) { + #else + fd_set fdreads; + + FD_ZERO(&fdreads); + FD_SET(epfd, &fdreads); + + if ((ret = rb_thread_select(epfd + 1, &fdreads, NULL, NULL, &tv)) < 1) { + #endif + if (ret == -1) { + assert(errno != EINVAL); + assert(errno != EBADF); + } + return; + } + + TRAP_BEG; + s = epoll_wait (epfd, epoll_events, MaxEvents, 0); + TRAP_END; + #else + int duration = 0; + duration = duration + (tv.tv_sec * 1000); + duration = duration + (tv.tv_usec / 1000); + s = epoll_wait (epfd, epoll_events, MaxEvents, duration); + #endif + + if (s > 0) { + for (int i=0; i < s; i++) { + EventableDescriptor *ed = (EventableDescriptor*) epoll_events[i].data.ptr; + + if (ed->IsWatchOnly() && ed->GetSocket() == INVALID_SOCKET) + continue; + + assert(ed->GetSocket() != INVALID_SOCKET); + + if (epoll_events[i].events & EPOLLIN) + ed->Read(); + if (epoll_events[i].events & EPOLLOUT) + ed->Write(); + if (epoll_events[i].events & (EPOLLERR | EPOLLHUP)) + ed->HandleError(); + } + } + else if (s < 0) { + // epoll_wait can fail on error in a handful of ways. + // If this happens, then wait for a little while to avoid busy-looping. + // If the error was EINTR, we probably caught SIGCHLD or something, + // so keep the wait short. + timeval tv = {0, ((errno == EINTR) ? 5 : 50) * 1000}; + EmSelect (0, NULL, NULL, NULL, &tv); + } + #else + throw std::runtime_error ("epoll is not implemented on this platform"); + #endif +} + + +/****************************** +EventMachine_t::_RunKqueueOnce +******************************/ + +#ifdef HAVE_KQUEUE +void EventMachine_t::_RunKqueueOnce() +{ + assert (kqfd != -1); + int k; + + timeval tv = _TimeTilNextEvent(); + + struct timespec ts; + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + + #ifdef BUILD_FOR_RUBY + int ret = 0; + + #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD + if ((ret = rb_wait_for_single_fd(kqfd, RB_WAITFD_IN|RB_WAITFD_PRI, &tv)) < 1) { + #else + fd_set fdreads; + + FD_ZERO(&fdreads); + FD_SET(kqfd, &fdreads); + + if ((ret = rb_thread_select(kqfd + 1, &fdreads, NULL, NULL, &tv)) < 1) { + #endif + if (ret == -1) { + assert(errno != EINVAL); + assert(errno != EBADF); + } + return; + } + + TRAP_BEG; + ts.tv_sec = ts.tv_nsec = 0; + k = kevent (kqfd, NULL, 0, Karray, MaxEvents, &ts); + TRAP_END; + #else + k = kevent (kqfd, NULL, 0, Karray, MaxEvents, &ts); + #endif + + struct kevent *ke = Karray; + while (k > 0) { + switch (ke->filter) + { + case EVFILT_VNODE: + _HandleKqueueFileEvent (ke); + break; + + case EVFILT_PROC: + _HandleKqueuePidEvent (ke); + break; + + case EVFILT_READ: + case EVFILT_WRITE: + EventableDescriptor *ed = (EventableDescriptor*) (ke->udata); + assert (ed); + + if (ed->IsWatchOnly() && ed->GetSocket() == INVALID_SOCKET) + break; + + if (ke->filter == EVFILT_READ) + ed->Read(); + else if (ke->filter == EVFILT_WRITE) + ed->Write(); + else + std::cerr << "Discarding unknown kqueue event " << ke->filter << std::endl; + + break; + } + + --k; + ++ke; + } + + // TODO, replace this with rb_thread_blocking_region for 1.9 builds. + #ifdef BUILD_FOR_RUBY + if (!rb_thread_alone()) { + rb_thread_schedule(); + } + #endif +} +#else +void EventMachine_t::_RunKqueueOnce() +{ + throw std::runtime_error ("kqueue is not implemented on this platform"); +} +#endif + + +/********************************* +EventMachine_t::_TimeTilNextEvent +*********************************/ + +timeval EventMachine_t::_TimeTilNextEvent() +{ + // 29jul11: Changed calculation base from MyCurrentLoopTime to the + // real time. As MyCurrentLoopTime is set at the beginning of an + // iteration and this calculation is done at the end, evenmachine + // will potentially oversleep by the amount of time the iteration + // took to execute. + uint64_t next_event = 0; + uint64_t current_time = GetRealTime(); + + if (!Heartbeats.empty()) { + std::multimap::iterator heartbeats = Heartbeats.begin(); + next_event = heartbeats->first; + } + + if (!Timers.empty()) { + std::multimap::iterator timers = Timers.begin(); + if (next_event == 0 || timers->first < next_event) + next_event = timers->first; + } + + if (!NewDescriptors.empty() || !ModifiedDescriptors.empty()) { + next_event = current_time; + } + + timeval tv; + + if (NumCloseScheduled > 0 || bTerminateSignalReceived) { + tv.tv_sec = tv.tv_usec = 0; + } else if (next_event == 0) { + tv = Quantum; + } else { + if (next_event > current_time) { + uint64_t duration = next_event - current_time; + tv.tv_sec = duration / 1000000; + tv.tv_usec = duration % 1000000; + } else { + tv.tv_sec = tv.tv_usec = 0; + } + } + + return tv; +} + +/******************************* +EventMachine_t::_CleanupSockets +*******************************/ + +void EventMachine_t::_CleanupSockets() +{ + // TODO, rip this out and only delete the descriptors we know have died, + // rather than traversing the whole list. + // Modified 05Jan08 per suggestions by Chris Heath. It's possible that + // an EventableDescriptor will have a descriptor value of -1. That will + // happen if EventableDescriptor::Close was called on it. In that case, + // don't call epoll_ctl to remove the socket's filters from the epoll set. + // According to the epoll docs, this happens automatically when the + // descriptor is closed anyway. This is different from the case where + // the socket has already been closed but the descriptor in the ED object + // hasn't yet been set to INVALID_SOCKET. + // In kqueue, closing a descriptor automatically removes its event filters. + int i, j; + int nSockets = Descriptors.size(); + for (i=0, j=0; i < nSockets; i++) { + EventableDescriptor *ed = Descriptors[i]; + assert (ed); + if (ed->ShouldDelete()) { + #ifdef HAVE_EPOLL + if (Poller == Poller_Epoll) { + assert (epfd != -1); + if (ed->GetSocket() != INVALID_SOCKET) { + int e = epoll_ctl (epfd, EPOLL_CTL_DEL, ed->GetSocket(), ed->GetEpollEvent()); + // ENOENT or EBADF are not errors because the socket may be already closed when we get here. + if (e && (errno != ENOENT) && (errno != EBADF) && (errno != EPERM)) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to delete epoll event: %s", strerror(errno)); + throw std::runtime_error (buf); + } + } + ModifiedDescriptors.erase(ed); + } + #endif + delete ed; + } + else + Descriptors [j++] = ed; + } + while ((size_t)j < Descriptors.size()) + Descriptors.pop_back(); +} + +/********************************* +EventMachine_t::_ModifyEpollEvent +*********************************/ + +#ifdef HAVE_EPOLL +void EventMachine_t::_ModifyEpollEvent (EventableDescriptor *ed) +{ + if (Poller == Poller_Epoll) { + assert (epfd != -1); + assert (ed); + assert (ed->GetSocket() != INVALID_SOCKET); + int e = epoll_ctl (epfd, EPOLL_CTL_MOD, ed->GetSocket(), ed->GetEpollEvent()); + if (e) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to modify epoll event: %s", strerror(errno)); + throw std::runtime_error (buf); + } + } +} +#else +void EventMachine_t::_ModifyEpollEvent (EventableDescriptor *ed UNUSED) { } +#endif + + +/************************** +SelectData_t::SelectData_t +**************************/ + +SelectData_t::SelectData_t() +{ + maxsocket = 0; + rb_fd_init (&fdreads); + rb_fd_init (&fdwrites); + rb_fd_init (&fderrors); +} + +SelectData_t::~SelectData_t() +{ + rb_fd_term (&fdreads); + rb_fd_term (&fdwrites); + rb_fd_term (&fderrors); +} + +#ifdef BUILD_FOR_RUBY +/***************** +_SelectDataSelect +*****************/ + +#if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) +static VALUE _SelectDataSelect (void *v) +{ + SelectData_t *sd = (SelectData_t*)v; + sd->nSockets = rb_fd_select (sd->maxsocket+1, &(sd->fdreads), &(sd->fdwrites), &(sd->fderrors), &(sd->tv)); + return Qnil; +} +#endif + +/********************* +SelectData_t::_Select +*********************/ + +int SelectData_t::_Select() +{ + #if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) + // added in ruby 1.9.3 + rb_thread_call_without_gvl ((void *(*)(void *))_SelectDataSelect, (void*)this, RUBY_UBF_IO, 0); + return nSockets; + #elif defined(HAVE_TBR) + // added in ruby 1.9.1, deprecated in ruby 2.0.0 + rb_thread_blocking_region (_SelectDataSelect, (void*)this, RUBY_UBF_IO, 0); + return nSockets; + #else + return EmSelect (maxsocket+1, &fdreads, &fdwrites, &fderrors, &tv); + #endif +} +#endif + +void SelectData_t::_Clear() +{ + maxsocket = 0; + rb_fd_zero (&fdreads); + rb_fd_zero (&fdwrites); + rb_fd_zero (&fderrors); +} + +/****************************** +EventMachine_t::_RunSelectOnce +******************************/ + +void EventMachine_t::_RunSelectOnce() +{ + // Crank the event machine once. + // If there are no descriptors to process, then sleep + // for a few hundred mills to avoid busy-looping. + // This is based on a select loop. Alternately provide epoll + // if we know we're running on a 2.6 kernel. + // epoll will be effective if we provide it as an alternative, + // however it has the same problem interoperating with Ruby + // threads that select does. + + // Get ready for select() + SelectData->_Clear(); + + // Always read the loop-breaker reader. + // Changed 23Aug06, provisionally implemented for Windows with a UDP socket + // running on localhost with a randomly-chosen port. (*Puke*) + // Windows has a version of the Unix pipe() library function, but it doesn't + // give you back descriptors that are selectable. + rb_fd_set (LoopBreakerReader, &(SelectData->fdreads)); + if (SelectData->maxsocket < LoopBreakerReader) + SelectData->maxsocket = LoopBreakerReader; + + // prepare the sockets for reading and writing + size_t i; + for (i = 0; i < Descriptors.size(); i++) { + EventableDescriptor *ed = Descriptors[i]; + assert (ed); + SOCKET sd = ed->GetSocket(); + if (ed->IsWatchOnly() && sd == INVALID_SOCKET) + continue; + assert (sd != INVALID_SOCKET); + + if (ed->SelectForRead()) + rb_fd_set (sd, &(SelectData->fdreads)); + if (ed->SelectForWrite()) + rb_fd_set (sd, &(SelectData->fdwrites)); + + #ifdef OS_WIN32 + /* 21Sep09: on windows, a non-blocking connect() that fails does not come up as writable. + Instead, it is added to the error set. See http://www.mail-archive.com/openssl-users@openssl.org/msg58500.html + */ + if (ed->IsConnectPending()) + rb_fd_set (sd, &(SelectData->fderrors)); + #endif + + if (SelectData->maxsocket < sd) + SelectData->maxsocket = sd; + } + + + { // read and write the sockets + //timeval tv = {1, 0}; // Solaris fails if the microseconds member is >= 1000000. + //timeval tv = Quantum; + SelectData->tv = _TimeTilNextEvent(); + int s = SelectData->_Select(); + //rb_thread_blocking_region(xxx,(void*)&SelectData,RUBY_UBF_IO,0); + //int s = EmSelect (SelectData.maxsocket+1, &(SelectData.fdreads), &(SelectData.fdwrites), NULL, &(SelectData.tv)); + //int s = SelectData.nSockets; + if (s > 0) { + /* Changed 01Jun07. We used to handle the Loop-breaker right here. + * Now we do it AFTER all the regular descriptors. There's an + * incredibly important and subtle reason for this. Code on + * loop breakers is sometimes used to cause the reactor core to + * cycle (for example, to allow outbound network buffers to drain). + * If a loop-breaker handler reschedules itself (say, after determining + * that the write buffers are still too full), then it will execute + * IMMEDIATELY if _ReadLoopBreaker is done here instead of after + * the other descriptors are processed. That defeats the whole purpose. + */ + for (i=0; i < Descriptors.size(); i++) { + EventableDescriptor *ed = Descriptors[i]; + assert (ed); + SOCKET sd = ed->GetSocket(); + if (ed->IsWatchOnly() && sd == INVALID_SOCKET) + continue; + assert (sd != INVALID_SOCKET); + + if (rb_fd_isset (sd, &(SelectData->fdwrites))) { + // Double-check SelectForWrite() still returns true. If not, one of the callbacks must have + // modified some value since we checked SelectForWrite() earlier in this method. + if (ed->SelectForWrite()) + ed->Write(); + } + if (rb_fd_isset (sd, &(SelectData->fdreads))) + ed->Read(); + if (rb_fd_isset (sd, &(SelectData->fderrors))) + ed->HandleError(); + } + + if (rb_fd_isset (LoopBreakerReader, &(SelectData->fdreads))) + _ReadLoopBreaker(); + } + else if (s < 0) { + switch (errno) { + case EBADF: + _CleanBadDescriptors(); + break; + case EINVAL: + throw std::runtime_error ("Somehow EM passed an invalid nfds or invalid timeout to select(2), please report this!"); + break; + default: + // select can fail on error in a handful of ways. + // If this happens, then wait for a little while to avoid busy-looping. + // If the error was EINTR, we probably caught SIGCHLD or something, + // so keep the wait short. + timeval tv = {0, ((errno == EINTR) ? 5 : 50) * 1000}; + EmSelect (0, NULL, NULL, NULL, &tv); + } + } + } +} + +void EventMachine_t::_CleanBadDescriptors() +{ + size_t i; + + for (i = 0; i < Descriptors.size(); i++) { + EventableDescriptor *ed = Descriptors[i]; + if (ed->ShouldDelete()) + continue; + + SOCKET sd = ed->GetSocket(); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + rb_fdset_t fds; + rb_fd_init(&fds); + rb_fd_set(sd, &fds); + + int ret = rb_fd_select(sd + 1, &fds, NULL, NULL, &tv); + rb_fd_term(&fds); + + if (ret == -1) { + if (errno == EBADF) + ed->ScheduleClose(false); + } + } +} + +/******************************** +EventMachine_t::_ReadLoopBreaker +********************************/ + +void EventMachine_t::_ReadLoopBreaker() +{ + /* The loop breaker has selected readable. + * Read it ONCE (it may block if we try to read it twice) + * and send a loop-break event back to user code. + */ + char buffer [1024]; + (void)read (LoopBreakerReader, buffer, sizeof(buffer)); + if (EventCallback) + (*EventCallback)(0, EM_LOOPBREAK_SIGNAL, "", 0); +} + + +/************************** +EventMachine_t::_RunTimers +**************************/ + +void EventMachine_t::_RunTimers() +{ + // These are caller-defined timer handlers. + // We rely on the fact that multimaps sort by their keys to avoid + // inspecting the whole list every time we come here. + // Just keep inspecting and processing the list head until we hit + // one that hasn't expired yet. + + while (true) { + std::multimap::iterator i = Timers.begin(); + if (i == Timers.end()) + break; + if (i->first > MyCurrentLoopTime) + break; + if (EventCallback) + (*EventCallback) (0, EM_TIMER_FIRED, NULL, i->second.GetBinding()); + Timers.erase (i); + } +} + + + +/*********************************** +EventMachine_t::InstallOneshotTimer +***********************************/ + +const uintptr_t EventMachine_t::InstallOneshotTimer (uint64_t milliseconds) +{ + if (Timers.size() > MaxOutstandingTimers) + return false; + + uint64_t fire_at = GetRealTime(); + fire_at += ((uint64_t)milliseconds) * 1000LL; + + Timer_t t; + #ifndef HAVE_MAKE_PAIR + std::multimap::iterator i = Timers.insert (std::multimap::value_type (fire_at, t)); + #else + std::multimap::iterator i = Timers.insert (std::make_pair (fire_at, t)); + #endif + return i->second.GetBinding(); +} + + +/******************************* +EventMachine_t::ConnectToServer +*******************************/ + +const uintptr_t EventMachine_t::ConnectToServer (const char *bind_addr, int bind_port, const char *server, int port) +{ + /* We want to spend no more than a few seconds waiting for a connection + * to a remote host. So we use a nonblocking connect. + * Linux disobeys the usual rules for nonblocking connects. + * Per Stevens (UNP p.410), you expect a nonblocking connect to select + * both readable and writable on error, and not to return EINPROGRESS + * if the connect can be fulfilled immediately. Linux violates both + * of these expectations. + * Any kind of nonblocking connect on Linux returns EINPROGRESS. + * The socket will then return writable when the disposition of the + * connect is known, but it will not also be readable in case of + * error! Weirdly, it will be readable in case there is data to read!!! + * (Which can happen with protocols like SSH and SMTP.) + * I suppose if you were so inclined you could consider this logical, + * but it's not the way Unix has historically done it. + * So we ignore the readable flag and read getsockopt to see if there + * was an error connecting. A select timeout works as expected. + * In regard to getsockopt: Linux does the Berkeley-style thing, + * not the Solaris-style, and returns zero with the error code in + * the error parameter. + * Return the binding-text of the newly-created pending connection, + * or NULL if there was a problem. + */ + + if (!server || !*server || !port) + throw std::runtime_error ("invalid server or port"); + + struct sockaddr_storage bind_as; + size_t bind_as_len = sizeof bind_as; + int gai = name2address (server, port, SOCK_STREAM, (struct sockaddr *)&bind_as, &bind_as_len); + if (gai != 0) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to resolve address: %s", gai_strerror(gai)); + throw std::runtime_error (buf); + } + + SOCKET sd = EmSocket (bind_as.ss_family, SOCK_STREAM, 0); + if (sd == INVALID_SOCKET) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to create new socket: %s", strerror(errno)); + throw std::runtime_error (buf); + } + + // From here on, ALL error returns must close the socket. + // Set the new socket nonblocking. + if (!SetSocketNonblocking (sd)) { + close (sd); + throw std::runtime_error ("unable to set socket as non-blocking"); + } + // Disable slow-start (Nagle algorithm). + int one = 1; + setsockopt (sd, IPPROTO_TCP, TCP_NODELAY, (char*) &one, sizeof(one)); + // Set reuseaddr to improve performance on restarts + setsockopt (sd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)); + + if (bind_addr) { + struct sockaddr_storage bind_to; + size_t bind_to_len = sizeof bind_to; + gai = name2address (bind_addr, bind_port, SOCK_STREAM, (struct sockaddr *)&bind_to, &bind_to_len); + if (gai != 0) { + close (sd); + char buf [200]; + snprintf (buf, sizeof(buf)-1, "invalid bind address: %s", gai_strerror(gai)); + throw std::runtime_error (buf); + } + if (bind (sd, (struct sockaddr *)&bind_to, bind_to_len) < 0) { + close (sd); + throw std::runtime_error ("couldn't bind to address"); + } + } + + uintptr_t out = 0; + + #ifdef OS_UNIX + int e_reason = 0; + if (connect (sd, (struct sockaddr *)&bind_as, bind_as_len) == 0) { + // This is a connect success, which Linux appears + // never to give when the socket is nonblocking, + // even if the connection is intramachine or to + // localhost. + + /* Changed this branch 08Aug06. Evidently some kernels + * (FreeBSD for example) will actually return success from + * a nonblocking connect. This is a pretty simple case, + * just set up the new connection and clear the pending flag. + * Thanks to Chris Ochs for helping track this down. + * This branch never gets taken on Linux or (oddly) OSX. + * The original behavior was to throw an unimplemented, + * which the user saw as a fatal exception. Very unfriendly. + * + * Tweaked 10Aug06. Even though the connect disposition is + * known, we still set the connect-pending flag. That way + * some needed initialization will happen in the ConnectionDescriptor. + * (To wit, the ConnectionCompleted event gets sent to the client.) + */ + ConnectionDescriptor *cd = new ConnectionDescriptor (sd, this); + if (!cd) + throw std::runtime_error ("no connection allocated"); + cd->SetConnectPending (true); + Add (cd); + out = cd->GetBinding(); + } + else if (errno == EINPROGRESS) { + // Errno will generally always be EINPROGRESS, but on Linux + // we have to look at getsockopt to be sure what really happened. + int error = 0; + socklen_t len; + len = sizeof(error); + int o = getsockopt (sd, SOL_SOCKET, SO_ERROR, &error, &len); + if ((o == 0) && (error == 0)) { + // Here, there's no disposition. + // Put the connection on the stack and wait for it to complete + // or time out. + ConnectionDescriptor *cd = new ConnectionDescriptor (sd, this); + if (!cd) + throw std::runtime_error ("no connection allocated"); + cd->SetConnectPending (true); + Add (cd); + out = cd->GetBinding(); + } else { + // Fall through to the !out case below. + e_reason = error; + } + } + else { + // The error from connect was something other then EINPROGRESS (EHOSTDOWN, etc). + // Fall through to the !out case below + e_reason = errno; + } + + if (!out) { + /* This could be connection refused or some such thing. + * We will come here on Linux if a localhost connection fails. + * Changed 16Jul06: Originally this branch was a no-op, and + * we'd drop down to the end of the method, close the socket, + * and return NULL, which would cause the caller to GET A + * FATAL EXCEPTION. Now we keep the socket around but schedule an + * immediate close on it, so the caller will get a close-event + * scheduled on it. This was only an issue for localhost connections + * to non-listening ports. We may eventually need to revise this + * revised behavior, in case it causes problems like making it hard + * for people to know that a failure occurred. + */ + ConnectionDescriptor *cd = new ConnectionDescriptor (sd, this); + if (!cd) + throw std::runtime_error ("no connection allocated"); + cd->SetUnbindReasonCode (e_reason); + cd->ScheduleClose (false); + Add (cd); + out = cd->GetBinding(); + } + #endif + + #ifdef OS_WIN32 + if (connect (sd, (struct sockaddr *)&bind_as, bind_as_len) == 0) { + // This is a connect success, which Windows appears + // never to give when the socket is nonblocking, + // even if the connection is intramachine or to + // localhost. + throw std::runtime_error ("unimplemented"); + } + else if (WSAGetLastError() == WSAEWOULDBLOCK) { + // Here, there's no disposition. + // Windows appears not to surface refused connections or + // such stuff at this point. + // Put the connection on the stack and wait for it to complete + // or time out. + ConnectionDescriptor *cd = new ConnectionDescriptor (sd, this); + if (!cd) + throw std::runtime_error ("no connection allocated"); + cd->SetConnectPending (true); + Add (cd); + out = cd->GetBinding(); + } + else { + // The error from connect was something other then WSAEWOULDBLOCK. + } + + #endif + + if (!out) + close (sd); + return out; +} + +/*********************************** +EventMachine_t::ConnectToUnixServer +***********************************/ + +#ifdef OS_UNIX +const uintptr_t EventMachine_t::ConnectToUnixServer (const char *server) +{ + /* Connect to a Unix-domain server, which by definition is running + * on the same host. + * There is no meaningful implementation on Windows. + * There's no need to do a nonblocking connect, since the connection + * is always local and can always be fulfilled immediately. + */ + + uintptr_t out = 0; + + if (!server || !*server) + return 0; + + sockaddr_un pun; + memset (&pun, 0, sizeof(pun)); + pun.sun_family = AF_LOCAL; + + // You ordinarily expect the server name field to be at least 1024 bytes long, + // but on Linux it can be MUCH shorter. + if (strlen(server) >= sizeof(pun.sun_path)) + throw std::runtime_error ("unix-domain server name is too long"); + + + strcpy (pun.sun_path, server); + + SOCKET fd = EmSocket (AF_LOCAL, SOCK_STREAM, 0); + if (fd == INVALID_SOCKET) + return 0; + + // From here on, ALL error returns must close the socket. + // NOTE: At this point, the socket is still a blocking socket. + if (connect (fd, (struct sockaddr*)&pun, sizeof(pun)) != 0) { + close (fd); + return 0; + } + + // Set the newly-connected socket nonblocking. + if (!SetSocketNonblocking (fd)) { + close (fd); + return 0; + } + + // Set up a connection descriptor and add it to the event-machine. + // Observe, even though we know the connection status is connect-success, + // we still set the "pending" flag, so some needed initializations take + // place. + ConnectionDescriptor *cd = new ConnectionDescriptor (fd, this); + if (!cd) + throw std::runtime_error ("no connection allocated"); + cd->SetConnectPending (true); + Add (cd); + out = cd->GetBinding(); + + if (!out) + close (fd); + + return out; +} +#else +const uintptr_t EventMachine_t::ConnectToUnixServer (const char *server UNUSED) +{ + throw std::runtime_error ("unix-domain connection unavailable on this platform"); +} +#endif + +/************************ +EventMachine_t::AttachFD +************************/ + +const uintptr_t EventMachine_t::AttachFD (SOCKET fd, bool watch_mode) +{ + #ifdef OS_UNIX + if (fcntl(fd, F_GETFL, 0) < 0) { + if (errno) { + throw std::runtime_error (strerror(errno)); + } else { + throw std::runtime_error ("invalid file descriptor"); + } + } + #endif + + #ifdef OS_WIN32 + // TODO: add better check for invalid file descriptors (see ioctlsocket or getsockopt) + if (fd == INVALID_SOCKET) + throw std::runtime_error ("invalid file descriptor"); + #endif + + {// Check for duplicate descriptors + size_t i; + for (i = 0; i < Descriptors.size(); i++) { + EventableDescriptor *ed = Descriptors[i]; + assert (ed); + if (ed->GetSocket() == fd) + throw std::runtime_error ("adding existing descriptor"); + } + + for (i = 0; i < NewDescriptors.size(); i++) { + EventableDescriptor *ed = NewDescriptors[i]; + assert (ed); + if (ed->GetSocket() == fd) + throw std::runtime_error ("adding existing new descriptor"); + } + } + + if (!watch_mode) + SetSocketNonblocking(fd); + + ConnectionDescriptor *cd = new ConnectionDescriptor (fd, this); + if (!cd) + throw std::runtime_error ("no connection allocated"); + + cd->SetAttached(true); + cd->SetWatchOnly(watch_mode); + cd->SetConnectPending (false); + + Add (cd); + + const uintptr_t out = cd->GetBinding(); + return out; +} + +/************************ +EventMachine_t::DetachFD +************************/ + +int EventMachine_t::DetachFD (EventableDescriptor *ed) +{ + if (!ed) + throw std::runtime_error ("detaching bad descriptor"); + + SOCKET fd = ed->GetSocket(); + + #ifdef HAVE_EPOLL + if (Poller == Poller_Epoll) { + if (ed->GetSocket() != INVALID_SOCKET) { + assert (epfd != -1); + int e = epoll_ctl (epfd, EPOLL_CTL_DEL, ed->GetSocket(), ed->GetEpollEvent()); + // ENOENT or EBADF are not errors because the socket may be already closed when we get here. + if (e && (errno != ENOENT) && (errno != EBADF)) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to delete epoll event: %s", strerror(errno)); + throw std::runtime_error (buf); + } + } + } + #endif + + #ifdef HAVE_KQUEUE + if (Poller == Poller_Kqueue) { + // remove any read/write events for this fd + struct kevent k; +#ifdef __NetBSD__ + EV_SET (&k, ed->GetSocket(), EVFILT_READ | EVFILT_WRITE, EV_DELETE, 0, 0, (intptr_t)ed); +#else + EV_SET (&k, ed->GetSocket(), EVFILT_READ | EVFILT_WRITE, EV_DELETE, 0, 0, ed); +#endif + int t = kevent (kqfd, &k, 1, NULL, 0, NULL); + if (t < 0 && (errno != ENOENT) && (errno != EBADF)) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to delete kqueue event: %s", strerror(errno)); + throw std::runtime_error (buf); + } + } + #endif + + // Prevent the descriptor from being modified, in case DetachFD was called from a timer or next_tick + ModifiedDescriptors.erase (ed); + + // Prevent the descriptor from being added, in case DetachFD was called in the same tick as AttachFD + for (size_t i = 0; i < NewDescriptors.size(); i++) { + if (ed == NewDescriptors[i]) { + NewDescriptors.erase(NewDescriptors.begin() + i); + break; + } + } + + // Set MySocket = INVALID_SOCKET so ShouldDelete() is true (and the descriptor gets deleted and removed), + // and also to prevent anyone from calling close() on the detached fd + ed->SetSocketInvalid(); + + return fd; +} + +/************ +name2address +************/ + +int EventMachine_t::name2address (const char *server, int port, int socktype, struct sockaddr *addr, size_t *addr_len) +{ + if (!server || !*server) + server = "0.0.0.0"; + + struct addrinfo *ai; + struct addrinfo hints; + memset (&hints, 0, sizeof(hints)); + hints.ai_socktype = socktype; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG; + + char portstr[12]; + snprintf(portstr, sizeof(portstr), "%u", port); + + int gai = getaddrinfo (server, portstr, &hints, &ai); + if (gai == 0) { + assert (ai->ai_addrlen <= *addr_len); + memcpy (addr, ai->ai_addr, ai->ai_addrlen); + *addr_len = ai->ai_addrlen; + freeaddrinfo(ai); + } + + return gai; +} + + +/******************************* +EventMachine_t::CreateTcpServer +*******************************/ + +const uintptr_t EventMachine_t::CreateTcpServer (const char *server, int port) +{ + /* Create a TCP-acceptor (server) socket and add it to the event machine. + * Return the binding of the new acceptor to the caller. + * This binding will be referenced when the new acceptor sends events + * to indicate accepted connections. + */ + + + struct sockaddr_storage bind_here; + size_t bind_here_len = sizeof bind_here; + if (0 != name2address (server, port, SOCK_STREAM, (struct sockaddr *)&bind_here, &bind_here_len)) + return 0; + + SOCKET sd_accept = EmSocket (bind_here.ss_family, SOCK_STREAM, 0); + if (sd_accept == INVALID_SOCKET) { + goto fail; + } + + { // set reuseaddr to improve performance on restarts. + int oval = 1; + if (setsockopt (sd_accept, SOL_SOCKET, SO_REUSEADDR, (char*)&oval, sizeof(oval)) < 0) { + //__warning ("setsockopt failed while creating listener",""); + goto fail; + } + } + + { // set CLOEXEC. Only makes sense on Unix + #ifdef OS_UNIX + int cloexec = fcntl (sd_accept, F_GETFD, 0); + assert (cloexec >= 0); + cloexec |= FD_CLOEXEC; + fcntl (sd_accept, F_SETFD, cloexec); + #endif + } + + + if (bind (sd_accept, (struct sockaddr *)&bind_here, bind_here_len)) { + //__warning ("binding failed"); + goto fail; + } + + if (listen (sd_accept, 100)) { + //__warning ("listen failed"); + goto fail; + } + + return AttachSD(sd_accept); + + fail: + if (sd_accept != INVALID_SOCKET) + close (sd_accept); + return 0; +} + + +/********************************** +EventMachine_t::OpenDatagramSocket +**********************************/ + +const uintptr_t EventMachine_t::OpenDatagramSocket (const char *address, int port) +{ + uintptr_t output_binding = 0; + + struct sockaddr_storage bind_here; + size_t bind_here_len = sizeof bind_here; + if (0 != name2address (address, port, SOCK_DGRAM, (struct sockaddr *)&bind_here, &bind_here_len)) + return 0; + + // from here on, early returns must close the socket! + SOCKET sd = EmSocket (bind_here.ss_family, SOCK_DGRAM, 0); + if (sd == INVALID_SOCKET) + goto fail; + + { // set the SO_REUSEADDR on the socket before we bind, otherwise it won't work for a second one + int oval = 1; + if (setsockopt (sd, SOL_SOCKET, SO_REUSEADDR, (char*)&oval, sizeof(oval)) < 0) + goto fail; + } + + // Set the new socket nonblocking. + if (!SetSocketNonblocking (sd)) + goto fail; + + if (bind (sd, (struct sockaddr *)&bind_here, bind_here_len) != 0) + goto fail; + + { // Looking good. + DatagramDescriptor *ds = new DatagramDescriptor (sd, this); + if (!ds) + throw std::runtime_error ("unable to allocate datagram-socket"); + Add (ds); + output_binding = ds->GetBinding(); + } + + return output_binding; + + fail: + if (sd != INVALID_SOCKET) + close (sd); + return 0; +} + + + +/******************* +EventMachine_t::Add +*******************/ + +void EventMachine_t::Add (EventableDescriptor *ed) +{ + if (!ed) + throw std::runtime_error ("added bad descriptor"); + ed->SetEventCallback (EventCallback); + NewDescriptors.push_back (ed); +} + + +/******************************* +EventMachine_t::ArmKqueueWriter +*******************************/ + +#ifdef HAVE_KQUEUE +void EventMachine_t::ArmKqueueWriter (EventableDescriptor *ed) +{ + if (Poller == Poller_Kqueue) { + if (!ed) + throw std::runtime_error ("added bad descriptor"); + struct kevent k; +#ifdef __NetBSD__ + EV_SET (&k, ed->GetSocket(), EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, (intptr_t)ed); +#else + EV_SET (&k, ed->GetSocket(), EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, ed); +#endif + int t = kevent (kqfd, &k, 1, NULL, 0, NULL); + if (t < 0) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "arm kqueue writer failed on %d: %s", ed->GetSocket(), strerror(errno)); + throw std::runtime_error (buf); + } + } +} +#else +void EventMachine_t::ArmKqueueWriter (EventableDescriptor *ed UNUSED) { } +#endif + +/******************************* +EventMachine_t::ArmKqueueReader +*******************************/ + +#ifdef HAVE_KQUEUE +void EventMachine_t::ArmKqueueReader (EventableDescriptor *ed) +{ + if (Poller == Poller_Kqueue) { + if (!ed) + throw std::runtime_error ("added bad descriptor"); + struct kevent k; +#ifdef __NetBSD__ + EV_SET (&k, ed->GetSocket(), EVFILT_READ, EV_ADD, 0, 0, (intptr_t)ed); +#else + EV_SET (&k, ed->GetSocket(), EVFILT_READ, EV_ADD, 0, 0, ed); +#endif + int t = kevent (kqfd, &k, 1, NULL, 0, NULL); + if (t < 0) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "arm kqueue reader failed on %d: %s", ed->GetSocket(), strerror(errno)); + throw std::runtime_error (buf); + } + } +} +#else +void EventMachine_t::ArmKqueueReader (EventableDescriptor *ed UNUSED) { } +#endif + +/********************************** +EventMachine_t::_AddNewDescriptors +**********************************/ + +void EventMachine_t::_AddNewDescriptors() +{ + /* Avoid adding descriptors to the main descriptor list + * while we're actually traversing the list. + * Any descriptors that are added as a result of processing timers + * or acceptors should go on a temporary queue and then added + * while we're not traversing the main list. + * Also, it (rarely) happens that a newly-created descriptor + * is immediately scheduled to close. It might be a good + * idea not to bother scheduling these for I/O but if + * we do that, we might bypass some important processing. + */ + + for (size_t i = 0; i < NewDescriptors.size(); i++) { + EventableDescriptor *ed = NewDescriptors[i]; + if (ed == NULL) + throw std::runtime_error ("adding bad descriptor"); + + #if HAVE_EPOLL + if (Poller == Poller_Epoll) { + assert (epfd != -1); + int e = epoll_ctl (epfd, EPOLL_CTL_ADD, ed->GetSocket(), ed->GetEpollEvent()); + if (e) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to add new descriptor: %s", strerror(errno)); + throw std::runtime_error (buf); + } + } + #endif + + #if HAVE_KQUEUE + /* + if (Poller == Poller_Kqueue) { + // INCOMPLETE. Some descriptors don't want to be readable. + assert (kqfd != -1); + struct kevent k; +#ifdef __NetBSD__ + EV_SET (&k, ed->GetSocket(), EVFILT_READ, EV_ADD, 0, 0, (intptr_t)ed); +#else + EV_SET (&k, ed->GetSocket(), EVFILT_READ, EV_ADD, 0, 0, ed); +#endif + int t = kevent (kqfd, &k, 1, NULL, 0, NULL); + assert (t == 0); + } + */ + #endif + + QueueHeartbeat(ed); + Descriptors.push_back (ed); + } + NewDescriptors.clear(); +} + + +/********************************** +EventMachine_t::_ModifyDescriptors +**********************************/ + +void EventMachine_t::_ModifyDescriptors() +{ + /* For implementations which don't level check every descriptor on + * every pass through the machine, as select does. + * If we're not selecting, then descriptors need a way to signal to the + * machine that their readable or writable status has changed. + * That's what the ::Modify call is for. We do it this way to avoid + * modifying descriptors during the loop traversal, where it can easily + * happen that an object (like a UDP socket) gets data written on it by + * the application during #post_init. That would take place BEFORE the + * descriptor even gets added to the epoll descriptor, so the modify + * operation will crash messily. + * Another really messy possibility is for a descriptor to put itself + * on the Modified list, and then get deleted before we get here. + * Remember, deletes happen after the I/O traversal and before the + * next pass through here. So we have to make sure when we delete a + * descriptor to remove it from the Modified list. + */ + + #ifdef HAVE_EPOLL + if (Poller == Poller_Epoll) { + std::set::iterator i = ModifiedDescriptors.begin(); + while (i != ModifiedDescriptors.end()) { + assert (*i); + _ModifyEpollEvent (*i); + ++i; + } + } + #endif + + #ifdef HAVE_KQUEUE + if (Poller == Poller_Kqueue) { + std::set::iterator i = ModifiedDescriptors.begin(); + while (i != ModifiedDescriptors.end()) { + assert (*i); + if ((*i)->GetKqueueArmWrite()) + ArmKqueueWriter (*i); + ++i; + } + } + #endif + + ModifiedDescriptors.clear(); +} + + +/********************** +EventMachine_t::Modify +**********************/ + +void EventMachine_t::Modify (EventableDescriptor *ed) +{ + if (!ed) + throw std::runtime_error ("modified bad descriptor"); + ModifiedDescriptors.insert (ed); +} + + +/*********************** +EventMachine_t::Deregister +***********************/ + +void EventMachine_t::Deregister (EventableDescriptor *ed) +{ + if (!ed) + throw std::runtime_error ("modified bad descriptor"); + #ifdef HAVE_EPOLL + // cut/paste from _CleanupSockets(). The error handling could be + // refactored out of there, but it is cut/paste all over the + // file already. + if (Poller == Poller_Epoll) { + assert (epfd != -1); + assert (ed->GetSocket() != INVALID_SOCKET); + int e = epoll_ctl (epfd, EPOLL_CTL_DEL, ed->GetSocket(), ed->GetEpollEvent()); + // ENOENT or EBADF are not errors because the socket may be already closed when we get here. + if (e && (errno != ENOENT) && (errno != EBADF) && (errno != EPERM)) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "unable to delete epoll event: %s", strerror(errno)); + throw std::runtime_error (buf); + } + ModifiedDescriptors.erase(ed); + } + #endif + + #ifdef HAVE_KQUEUE + if (Poller == Poller_Kqueue) { + assert (ed->GetSocket() != INVALID_SOCKET); + + ModifiedDescriptors.erase(ed); + } + #endif +} + + +/************************************** +EventMachine_t::CreateUnixDomainServer +**************************************/ + +#ifdef OS_UNIX +const uintptr_t EventMachine_t::CreateUnixDomainServer (const char *filename) +{ + /* Create a UNIX-domain acceptor (server) socket and add it to the event machine. + * Return the binding of the new acceptor to the caller. + * This binding will be referenced when the new acceptor sends events + * to indicate accepted connections. + * THERE IS NO MEANINGFUL IMPLEMENTATION ON WINDOWS. + */ + + struct sockaddr_un s_sun; + + SOCKET sd_accept = EmSocket (AF_LOCAL, SOCK_STREAM, 0); + if (sd_accept == INVALID_SOCKET) { + goto fail; + } + + if (!filename || !*filename) + goto fail; + unlink (filename); + + bzero (&s_sun, sizeof(s_sun)); + s_sun.sun_family = AF_LOCAL; + strncpy (s_sun.sun_path, filename, sizeof(s_sun.sun_path)-1); + + // don't bother with reuseaddr for a local socket. + + { // set CLOEXEC. Only makes sense on Unix + #ifdef OS_UNIX + int cloexec = fcntl (sd_accept, F_GETFD, 0); + assert (cloexec >= 0); + cloexec |= FD_CLOEXEC; + fcntl (sd_accept, F_SETFD, cloexec); + #endif + } + + if (bind (sd_accept, (struct sockaddr*)&s_sun, sizeof(s_sun))) { + //__warning ("binding failed"); + goto fail; + } + + if (listen (sd_accept, 100)) { + //__warning ("listen failed"); + goto fail; + } + + return AttachSD(sd_accept); + + fail: + if (sd_accept != INVALID_SOCKET) + close (sd_accept); + return 0; +} +#else +const uintptr_t EventMachine_t::CreateUnixDomainServer (const char *filename UNUSED) +{ + throw std::runtime_error ("unix-domain server unavailable on this platform"); +} +#endif + + +/************************************** +EventMachine_t::AttachSD +**************************************/ + +const uintptr_t EventMachine_t::AttachSD (SOCKET sd_accept) +{ + uintptr_t output_binding = 0; + + { + // Set the acceptor non-blocking. + // THIS IS CRUCIALLY IMPORTANT because we read it in a select loop. + if (!SetSocketNonblocking (sd_accept)) { + //int val = fcntl (sd_accept, F_GETFL, 0); + //if (fcntl (sd_accept, F_SETFL, val | O_NONBLOCK) == -1) { + goto fail; + } + } + + { // Looking good. + AcceptorDescriptor *ad = new AcceptorDescriptor (sd_accept, this); + if (!ad) + throw std::runtime_error ("unable to allocate acceptor"); + Add (ad); + output_binding = ad->GetBinding(); + } + + return output_binding; + + fail: + if (sd_accept != INVALID_SOCKET) + close (sd_accept); + return 0; +} + + +/************************** +EventMachine_t::Socketpair +**************************/ + +#ifdef OS_UNIX +const uintptr_t EventMachine_t::Socketpair (char * const * cmd_strings) +{ + // Make sure the incoming array of command strings is sane. + if (!cmd_strings) + return 0; + int j; + for (j=0; j < 2048 && cmd_strings[j]; j++) + ; + if ((j==0) || (j==2048)) + return 0; + + uintptr_t output_binding = 0; + + int sv[2]; + if (socketpair (AF_LOCAL, SOCK_STREAM, 0, sv) < 0) + return 0; + // from here, all early returns must close the pair of sockets. + + // Set the parent side of the socketpair nonblocking. + // We don't care about the child side, and most child processes will expect their + // stdout to be blocking. Thanks to Duane Johnson and Bill Kelly for pointing this out. + // Obviously DON'T set CLOEXEC. + if (!SetSocketNonblocking (sv[0])) { + close (sv[0]); + close (sv[1]); + return 0; + } + + pid_t f = fork(); + if (f > 0) { + close (sv[1]); + PipeDescriptor *pd = new PipeDescriptor (sv[0], f, this); + if (!pd) + throw std::runtime_error ("unable to allocate pipe"); + Add (pd); + output_binding = pd->GetBinding(); + } + else if (f == 0) { + close (sv[0]); + dup2 (sv[1], STDIN_FILENO); + close (sv[1]); + dup2 (STDIN_FILENO, STDOUT_FILENO); + execvp (cmd_strings[0], cmd_strings+1); + exit (-1); // end the child process if the exec doesn't work. + } + else + throw std::runtime_error ("no fork"); + + return output_binding; +} +#else +const uintptr_t EventMachine_t::Socketpair (char * const * cmd_strings UNUSED) +{ + throw std::runtime_error ("socketpair is currently unavailable on this platform"); +} +#endif + + + +/**************************** +EventMachine_t::OpenKeyboard +****************************/ + +const uintptr_t EventMachine_t::OpenKeyboard() +{ + KeyboardDescriptor *kd = new KeyboardDescriptor (this); + if (!kd) + throw std::runtime_error ("no keyboard-object allocated"); + Add (kd); + return kd->GetBinding(); +} + + +/********************************** +EventMachine_t::GetConnectionCount +**********************************/ + +int EventMachine_t::GetConnectionCount () +{ + int i = 0; + // Subtract one for epoll or kqueue because of the LoopbreakDescriptor + if (Poller == Poller_Epoll || Poller == Poller_Kqueue) + i = 1; + + return Descriptors.size() + NewDescriptors.size() - i; +} + + +/************************ +EventMachine_t::WatchPid +************************/ + +#ifdef HAVE_KQUEUE +const uintptr_t EventMachine_t::WatchPid (int pid) +{ + if (Poller != Poller_Kqueue) + throw std::runtime_error("must enable kqueue (EM.kqueue=true) for pid watching support"); + + struct kevent event; + int kqres; + + EV_SET(&event, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT | NOTE_FORK, 0, 0); + + // Attempt to register the event + kqres = kevent(kqfd, &event, 1, NULL, 0, NULL); + if (kqres == -1) { + char errbuf[200]; + sprintf(errbuf, "failed to register file watch descriptor with kqueue: %s", strerror(errno)); + throw std::runtime_error(errbuf); + } + Bindable_t* b = new Bindable_t(); + Pids.insert(std::make_pair (pid, b)); + + return b->GetBinding(); +} +#else +const uintptr_t EventMachine_t::WatchPid (int pid UNUSED) +{ + throw std::runtime_error("no pid watching support on this system"); +} +#endif + +/************************** +EventMachine_t::UnwatchPid +**************************/ + +void EventMachine_t::UnwatchPid (int pid) +{ + Bindable_t *b = Pids[pid]; + assert(b); + Pids.erase(pid); + + #ifdef HAVE_KQUEUE + struct kevent k; + + EV_SET(&k, pid, EVFILT_PROC, EV_DELETE, 0, 0, 0); + /*int t =*/ kevent (kqfd, &k, 1, NULL, 0, NULL); + // t==-1 if the process already exited; ignore this for now + #endif + + if (EventCallback) + (*EventCallback)(b->GetBinding(), EM_CONNECTION_UNBOUND, NULL, 0); + + delete b; +} + +void EventMachine_t::UnwatchPid (const uintptr_t sig) +{ + for(std::map::iterator i=Pids.begin(); i != Pids.end(); i++) + { + if (i->second->GetBinding() == sig) { + UnwatchPid (i->first); + return; + } + } + + throw std::runtime_error("attempted to remove invalid pid signature"); +} + + +/************************* +EventMachine_t::WatchFile +*************************/ + +const uintptr_t EventMachine_t::WatchFile (const char *fpath) +{ + struct stat sb; + int sres; + int wd = -1; + + sres = stat(fpath, &sb); + + if (sres == -1) { + char errbuf[300]; + sprintf(errbuf, "error registering file %s for watching: %s", fpath, strerror(errno)); + throw std::runtime_error(errbuf); + } + + #ifdef HAVE_INOTIFY + if (!inotify) { + inotify = new InotifyDescriptor(this); + assert (inotify); + Add(inotify); + } + + wd = inotify_add_watch(inotify->GetSocket(), fpath, + IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF | IN_CREATE | IN_DELETE | IN_MOVE) ; + if (wd == -1) { + char errbuf[300]; + sprintf(errbuf, "failed to open file %s for registering with inotify: %s", fpath, strerror(errno)); + throw std::runtime_error(errbuf); + } + #endif + + #ifdef HAVE_KQUEUE + if (Poller != Poller_Kqueue) + throw std::runtime_error("must enable kqueue (EM.kqueue=true) for file watching support"); + + // With kqueue we have to open the file first and use the resulting fd to register for events + wd = open(fpath, O_RDONLY); + if (wd == -1) { + char errbuf[300]; + sprintf(errbuf, "failed to open file %s for registering with kqueue: %s", fpath, strerror(errno)); + throw std::runtime_error(errbuf); + } + _RegisterKqueueFileEvent(wd); + #endif + + if (wd != -1) { + Bindable_t* b = new Bindable_t(); + Files.insert(std::make_pair (wd, b)); + + return b->GetBinding(); + } + + throw std::runtime_error("no file watching support on this system"); // is this the right thing to do? +} + + +/*************************** +EventMachine_t::UnwatchFile +***************************/ + +void EventMachine_t::UnwatchFile (int wd) +{ + Bindable_t *b = Files[wd]; + assert(b); + Files.erase(wd); + + #ifdef HAVE_INOTIFY + inotify_rm_watch(inotify->GetSocket(), wd); + #elif HAVE_KQUEUE + // With kqueue, closing the monitored fd automatically clears all registered events for it + close(wd); + #endif + + if (EventCallback) + (*EventCallback)(b->GetBinding(), EM_CONNECTION_UNBOUND, NULL, 0); + + delete b; +} + +void EventMachine_t::UnwatchFile (const uintptr_t sig) +{ + for(std::map::iterator i=Files.begin(); i != Files.end(); i++) + { + if (i->second->GetBinding() == sig) { + UnwatchFile (i->first); + return; + } + } + throw std::runtime_error("attempted to remove invalid watch signature"); +} + + +/*********************************** +EventMachine_t::_ReadInotify_Events +************************************/ + +void EventMachine_t::_ReadInotifyEvents() +{ + #ifdef HAVE_INOTIFY + char buffer[1024]; + + assert(EventCallback); + + for (;;) { + int returned = read(inotify->GetSocket(), buffer, sizeof(buffer)); + assert(!(returned == 0 || (returned == -1 && errno == EINVAL))); + if (returned <= 0) { + break; + } + int current = 0; + while (current < returned) { + struct inotify_event* event = (struct inotify_event*)(buffer+current); + std::map::const_iterator bindable = Files.find(event->wd); + if (bindable != Files.end()) { + if (event->mask & (IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE)){ + (*EventCallback)(bindable->second->GetBinding(), EM_CONNECTION_READ, "modified", 8); + } + if (event->mask & IN_MOVE_SELF){ + (*EventCallback)(bindable->second->GetBinding(), EM_CONNECTION_READ, "moved", 5); + } + if (event->mask & IN_DELETE_SELF) { + (*EventCallback)(bindable->second->GetBinding(), EM_CONNECTION_READ, "deleted", 7); + UnwatchFile ((int)event->wd); + } + } + current += sizeof(struct inotify_event) + event->len; + } + } + #endif +} + + +/************************************* +EventMachine_t::_HandleKqueuePidEvent +*************************************/ + +#ifdef HAVE_KQUEUE +void EventMachine_t::_HandleKqueuePidEvent(struct kevent *event) +{ + assert(EventCallback); + + if (event->fflags & NOTE_FORK) + (*EventCallback)(Pids [(int) event->ident]->GetBinding(), EM_CONNECTION_READ, "fork", 4); + if (event->fflags & NOTE_EXIT) { + (*EventCallback)(Pids [(int) event->ident]->GetBinding(), EM_CONNECTION_READ, "exit", 4); + // stop watching the pid if it died + UnwatchPid ((int)event->ident); + } +} +#endif + + +/************************************** +EventMachine_t::_HandleKqueueFileEvent +***************************************/ + +#ifdef HAVE_KQUEUE +void EventMachine_t::_HandleKqueueFileEvent(struct kevent *event) +{ + assert(EventCallback); + + if (event->fflags & NOTE_WRITE) + (*EventCallback)(Files [(int) event->ident]->GetBinding(), EM_CONNECTION_READ, "modified", 8); + if (event->fflags & NOTE_RENAME) + (*EventCallback)(Files [(int) event->ident]->GetBinding(), EM_CONNECTION_READ, "moved", 5); + if (event->fflags & NOTE_DELETE) { + (*EventCallback)(Files [(int) event->ident]->GetBinding(), EM_CONNECTION_READ, "deleted", 7); + UnwatchFile ((int)event->ident); + } +} +#endif + + +/**************************************** +EventMachine_t::_RegisterKqueueFileEvent +*****************************************/ + +#ifdef HAVE_KQUEUE +void EventMachine_t::_RegisterKqueueFileEvent(int fd) +{ + struct kevent newevent; + int kqres; + + // Setup the event with our fd and proper flags + EV_SET(&newevent, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_RENAME | NOTE_WRITE, 0, 0); + + // Attempt to register the event + kqres = kevent(kqfd, &newevent, 1, NULL, 0, NULL); + if (kqres == -1) { + char errbuf[200]; + sprintf(errbuf, "failed to register file watch descriptor with kqueue: %s", strerror(errno)); + close(fd); + throw std::runtime_error(errbuf); + } +} +#endif + + +/************************************ +EventMachine_t::GetHeartbeatInterval +*************************************/ + +float EventMachine_t::GetHeartbeatInterval() +{ + return ((float)HeartbeatInterval / 1000000); +} + + +/************************************ +EventMachine_t::SetHeartbeatInterval +*************************************/ + +int EventMachine_t::SetHeartbeatInterval(float interval) +{ + int iv = (int)(interval * 1000000); + if (iv > 0) { + HeartbeatInterval = iv; + return 1; + } + return 0; +} +//#endif // OS_UNIX diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.h new file mode 100644 index 0000000..874a127 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/em.h @@ -0,0 +1,308 @@ +/***************************************************************************** + +$Id$ + +File: em.h +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#ifndef __EventMachine__H_ +#define __EventMachine__H_ + +#ifdef BUILD_FOR_RUBY + #include + #ifdef HAVE_RB_THREAD_FD_SELECT + #define EmSelect rb_thread_fd_select + #else + // ruby 1.9.1 and below + #define EmSelect rb_thread_select + #endif + + #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL + #include + #endif + + #ifdef HAVE_RB_WAIT_FOR_SINGLE_FD + #include + #endif + + #if defined(HAVE_RB_TRAP_IMMEDIATE) + #include + #elif defined(HAVE_RB_ENABLE_INTERRUPT) + extern "C" { + void rb_enable_interrupt(void); + void rb_disable_interrupt(void); + } + + #define TRAP_BEG rb_enable_interrupt() + #define TRAP_END do { rb_disable_interrupt(); rb_thread_check_ints(); } while(0) + #else + #define TRAP_BEG + #define TRAP_END + #endif + + // 1.9.0 compat + #ifndef RUBY_UBF_IO + #define RUBY_UBF_IO RB_UBF_DFL + #endif + #ifndef RSTRING_PTR + #define RSTRING_PTR(str) RSTRING(str)->ptr + #endif + #ifndef RSTRING_LEN + #define RSTRING_LEN(str) RSTRING(str)->len + #endif + #ifndef RSTRING_LENINT + #define RSTRING_LENINT(str) RSTRING_LEN(str) + #endif +#else + #define EmSelect select +#endif + +#if !defined(HAVE_TYPE_RB_FDSET_T) +#define fd_check(n) (((n) < FD_SETSIZE) ? 1 : 0*fprintf(stderr, "fd %d too large for select\n", (n))) +// These definitions are cribbed from include/ruby/intern.h in Ruby 1.9.3, +// with this change: any macros that read or write the nth element of an +// fdset first call fd_check to make sure n is in bounds. +typedef fd_set rb_fdset_t; +#define rb_fd_zero(f) FD_ZERO(f) +#define rb_fd_set(n, f) do { if (fd_check(n)) FD_SET((n), (f)); } while(0) +#define rb_fd_clr(n, f) do { if (fd_check(n)) FD_CLR((n), (f)); } while(0) +#define rb_fd_isset(n, f) (fd_check(n) ? FD_ISSET((n), (f)) : 0) +#define rb_fd_copy(d, s, n) (*(d) = *(s)) +#define rb_fd_dup(d, s) (*(d) = *(s)) +#define rb_fd_resize(n, f) ((void)(f)) +#define rb_fd_ptr(f) (f) +#define rb_fd_init(f) FD_ZERO(f) +#define rb_fd_init_copy(d, s) (*(d) = *(s)) +#define rb_fd_term(f) ((void)(f)) +#define rb_fd_max(f) FD_SETSIZE +#define rb_fd_select(n, rfds, wfds, efds, timeout) \ + select(fd_check((n)-1) ? (n) : FD_SETSIZE, (rfds), (wfds), (efds), (timeout)) +#define rb_thread_fd_select(n, rfds, wfds, efds, timeout) \ + rb_thread_select(fd_check((n)-1) ? (n) : FD_SETSIZE, (rfds), (wfds), (efds), (timeout)) +#endif + + +// This Solaris fix is adapted from eval_intern.h in Ruby 1.9.3: +// Solaris sys/select.h switches select to select_large_fdset to support larger +// file descriptors if FD_SETSIZE is larger than 1024 on 32bit environment. +// But Ruby doesn't change FD_SETSIZE because fd_set is allocated dynamically. +// So following definition is required to use select_large_fdset. +#ifdef HAVE_SELECT_LARGE_FDSET +#define select(n, r, w, e, t) select_large_fdset((n), (r), (w), (e), (t)) +extern "C" { + int select_large_fdset(int, fd_set *, fd_set *, fd_set *, struct timeval *); +} +#endif + +class EventableDescriptor; +class InotifyDescriptor; +struct SelectData_t; + +/************* +enum Poller_t +*************/ +enum Poller_t { + Poller_Default, // typically Select + Poller_Epoll, + Poller_Kqueue +}; + + +/******************** +class EventMachine_t +********************/ + +class EventMachine_t +{ + public: + static int GetMaxTimerCount(); + static void SetMaxTimerCount (int); + + static int GetSimultaneousAcceptCount(); + static void SetSimultaneousAcceptCount (int); + + public: + EventMachine_t (EMCallback, Poller_t); + virtual ~EventMachine_t(); + + bool RunOnce(); + void Run(); + void ScheduleHalt(); + bool Stopping(); + void SignalLoopBreaker(); + const uintptr_t InstallOneshotTimer (uint64_t); + const uintptr_t ConnectToServer (const char *, int, const char *, int); + const uintptr_t ConnectToUnixServer (const char *); + + const uintptr_t CreateTcpServer (const char *, int); + const uintptr_t OpenDatagramSocket (const char *, int); + const uintptr_t CreateUnixDomainServer (const char*); + const uintptr_t AttachSD (SOCKET); + const uintptr_t OpenKeyboard(); + //const char *Popen (const char*, const char*); + const uintptr_t Socketpair (char* const*); + + void Add (EventableDescriptor*); + void Modify (EventableDescriptor*); + void Deregister (EventableDescriptor*); + + const uintptr_t AttachFD (SOCKET, bool); + int DetachFD (EventableDescriptor*); + + void ArmKqueueWriter (EventableDescriptor*); + void ArmKqueueReader (EventableDescriptor*); + + void SetTimerQuantum (int); + static void SetuidString (const char*); + static int SetRlimitNofile (int); + + pid_t SubprocessPid; + int SubprocessExitStatus; + + int GetConnectionCount(); + float GetHeartbeatInterval(); + int SetHeartbeatInterval(float); + + const uintptr_t WatchFile (const char*); + void UnwatchFile (int); + void UnwatchFile (const uintptr_t); + + #ifdef HAVE_KQUEUE + void _HandleKqueueFileEvent (struct kevent*); + void _RegisterKqueueFileEvent(int); + #endif + + const uintptr_t WatchPid (int); + void UnwatchPid (int); + void UnwatchPid (const uintptr_t); + + #ifdef HAVE_KQUEUE + void _HandleKqueuePidEvent (struct kevent*); + #endif + + uint64_t GetCurrentLoopTime() { return MyCurrentLoopTime; } + + void QueueHeartbeat(EventableDescriptor*); + void ClearHeartbeat(uint64_t, EventableDescriptor*); + + uint64_t GetRealTime(); + + Poller_t GetPoller() { return Poller; } + + static int name2address (const char *server, int port, int socktype, struct sockaddr *addr, size_t *addr_len); + + private: + void _RunTimers(); + void _UpdateTime(); + void _AddNewDescriptors(); + void _ModifyDescriptors(); + void _InitializeLoopBreaker(); + void _CleanupSockets(); + + void _RunSelectOnce(); + void _RunEpollOnce(); + void _RunKqueueOnce(); + + void _ModifyEpollEvent (EventableDescriptor*); + void _DispatchHeartbeats(); + timeval _TimeTilNextEvent(); + void _CleanBadDescriptors(); + + public: + void _ReadLoopBreaker(); + void _ReadInotifyEvents(); + int NumCloseScheduled; + + private: + enum { + MaxEpollDescriptors = 64*1024, + MaxEvents = 4096 + }; + int HeartbeatInterval; + EMCallback EventCallback; + + class Timer_t: public Bindable_t { + }; + + std::multimap Timers; + std::multimap Heartbeats; + std::map Files; + std::map Pids; + std::vector Descriptors; + std::vector NewDescriptors; + std::set ModifiedDescriptors; + + SOCKET LoopBreakerReader; + SOCKET LoopBreakerWriter; + #ifdef OS_WIN32 + struct sockaddr_in LoopBreakerTarget; + #endif + + timeval Quantum; + + uint64_t MyCurrentLoopTime; + + #ifdef OS_WIN32 + unsigned TickCountTickover; + unsigned LastTickCount; + #endif + + #ifdef OS_DARWIN + mach_timebase_info_data_t mach_timebase; + #endif + + private: + bool bTerminateSignalReceived; + SelectData_t *SelectData; + + Poller_t Poller; + + int epfd; // Epoll file-descriptor + #ifdef HAVE_EPOLL + struct epoll_event epoll_events [MaxEvents]; + #endif + + int kqfd; // Kqueue file-descriptor + #ifdef HAVE_KQUEUE + struct kevent Karray [MaxEvents]; + #endif + + #ifdef HAVE_INOTIFY + InotifyDescriptor *inotify; // pollable descriptor for our inotify instance + #endif +}; + + +/******************* +struct SelectData_t +*******************/ + +struct SelectData_t +{ + SelectData_t(); + ~SelectData_t(); + + int _Select(); + void _Clear(); + + SOCKET maxsocket; + rb_fdset_t fdreads; + rb_fdset_t fdwrites; + rb_fdset_t fderrors; + timeval tv; + int nSockets; +}; + +#endif // __EventMachine__H_ diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/eventmachine.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/eventmachine.h new file mode 100644 index 0000000..5100e20 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/eventmachine.h @@ -0,0 +1,143 @@ +/***************************************************************************** + +$Id$ + +File: eventmachine.h +Date: 15Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#ifndef __EVMA_EventMachine__H_ +#define __EVMA_EventMachine__H_ + +#if __cplusplus +extern "C" { +#endif + + enum { // Event names + EM_TIMER_FIRED = 100, + EM_CONNECTION_READ = 101, + EM_CONNECTION_UNBOUND = 102, + EM_CONNECTION_ACCEPTED = 103, + EM_CONNECTION_COMPLETED = 104, + EM_LOOPBREAK_SIGNAL = 105, + EM_CONNECTION_NOTIFY_READABLE = 106, + EM_CONNECTION_NOTIFY_WRITABLE = 107, + EM_SSL_HANDSHAKE_COMPLETED = 108, + EM_SSL_VERIFY = 109, + EM_PROXY_TARGET_UNBOUND = 110, + EM_PROXY_COMPLETED = 111 + }; + + enum { // SSL/TLS Protocols + EM_PROTO_SSLv2 = 2, + EM_PROTO_SSLv3 = 4, + EM_PROTO_TLSv1 = 8, + EM_PROTO_TLSv1_1 = 16, + EM_PROTO_TLSv1_2 = 32 + }; + + void evma_initialize_library (EMCallback); + bool evma_run_machine_once(); + void evma_run_machine(); + void evma_release_library(); + const uintptr_t evma_install_oneshot_timer (uint64_t milliseconds); + const uintptr_t evma_connect_to_server (const char *bind_addr, int bind_port, const char *server, int port); + const uintptr_t evma_connect_to_unix_server (const char *server); + + const uintptr_t evma_attach_fd (int file_descriptor, int watch_mode); + int evma_detach_fd (const uintptr_t binding); + int evma_get_file_descriptor (const uintptr_t binding); + int evma_is_notify_readable (const uintptr_t binding); + void evma_set_notify_readable (const uintptr_t binding, int mode); + int evma_is_notify_writable (const uintptr_t binding); + void evma_set_notify_writable (const uintptr_t binding, int mode); + + int evma_pause(const uintptr_t binding); + int evma_is_paused(const uintptr_t binding); + int evma_resume(const uintptr_t binding); + + int evma_num_close_scheduled(); + + void evma_stop_tcp_server (const uintptr_t binding); + const uintptr_t evma_create_tcp_server (const char *address, int port); + const uintptr_t evma_create_unix_domain_server (const char *filename); + const uintptr_t evma_attach_sd (int sd); + const uintptr_t evma_open_datagram_socket (const char *server, int port); + const uintptr_t evma_open_keyboard(); + void evma_set_tls_parms (const uintptr_t binding, const char *privatekey_filename, const char *certchain_filenane, int verify_peer, int fail_if_no_peer_cert, const char *sni_hostname, const char *cipherlist, const char *ecdh_curve, const char *dhparam, int protocols); + void evma_start_tls (const uintptr_t binding); + + #ifdef WITH_SSL + X509 *evma_get_peer_cert (const uintptr_t binding); + int evma_get_cipher_bits (const uintptr_t binding); + const char *evma_get_cipher_name (const uintptr_t binding); + const char *evma_get_cipher_protocol (const uintptr_t binding); + const char *evma_get_sni_hostname (const uintptr_t binding); + void evma_accept_ssl_peer (const uintptr_t binding); + #endif + + int evma_get_peername (const uintptr_t binding, struct sockaddr*, socklen_t*); + int evma_get_sockname (const uintptr_t binding, struct sockaddr*, socklen_t*); + int evma_get_subprocess_pid (const uintptr_t binding, pid_t*); + int evma_get_subprocess_status (const uintptr_t binding, int*); + int evma_get_connection_count(); + int evma_send_data_to_connection (const uintptr_t binding, const char *data, int data_length); + int evma_send_datagram (const uintptr_t binding, const char *data, int data_length, const char *address, int port); + float evma_get_comm_inactivity_timeout (const uintptr_t binding); + int evma_set_comm_inactivity_timeout (const uintptr_t binding, float value); + float evma_get_pending_connect_timeout (const uintptr_t binding); + int evma_set_pending_connect_timeout (const uintptr_t binding, float value); + int evma_get_outbound_data_size (const uintptr_t binding); + uint64_t evma_get_last_activity_time (const uintptr_t binding); + int evma_send_file_data_to_connection (const uintptr_t binding, const char *filename); + + void evma_close_connection (const uintptr_t binding, int after_writing); + int evma_report_connection_error_status (const uintptr_t binding); + void evma_signal_loopbreak(); + void evma_set_timer_quantum (int); + int evma_get_max_timer_count(); + void evma_set_max_timer_count (int); + int evma_get_simultaneous_accept_count(); + void evma_set_simultaneous_accept_count (int); + void evma_setuid_string (const char *username); + void evma_stop_machine(); + bool evma_stopping(); + float evma_get_heartbeat_interval(); + int evma_set_heartbeat_interval(float); + + const uintptr_t evma_popen (char * const*cmd_strings); + + const uintptr_t evma_watch_filename (const char *fname); + void evma_unwatch_filename (const uintptr_t binding); + + const uintptr_t evma_watch_pid (int); + void evma_unwatch_pid (const uintptr_t binding); + + void evma_start_proxy(const uintptr_t from, const uintptr_t to, const unsigned long bufsize, const unsigned long length); + void evma_stop_proxy(const uintptr_t from); + unsigned long evma_proxied_bytes(const uintptr_t from); + + int evma_set_rlimit_nofile (int n_files); + + void evma_set_epoll (int use); + void evma_set_kqueue (int use); + + uint64_t evma_get_current_loop_time(); +#if __cplusplus +} +#endif + + +#endif // __EventMachine__H_ + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/extconf.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/extconf.rb new file mode 100644 index 0000000..676b4c4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/extconf.rb @@ -0,0 +1,270 @@ +require 'fileutils' +require 'mkmf' + +# Eager check devs tools +have_devel? if respond_to?(:have_devel?) + +def check_libs libs = [], fatal = false + libs.all? { |lib| have_library(lib) || (abort("could not find library: #{lib}") if fatal) } +end + +def check_heads heads = [], fatal = false + heads.all? { |head| have_header(head) || (abort("could not find header: #{head}") if fatal)} +end + +def add_define(name) + $defs.push("-D#{name}") +end + +## +# OpenSSL: + +# override append_library, so it actually appends (instead of prepending) +# this fixes issues with linking ssl, since libcrypto depends on symbols in libssl +def append_library(libs, lib) + libs + " " + format(LIBARG, lib) +end + +SSL_HEADS = %w(openssl/ssl.h openssl/err.h) +SSL_LIBS = %w(crypto ssl) +# OpenSSL 1.1.0 and above for Windows use the Unix library names +# OpenSSL 0.9.8 and 1.0.x for Windows use the *eay32 library names +SSL_LIBS_WIN = RUBY_PLATFORM =~ /mswin|mingw|bccwin/ ? %w(ssleay32 libeay32) : [] + +def dir_config_wrapper(pretty_name, name, idefault=nil, ldefault=nil) + inc, lib = dir_config(name, idefault, ldefault) + if inc && lib + # TODO: Remove when 2.0.0 is the minimum supported version + # Ruby versions not incorporating the mkmf fix at + # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717 + # do not properly search for lib directories, and must be corrected + unless lib && lib[-3, 3] == 'lib' + @libdir_basename = 'lib' + inc, lib = dir_config(name, idefault, ldefault) + end + unless idefault && ldefault + abort "-----\nCannot find #{pretty_name} include path #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } + abort "-----\nCannot find #{pretty_name} library path #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) } + warn "-----\nUsing #{pretty_name} in path #{File.dirname inc}\n-----" + end + true + end +end + +def dir_config_search(pretty_name, name, paths, &b) + paths.each do |p| + if dir_config_wrapper('OpenSSL', 'ssl', p + '/include', p + '/lib') && yield + warn "-----\nFound #{pretty_name} in path #{p}\n-----" + return true + end + end + false +end + +def pkg_config_wrapper(pretty_name, name) + cflags, ldflags, libs = pkg_config(name) + unless [cflags, ldflags, libs].any?(&:nil?) || [cflags, ldflags, libs].any?(&:empty?) + warn "-----\nUsing #{pretty_name} from pkg-config #{cflags} && #{ldflags} && #{libs}\n-----" + true + end +end + +if ENV['CROSS_COMPILING'] + openssl_version = ENV.fetch("OPENSSL_VERSION", "1.0.2e") + openssl_dir = File.expand_path("~/.rake-compiler/builds/openssl-#{openssl_version}/") + if File.exist?(openssl_dir) + FileUtils.mkdir_p Dir.pwd+"/openssl/" + FileUtils.cp Dir[openssl_dir+"/include/openssl/*.h"], Dir.pwd+"/openssl/", :verbose => true + FileUtils.cp Dir[openssl_dir+"/lib*.a"], Dir.pwd, :verbose => true + $INCFLAGS << " -I#{Dir.pwd}" # for the openssl headers + add_define "WITH_SSL" + else + STDERR.puts + STDERR.puts "**************************************************************************************" + STDERR.puts "**** Cross-compiled OpenSSL not found" + STDERR.puts "**** Run: hg clone http://bitbucket.org/ged/ruby-pg && cd ruby-pg && rake openssl_libs" + STDERR.puts "**************************************************************************************" + STDERR.puts + end +elsif dir_config_wrapper('OpenSSL', 'ssl') + # If the user has provided a --with-ssl-dir argument, we must respect it or fail. + add_define 'WITH_SSL' if (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS) +elsif pkg_config_wrapper('OpenSSL', 'openssl') + # If we can detect OpenSSL by pkg-config, use it as the next-best option + add_define 'WITH_SSL' if (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS) +elsif (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS) + # If we don't even need any options to find a usable OpenSSL, go with it + add_define 'WITH_SSL' +elsif dir_config_search('OpenSSL', 'ssl', ['/usr/local', '/opt/local', '/usr/local/opt/openssl']) do + (check_libs(SSL_LIBS) || check_libs(SSL_LIBS_WIN)) && check_heads(SSL_HEADS) + end + # Finally, look for OpenSSL in alternate locations including MacPorts and HomeBrew + add_define 'WITH_SSL' +end + +add_define 'BUILD_FOR_RUBY' + +# Ruby features: + +have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h']) +have_func('rb_thread_blocking_region') +have_func('rb_thread_call_without_gvl', 'ruby/thread.h') +have_func('rb_thread_fd_select') +have_type('rb_fdset_t', 'ruby/intern.h') +have_func('rb_wait_for_single_fd') +have_func('rb_enable_interrupt') +have_func('rb_time_new') + +# System features: + +add_define('HAVE_INOTIFY') if inotify = have_func('inotify_init', 'sys/inotify.h') +add_define('HAVE_OLD_INOTIFY') if !inotify && have_macro('__NR_inotify_init', 'sys/syscall.h') +have_func('writev', 'sys/uio.h') +have_func('pipe2', 'unistd.h') +have_func('accept4', 'sys/socket.h') +have_const('SOCK_CLOEXEC', 'sys/socket.h') + +# Minor platform details between *nix and Windows: + +if RUBY_PLATFORM =~ /(mswin|mingw|bccwin)/ + GNU_CHAIN = ENV['CROSS_COMPILING'] || $1 == 'mingw' + OS_WIN32 = true + add_define "OS_WIN32" +else + GNU_CHAIN = true + OS_UNIX = true + add_define 'OS_UNIX' + + add_define "HAVE_KQUEUE" if have_header("sys/event.h") && have_header("sys/queue.h") +end + +# Adjust number of file descriptors (FD) on Windows + +if RbConfig::CONFIG["host_os"] =~ /mingw/ + found = RbConfig::CONFIG.values_at("CFLAGS", "CPPFLAGS"). + any? { |v| v.include?("FD_SETSIZE") } + + add_define "FD_SETSIZE=32767" unless found +end + +# Main platform invariances: + +case RUBY_PLATFORM +when /mswin32/, /mingw32/, /bccwin32/ + check_heads(%w[windows.h winsock.h], true) + check_libs(%w[kernel32 rpcrt4 gdi32], true) + + if GNU_CHAIN + CONFIG['LDSHAREDXX'] = "$(CXX) -shared -static-libgcc -static-libstdc++" + else + $defs.push "-EHs" + $defs.push "-GR" + end + + # Newer versions of Ruby already define _WIN32_WINNT, which is needed + # to get access to newer POSIX networking functions (e.g. getaddrinfo) + add_define '_WIN32_WINNT=0x0501' unless have_func('getaddrinfo') + +when /solaris/ + add_define 'OS_SOLARIS8' + check_libs(%w[nsl socket], true) + + # If Ruby was compiled for 32-bits, then select() can only handle 1024 fds + # There is an alternate function, select_large_fdset, that supports more. + have_func('select_large_fdset', 'sys/select.h') + + if CONFIG['CC'] == 'cc' && ( + `cc -flags 2>&1` =~ /Sun/ || # detect SUNWspro compiler + `cc -V 2>&1` =~ /Sun/ # detect Solaris Studio compiler + ) + # SUN CHAIN + add_define 'CC_SUNWspro' + $preload = ["\nCXX = CC"] # hack a CXX= line into the makefile + $CFLAGS = CONFIG['CFLAGS'] = "-KPIC" + CONFIG['CCDLFLAGS'] = "-KPIC" + CONFIG['LDSHARED'] = "$(CXX) -G -KPIC -lCstd" + CONFIG['LDSHAREDXX'] = "$(CXX) -G -KPIC -lCstd" + else + # GNU CHAIN + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" + end + +when /openbsd/ + # OpenBSD branch contributed by Guillaume Sellier. + + # on Unix we need a g++ link, not gcc. On OpenBSD, linking against libstdc++ have to be explicitly done for shared libs + CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++ -fPIC" + CONFIG['LDSHAREDXX'] = "$(CXX) -shared -lstdc++ -fPIC" + +when /darwin/ + add_define 'OS_DARWIN' + + # on Unix we need a g++ link, not gcc. + # Ff line contributed by Daniel Harple. + CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ') + +when /linux/ + add_define 'HAVE_EPOLL' if have_func('epoll_create', 'sys/epoll.h') + + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" + +when /aix/ + CONFIG['LDSHARED'] = "$(CXX) -Wl,-bstatic -Wl,-bdynamic -Wl,-G -Wl,-brtl" + +when /cygwin/ + # For rubies built with Cygwin, CXX may be set to CC, which is just + # a wrapper for gcc. + # This will compile, but it will not link to the C++ std library. + # Explicitly set CXX to use g++. + CONFIG['CXX'] = "g++" + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" + +else + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" +end + +# Platform-specific time functions +if have_func('clock_gettime') + # clock_gettime is POSIX, but the monotonic clocks are not + have_const('CLOCK_MONOTONIC_RAW', 'time.h') # Linux + have_const('CLOCK_MONOTONIC', 'time.h') # Linux, Solaris, BSDs +else + have_func('gethrtime') # Older Solaris and HP-UX +end + +# Hack so that try_link will test with a C++ compiler instead of a C compiler +TRY_LINK.sub!('$(CC)', '$(CXX)') + +# This is our wishlist. We use whichever flags work on the host. +# In the future, add -Werror to make sure all warnings are resolved. +# deprecated-declarations are used in OS X OpenSSL +# ignored-qualifiers are used by the Bindings (would-be void *) +# unused-result because GCC 4.6 no longer silences (void) ignore_this(function) +# address because on Windows, rb_fd_select checks if &fds is non-NULL, which it cannot be +%w( + -Wall + -Wextra + -Wno-deprecated-declarations + -Wno-ignored-qualifiers + -Wno-unused-result + -Wno-address +).select do |flag| + try_link('int main() {return 0;}', flag) +end.each do |flag| + CONFIG['CXXFLAGS'] << ' ' << flag +end +puts "CXXFLAGS=#{CONFIG['CXXFLAGS']}" + +# Solaris C++ compiler doesn't have make_pair() +add_define 'HAVE_MAKE_PAIR' if try_link(< + using namespace std; + int main(){ pair tuple = make_pair(1,2); } +SRC +TRY_LINK.sub!('$(CXX)', '$(CC)') + +create_makefile "rubyeventmachine" diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/Makefile b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/Makefile new file mode 100644 index 0000000..b64d9b4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/Makefile @@ -0,0 +1,273 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +V0 = $(V:0=) +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /Users/ben/.rubies/ruby-3.4.1/include/ruby-3.4.0 +hdrdir = $(topdir) +arch_hdrdir = /Users/ben/.rubies/ruby-3.4.1/include/ruby-3.4.0/arm64-darwin24 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/Users/ben/.rubies/ruby-3.4.1 +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(sitelibdir)/$(sitearch) +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +modular_gc_dir = $(DESTDIR) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(SDKROOT)$(prefix)/include +runstatedir = $(localstatedir)/run +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = clang +CXX = clang++ -std=gnu++11 +LIBRUBY = $(LIBRUBY_A) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(hardenflags) -fdeclspec $(optflags) $(debugflags) $(warnflags) +cxxflags = +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef +cppflags = +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -pipe $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -DBUILD_FOR_RUBY -DOS_UNIX -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/gdbm/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG) +ldflags = -L. -fstack-protector-strong -L/opt/homebrew/Cellar/readline/8.2.13/lib -L/opt/homebrew/Cellar/libyaml/0.2.5/lib -L/opt/homebrew/Cellar/gdbm/1.24/lib +dldflags = -L/opt/homebrew/Cellar/readline/8.2.13/lib -L/opt/homebrew/Cellar/libyaml/0.2.5/lib -L/opt/homebrew/Cellar/gdbm/1.24/lib -Wl,-undefined,dynamic_lookup -bundle_loader '$(BUILTRUBY)' +ARCH_FLAG = -arch arm64 +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CXX) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +POSTLINK = dsymutil $@ 2>/dev/null; { test -z '$(RUBY_CODESIGN)' || codesign -s '$(RUBY_CODESIGN)' $@; } +AR = ar +LD = ld +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby.3.4 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = arm64-darwin24 +sitearch = $(arch) +ruby_version = 3.4.0 +ruby = $(bindir)/$(RUBY_BASE_NAME) +RUBY = $(ruby) +BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = rm -fr +RMDIRS = rmdir -p +MAKEDIRS = mkdir -p +INSTALL = /usr/bin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(libdir) /opt/homebrew/opt/readline/lib /opt/homebrew/opt/libyaml/lib /opt/homebrew/opt/gdbm/lib +LIBPATH = -L. -L$(libdir) -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/libyaml/lib -L/opt/homebrew/opt/gdbm/lib +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = +LOCAL_LIBS = +LIBS = -lpthread +ORIG_SRCS = mapper.cpp rubymain.cpp +SRCS = $(ORIG_SRCS) +OBJS = mapper.o rubymain.o +HDRS = $(srcdir)/mapper.h +LOCAL_HDRS = +TARGET = fastfilereaderext +TARGET_NAME = fastfilereaderext +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(sitehdrdir)$(target_prefix) +ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) $(TARGET_SO:=.dSYM) +CLEANOBJS = $(OBJS) *.bak +TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP) + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TARGET_SO_DIR_TIMESTAMP): + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object $(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHAREDXX) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/extconf.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/extconf.rb new file mode 100644 index 0000000..e4d64e4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/extconf.rb @@ -0,0 +1,109 @@ +require 'mkmf' + +def check_libs libs = [], fatal = false + libs.all? { |lib| have_library(lib) || (abort("could not find library: #{lib}") if fatal) } +end + +def check_heads heads = [], fatal = false + heads.all? { |head| have_header(head) || (abort("could not find header: #{head}") if fatal)} +end + +def add_define(name) + $defs.push("-D#{name}") +end + +# Eager check devs tools +have_devel? if respond_to?(:have_devel?) + +add_define 'BUILD_FOR_RUBY' + +# Minor platform details between *nix and Windows: + +if RUBY_PLATFORM =~ /(mswin|mingw|bccwin)/ + GNU_CHAIN = ENV['CROSS_COMPILING'] || $1 == 'mingw' + OS_WIN32 = true + add_define "OS_WIN32" +else + GNU_CHAIN = true + OS_UNIX = true + add_define 'OS_UNIX' +end + +# Adjust number of file descriptors (FD) on Windows + +if RbConfig::CONFIG["host_os"] =~ /mingw/ + found = RbConfig::CONFIG.values_at("CFLAGS", "CPPFLAGS"). + any? { |v| v.include?("FD_SETSIZE") } + + add_define "FD_SETSIZE=32767" unless found +end + +# Main platform invariances: + +case RUBY_PLATFORM +when /mswin32/, /mingw32/, /bccwin32/ + check_heads(%w[windows.h winsock.h], true) + check_libs(%w[kernel32 rpcrt4 gdi32], true) + + if GNU_CHAIN + CONFIG['LDSHAREDXX'] = "$(CXX) -shared -static-libgcc -static-libstdc++" + else + $defs.push "-EHs" + $defs.push "-GR" + end + +when /solaris/ + add_define 'OS_SOLARIS8' + check_libs(%w[nsl socket], true) + + if CONFIG['CC'] == 'cc' && ( + `cc -flags 2>&1` =~ /Sun/ || # detect SUNWspro compiler + `cc -V 2>&1` =~ /Sun/ # detect Solaris Studio compiler + ) + # SUN CHAIN + add_define 'CC_SUNWspro' + $preload = ["\nCXX = CC"] # hack a CXX= line into the makefile + $CFLAGS = CONFIG['CFLAGS'] = "-KPIC" + CONFIG['CCDLFLAGS'] = "-KPIC" + CONFIG['LDSHARED'] = "$(CXX) -G -KPIC -lCstd" + CONFIG['LDSHAREDXX'] = "$(CXX) -G -KPIC -lCstd" + else + # GNU CHAIN + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" + end + +when /openbsd/ + # OpenBSD branch contributed by Guillaume Sellier. + + # on Unix we need a g++ link, not gcc. On OpenBSD, linking against libstdc++ have to be explicitly done for shared libs + CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++ -fPIC" + CONFIG['LDSHAREDXX'] = "$(CXX) -shared -lstdc++ -fPIC" + +when /darwin/ + # on Unix we need a g++ link, not gcc. + # Ff line contributed by Daniel Harple. + CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ') + +when /linux/ + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" + +when /aix/ + CONFIG['LDSHARED'] = "$(CXX) -Wl,-bstatic -Wl,-bdynamic -Wl,-G -Wl,-brtl" + +when /cygwin/ + # For rubies built with Cygwin, CXX may be set to CC, which is just + # a wrapper for gcc. + # This will compile, but it will not link to the C++ std library. + # Explicitly set CXX to use g++. + CONFIG['CXX'] = "g++" + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" + +else + # on Unix we need a g++ link, not gcc. + CONFIG['LDSHARED'] = "$(CXX) -shared" +end + +create_makefile "fastfilereaderext" diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.cpp new file mode 100644 index 0000000..f226f2d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.cpp @@ -0,0 +1,214 @@ +/***************************************************************************** + +$Id: mapper.cpp 4527 2007-07-04 10:21:34Z francis $ + +File: mapper.cpp +Date: 02Jul07 + +Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved. +Gmail: garbagecat10 + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +////////////////////////////////////////////////////////////////////// +// UNIX implementation +////////////////////////////////////////////////////////////////////// + + +#ifdef OS_UNIX + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mapper.h" + +/****************** +Mapper_t::Mapper_t +******************/ + +Mapper_t::Mapper_t (const std::string &filename) +{ + /* We ASSUME we can open the file. + * (More precisely, we assume someone else checked before we got here.) + */ + + Fd = open (filename.c_str(), O_RDONLY); + if (Fd < 0) + throw std::runtime_error (strerror (errno)); + + struct stat st; + if (fstat (Fd, &st)) + throw std::runtime_error (strerror (errno)); + FileSize = st.st_size; + + #ifdef OS_WIN32 + MapPoint = (char*) mmap (0, FileSize, PROT_READ, MAP_SHARED, Fd, 0); + #else + MapPoint = (const char*) mmap (0, FileSize, PROT_READ, MAP_SHARED, Fd, 0); + #endif + if (MapPoint == MAP_FAILED) + throw std::runtime_error (strerror (errno)); +} + + +/******************* +Mapper_t::~Mapper_t +*******************/ + +Mapper_t::~Mapper_t() +{ + Close(); +} + + +/*************** +Mapper_t::Close +***************/ + +void Mapper_t::Close() +{ + // Can be called multiple times. + // Calls to GetChunk are invalid after a call to Close. + if (MapPoint) { + #ifdef CC_SUNWspro + // TODO: The void * cast works fine on Solaris 11, but + // I don't know at what point that changed from older Solaris. + munmap ((char*)MapPoint, FileSize); + #else + munmap ((void*)MapPoint, FileSize); + #endif + MapPoint = NULL; + } + if (Fd >= 0) { + close (Fd); + Fd = -1; + } +} + +/****************** +Mapper_t::GetChunk +******************/ + +const char *Mapper_t::GetChunk (unsigned start) +{ + return MapPoint + start; +} + + + +#endif // OS_UNIX + + +////////////////////////////////////////////////////////////////////// +// WINDOWS implementation +////////////////////////////////////////////////////////////////////// + +#ifdef OS_WIN32 + +#include + +#include +#include +#include + +#include "mapper.h" + +/****************** +Mapper_t::Mapper_t +******************/ + +Mapper_t::Mapper_t (const std::string &filename) +{ + /* We ASSUME we can open the file. + * (More precisely, we assume someone else checked before we got here.) + */ + + hFile = INVALID_HANDLE_VALUE; + hMapping = NULL; + MapPoint = NULL; + FileSize = 0; + + hFile = CreateFile (filename.c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + throw std::runtime_error ("File not found"); + + BY_HANDLE_FILE_INFORMATION i; + if (GetFileInformationByHandle (hFile, &i)) + FileSize = i.nFileSizeLow; + + hMapping = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL); + if (!hMapping) + throw std::runtime_error ("File not mapped"); + + #ifdef OS_WIN32 + MapPoint = (char*) MapViewOfFile (hMapping, FILE_MAP_WRITE, 0, 0, 0); + #else + MapPoint = (const char*) MapViewOfFile (hMapping, FILE_MAP_WRITE, 0, 0, 0); + #endif + if (!MapPoint) + throw std::runtime_error ("Mappoint not read"); +} + + +/******************* +Mapper_t::~Mapper_t +*******************/ + +Mapper_t::~Mapper_t() +{ + Close(); +} + +/*************** +Mapper_t::Close +***************/ + +void Mapper_t::Close() +{ + // Can be called multiple times. + // Calls to GetChunk are invalid after a call to Close. + if (MapPoint) { + UnmapViewOfFile (MapPoint); + MapPoint = NULL; + } + if (hMapping != NULL) { + CloseHandle (hMapping); + hMapping = NULL; + } + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle (hFile); + hFile = INVALID_HANDLE_VALUE; + } +} + + +/****************** +Mapper_t::GetChunk +******************/ + +const char *Mapper_t::GetChunk (unsigned start) +{ + return MapPoint + start; +} + + + +#endif // OS_WINDOWS diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.h new file mode 100644 index 0000000..3db0eea --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/mapper.h @@ -0,0 +1,59 @@ +/***************************************************************************** + +$Id: mapper.h 4529 2007-07-04 11:32:22Z francis $ + +File: mapper.h +Date: 02Jul07 + +Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved. +Gmail: garbagecat10 + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +#ifndef __Mapper__H_ +#define __Mapper__H_ + + +/************** +class Mapper_t +**************/ + +class Mapper_t +{ + public: + Mapper_t (const std::string&); + virtual ~Mapper_t(); + + const char *GetChunk (unsigned); + void Close(); + size_t GetFileSize() {return FileSize;} + + private: + size_t FileSize; + + #ifdef OS_UNIX + private: + int Fd; + const char *MapPoint; + #endif // OS_UNIX + + #ifdef OS_WIN32 + private: + HANDLE hFile; + HANDLE hMapping; + char *MapPoint; + #endif // OS_WIN32 + +}; + + +#endif // __Mapper__H_ + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/rubymain.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/rubymain.cpp new file mode 100644 index 0000000..b962bf0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/fastfilereader/rubymain.cpp @@ -0,0 +1,126 @@ +/***************************************************************************** + +$Id: rubymain.cpp 4529 2007-07-04 11:32:22Z francis $ + +File: rubymain.cpp +Date: 02Jul07 + +Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved. +Gmail: garbagecat10 + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + + +#include +#include + +#include +#include "mapper.h" + +static VALUE EmModule; +static VALUE FastFileReader; +static VALUE Mapper; + + + +/********* +mapper_dt +*********/ + +static void mapper_dt (void *ptr) +{ + if (ptr) + delete (Mapper_t*) ptr; +} + +/********** +mapper_new +**********/ + +static VALUE mapper_new (VALUE self, VALUE filename) +{ + Mapper_t *m = new Mapper_t (StringValueCStr (filename)); + if (!m) + rb_raise (rb_eStandardError, "No Mapper Object"); + VALUE v = Data_Wrap_Struct (Mapper, 0, mapper_dt, (void*)m); + return v; +} + + +/**************** +mapper_get_chunk +****************/ + +static VALUE mapper_get_chunk (VALUE self, VALUE start, VALUE length) +{ + Mapper_t *m = NULL; + Data_Get_Struct (self, Mapper_t, m); + if (!m) + rb_raise (rb_eStandardError, "No Mapper Object"); + + // TODO, what if some moron sends us a negative start value? + unsigned _start = NUM2INT (start); + unsigned _length = NUM2INT (length); + if ((_start + _length) > m->GetFileSize()) + rb_raise (rb_eStandardError, "Mapper Range Error"); + + const char *chunk = m->GetChunk (_start); + if (!chunk) + rb_raise (rb_eStandardError, "No Mapper Chunk"); + return rb_str_new (chunk, _length); +} + +/************ +mapper_close +************/ + +static VALUE mapper_close (VALUE self) +{ + Mapper_t *m = NULL; + Data_Get_Struct (self, Mapper_t, m); + if (!m) + rb_raise (rb_eStandardError, "No Mapper Object"); + m->Close(); + return Qnil; +} + +/*********** +mapper_size +***********/ + +static VALUE mapper_size (VALUE self) +{ + Mapper_t *m = NULL; + Data_Get_Struct (self, Mapper_t, m); + if (!m) + rb_raise (rb_eStandardError, "No Mapper Object"); + return INT2NUM (m->GetFileSize()); +} + + +/********************** +Init_fastfilereaderext +**********************/ + +extern "C" void Init_fastfilereaderext() +{ + EmModule = rb_define_module ("EventMachine"); + FastFileReader = rb_define_class_under (EmModule, "FastFileReader", rb_cObject); + Mapper = rb_define_class_under (FastFileReader, "Mapper", rb_cObject); + + rb_define_module_function (Mapper, "new", (VALUE(*)(...))mapper_new, 1); + rb_define_method (Mapper, "size", (VALUE(*)(...))mapper_size, 0); + rb_define_method (Mapper, "close", (VALUE(*)(...))mapper_close, 0); + rb_define_method (Mapper, "get_chunk", (VALUE(*)(...))mapper_get_chunk, 2); +} + + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/kb.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/kb.cpp new file mode 100644 index 0000000..2187ae2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/kb.cpp @@ -0,0 +1,79 @@ +/***************************************************************************** + +$Id$ + +File: kb.cpp +Date: 24Aug07 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#include "project.h" + + +/************************************** +KeyboardDescriptor::KeyboardDescriptor +**************************************/ + +KeyboardDescriptor::KeyboardDescriptor (EventMachine_t *parent_em): + EventableDescriptor (0, parent_em), + bReadAttemptedAfterClose (false) +{ + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + #endif + #ifdef HAVE_KQUEUE + MyEventMachine->ArmKqueueReader (this); + #endif +} + + +/*************************************** +KeyboardDescriptor::~KeyboardDescriptor +***************************************/ + +KeyboardDescriptor::~KeyboardDescriptor() +{ +} + + +/************************* +KeyboardDescriptor::Write +*************************/ + +void KeyboardDescriptor::Write() +{ + // Why are we here? + throw std::runtime_error ("bad code path in keyboard handler"); +} + + +/***************************** +KeyboardDescriptor::Heartbeat +*****************************/ + +void KeyboardDescriptor::Heartbeat() +{ + // no-op +} + + +/************************ +KeyboardDescriptor::Read +************************/ + +void KeyboardDescriptor::Read() +{ + char c; + (void)read (GetSocket(), &c, 1); + _GenericInboundDispatch(&c, 1); +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.cpp new file mode 100644 index 0000000..a3a2340 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.cpp @@ -0,0 +1,107 @@ +/***************************************************************************** + +$Id$ + +File: page.cpp +Date: 30Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +#include "project.h" + + +/****************** +PageList::PageList +******************/ + +PageList::PageList() +{ +} + + +/******************* +PageList::~PageList +*******************/ + +PageList::~PageList() +{ + while (HasPages()) + PopFront(); +} + + +/*************** +PageList::Front +***************/ + +void PageList::Front (const char **page, int *length) +{ + assert (page && length); + + if (HasPages()) { + Page p = Pages.front(); + *page = p.Buffer; + *length = p.Size; + } + else { + *page = NULL; + *length = 0; + } +} + + +/****************** +PageList::PopFront +******************/ + +void PageList::PopFront() +{ + if (HasPages()) { + Page p = Pages.front(); + Pages.pop_front(); + if (p.Buffer) + free ((void*)p.Buffer); + } +} + + +/****************** +PageList::HasPages +******************/ + +bool PageList::HasPages() +{ + return (Pages.size() > 0) ? true : false; +} + + +/************** +PageList::Push +**************/ + +void PageList::Push (const char *buf, int size) +{ + if (buf && (size > 0)) { + char *copy = (char*) malloc (size); + if (!copy) + throw std::runtime_error ("no memory in pagelist"); + memcpy (copy, buf, size); + Pages.push_back (Page (copy, size)); + } +} + + + + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.h new file mode 100644 index 0000000..969fc91 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/page.h @@ -0,0 +1,51 @@ +/***************************************************************************** + +$Id$ + +File: page.h +Date: 30Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +#ifndef __PageManager__H_ +#define __PageManager__H_ + + +/************** +class PageList +**************/ + +class PageList +{ + struct Page { + Page (const char *b, size_t s): Buffer(b), Size(s) {} + const char *Buffer; + size_t Size; + }; + + public: + PageList(); + virtual ~PageList(); + + void Push (const char*, int); + bool HasPages(); + void Front (const char**, int*); + void PopFront(); + + private: + std::deque Pages; +}; + + +#endif // __PageManager__H_ diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/pipe.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/pipe.cpp new file mode 100644 index 0000000..a5f0d9f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/pipe.cpp @@ -0,0 +1,354 @@ +/***************************************************************************** + +$Id$ + +File: pipe.cpp +Date: 30May07 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#include "project.h" + + +#ifdef OS_UNIX +// THIS ENTIRE FILE IS ONLY COMPILED ON UNIX-LIKE SYSTEMS. + +/****************************** +PipeDescriptor::PipeDescriptor +******************************/ + +PipeDescriptor::PipeDescriptor (int fd, pid_t subpid, EventMachine_t *parent_em): + EventableDescriptor (fd, parent_em), + bReadAttemptedAfterClose (false), + OutboundDataSize (0), + SubprocessPid (subpid) +{ + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + #endif + #ifdef HAVE_KQUEUE + MyEventMachine->ArmKqueueReader (this); + #endif +} + + +/******************************* +PipeDescriptor::~PipeDescriptor +*******************************/ + +PipeDescriptor::~PipeDescriptor() NO_EXCEPT_FALSE +{ + // Run down any stranded outbound data. + for (size_t i=0; i < OutboundPages.size(); i++) + OutboundPages[i].Free(); + + /* As a virtual destructor, we come here before the base-class + * destructor that closes our file-descriptor. + * We have to make sure the subprocess goes down (if it's not + * already down) and we have to reap the zombie. + * + * This implementation is PROVISIONAL and will surely be improved. + * The intention here is that we never block, hence the highly + * undesirable sleeps. But if we can't reap the subprocess even + * after sending it SIGKILL, then something is wrong and we + * throw a fatal exception, which is also not something we should + * be doing. + * + * Eventually the right thing to do will be to have the reactor + * core respond to SIGCHLD by chaining a handler on top of the + * one Ruby may have installed, and dealing with a list of dead + * children that are pending cleanup. + * + * Since we want to have a signal processor integrated into the + * client-visible API, let's wait until that is done before cleaning + * this up. + * + * Added a very ugly hack to support passing the subprocess's exit + * status to the user. It only makes logical sense for user code to access + * the subprocess exit status in the unbind callback. But unbind is called + * back during the EventableDescriptor destructor. So by that time there's + * no way to call back this object through an object binding, because it's + * already been cleaned up. We might have added a parameter to the unbind + * callback, but that would probably break a huge amount of existing code. + * So the hack-solution is to define an instance variable in the EventMachine + * object and stick the exit status in there, where it can easily be accessed + * with an accessor visible to user code. + * User code should ONLY access the exit status from within the unbind callback. + * Otherwise there's no guarantee it'll be valid. + * This hack won't make it impossible to run multiple EventMachines in a single + * process, but it will make it impossible to reliably nest unbind calls + * within other unbind calls. (Not sure if that's even possible.) + */ + + assert (MyEventMachine); + + /* Another hack to make the SubprocessPid available to get_subprocess_status */ + MyEventMachine->SubprocessPid = SubprocessPid; + + /* 01Mar09: Updated to use a small nanosleep in a loop. When nanosleep is interrupted by SIGCHLD, + * it resumes the system call after processing the signal (resulting in unnecessary latency). + * Calling nanosleep in a loop avoids this problem. + */ + struct timespec req = {0, 50000000}; // 0.05s + int n; + + // wait 0.5s for the process to die + for (n=0; n<10; n++) { + if (waitpid (SubprocessPid, &(MyEventMachine->SubprocessExitStatus), WNOHANG) != 0) return; + nanosleep (&req, NULL); + } + + // send SIGTERM and wait another 1s + kill (SubprocessPid, SIGTERM); + for (n=0; n<20; n++) { + nanosleep (&req, NULL); + if (waitpid (SubprocessPid, &(MyEventMachine->SubprocessExitStatus), WNOHANG) != 0) return; + } + + // send SIGKILL and wait another 5s + kill (SubprocessPid, SIGKILL); + for (n=0; n<100; n++) { + nanosleep (&req, NULL); + if (waitpid (SubprocessPid, &(MyEventMachine->SubprocessExitStatus), WNOHANG) != 0) return; + } + + // still not dead, give up! + throw std::runtime_error ("unable to reap subprocess"); +} + + + +/******************** +PipeDescriptor::Read +********************/ + +void PipeDescriptor::Read() +{ + int sd = GetSocket(); + if (sd == INVALID_SOCKET) { + assert (!bReadAttemptedAfterClose); + bReadAttemptedAfterClose = true; + return; + } + + LastActivity = MyEventMachine->GetCurrentLoopTime(); + + int total_bytes_read = 0; + char readbuffer [16 * 1024]; + + for (int i=0; i < 10; i++) { + // Don't read just one buffer and then move on. This is faster + // if there is a lot of incoming. + // But don't read indefinitely. Give other sockets a chance to run. + // NOTICE, we're reading one less than the buffer size. + // That's so we can put a guard byte at the end of what we send + // to user code. + // Use read instead of recv, which on Linux gives a "socket operation + // on nonsocket" error. + + + int r = read (sd, readbuffer, sizeof(readbuffer) - 1); + //cerr << ""; + + if (r > 0) { + total_bytes_read += r; + + // Add a null-terminator at the the end of the buffer + // that we will send to the callback. + // DO NOT EVER CHANGE THIS. We want to explicitly allow users + // to be able to depend on this behavior, so they will have + // the option to do some things faster. Additionally it's + // a security guard against buffer overflows. + readbuffer [r] = 0; + _GenericInboundDispatch(readbuffer, r); + } + else if (r == 0) { + break; + } + else { + // Basically a would-block, meaning we've read everything there is to read. + break; + } + + } + + + if (total_bytes_read == 0) { + // If we read no data on a socket that selected readable, + // it generally means the other end closed the connection gracefully. + ScheduleClose (false); + //bCloseNow = true; + } + +} + +/********************* +PipeDescriptor::Write +*********************/ + +void PipeDescriptor::Write() +{ + int sd = GetSocket(); + assert (sd != INVALID_SOCKET); + + LastActivity = MyEventMachine->GetCurrentLoopTime(); + char output_buffer [16 * 1024]; + size_t nbytes = 0; + + while ((OutboundPages.size() > 0) && (nbytes < sizeof(output_buffer))) { + OutboundPage *op = &(OutboundPages[0]); + if ((nbytes + op->Length - op->Offset) < sizeof (output_buffer)) { + memcpy (output_buffer + nbytes, op->Buffer + op->Offset, op->Length - op->Offset); + nbytes += (op->Length - op->Offset); + op->Free(); + OutboundPages.pop_front(); + } + else { + int len = sizeof(output_buffer) - nbytes; + memcpy (output_buffer + nbytes, op->Buffer + op->Offset, len); + op->Offset += len; + nbytes += len; + } + } + + // We should never have gotten here if there were no data to write, + // so assert that as a sanity check. + // Don't bother to make sure nbytes is less than output_buffer because + // if it were we probably would have crashed already. + assert (nbytes > 0); + + assert (GetSocket() != INVALID_SOCKET); + int bytes_written = write (GetSocket(), output_buffer, nbytes); +#ifdef OS_WIN32 + int e = WSAGetLastError(); +#else + int e = errno; +#endif + + if (bytes_written > 0) { + OutboundDataSize -= bytes_written; + if ((size_t)bytes_written < nbytes) { + int len = nbytes - bytes_written; + char *buffer = (char*) malloc (len + 1); + if (!buffer) + throw std::runtime_error ("bad alloc throwing back data"); + memcpy (buffer, output_buffer + bytes_written, len); + buffer [len] = 0; + OutboundPages.push_front (OutboundPage (buffer, len)); + } + #ifdef HAVE_EPOLL + EpollEvent.events = EPOLLIN; + if (SelectForWrite()) + EpollEvent.events |= EPOLLOUT; + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + } + else { + #ifdef OS_UNIX + if ((e != EINPROGRESS) && (e != EWOULDBLOCK) && (e != EINTR)) + #endif + #ifdef OS_WIN32 + if ((e != WSAEINPROGRESS) && (e != WSAEWOULDBLOCK)) + #endif + Close(); + } +} + + +/************************* +PipeDescriptor::Heartbeat +*************************/ + +void PipeDescriptor::Heartbeat() +{ + // If an inactivity timeout is defined, then check for it. + if (InactivityTimeout && ((MyEventMachine->GetCurrentLoopTime() - LastActivity) >= InactivityTimeout)) + ScheduleClose (false); + //bCloseNow = true; +} + + +/***************************** +PipeDescriptor::SelectForRead +*****************************/ + +bool PipeDescriptor::SelectForRead() +{ + /* Pipe descriptors, being local by definition, don't have + * a pending state, so this is simpler than for the + * ConnectionDescriptor object. + */ + return bPaused ? false : true; +} + +/****************************** +PipeDescriptor::SelectForWrite +******************************/ + +bool PipeDescriptor::SelectForWrite() +{ + /* Pipe descriptors, being local by definition, don't have + * a pending state, so this is simpler than for the + * ConnectionDescriptor object. + */ + return (GetOutboundDataSize() > 0) && !bPaused ? true : false; +} + + + + +/******************************** +PipeDescriptor::SendOutboundData +********************************/ + +int PipeDescriptor::SendOutboundData (const char *data, unsigned long length) +{ + //if (bCloseNow || bCloseAfterWriting) + if (IsCloseScheduled()) + return 0; + + if (!data && (length > 0)) + throw std::runtime_error ("bad outbound data"); + char *buffer = (char *) malloc (length + 1); + if (!buffer) + throw std::runtime_error ("no allocation for outbound data"); + memcpy (buffer, data, length); + buffer [length] = 0; + OutboundPages.push_back (OutboundPage (buffer, length)); + OutboundDataSize += length; + #ifdef HAVE_EPOLL + EpollEvent.events = (EPOLLIN | EPOLLOUT); + assert (MyEventMachine); + MyEventMachine->Modify (this); + #endif + return length; +} + +/******************************** +PipeDescriptor::GetSubprocessPid +********************************/ + +bool PipeDescriptor::GetSubprocessPid (pid_t *pid) +{ + bool ok = false; + if (pid && (SubprocessPid > 0)) { + *pid = SubprocessPid; + ok = true; + } + return ok; +} + + +#endif // OS_UNIX + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/project.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/project.h new file mode 100644 index 0000000..757f701 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/project.h @@ -0,0 +1,174 @@ +/***************************************************************************** + +$Id$ + +File: project.h +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +#ifndef __Project__H_ +#define __Project__H_ + + +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef OS_UNIX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef int SOCKET; +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#ifdef OS_SOLARIS8 +#include +#include +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif +// INADDR_NONE is undefined on Solaris < 8. Thanks to Brett Eisenberg and Tim Pease. +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long)-1) +#endif +#endif /* OS_SOLARIS8 */ + +#ifdef _AIX +#include +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif +#endif /* _AIX */ + +#ifdef OS_DARWIN +#include +#include +#endif /* OS_DARWIN */ + +#endif /* OS_UNIX */ + +#ifdef OS_WIN32 +// 21Sep09: windows limits select() to 64 sockets by default, we increase it to 1024 here (before including winsock2.h) +// 18Jun12: fd_setsize must be changed in the ruby binary (not in this extension). redefining it also causes segvs, see eventmachine/eventmachine#333 +//#define FD_SETSIZE 1024 + +// WIN32_LEAN_AND_MEAN excludes APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets. +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include + +// Older versions of MinGW in the Ruby Dev Kit do not provide the getaddrinfo hint flags +#ifndef AI_ADDRCONFIG +#define AI_ADDRCONFIG 0x0400 +#endif + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0x0008 +#endif + +// Use the Win32 wrapper library that Ruby owns to be able to close sockets with the close() function +#define RUBY_EXPORT +#include +#include +#endif /* OS_WIN32 */ + +#if !defined(_MSC_VER) || _MSC_VER > 1500 +#include +#endif + +#ifdef WITH_SSL +#include +#include +#endif + +#ifdef HAVE_EPOLL +#include +#endif + +#ifdef HAVE_KQUEUE +#include +#include +#endif + +#ifdef HAVE_INOTIFY +#include +#endif + +#ifdef HAVE_OLD_INOTIFY +#include +#include +static inline int inotify_init (void) { return syscall (__NR_inotify_init); } +static inline int inotify_add_watch (int fd, const char *name, __u32 mask) { return syscall (__NR_inotify_add_watch, fd, name, mask); } +static inline int inotify_rm_watch (int fd, __u32 wd) { return syscall (__NR_inotify_rm_watch, fd, wd); } +#define HAVE_INOTIFY 1 +#endif + +#ifdef HAVE_INOTIFY +#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event)) +#endif + +#ifdef HAVE_WRITEV +#include +#endif + +#if __cplusplus +extern "C" { +#endif + typedef void (*EMCallback)(const unsigned long, int, const char*, const unsigned long); +#if __cplusplus +} +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define UNUSED __attribute__ ((unused)) +#else +#define UNUSED +#endif + +#include "binder.h" +#include "em.h" +#include "ed.h" +#include "page.h" +#include "ssl.h" +#include "eventmachine.h" + +#endif // __Project__H_ diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/rubymain.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/rubymain.cpp new file mode 100644 index 0000000..305f9cb --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/rubymain.cpp @@ -0,0 +1,1515 @@ +/***************************************************************************** + +$Id$ + +File: rubymain.cpp +Date: 06Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + +#include "project.h" +#include "eventmachine.h" +#include + +#ifndef RFLOAT_VALUE +#define RFLOAT_VALUE(arg) RFLOAT(arg)->value +#endif + +/* Adapted from NUM2BSIG / BSIG2NUM in ext/fiddle/conversions.h, + * we'll call it a BSIG for Binding Signature here. */ +#if SIZEOF_VOIDP == SIZEOF_LONG +# define BSIG2NUM(x) (ULONG2NUM((unsigned long)(x))) +# define NUM2BSIG(x) (NUM2ULONG(x)) +# ifdef OS_WIN32 +# define PRIFBSIG "I32u" +# else +# define PRIFBSIG "lu" +# endif +#else +# define BSIG2NUM(x) (ULL2NUM((unsigned long long)(x))) +# define NUM2BSIG(x) (NUM2ULL(x)) +# ifdef OS_WIN32 +# define PRIFBSIG "I64u" +# else +# define PRIFBSIG "llu" +# endif +#endif + +/******* +Statics +*******/ + +static VALUE EmModule; +static VALUE EmConnection; +static VALUE EmConnsHash; +static VALUE EmTimersHash; + +static VALUE EM_eConnectionError; +static VALUE EM_eUnknownTimerFired; +static VALUE EM_eConnectionNotBound; +static VALUE EM_eUnsupported; +static VALUE EM_eInvalidSignature; + +static VALUE Intern_at_signature; +static VALUE Intern_at_timers; +static VALUE Intern_at_conns; +static VALUE Intern_at_error_handler; +static VALUE Intern_event_callback; +static VALUE Intern_run_deferred_callbacks; +static VALUE Intern_delete; +static VALUE Intern_call; +static VALUE Intern_at; +static VALUE Intern_receive_data; +static VALUE Intern_ssl_handshake_completed; +static VALUE Intern_ssl_verify_peer; +static VALUE Intern_notify_readable; +static VALUE Intern_notify_writable; +static VALUE Intern_proxy_target_unbound; +static VALUE Intern_proxy_completed; +static VALUE Intern_connection_completed; + +static VALUE rb_cProcStatus; + +struct em_event { + uintptr_t signature; + int event; + const char *data_str; + unsigned long data_num; +}; + +static inline VALUE ensure_conn(const uintptr_t signature) +{ + VALUE conn = rb_hash_aref (EmConnsHash, BSIG2NUM (signature)); + if (conn == Qnil) + rb_raise (EM_eConnectionNotBound, "unknown connection: %" PRIFBSIG, signature); + return conn; +} + + +/**************** +t_event_callback +****************/ + +static inline void event_callback (struct em_event* e) +{ + const uintptr_t signature = e->signature; + int event = e->event; + const char *data_str = e->data_str; + const unsigned long data_num = e->data_num; + + switch (event) { + case EM_CONNECTION_READ: + { + VALUE conn = rb_hash_aref (EmConnsHash, BSIG2NUM (signature)); + if (conn == Qnil) + rb_raise (EM_eConnectionNotBound, "received %lu bytes of data for unknown signature: %" PRIFBSIG, data_num, signature); + rb_funcall (conn, Intern_receive_data, 1, rb_str_new (data_str, data_num)); + return; + } + case EM_CONNECTION_ACCEPTED: + { + rb_funcall (EmModule, Intern_event_callback, 3, BSIG2NUM(signature), INT2FIX(event), ULONG2NUM(data_num)); + return; + } + case EM_CONNECTION_UNBOUND: + { + rb_funcall (EmModule, Intern_event_callback, 3, BSIG2NUM(signature), INT2FIX(event), ULONG2NUM(data_num)); + return; + } + case EM_CONNECTION_COMPLETED: + { + VALUE conn = ensure_conn(signature); + rb_funcall (conn, Intern_connection_completed, 0); + return; + } + case EM_CONNECTION_NOTIFY_READABLE: + { + VALUE conn = ensure_conn(signature); + rb_funcall (conn, Intern_notify_readable, 0); + return; + } + case EM_CONNECTION_NOTIFY_WRITABLE: + { + VALUE conn = ensure_conn(signature); + rb_funcall (conn, Intern_notify_writable, 0); + return; + } + case EM_LOOPBREAK_SIGNAL: + { + rb_funcall (EmModule, Intern_run_deferred_callbacks, 0); + return; + } + case EM_TIMER_FIRED: + { + VALUE timer = rb_funcall (EmTimersHash, Intern_delete, 1, ULONG2NUM (data_num)); + if (timer == Qnil) { + rb_raise (EM_eUnknownTimerFired, "no such timer: %lu", data_num); + } else if (timer == Qfalse) { + /* Timer Canceled */ + } else { + rb_funcall (timer, Intern_call, 0); + } + return; + } + #ifdef WITH_SSL + case EM_SSL_HANDSHAKE_COMPLETED: + { + VALUE conn = ensure_conn(signature); + rb_funcall (conn, Intern_ssl_handshake_completed, 0); + return; + } + case EM_SSL_VERIFY: + { + VALUE conn = ensure_conn(signature); + VALUE should_accept = rb_funcall (conn, Intern_ssl_verify_peer, 1, rb_str_new(data_str, data_num)); + if (RTEST(should_accept)) + evma_accept_ssl_peer (signature); + return; + } + #endif + case EM_PROXY_TARGET_UNBOUND: + { + VALUE conn = ensure_conn(signature); + rb_funcall (conn, Intern_proxy_target_unbound, 0); + return; + } + case EM_PROXY_COMPLETED: + { + VALUE conn = ensure_conn(signature); + rb_funcall (conn, Intern_proxy_completed, 0); + return; + } + } +} + +/******************* +event_error_handler +*******************/ + +static void event_error_handler(VALUE self UNUSED, VALUE err) +{ + VALUE error_handler = rb_ivar_get(EmModule, Intern_at_error_handler); + rb_funcall (error_handler, Intern_call, 1, err); +} + +/********************** +event_callback_wrapper +**********************/ + +static void event_callback_wrapper (const uintptr_t signature, int event, const char *data_str, const unsigned long data_num) +{ + struct em_event e; + e.signature = signature; + e.event = event; + e.data_str = data_str; + e.data_num = data_num; + + if (!rb_ivar_defined(EmModule, Intern_at_error_handler)) + event_callback(&e); + else + rb_rescue((VALUE (*)(ANYARGS))event_callback, (VALUE)&e, (VALUE (*)(ANYARGS))event_error_handler, Qnil); +} + +/************************** +t_initialize_event_machine +**************************/ + +static VALUE t_initialize_event_machine (VALUE self UNUSED) +{ + EmConnsHash = rb_ivar_get (EmModule, Intern_at_conns); + EmTimersHash = rb_ivar_get (EmModule, Intern_at_timers); + assert(EmConnsHash != Qnil); + assert(EmTimersHash != Qnil); + evma_initialize_library ((EMCallback)event_callback_wrapper); + return Qnil; +} + + +/****************** +t_run_machine_once +******************/ + +static VALUE t_run_machine_once (VALUE self UNUSED) +{ + return evma_run_machine_once () ? Qtrue : Qfalse; +} + + +/************* +t_run_machine +*************/ + +static VALUE t_run_machine (VALUE self UNUSED) +{ + evma_run_machine(); + return Qnil; +} + + +/******************* +t_add_oneshot_timer +*******************/ + +static VALUE t_add_oneshot_timer (VALUE self UNUSED, VALUE interval) +{ + const uintptr_t f = evma_install_oneshot_timer (FIX2LONG (interval)); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "ran out of timers; use #set_max_timers to increase limit"); + return BSIG2NUM (f); +} + + +/************** +t_start_server +**************/ + +static VALUE t_start_server (VALUE self UNUSED, VALUE server, VALUE port) +{ + const uintptr_t f = evma_create_tcp_server (StringValueCStr(server), FIX2INT(port)); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no acceptor (port is in use or requires root privileges)"); + return BSIG2NUM (f); +} + +/************* +t_stop_server +*************/ + +static VALUE t_stop_server (VALUE self UNUSED, VALUE signature) +{ + evma_stop_tcp_server (NUM2BSIG (signature)); + return Qnil; +} + + +/******************* +t_start_unix_server +*******************/ + +static VALUE t_start_unix_server (VALUE self UNUSED, VALUE filename) +{ + const uintptr_t f = evma_create_unix_domain_server (StringValueCStr(filename)); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no unix-domain acceptor"); + return BSIG2NUM (f); +} + +/******************** +t_attach_sd +********************/ + +static VALUE t_attach_sd(VALUE self UNUSED, VALUE sd) +{ + const uintptr_t f = evma_attach_sd(FIX2INT(sd)); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no socket descriptor acceptor"); + return BSIG2NUM (f); +} + + +/*********** +t_send_data +***********/ + +static VALUE t_send_data (VALUE self UNUSED, VALUE signature, VALUE data, VALUE data_length) +{ + int b = evma_send_data_to_connection (NUM2BSIG (signature), StringValuePtr (data), FIX2INT (data_length)); + return INT2NUM (b); +} + + +/*********** +t_start_tls +***********/ + +static VALUE t_start_tls (VALUE self UNUSED, VALUE signature) +{ + evma_start_tls (NUM2BSIG (signature)); + return Qnil; +} + +/*************** +t_set_tls_parms +***************/ + +static VALUE t_set_tls_parms (VALUE self UNUSED, VALUE signature, VALUE privkeyfile, VALUE certchainfile, VALUE verify_peer, VALUE fail_if_no_peer_cert, VALUE snihostname, VALUE cipherlist, VALUE ecdh_curve, VALUE dhparam, VALUE ssl_version) +{ + /* set_tls_parms takes a series of positional arguments for specifying such things + * as private keys and certificate chains. + * It's expected that the parameter list will grow as we add more supported features. + * ALL of these parameters are optional, and can be specified as empty or NULL strings. + */ + evma_set_tls_parms (NUM2BSIG (signature), StringValueCStr (privkeyfile), StringValueCStr (certchainfile), (verify_peer == Qtrue ? 1 : 0), (fail_if_no_peer_cert == Qtrue ? 1 : 0), StringValueCStr (snihostname), StringValueCStr (cipherlist), StringValueCStr (ecdh_curve), StringValueCStr (dhparam), NUM2INT (ssl_version)); + return Qnil; +} + +/*************** +t_get_peer_cert +***************/ + +#ifdef WITH_SSL +static VALUE t_get_peer_cert (VALUE self UNUSED, VALUE signature) +{ + VALUE ret = Qnil; + + X509 *cert = NULL; + BUF_MEM *buf; + BIO *out; + + cert = evma_get_peer_cert (NUM2BSIG (signature)); + + if (cert != NULL) { + out = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(out, cert); + BIO_get_mem_ptr(out, &buf); + ret = rb_str_new(buf->data, buf->length); + X509_free(cert); + BIO_free(out); + } + + return ret; +} +#else +static VALUE t_get_peer_cert (VALUE self UNUSED, VALUE signature UNUSED) +{ + return Qnil; +} +#endif + +/*************** +t_get_cipher_bits +***************/ + +#ifdef WITH_SSL +static VALUE t_get_cipher_bits (VALUE self UNUSED, VALUE signature) +{ + int bits = evma_get_cipher_bits (NUM2BSIG (signature)); + if (bits == -1) + return Qnil; + return INT2NUM (bits); +} +#else +static VALUE t_get_cipher_bits (VALUE self UNUSED, VALUE signature UNUSED) +{ + return Qnil; +} +#endif + +/*************** +t_get_cipher_name +***************/ + +#ifdef WITH_SSL +static VALUE t_get_cipher_name (VALUE self UNUSED, VALUE signature) +{ + const char *protocol = evma_get_cipher_name (NUM2BSIG (signature)); + if (protocol) + return rb_str_new2 (protocol); + + return Qnil; +} +#else +static VALUE t_get_cipher_name (VALUE self UNUSED, VALUE signature UNUSED) +{ + return Qnil; +} +#endif + +/*************** +t_get_cipher_protocol +***************/ + +#ifdef WITH_SSL +static VALUE t_get_cipher_protocol (VALUE self UNUSED, VALUE signature) +{ + const char *cipher = evma_get_cipher_protocol (NUM2BSIG (signature)); + if (cipher) + return rb_str_new2 (cipher); + + return Qnil; +} +#else +static VALUE t_get_cipher_protocol (VALUE self UNUSED, VALUE signature UNUSED) +{ + return Qnil; +} +#endif + +/*************** +t_get_sni_hostname +***************/ + +#ifdef WITH_SSL +static VALUE t_get_sni_hostname (VALUE self UNUSED, VALUE signature) +{ + const char *sni_hostname = evma_get_sni_hostname (NUM2BSIG (signature)); + if (sni_hostname) + return rb_str_new2 (sni_hostname); + + return Qnil; +} +#else +static VALUE t_get_sni_hostname (VALUE self UNUSED, VALUE signature UNUSED) +{ + return Qnil; +} +#endif + +/************** +t_get_peername +**************/ + +static VALUE t_get_peername (VALUE self UNUSED, VALUE signature) +{ + char buf[1024]; + socklen_t len = sizeof buf; + try { + if (evma_get_peername (NUM2BSIG (signature), (struct sockaddr*)buf, &len)) { + return rb_str_new (buf, len); + } + } catch (std::runtime_error e) { + rb_raise (rb_eRuntimeError, "%s", e.what()); + } + + return Qnil; +} + +/************** +t_get_sockname +**************/ + +static VALUE t_get_sockname (VALUE self UNUSED, VALUE signature) +{ + char buf[1024]; + socklen_t len = sizeof buf; + try { + if (evma_get_sockname (NUM2BSIG (signature), (struct sockaddr*)buf, &len)) { + return rb_str_new (buf, len); + } + } catch (std::runtime_error e) { + rb_raise (rb_eRuntimeError, "%s", e.what()); + } + + return Qnil; +} + +/******************** +t_get_subprocess_pid +********************/ + +static VALUE t_get_subprocess_pid (VALUE self UNUSED, VALUE signature) +{ + pid_t pid; + if (evma_get_subprocess_pid (NUM2BSIG (signature), &pid)) { + return INT2NUM (pid); + } + + return Qnil; +} + +/*********************** +t_get_subprocess_status +***********************/ + +static VALUE t_get_subprocess_status (VALUE self UNUSED, VALUE signature) +{ + VALUE proc_status = Qnil; + + int status; + pid_t pid; + + if (evma_get_subprocess_status (NUM2BSIG (signature), &status)) { + if (evma_get_subprocess_pid (NUM2BSIG (signature), &pid)) { + proc_status = rb_obj_alloc(rb_cProcStatus); + + /* MRI Ruby uses hidden instance vars */ + rb_iv_set(proc_status, "status", INT2FIX(status)); + rb_iv_set(proc_status, "pid", INT2FIX(pid)); + +#ifdef RUBINIUS + /* Rubinius uses standard instance vars */ + rb_iv_set(proc_status, "@pid", INT2FIX(pid)); + if (WIFEXITED(status)) { + rb_iv_set(proc_status, "@status", INT2FIX(WEXITSTATUS(status))); + } else if (WIFSIGNALED(status)) { + rb_iv_set(proc_status, "@termsig", INT2FIX(WTERMSIG(status))); + } else if (WIFSTOPPED(status)) { + rb_iv_set(proc_status, "@stopsig", INT2FIX(WSTOPSIG(status))); + } +#endif + } + } + + return proc_status; +} + +/********************** +t_get_connection_count +**********************/ + +static VALUE t_get_connection_count (VALUE self UNUSED) +{ + return INT2NUM(evma_get_connection_count()); +} + +/***************************** +t_get_comm_inactivity_timeout +*****************************/ + +static VALUE t_get_comm_inactivity_timeout (VALUE self UNUSED, VALUE signature) +{ + return rb_float_new(evma_get_comm_inactivity_timeout(NUM2BSIG (signature))); +} + +/***************************** +t_set_comm_inactivity_timeout +*****************************/ + +static VALUE t_set_comm_inactivity_timeout (VALUE self UNUSED, VALUE signature, VALUE timeout) +{ + float ti = RFLOAT_VALUE(timeout); + if (evma_set_comm_inactivity_timeout(NUM2BSIG(signature), ti)) { + return Qtrue; + } + return Qfalse; +} + +/***************************** +t_get_pending_connect_timeout +*****************************/ + +static VALUE t_get_pending_connect_timeout (VALUE self UNUSED, VALUE signature) +{ + return rb_float_new(evma_get_pending_connect_timeout(NUM2BSIG (signature))); +} + +/***************************** +t_set_pending_connect_timeout +*****************************/ + +static VALUE t_set_pending_connect_timeout (VALUE self UNUSED, VALUE signature, VALUE timeout) +{ + float ti = RFLOAT_VALUE(timeout); + if (evma_set_pending_connect_timeout(NUM2BSIG(signature), ti)) { + return Qtrue; + } + return Qfalse; +} + +/*************** +t_send_datagram +***************/ + +static VALUE t_send_datagram (VALUE self UNUSED, VALUE signature, VALUE data, VALUE data_length, VALUE address, VALUE port) +{ + int b = evma_send_datagram (NUM2BSIG (signature), StringValuePtr (data), FIX2INT (data_length), StringValueCStr(address), FIX2INT(port)); + if (b < 0) + rb_raise (EM_eConnectionError, "%s", "error in sending datagram"); // FIXME: this could be more specific. + return INT2NUM (b); +} + + +/****************** +t_close_connection +******************/ + +static VALUE t_close_connection (VALUE self UNUSED, VALUE signature, VALUE after_writing) +{ + evma_close_connection (NUM2BSIG (signature), ((after_writing == Qtrue) ? 1 : 0)); + return Qnil; +} + +/******************************** +t_report_connection_error_status +********************************/ + +static VALUE t_report_connection_error_status (VALUE self UNUSED, VALUE signature) +{ + int b = evma_report_connection_error_status (NUM2BSIG (signature)); + return INT2NUM (b); +} + + + +/**************** +t_connect_server +****************/ + +static VALUE t_connect_server (VALUE self UNUSED, VALUE server, VALUE port) +{ + // Avoid FIX2INT in this case, because it doesn't deal with type errors properly. + // Specifically, if the value of port comes in as a string rather than an integer, + // NUM2INT will throw a type error, but FIX2INT will generate garbage. + + try { + const uintptr_t f = evma_connect_to_server (NULL, 0, StringValueCStr(server), NUM2INT(port)); + if (!f) + rb_raise (EM_eConnectionError, "%s", "no connection"); + return BSIG2NUM (f); + } catch (std::runtime_error e) { + rb_raise (EM_eConnectionError, "%s", e.what()); + } + return Qnil; +} + +/********************* +t_bind_connect_server +*********************/ + +static VALUE t_bind_connect_server (VALUE self UNUSED, VALUE bind_addr, VALUE bind_port, VALUE server, VALUE port) +{ + // Avoid FIX2INT in this case, because it doesn't deal with type errors properly. + // Specifically, if the value of port comes in as a string rather than an integer, + // NUM2INT will throw a type error, but FIX2INT will generate garbage. + + try { + const uintptr_t f = evma_connect_to_server (StringValueCStr(bind_addr), NUM2INT(bind_port), StringValueCStr(server), NUM2INT(port)); + if (!f) + rb_raise (EM_eConnectionError, "%s", "no connection"); + return BSIG2NUM (f); + } catch (std::runtime_error e) { + rb_raise (EM_eConnectionError, "%s", e.what()); + } + return Qnil; +} + +/********************* +t_connect_unix_server +*********************/ + +static VALUE t_connect_unix_server (VALUE self UNUSED, VALUE serversocket) +{ + const uintptr_t f = evma_connect_to_unix_server (StringValueCStr(serversocket)); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no connection"); + return BSIG2NUM (f); +} + +/*********** +t_attach_fd +***********/ + +static VALUE t_attach_fd (VALUE self UNUSED, VALUE file_descriptor, VALUE watch_mode) +{ + const uintptr_t f = evma_attach_fd (NUM2INT(file_descriptor), watch_mode == Qtrue); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no connection"); + return BSIG2NUM (f); +} + +/*********** +t_detach_fd +***********/ + +static VALUE t_detach_fd (VALUE self UNUSED, VALUE signature) +{ + return INT2NUM(evma_detach_fd (NUM2BSIG (signature))); +} + +/********************* +t_get_file_descriptor +*********************/ +static VALUE t_get_file_descriptor (VALUE self UNUSED, VALUE signature) +{ + return INT2NUM(evma_get_file_descriptor (NUM2BSIG (signature))); +} + +/************** +t_get_sock_opt +**************/ + +static VALUE t_get_sock_opt (VALUE self UNUSED, VALUE signature, VALUE lev, VALUE optname) +{ + int fd = evma_get_file_descriptor (NUM2BSIG (signature)); + int level = NUM2INT(lev), option = NUM2INT(optname); + socklen_t len = 128; + char buf[128]; + + if (getsockopt(fd, level, option, buf, &len) < 0) + rb_sys_fail("getsockopt"); + + return rb_str_new(buf, len); +} + +/************** +t_set_sock_opt +**************/ + +static VALUE t_set_sock_opt (VALUE self UNUSED, VALUE signature, VALUE lev, VALUE optname, VALUE optval) +{ + int fd = evma_get_file_descriptor (NUM2BSIG (signature)); + int level = NUM2INT(lev), option = NUM2INT(optname); + int i; + const void *v; + socklen_t len; + + switch (TYPE(optval)) { + case T_FIXNUM: + i = FIX2INT(optval); + goto numval; + case T_FALSE: + i = 0; + goto numval; + case T_TRUE: + i = 1; + numval: + v = (void*)&i; len = sizeof(i); + break; + default: + StringValue(optval); + v = RSTRING_PTR(optval); + len = RSTRING_LENINT(optval); + break; + } + + + if (setsockopt(fd, level, option, (char *)v, len) < 0) + rb_sys_fail("setsockopt"); + + return INT2FIX(0); +} + +/******************** +t_is_notify_readable +********************/ + +static VALUE t_is_notify_readable (VALUE self UNUSED, VALUE signature) +{ + return evma_is_notify_readable(NUM2BSIG (signature)) ? Qtrue : Qfalse; +} + +/********************* +t_set_notify_readable +*********************/ + +static VALUE t_set_notify_readable (VALUE self UNUSED, VALUE signature, VALUE mode) +{ + evma_set_notify_readable(NUM2BSIG(signature), mode == Qtrue); + return Qnil; +} + +/******************** +t_is_notify_readable +********************/ + +static VALUE t_is_notify_writable (VALUE self UNUSED, VALUE signature) +{ + return evma_is_notify_writable(NUM2BSIG (signature)) ? Qtrue : Qfalse; +} + +/********************* +t_set_notify_writable +*********************/ + +static VALUE t_set_notify_writable (VALUE self UNUSED, VALUE signature, VALUE mode) +{ + evma_set_notify_writable(NUM2BSIG (signature), mode == Qtrue); + return Qnil; +} + +/******* +t_pause +*******/ + +static VALUE t_pause (VALUE self UNUSED, VALUE signature) +{ + return evma_pause(NUM2BSIG (signature)) ? Qtrue : Qfalse; +} + +/******** +t_resume +********/ + +static VALUE t_resume (VALUE self UNUSED, VALUE signature) +{ + return evma_resume(NUM2BSIG (signature)) ? Qtrue : Qfalse; +} + +/********** +t_paused_p +**********/ + +static VALUE t_paused_p (VALUE self UNUSED, VALUE signature) +{ + return evma_is_paused(NUM2BSIG (signature)) ? Qtrue : Qfalse; +} + +/********************* +t_num_close_scheduled +*********************/ + +static VALUE t_num_close_scheduled (VALUE self UNUSED) +{ + return INT2FIX(evma_num_close_scheduled()); +} + +/***************** +t_open_udp_socket +*****************/ + +static VALUE t_open_udp_socket (VALUE self UNUSED, VALUE server, VALUE port) +{ + const uintptr_t f = evma_open_datagram_socket (StringValueCStr(server), FIX2INT(port)); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no datagram socket"); + return BSIG2NUM(f); +} + + + +/***************** +t_release_machine +*****************/ + +static VALUE t_release_machine (VALUE self UNUSED) +{ + evma_release_library(); + return Qnil; +} + + +/****** +t_stop +******/ + +static VALUE t_stop (VALUE self UNUSED) +{ + evma_stop_machine(); + return Qnil; +} + +/****************** +t_signal_loopbreak +******************/ + +static VALUE t_signal_loopbreak (VALUE self UNUSED) +{ + evma_signal_loopbreak(); + return Qnil; +} + +/************** +t_library_type +**************/ + +static VALUE t_library_type (VALUE self UNUSED) +{ + return rb_eval_string (":extension"); +} + + + +/******************* +t_set_timer_quantum +*******************/ + +static VALUE t_set_timer_quantum (VALUE self UNUSED, VALUE interval) +{ + evma_set_timer_quantum (FIX2INT (interval)); + return Qnil; +} + +/******************** +t_get_max_timer_count +********************/ + +static VALUE t_get_max_timer_count (VALUE self UNUSED) +{ + return INT2FIX (evma_get_max_timer_count()); +} + +/******************** +t_set_max_timer_count +********************/ + +static VALUE t_set_max_timer_count (VALUE self UNUSED, VALUE ct) +{ + evma_set_max_timer_count (FIX2INT (ct)); + return Qnil; +} + +/******************** +t_get/set_simultaneous_accept_count +********************/ + +static VALUE t_get_simultaneous_accept_count (VALUE self UNUSED) +{ + return INT2FIX (evma_get_simultaneous_accept_count()); +} + +static VALUE t_set_simultaneous_accept_count (VALUE self UNUSED, VALUE ct) +{ + evma_set_simultaneous_accept_count (FIX2INT (ct)); + return Qnil; +} + +/*************** +t_setuid_string +***************/ + +static VALUE t_setuid_string (VALUE self UNUSED, VALUE username) +{ + evma_setuid_string (StringValueCStr (username)); + return Qnil; +} + + + +/************** +t_invoke_popen +**************/ + +static VALUE t_invoke_popen (VALUE self UNUSED, VALUE cmd) +{ + #ifdef OS_WIN32 + rb_raise (EM_eUnsupported, "popen is not available on this platform"); + #endif + + int len = RARRAY_LEN(cmd); + if (len >= 2048) + rb_raise (rb_eRuntimeError, "%s", "too many arguments to popen"); + char *strings [2048]; + for (int i=0; i < len; i++) { + VALUE ix = INT2FIX (i); + VALUE s = rb_ary_aref (1, &ix, cmd); + strings[i] = StringValueCStr (s); + } + strings[len] = NULL; + + uintptr_t f = 0; + try { + f = evma_popen (strings); + } catch (std::runtime_error e) { + rb_raise (rb_eRuntimeError, "%s", e.what()); + } + if (!f) { + char *err = strerror (errno); + char buf[100]; + memset (buf, 0, sizeof(buf)); + snprintf (buf, sizeof(buf)-1, "no popen: %s", (err?err:"???")); + rb_raise (rb_eRuntimeError, "%s", buf); + } + return BSIG2NUM (f); +} + + +/*************** +t_read_keyboard +***************/ + +static VALUE t_read_keyboard (VALUE self UNUSED) +{ + const uintptr_t f = evma_open_keyboard(); + if (!f) + rb_raise (rb_eRuntimeError, "%s", "no keyboard reader"); + return BSIG2NUM (f); +} + + +/**************** +t_watch_filename +****************/ + +static VALUE t_watch_filename (VALUE self UNUSED, VALUE fname) +{ + try { + return BSIG2NUM(evma_watch_filename(StringValueCStr(fname))); + } catch (std::runtime_error e) { + rb_raise (EM_eUnsupported, "%s", e.what()); + } + return Qnil; +} + + +/****************** +t_unwatch_filename +******************/ + +static VALUE t_unwatch_filename (VALUE self UNUSED, VALUE sig) +{ + try { + evma_unwatch_filename(NUM2BSIG (sig)); + } catch (std::runtime_error e) { + rb_raise (EM_eInvalidSignature, "%s", e.what()); + } + + return Qnil; +} + + +/*********** +t_watch_pid +***********/ + +static VALUE t_watch_pid (VALUE self UNUSED, VALUE pid) +{ + try { + return BSIG2NUM(evma_watch_pid(NUM2INT(pid))); + } catch (std::runtime_error e) { + rb_raise (EM_eUnsupported, "%s", e.what()); + } + return Qnil; +} + + +/************* +t_unwatch_pid +*************/ + +static VALUE t_unwatch_pid (VALUE self UNUSED, VALUE sig) +{ + evma_unwatch_pid(NUM2BSIG (sig)); + return Qnil; +} + + +/********** +t__epoll_p +**********/ + +static VALUE t__epoll_p (VALUE self UNUSED) +{ + #ifdef HAVE_EPOLL + return Qtrue; + #else + return Qfalse; + #endif +} + +/******** +t__epoll +********/ + +static VALUE t__epoll (VALUE self UNUSED) +{ + if (t__epoll_p(self) == Qfalse) + return Qfalse; + + evma_set_epoll (1); + return Qtrue; +} + +/*********** +t__epoll_set +***********/ + +static VALUE t__epoll_set (VALUE self, VALUE val) +{ + if (t__epoll_p(self) == Qfalse && val == Qtrue) + rb_raise (EM_eUnsupported, "%s", "epoll is not supported on this platform"); + + evma_set_epoll (val == Qtrue ? 1 : 0); + return val; +} + + +/*********** +t__kqueue_p +***********/ + +static VALUE t__kqueue_p (VALUE self UNUSED) +{ + #ifdef HAVE_KQUEUE + return Qtrue; + #else + return Qfalse; + #endif +} + +/********* +t__kqueue +*********/ + +static VALUE t__kqueue (VALUE self UNUSED) +{ + if (t__kqueue_p(self) == Qfalse) + return Qfalse; + + evma_set_kqueue (1); + return Qtrue; +} + +/************* +t__kqueue_set +*************/ + +static VALUE t__kqueue_set (VALUE self, VALUE val) +{ + if (t__kqueue_p(self) == Qfalse && val == Qtrue) + rb_raise (EM_eUnsupported, "%s", "kqueue is not supported on this platform"); + + evma_set_kqueue (val == Qtrue ? 1 : 0); + return val; +} + + +/******** +t__ssl_p +********/ + +static VALUE t__ssl_p (VALUE self UNUSED) +{ + #ifdef WITH_SSL + return Qtrue; + #else + return Qfalse; + #endif +} + +/******** +t_stopping +********/ + +static VALUE t_stopping () +{ + if (evma_stopping()) { + return Qtrue; + } else { + return Qfalse; + } +} + + +/**************** +t_send_file_data +****************/ + +static VALUE t_send_file_data (VALUE self UNUSED, VALUE signature, VALUE filename) +{ + + /* The current implementation of evma_send_file_data_to_connection enforces a strict + * upper limit on the file size it will transmit (currently 32K). The function returns + * zero on success, -1 if the requested file exceeds its size limit, and a positive + * number for other errors. + * TODO: Positive return values are actually errno's, which is probably the wrong way to + * do this. For one thing it's ugly. For another, we can't be sure zero is never a real errno. + */ + + int b = evma_send_file_data_to_connection (NUM2BSIG (signature), StringValueCStr(filename)); + if (b == -1) + rb_raise(rb_eRuntimeError, "%s", "File too large. send_file_data() supports files under 32k."); + if (b > 0) { + char *err = strerror (b); + char buf[1024]; + memset (buf, 0, sizeof(buf)); + snprintf (buf, sizeof(buf)-1, ": %s %s", StringValueCStr(filename),(err?err:"???")); + + rb_raise (rb_eIOError, "%s", buf); + } + + return INT2NUM (0); +} + + +/******************* +t_set_rlimit_nofile +*******************/ + +static VALUE t_set_rlimit_nofile (VALUE self UNUSED, VALUE arg) +{ + arg = (NIL_P(arg)) ? -1 : NUM2INT (arg); + return INT2NUM (evma_set_rlimit_nofile (arg)); +} + +/*************************** +conn_get_outbound_data_size +***************************/ + +static VALUE conn_get_outbound_data_size (VALUE self) +{ + VALUE sig = rb_ivar_get (self, Intern_at_signature); + return INT2NUM (evma_get_outbound_data_size (NUM2BSIG (sig))); +} + + +/****************************** +conn_associate_callback_target +******************************/ + +static VALUE conn_associate_callback_target (VALUE self UNUSED, VALUE sig UNUSED) +{ + // No-op for the time being. + return Qnil; +} + + +/*************** +t_get_loop_time +****************/ + +static VALUE t_get_loop_time (VALUE self UNUSED) +{ + uint64_t current_time = evma_get_current_loop_time(); + if (current_time == 0) { + return Qnil; + } + + // Generally the industry has moved to 64-bit time_t, this is just in case we're 32-bit time_t. + if (sizeof(time_t) < 8 && current_time > INT_MAX) { + return rb_funcall(rb_cTime, Intern_at, 2, INT2NUM(current_time / 1000000), INT2NUM(current_time % 1000000)); + } else { + return rb_time_new(current_time / 1000000, current_time % 1000000); + } +} + + +/************* +t_start_proxy +**************/ + +static VALUE t_start_proxy (VALUE self UNUSED, VALUE from, VALUE to, VALUE bufsize, VALUE length) +{ + try { + evma_start_proxy(NUM2BSIG (from), NUM2BSIG (to), NUM2ULONG(bufsize), NUM2ULONG(length)); + } catch (std::runtime_error e) { + rb_raise (EM_eConnectionError, "%s", e.what()); + } + return Qnil; +} + + +/************ +t_stop_proxy +*************/ + +static VALUE t_stop_proxy (VALUE self UNUSED, VALUE from) +{ + try{ + evma_stop_proxy(NUM2BSIG (from)); + } catch (std::runtime_error e) { + rb_raise (EM_eConnectionError, "%s", e.what()); + } + return Qnil; +} + +/*************** +t_proxied_bytes +****************/ + +static VALUE t_proxied_bytes (VALUE self UNUSED, VALUE from) +{ + try{ + return BSIG2NUM(evma_proxied_bytes(NUM2BSIG (from))); + } catch (std::runtime_error e) { + rb_raise (EM_eConnectionError, "%s", e.what()); + } + return Qnil; +} + +/*************** +t_get_idle_time +****************/ + +static VALUE t_get_idle_time (VALUE self UNUSED, VALUE from) +{ + try{ + uint64_t current_time = evma_get_current_loop_time(); + uint64_t time = evma_get_last_activity_time(NUM2BSIG (from)); + if (current_time != 0 && time != 0) { + if (time >= current_time) + return BSIG2NUM(0); + else { + uint64_t diff = current_time - time; + float seconds = diff / (1000.0*1000.0); + return rb_float_new(seconds); + } + return Qnil; + } + } catch (std::runtime_error e) { + rb_raise (EM_eConnectionError, "%s", e.what()); + } + return Qnil; +} + +/************************ +t_get_heartbeat_interval +*************************/ + +static VALUE t_get_heartbeat_interval (VALUE self UNUSED) +{ + return rb_float_new(evma_get_heartbeat_interval()); +} + + +/************************ +t_set_heartbeat_interval +*************************/ + +static VALUE t_set_heartbeat_interval (VALUE self UNUSED, VALUE interval) +{ + float iv = RFLOAT_VALUE(interval); + if (evma_set_heartbeat_interval(iv)) + return Qtrue; + return Qfalse; +} + + +/********************* +Init_rubyeventmachine +*********************/ + +extern "C" void Init_rubyeventmachine() +{ + // Lookup Process::Status for get_subprocess_status + VALUE rb_mProcess = rb_const_get(rb_cObject, rb_intern("Process")); + rb_cProcStatus = rb_const_get(rb_mProcess, rb_intern("Status")); + + // Tuck away some symbol values so we don't have to look 'em up every time we need 'em. + Intern_at_signature = rb_intern ("@signature"); + Intern_at_timers = rb_intern ("@timers"); + Intern_at_conns = rb_intern ("@conns"); + Intern_at_error_handler = rb_intern("@error_handler"); + + Intern_event_callback = rb_intern ("event_callback"); + Intern_run_deferred_callbacks = rb_intern ("run_deferred_callbacks"); + Intern_delete = rb_intern ("delete"); + Intern_call = rb_intern ("call"); + Intern_at = rb_intern("at"); + Intern_receive_data = rb_intern ("receive_data"); + Intern_ssl_handshake_completed = rb_intern ("ssl_handshake_completed"); + Intern_ssl_verify_peer = rb_intern ("ssl_verify_peer"); + Intern_notify_readable = rb_intern ("notify_readable"); + Intern_notify_writable = rb_intern ("notify_writable"); + Intern_proxy_target_unbound = rb_intern ("proxy_target_unbound"); + Intern_proxy_completed = rb_intern ("proxy_completed"); + Intern_connection_completed = rb_intern ("connection_completed"); + + // INCOMPLETE, we need to define class Connections inside module EventMachine + // run_machine and run_machine_without_threads are now identical. + // Must deprecate the without_threads variant. + EmModule = rb_define_module ("EventMachine"); + EmConnection = rb_define_class_under (EmModule, "Connection", rb_cObject); + + rb_define_class_under (EmModule, "NoHandlerForAcceptedConnection", rb_eRuntimeError); + EM_eConnectionError = rb_define_class_under (EmModule, "ConnectionError", rb_eRuntimeError); + EM_eConnectionNotBound = rb_define_class_under (EmModule, "ConnectionNotBound", rb_eRuntimeError); + EM_eUnknownTimerFired = rb_define_class_under (EmModule, "UnknownTimerFired", rb_eRuntimeError); + EM_eUnsupported = rb_define_class_under (EmModule, "Unsupported", rb_eRuntimeError); + EM_eInvalidSignature = rb_define_class_under (EmModule, "InvalidSignature", rb_eRuntimeError); + + rb_define_module_function (EmModule, "initialize_event_machine", (VALUE(*)(...))t_initialize_event_machine, 0); + rb_define_module_function (EmModule, "run_machine_once", (VALUE(*)(...))t_run_machine_once, 0); + rb_define_module_function (EmModule, "run_machine", (VALUE(*)(...))t_run_machine, 0); + rb_define_module_function (EmModule, "run_machine_without_threads", (VALUE(*)(...))t_run_machine, 0); + rb_define_module_function (EmModule, "add_oneshot_timer", (VALUE(*)(...))t_add_oneshot_timer, 1); + rb_define_module_function (EmModule, "start_tcp_server", (VALUE(*)(...))t_start_server, 2); + rb_define_module_function (EmModule, "stop_tcp_server", (VALUE(*)(...))t_stop_server, 1); + rb_define_module_function (EmModule, "start_unix_server", (VALUE(*)(...))t_start_unix_server, 1); + rb_define_module_function (EmModule, "attach_sd", (VALUE(*)(...))t_attach_sd, 1); + rb_define_module_function (EmModule, "set_tls_parms", (VALUE(*)(...))t_set_tls_parms, 10); + rb_define_module_function (EmModule, "start_tls", (VALUE(*)(...))t_start_tls, 1); + rb_define_module_function (EmModule, "get_peer_cert", (VALUE(*)(...))t_get_peer_cert, 1); + rb_define_module_function (EmModule, "get_cipher_bits", (VALUE(*)(...))t_get_cipher_bits, 1); + rb_define_module_function (EmModule, "get_cipher_name", (VALUE(*)(...))t_get_cipher_name, 1); + rb_define_module_function (EmModule, "get_cipher_protocol", (VALUE(*)(...))t_get_cipher_protocol, 1); + rb_define_module_function (EmModule, "get_sni_hostname", (VALUE(*)(...))t_get_sni_hostname, 1); + rb_define_module_function (EmModule, "send_data", (VALUE(*)(...))t_send_data, 3); + rb_define_module_function (EmModule, "send_datagram", (VALUE(*)(...))t_send_datagram, 5); + rb_define_module_function (EmModule, "close_connection", (VALUE(*)(...))t_close_connection, 2); + rb_define_module_function (EmModule, "report_connection_error_status", (VALUE(*)(...))t_report_connection_error_status, 1); + rb_define_module_function (EmModule, "connect_server", (VALUE(*)(...))t_connect_server, 2); + rb_define_module_function (EmModule, "bind_connect_server", (VALUE(*)(...))t_bind_connect_server, 4); + rb_define_module_function (EmModule, "connect_unix_server", (VALUE(*)(...))t_connect_unix_server, 1); + + rb_define_module_function (EmModule, "attach_fd", (VALUE (*)(...))t_attach_fd, 2); + rb_define_module_function (EmModule, "detach_fd", (VALUE (*)(...))t_detach_fd, 1); + rb_define_module_function (EmModule, "get_file_descriptor", (VALUE (*)(...))t_get_file_descriptor, 1); + rb_define_module_function (EmModule, "get_sock_opt", (VALUE (*)(...))t_get_sock_opt, 3); + rb_define_module_function (EmModule, "set_sock_opt", (VALUE (*)(...))t_set_sock_opt, 4); + rb_define_module_function (EmModule, "set_notify_readable", (VALUE (*)(...))t_set_notify_readable, 2); + rb_define_module_function (EmModule, "set_notify_writable", (VALUE (*)(...))t_set_notify_writable, 2); + rb_define_module_function (EmModule, "is_notify_readable", (VALUE (*)(...))t_is_notify_readable, 1); + rb_define_module_function (EmModule, "is_notify_writable", (VALUE (*)(...))t_is_notify_writable, 1); + + rb_define_module_function (EmModule, "pause_connection", (VALUE (*)(...))t_pause, 1); + rb_define_module_function (EmModule, "resume_connection", (VALUE (*)(...))t_resume, 1); + rb_define_module_function (EmModule, "connection_paused?", (VALUE (*)(...))t_paused_p, 1); + rb_define_module_function (EmModule, "num_close_scheduled", (VALUE (*)(...))t_num_close_scheduled, 0); + + rb_define_module_function (EmModule, "start_proxy", (VALUE (*)(...))t_start_proxy, 4); + rb_define_module_function (EmModule, "stop_proxy", (VALUE (*)(...))t_stop_proxy, 1); + rb_define_module_function (EmModule, "get_proxied_bytes", (VALUE (*)(...))t_proxied_bytes, 1); + + rb_define_module_function (EmModule, "watch_filename", (VALUE (*)(...))t_watch_filename, 1); + rb_define_module_function (EmModule, "unwatch_filename", (VALUE (*)(...))t_unwatch_filename, 1); + + rb_define_module_function (EmModule, "watch_pid", (VALUE (*)(...))t_watch_pid, 1); + rb_define_module_function (EmModule, "unwatch_pid", (VALUE (*)(...))t_unwatch_pid, 1); + + rb_define_module_function (EmModule, "current_time", (VALUE(*)(...))t_get_loop_time, 0); + + rb_define_module_function (EmModule, "open_udp_socket", (VALUE(*)(...))t_open_udp_socket, 2); + rb_define_module_function (EmModule, "read_keyboard", (VALUE(*)(...))t_read_keyboard, 0); + rb_define_module_function (EmModule, "release_machine", (VALUE(*)(...))t_release_machine, 0); + rb_define_module_function (EmModule, "stop", (VALUE(*)(...))t_stop, 0); + rb_define_module_function (EmModule, "signal_loopbreak", (VALUE(*)(...))t_signal_loopbreak, 0); + rb_define_module_function (EmModule, "library_type", (VALUE(*)(...))t_library_type, 0); + rb_define_module_function (EmModule, "set_timer_quantum", (VALUE(*)(...))t_set_timer_quantum, 1); + rb_define_module_function (EmModule, "get_max_timer_count", (VALUE(*)(...))t_get_max_timer_count, 0); + rb_define_module_function (EmModule, "set_max_timer_count", (VALUE(*)(...))t_set_max_timer_count, 1); + rb_define_module_function (EmModule, "get_simultaneous_accept_count", (VALUE(*)(...))t_get_simultaneous_accept_count, 0); + rb_define_module_function (EmModule, "set_simultaneous_accept_count", (VALUE(*)(...))t_set_simultaneous_accept_count, 1); + rb_define_module_function (EmModule, "setuid_string", (VALUE(*)(...))t_setuid_string, 1); + rb_define_module_function (EmModule, "invoke_popen", (VALUE(*)(...))t_invoke_popen, 1); + rb_define_module_function (EmModule, "send_file_data", (VALUE(*)(...))t_send_file_data, 2); + rb_define_module_function (EmModule, "get_heartbeat_interval", (VALUE(*)(...))t_get_heartbeat_interval, 0); + rb_define_module_function (EmModule, "set_heartbeat_interval", (VALUE(*)(...))t_set_heartbeat_interval, 1); + rb_define_module_function (EmModule, "get_idle_time", (VALUE(*)(...))t_get_idle_time, 1); + + rb_define_module_function (EmModule, "get_peername", (VALUE(*)(...))t_get_peername, 1); + rb_define_module_function (EmModule, "get_sockname", (VALUE(*)(...))t_get_sockname, 1); + rb_define_module_function (EmModule, "get_subprocess_pid", (VALUE(*)(...))t_get_subprocess_pid, 1); + rb_define_module_function (EmModule, "get_subprocess_status", (VALUE(*)(...))t_get_subprocess_status, 1); + rb_define_module_function (EmModule, "get_comm_inactivity_timeout", (VALUE(*)(...))t_get_comm_inactivity_timeout, 1); + rb_define_module_function (EmModule, "set_comm_inactivity_timeout", (VALUE(*)(...))t_set_comm_inactivity_timeout, 2); + rb_define_module_function (EmModule, "get_pending_connect_timeout", (VALUE(*)(...))t_get_pending_connect_timeout, 1); + rb_define_module_function (EmModule, "set_pending_connect_timeout", (VALUE(*)(...))t_set_pending_connect_timeout, 2); + rb_define_module_function (EmModule, "set_rlimit_nofile", (VALUE(*)(...))t_set_rlimit_nofile, 1); + rb_define_module_function (EmModule, "get_connection_count", (VALUE(*)(...))t_get_connection_count, 0); + + rb_define_module_function (EmModule, "epoll", (VALUE(*)(...))t__epoll, 0); + rb_define_module_function (EmModule, "epoll=", (VALUE(*)(...))t__epoll_set, 1); + rb_define_module_function (EmModule, "epoll?", (VALUE(*)(...))t__epoll_p, 0); + + rb_define_module_function (EmModule, "kqueue", (VALUE(*)(...))t__kqueue, 0); + rb_define_module_function (EmModule, "kqueue=", (VALUE(*)(...))t__kqueue_set, 1); + rb_define_module_function (EmModule, "kqueue?", (VALUE(*)(...))t__kqueue_p, 0); + + rb_define_module_function (EmModule, "ssl?", (VALUE(*)(...))t__ssl_p, 0); + rb_define_module_function(EmModule, "stopping?",(VALUE(*)(...))t_stopping, 0); + + rb_define_method (EmConnection, "get_outbound_data_size", (VALUE(*)(...))conn_get_outbound_data_size, 0); + rb_define_method (EmConnection, "associate_callback_target", (VALUE(*)(...))conn_associate_callback_target, 1); + + // Connection states + rb_define_const (EmModule, "TimerFired", INT2NUM(EM_TIMER_FIRED )); + rb_define_const (EmModule, "ConnectionData", INT2NUM(EM_CONNECTION_READ )); + rb_define_const (EmModule, "ConnectionUnbound", INT2NUM(EM_CONNECTION_UNBOUND )); + rb_define_const (EmModule, "ConnectionAccepted", INT2NUM(EM_CONNECTION_ACCEPTED )); + rb_define_const (EmModule, "ConnectionCompleted", INT2NUM(EM_CONNECTION_COMPLETED )); + rb_define_const (EmModule, "LoopbreakSignalled", INT2NUM(EM_LOOPBREAK_SIGNAL )); + rb_define_const (EmModule, "ConnectionNotifyReadable", INT2NUM(EM_CONNECTION_NOTIFY_READABLE)); + rb_define_const (EmModule, "ConnectionNotifyWritable", INT2NUM(EM_CONNECTION_NOTIFY_WRITABLE)); + rb_define_const (EmModule, "SslHandshakeCompleted", INT2NUM(EM_SSL_HANDSHAKE_COMPLETED )); + rb_define_const (EmModule, "SslVerify", INT2NUM(EM_SSL_VERIFY )); + // EM_PROXY_TARGET_UNBOUND = 110, + // EM_PROXY_COMPLETED = 111 + + // SSL Protocols + rb_define_const (EmModule, "EM_PROTO_SSLv2", INT2NUM(EM_PROTO_SSLv2 )); + rb_define_const (EmModule, "EM_PROTO_SSLv3", INT2NUM(EM_PROTO_SSLv3 )); + rb_define_const (EmModule, "EM_PROTO_TLSv1", INT2NUM(EM_PROTO_TLSv1 )); + rb_define_const (EmModule, "EM_PROTO_TLSv1_1", INT2NUM(EM_PROTO_TLSv1_1)); + rb_define_const (EmModule, "EM_PROTO_TLSv1_2", INT2NUM(EM_PROTO_TLSv1_2)); +} + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.cpp b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.cpp new file mode 100644 index 0000000..8d5e038 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.cpp @@ -0,0 +1,619 @@ +/***************************************************************************** + +$Id$ + +File: ssl.cpp +Date: 30Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +#ifdef WITH_SSL + +#include "project.h" + + +bool SslContext_t::bLibraryInitialized = false; + + + +static void InitializeDefaultCredentials(); +static EVP_PKEY *DefaultPrivateKey = NULL; +static X509 *DefaultCertificate = NULL; + +static char PrivateMaterials[] = { +"-----BEGIN RSA PRIVATE KEY-----\n" +"MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV\n" +"Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/\n" +"AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQIDAQAB\n" +"AoGALA89gIFcr6BIBo8N5fL3aNHpZXjAICtGav+kTUpuxSiaym9cAeTHuAVv8Xgk\n" +"H2Wbq11uz+6JMLpkQJH/WZ7EV59DPOicXrp0Imr73F3EXBfR7t2EQDYHPMthOA1D\n" +"I9EtCzvV608Ze90hiJ7E3guGrGppZfJ+eUWCPgy8CZH1vRECQQDv67rwV/oU1aDo\n" +"6/+d5nqjeW6mWkGqTnUU96jXap8EIw6B+0cUKskwx6mHJv+tEMM2748ZY7b0yBlg\n" +"w4KDghbFAkEAz2h8PjSJG55LwqmXih1RONSgdN9hjB12LwXL1CaDh7/lkEhq0PlK\n" +"PCAUwQSdM17Sl0Xxm2CZiekTSlwmHrtqXQJAF3+8QJwtV2sRJp8u2zVe37IeH1cJ\n" +"xXeHyjTzqZ2803fnjN2iuZvzNr7noOA1/Kp+pFvUZUU5/0G2Ep8zolPUjQJAFA7k\n" +"xRdLkzIx3XeNQjwnmLlncyYPRv+qaE3FMpUu7zftuZBnVCJnvXzUxP3vPgKTlzGa\n" +"dg5XivDRfsV+okY5uQJBAMV4FesUuLQVEKb6lMs7rzZwpeGQhFDRfywJzfom2TLn\n" +"2RdJQQ3dcgnhdVDgt5o1qkmsqQh8uJrJ9SdyLIaZQIc=\n" +"-----END RSA PRIVATE KEY-----\n" +"-----BEGIN CERTIFICATE-----\n" +"MIID6TCCA1KgAwIBAgIJANm4W/Tzs+s+MA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n" +"VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYw\n" +"FAYDVQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsG\n" +"A1UEAxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2lu\n" +"ZWVyaW5nQHN0ZWFtaGVhdC5uZXQwHhcNMDYwNTA1MTcwNjAzWhcNMjQwMjIwMTcw\n" +"NjAzWjCBqjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQH\n" +"EwhOZXcgWW9yazEWMBQGA1UEChMNU3RlYW1oZWF0Lm5ldDEUMBIGA1UECxMLRW5n\n" +"aW5lZXJpbmcxHTAbBgNVBAMTFG9wZW5jYS5zdGVhbWhlYXQubmV0MSgwJgYJKoZI\n" +"hvcNAQkBFhllbmdpbmVlcmluZ0BzdGVhbWhlYXQubmV0MIGfMA0GCSqGSIb3DQEB\n" +"AQUAA4GNADCBiQKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxw\n" +"VDWVIgdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t3\n" +"9hJ/AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQID\n" +"AQABo4IBEzCCAQ8wHQYDVR0OBBYEFPJvPd1Fcmd8o/Tm88r+NjYPICCkMIHfBgNV\n" +"HSMEgdcwgdSAFPJvPd1Fcmd8o/Tm88r+NjYPICCkoYGwpIGtMIGqMQswCQYDVQQG\n" +"EwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYD\n" +"VQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsGA1UE\n" +"AxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2luZWVy\n" +"aW5nQHN0ZWFtaGVhdC5uZXSCCQDZuFv087PrPjAMBgNVHRMEBTADAQH/MA0GCSqG\n" +"SIb3DQEBBQUAA4GBAC1CXey/4UoLgJiwcEMDxOvW74plks23090iziFIlGgcIhk0\n" +"Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j\n" +"uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy\n" +"-----END CERTIFICATE-----\n"}; + +/* These private materials were made with: + * openssl req -new -x509 -keyout cakey.pem -out cacert.pem -nodes -days 6500 + * TODO: We need a full-blown capability to work with user-supplied + * keypairs and properly-signed certificates. + */ + + +/***************** +builtin_passwd_cb +*****************/ + +extern "C" int builtin_passwd_cb (char *buf UNUSED, int bufsize UNUSED, int rwflag UNUSED, void *userdata UNUSED) +{ + strcpy (buf, "kittycat"); + return 8; +} + +/**************************** +InitializeDefaultCredentials +****************************/ + +static void InitializeDefaultCredentials() +{ + BIO *bio = BIO_new_mem_buf (PrivateMaterials, -1); + assert (bio); + + if (DefaultPrivateKey) { + // we may come here in a restart. + EVP_PKEY_free (DefaultPrivateKey); + DefaultPrivateKey = NULL; + } + PEM_read_bio_PrivateKey (bio, &DefaultPrivateKey, builtin_passwd_cb, 0); + + if (DefaultCertificate) { + // we may come here in a restart. + X509_free (DefaultCertificate); + DefaultCertificate = NULL; + } + PEM_read_bio_X509 (bio, &DefaultCertificate, NULL, 0); + + BIO_free (bio); +} + + + +/************************** +SslContext_t::SslContext_t +**************************/ + +SslContext_t::SslContext_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version) : + bIsServer (is_server), + pCtx (NULL), + PrivateKey (NULL), + Certificate (NULL) +{ + /* TODO: the usage of the specified private-key and cert-chain filenames only applies to + * client-side connections at this point. Server connections currently use the default materials. + * That needs to be fixed asap. + * Also, in this implementation, server-side connections use statically defined X-509 defaults. + * One thing I'm really not clear on is whether or not you have to explicitly free X509 and EVP_PKEY + * objects when we call our destructor, or whether just calling SSL_CTX_free is enough. + */ + + if (!bLibraryInitialized) { + bLibraryInitialized = true; + SSL_library_init(); + OpenSSL_add_ssl_algorithms(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + ERR_load_crypto_strings(); + + InitializeDefaultCredentials(); + } + + pCtx = SSL_CTX_new (bIsServer ? SSLv23_server_method() : SSLv23_client_method()); + if (!pCtx) + throw std::runtime_error ("no SSL context"); + + SSL_CTX_set_options (pCtx, SSL_OP_ALL); + + #ifdef SSL_CTRL_CLEAR_OPTIONS + SSL_CTX_clear_options (pCtx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1); + # ifdef SSL_OP_NO_TLSv1_1 + SSL_CTX_clear_options (pCtx, SSL_OP_NO_TLSv1_1); + # endif + # ifdef SSL_OP_NO_TLSv1_2 + SSL_CTX_clear_options (pCtx, SSL_OP_NO_TLSv1_2); + # endif + #endif + + if (!(ssl_version & EM_PROTO_SSLv2)) + SSL_CTX_set_options (pCtx, SSL_OP_NO_SSLv2); + + if (!(ssl_version & EM_PROTO_SSLv3)) + SSL_CTX_set_options (pCtx, SSL_OP_NO_SSLv3); + + if (!(ssl_version & EM_PROTO_TLSv1)) + SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1); + + #ifdef SSL_OP_NO_TLSv1_1 + if (!(ssl_version & EM_PROTO_TLSv1_1)) + SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1_1); + #endif + + #ifdef SSL_OP_NO_TLSv1_2 + if (!(ssl_version & EM_PROTO_TLSv1_2)) + SSL_CTX_set_options (pCtx, SSL_OP_NO_TLSv1_2); + #endif + + #ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode (pCtx, SSL_MODE_RELEASE_BUFFERS); + #endif + + if (bIsServer) { + + // The SSL_CTX calls here do NOT allocate memory. + int e; + if (privkeyfile.length() > 0) + e = SSL_CTX_use_PrivateKey_file (pCtx, privkeyfile.c_str(), SSL_FILETYPE_PEM); + else + e = SSL_CTX_use_PrivateKey (pCtx, DefaultPrivateKey); + if (e <= 0) ERR_print_errors_fp(stderr); + assert (e > 0); + + if (certchainfile.length() > 0) + e = SSL_CTX_use_certificate_chain_file (pCtx, certchainfile.c_str()); + else + e = SSL_CTX_use_certificate (pCtx, DefaultCertificate); + if (e <= 0) ERR_print_errors_fp(stderr); + assert (e > 0); + + if (dhparam.length() > 0) { + DH *dh; + BIO *bio; + + bio = BIO_new_file(dhparam.c_str(), "r"); + if (bio == NULL) { + char buf [500]; + snprintf (buf, sizeof(buf)-1, "dhparam: BIO_new_file(%s) failed", dhparam.c_str()); + throw std::runtime_error (buf); + } + + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + + if (dh == NULL) { + BIO_free(bio); + char buf [500]; + snprintf (buf, sizeof(buf)-1, "dhparam: PEM_read_bio_DHparams(%s) failed", dhparam.c_str()); + throw std::runtime_error (buf); + } + + SSL_CTX_set_tmp_dh(pCtx, dh); + + DH_free(dh); + BIO_free(bio); + } + + if (ecdh_curve.length() > 0) { + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH) + int nid; + EC_KEY *ecdh; + + nid = OBJ_sn2nid((const char *) ecdh_curve.c_str()); + if (nid == 0) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "ecdh_curve: Unknown curve name: %s", ecdh_curve.c_str()); + throw std::runtime_error (buf); + } + + ecdh = EC_KEY_new_by_curve_name(nid); + if (ecdh == NULL) { + char buf [200]; + snprintf (buf, sizeof(buf)-1, "ecdh_curve: Unable to create: %s", ecdh_curve.c_str()); + throw std::runtime_error (buf); + } + + SSL_CTX_set_options(pCtx, SSL_OP_SINGLE_ECDH_USE); + + SSL_CTX_set_tmp_ecdh(pCtx, ecdh); + + EC_KEY_free(ecdh); + #else + throw std::runtime_error ("No openssl ECDH support"); + #endif + } + } + + if (cipherlist.length() > 0) + SSL_CTX_set_cipher_list (pCtx, cipherlist.c_str()); + else + SSL_CTX_set_cipher_list (pCtx, "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH"); + + if (bIsServer) { + SSL_CTX_sess_set_cache_size (pCtx, 128); + SSL_CTX_set_session_id_context (pCtx, (unsigned char*)"eventmachine", 12); + } + else { + int e; + if (privkeyfile.length() > 0) { + e = SSL_CTX_use_PrivateKey_file (pCtx, privkeyfile.c_str(), SSL_FILETYPE_PEM); + if (e <= 0) ERR_print_errors_fp(stderr); + assert (e > 0); + } + if (certchainfile.length() > 0) { + e = SSL_CTX_use_certificate_chain_file (pCtx, certchainfile.c_str()); + if (e <= 0) ERR_print_errors_fp(stderr); + assert (e > 0); + } + } +} + + + +/*************************** +SslContext_t::~SslContext_t +***************************/ + +SslContext_t::~SslContext_t() +{ + if (pCtx) + SSL_CTX_free (pCtx); + if (PrivateKey) + EVP_PKEY_free (PrivateKey); + if (Certificate) + X509_free (Certificate); +} + + + +/****************** +SslBox_t::SslBox_t +******************/ + +SslBox_t::SslBox_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, bool verify_peer, bool fail_if_no_peer_cert, const std::string &snihostname, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version, const uintptr_t binding): + bIsServer (is_server), + bHandshakeCompleted (false), + bVerifyPeer (verify_peer), + bFailIfNoPeerCert (fail_if_no_peer_cert), + pSSL (NULL), + pbioRead (NULL), + pbioWrite (NULL) +{ + /* TODO someday: make it possible to re-use SSL contexts so we don't have to create + * a new one every time we come here. + */ + + Context = new SslContext_t (bIsServer, privkeyfile, certchainfile, cipherlist, ecdh_curve, dhparam, ssl_version); + assert (Context); + + pbioRead = BIO_new (BIO_s_mem()); + assert (pbioRead); + + pbioWrite = BIO_new (BIO_s_mem()); + assert (pbioWrite); + + pSSL = SSL_new (Context->pCtx); + assert (pSSL); + + if (snihostname.length() > 0) { + SSL_set_tlsext_host_name (pSSL, snihostname.c_str()); + } + + SSL_set_bio (pSSL, pbioRead, pbioWrite); + + // Store a pointer to the binding signature in the SSL object so we can retrieve it later + SSL_set_ex_data(pSSL, 0, (void*) binding); + + if (bVerifyPeer) { + int mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + if (bFailIfNoPeerCert) + mode = mode | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_set_verify(pSSL, mode, ssl_verify_wrapper); + } + + if (!bIsServer) { + int e = SSL_connect (pSSL); + if (e != 1) + ERR_print_errors_fp(stderr); + } +} + + + +/******************* +SslBox_t::~SslBox_t +*******************/ + +SslBox_t::~SslBox_t() +{ + // Freeing pSSL will also free the associated BIOs, so DON'T free them separately. + if (pSSL) { + if (SSL_get_shutdown (pSSL) & SSL_RECEIVED_SHUTDOWN) + SSL_shutdown (pSSL); + else + SSL_clear (pSSL); + SSL_free (pSSL); + } + + delete Context; +} + + + +/*********************** +SslBox_t::PutCiphertext +***********************/ + +bool SslBox_t::PutCiphertext (const char *buf, int bufsize) +{ + assert (buf && (bufsize > 0)); + + assert (pbioRead); + int n = BIO_write (pbioRead, buf, bufsize); + + return (n == bufsize) ? true : false; +} + + +/********************** +SslBox_t::GetPlaintext +**********************/ + +int SslBox_t::GetPlaintext (char *buf, int bufsize) +{ + if (!SSL_is_init_finished (pSSL)) { + int e = bIsServer ? SSL_accept (pSSL) : SSL_connect (pSSL); + if (e != 1) { + int er = SSL_get_error (pSSL, e); + if (er != SSL_ERROR_WANT_READ) { + ERR_print_errors_fp(stderr); + // Return -1 for a nonfatal error, -2 for an error that should force the connection down. + return (er == SSL_ERROR_SSL) ? (-2) : (-1); + } + else + return 0; + } + bHandshakeCompleted = true; + // If handshake finished, FALL THROUGH and return the available plaintext. + } + + if (!SSL_is_init_finished (pSSL)) { + // We can get here if a browser abandons a handshake. + // The user can see a warning dialog and abort the connection. + //cerr << ""; + return 0; + } + + //cerr << "CIPH: " << SSL_get_cipher (pSSL) << endl; + + int n = SSL_read (pSSL, buf, bufsize); + if (n >= 0) { + return n; + } + else { + if (SSL_get_error (pSSL, n) == SSL_ERROR_WANT_READ) { + return 0; + } + else { + return -1; + } + } + + return 0; +} + + + +/************************** +SslBox_t::CanGetCiphertext +**************************/ + +bool SslBox_t::CanGetCiphertext() +{ + assert (pbioWrite); + return BIO_pending (pbioWrite) ? true : false; +} + + + +/*********************** +SslBox_t::GetCiphertext +***********************/ + +int SslBox_t::GetCiphertext (char *buf, int bufsize) +{ + assert (pbioWrite); + assert (buf && (bufsize > 0)); + + return BIO_read (pbioWrite, buf, bufsize); +} + + + +/********************** +SslBox_t::PutPlaintext +**********************/ + +int SslBox_t::PutPlaintext (const char *buf, int bufsize) +{ + // The caller will interpret the return value as the number of bytes written. + // WARNING WARNING WARNING, are there any situations in which a 0 or -1 return + // from SSL_write means we should immediately retry? The socket-machine loop + // will probably wait for a time-out cycle (perhaps a second) before re-trying. + // THIS WOULD CAUSE A PERCEPTIBLE DELAY! + + /* We internally queue any outbound plaintext that can't be dispatched + * because we're in the middle of a handshake or something. + * When we get called, try to send any queued data first, and then + * send the caller's data (or queue it). We may get called with no outbound + * data, which means we try to send the outbound queue and that's all. + * + * Return >0 if we wrote any data, 0 if we didn't, and <0 for a fatal error. + * Note that if we return 0, the connection is still considered live + * and we are signalling that we have accepted the outbound data (if any). + */ + + OutboundQ.Push (buf, bufsize); + + if (!SSL_is_init_finished (pSSL)) + return 0; + + bool fatal = false; + bool did_work = false; + int pending = BIO_pending(pbioWrite); + + while (OutboundQ.HasPages() && pending < SSLBOX_WRITE_BUFFER_SIZE) { + const char *page; + int length; + OutboundQ.Front (&page, &length); + assert (page && (length > 0)); + int n = SSL_write (pSSL, page, length); + pending = BIO_pending(pbioWrite); + + if (n > 0) { + did_work = true; + OutboundQ.PopFront(); + } + else { + int er = SSL_get_error (pSSL, n); + if ((er != SSL_ERROR_WANT_READ) && (er != SSL_ERROR_WANT_WRITE)) + fatal = true; + break; + } + } + + + if (did_work) + return 1; + else if (fatal) + return -1; + else + return 0; +} + +/********************** +SslBox_t::GetPeerCert +**********************/ + +X509 *SslBox_t::GetPeerCert() +{ + X509 *cert = NULL; + + if (pSSL) + cert = SSL_get_peer_certificate(pSSL); + + return cert; +} + +/********************** +SslBox_t::GetCipherBits +**********************/ + +int SslBox_t::GetCipherBits() +{ + int bits = -1; + if (pSSL) + SSL_get_cipher_bits(pSSL, &bits); + return bits; +} + +/********************** +SslBox_t::GetCipherName +**********************/ + +const char *SslBox_t::GetCipherName() +{ + if (pSSL) + return SSL_get_cipher_name(pSSL); + return NULL; +} + +/********************** +SslBox_t::GetCipherProtocol +**********************/ + +const char *SslBox_t::GetCipherProtocol() +{ + if (pSSL) + return SSL_get_cipher_version(pSSL); + return NULL; +} + +/********************** +SslBox_t::GetSNIHostname +**********************/ + +const char *SslBox_t::GetSNIHostname() +{ + #ifdef TLSEXT_NAMETYPE_host_name + if (pSSL) + return SSL_get_servername (pSSL, TLSEXT_NAMETYPE_host_name); + #endif + return NULL; +} + +/****************** +ssl_verify_wrapper +*******************/ + +extern "C" int ssl_verify_wrapper(int preverify_ok UNUSED, X509_STORE_CTX *ctx) +{ + uintptr_t binding; + X509 *cert; + SSL *ssl; + BUF_MEM *buf; + BIO *out; + int result; + + cert = X509_STORE_CTX_get_current_cert(ctx); + ssl = (SSL*) X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + binding = (uintptr_t) SSL_get_ex_data(ssl, 0); + + out = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(out, cert); + BIO_write(out, "\0", 1); + BIO_get_mem_ptr(out, &buf); + + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject(binding)); + result = (cd->VerifySslPeer(buf->data) == true ? 1 : 0); + BIO_free(out); + + return result; +} + +#endif // WITH_SSL + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.h b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.h new file mode 100644 index 0000000..64ff6e1 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/ext/ssl.h @@ -0,0 +1,103 @@ +/***************************************************************************** + +$Id$ + +File: ssl.h +Date: 30Apr06 + +Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +Gmail: blackhedd + +This program is free software; you can redistribute it and/or modify +it under the terms of either: 1) the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version; or 2) Ruby's License. + +See the file COPYING for complete licensing information. + +*****************************************************************************/ + + +#ifndef __SslBox__H_ +#define __SslBox__H_ + + + + +#ifdef WITH_SSL + +/****************** +class SslContext_t +******************/ + +class SslContext_t +{ + public: + SslContext_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version); + virtual ~SslContext_t(); + + private: + static bool bLibraryInitialized; + + private: + bool bIsServer; + SSL_CTX *pCtx; + + EVP_PKEY *PrivateKey; + X509 *Certificate; + + friend class SslBox_t; +}; + + +/************** +class SslBox_t +**************/ + +#define SSLBOX_INPUT_CHUNKSIZE 2019 +#define SSLBOX_OUTPUT_CHUNKSIZE 2048 +#define SSLBOX_WRITE_BUFFER_SIZE 8192 // (SSLBOX_OUTPUT_CHUNKSIZE * 4) + +class SslBox_t +{ + public: + SslBox_t (bool is_server, const std::string &privkeyfile, const std::string &certchainfile, bool verify_peer, bool fail_if_no_peer_cert, const std::string &snihostname, const std::string &cipherlist, const std::string &ecdh_curve, const std::string &dhparam, int ssl_version, const uintptr_t binding); + virtual ~SslBox_t(); + + int PutPlaintext (const char*, int); + int GetPlaintext (char*, int); + + bool PutCiphertext (const char*, int); + bool CanGetCiphertext(); + int GetCiphertext (char*, int); + bool IsHandshakeCompleted() {return bHandshakeCompleted;} + + X509 *GetPeerCert(); + int GetCipherBits(); + const char *GetCipherName(); + const char *GetCipherProtocol(); + const char *GetSNIHostname(); + + void Shutdown(); + + protected: + SslContext_t *Context; + + bool bIsServer; + bool bHandshakeCompleted; + bool bVerifyPeer; + bool bFailIfNoPeerCert; + SSL *pSSL; + BIO *pbioRead; + BIO *pbioWrite; + + PageList OutboundQ; +}; + +extern "C" int ssl_verify_wrapper(int, X509_STORE_CTX*); + +#endif // WITH_SSL + + +#endif // __SslBox__H_ + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.classpath b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.classpath new file mode 100644 index 0000000..11672dd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.project b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.project new file mode 100644 index 0000000..c7766db --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/.project @@ -0,0 +1,17 @@ + + + em_reactor + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactor.java b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactor.java new file mode 100644 index 0000000..31642f3 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactor.java @@ -0,0 +1,613 @@ +/** + * $Id$ + * + * Author:: Francis Cianfrocca (gmail: blackhedd) + * Homepage:: http://rubyeventmachine.com + * Date:: 15 Jul 2007 + * + * See EventMachine and EventMachine::Connection for documentation and + * usage examples. + * + * + *---------------------------------------------------------------------------- + * + * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. + * Gmail: blackhedd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of either: 1) the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version; or 2) Ruby's License. + * + * See the file COPYING for complete licensing information. + * + *--------------------------------------------------------------------------- + * + * + */ + +package com.rubyeventmachine; + +import java.io.*; +import java.nio.channels.*; +import java.util.*; +import java.nio.*; +import java.net.*; +import java.util.concurrent.atomic.*; +import java.security.*; + +public class EmReactor { + public final int EM_TIMER_FIRED = 100; + public final int EM_CONNECTION_READ = 101; + public final int EM_CONNECTION_UNBOUND = 102; + public final int EM_CONNECTION_ACCEPTED = 103; + public final int EM_CONNECTION_COMPLETED = 104; + public final int EM_LOOPBREAK_SIGNAL = 105; + public final int EM_CONNECTION_NOTIFY_READABLE = 106; + public final int EM_CONNECTION_NOTIFY_WRITABLE = 107; + public final int EM_SSL_HANDSHAKE_COMPLETED = 108; + public final int EM_SSL_VERIFY = 109; + public final int EM_PROXY_TARGET_UNBOUND = 110; + public final int EM_PROXY_COMPLETED = 111; + + public final int EM_PROTO_SSLv2 = 2; + public final int EM_PROTO_SSLv3 = 4; + public final int EM_PROTO_TLSv1 = 8; + public final int EM_PROTO_TLSv1_1 = 16; + public final int EM_PROTO_TLSv1_2 = 32; + + private Selector mySelector; + private TreeMap> Timers; + private HashMap Connections; + private HashMap Acceptors; + private ArrayList NewConnections; + private ArrayList UnboundConnections; + private ArrayList DetachedConnections; + + private boolean bRunReactor; + private long BindingIndex; + private AtomicBoolean loopBreaker; + private ByteBuffer myReadBuffer; + private int timerQuantum; + + public EmReactor() { + Timers = new TreeMap>(); + Connections = new HashMap(); + Acceptors = new HashMap(); + NewConnections = new ArrayList(); + UnboundConnections = new ArrayList(); + DetachedConnections = new ArrayList(); + + BindingIndex = 0; + loopBreaker = new AtomicBoolean(); + loopBreaker.set(false); + myReadBuffer = ByteBuffer.allocate(32*1024); // don't use a direct buffer. Ruby doesn't seem to like them. + timerQuantum = 98; + } + + /** + * This is a no-op stub, intended to be overridden in user code. + */ + public void eventCallback (long sig, int eventType, ByteBuffer data, long data2) { + System.out.println ("Default callback: "+sig+" "+eventType+" "+data+" "+data2); + } + public void eventCallback (long sig, int eventType, ByteBuffer data) { + eventCallback (sig, eventType, data, 0); + } + + + public void run() { + try { + mySelector = Selector.open(); + bRunReactor = true; + } catch (IOException e) { + throw new RuntimeException ("Could not open selector", e); + } + + while (bRunReactor) { + runLoopbreaks(); + if (!bRunReactor) break; + + runTimers(); + if (!bRunReactor) break; + + removeUnboundConnections(); + checkIO(); + addNewConnections(); + processIO(); + } + + close(); + } + + void addNewConnections() { + ListIterator iter = DetachedConnections.listIterator(0); + while (iter.hasNext()) { + EventableSocketChannel ec = iter.next(); + ec.cleanup(); + } + DetachedConnections.clear(); + + ListIterator iter2 = NewConnections.listIterator(0); + while (iter2.hasNext()) { + long b = iter2.next(); + + EventableChannel ec = Connections.get(b); + if (ec != null) { + try { + ec.register(); + } catch (ClosedChannelException e) { + UnboundConnections.add (ec.getBinding()); + } + } + } + NewConnections.clear(); + } + + void removeUnboundConnections() { + ListIterator iter = UnboundConnections.listIterator(0); + while (iter.hasNext()) { + long b = iter.next(); + + EventableChannel ec = Connections.remove(b); + if (ec != null) { + eventCallback (b, EM_CONNECTION_UNBOUND, null); + ec.close(); + + EventableSocketChannel sc = (EventableSocketChannel) ec; + if (sc != null && sc.isAttached()) + DetachedConnections.add (sc); + } + } + UnboundConnections.clear(); + } + + void checkIO() { + long timeout; + + if (NewConnections.size() > 0) { + timeout = -1; + } else if (!Timers.isEmpty()) { + long now = new Date().getTime(); + long k = Timers.firstKey(); + long diff = k-now; + + if (diff <= 0) + timeout = -1; // don't wait, just poll once + else + timeout = diff; + } else { + timeout = 0; // wait indefinitely + } + + try { + if (timeout == -1) + mySelector.selectNow(); + else + mySelector.select(timeout); + } catch (IOException e) { + e.printStackTrace(); + } + } + + void processIO() { + Iterator it = mySelector.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey k = it.next(); + it.remove(); + + if (k.isConnectable()) + isConnectable(k); + + else if (k.isAcceptable()) + isAcceptable(k); + + else { + if (k.isWritable()) + isWritable(k); + + if (k.isReadable()) + isReadable(k); + } + } + } + + void isAcceptable (SelectionKey k) { + ServerSocketChannel ss = (ServerSocketChannel) k.channel(); + SocketChannel sn; + long b; + + for (int n = 0; n < 10; n++) { + try { + sn = ss.accept(); + if (sn == null) + break; + } catch (IOException e) { + e.printStackTrace(); + k.cancel(); + + ServerSocketChannel server = Acceptors.remove(k.attachment()); + if (server != null) + try{ server.close(); } catch (IOException ex) {}; + break; + } + + try { + sn.configureBlocking(false); + } catch (IOException e) { + e.printStackTrace(); + continue; + } + + b = createBinding(); + EventableSocketChannel ec = new EventableSocketChannel (sn, b, mySelector); + Connections.put (b, ec); + NewConnections.add (b); + + eventCallback (((Long)k.attachment()).longValue(), EM_CONNECTION_ACCEPTED, null, b); + } + } + + void isReadable (SelectionKey k) { + EventableChannel ec = (EventableChannel) k.attachment(); + long b = ec.getBinding(); + + if (ec.isWatchOnly()) { + if (ec.isNotifyReadable()) + eventCallback (b, EM_CONNECTION_NOTIFY_READABLE, null); + } else { + myReadBuffer.clear(); + + try { + ec.readInboundData (myReadBuffer); + myReadBuffer.flip(); + if (myReadBuffer.limit() > 0) + eventCallback (b, EM_CONNECTION_READ, myReadBuffer); + } catch (IOException e) { + UnboundConnections.add (b); + } + } + } + + void isWritable (SelectionKey k) { + EventableChannel ec = (EventableChannel) k.attachment(); + long b = ec.getBinding(); + + if (ec.isWatchOnly()) { + if (ec.isNotifyWritable()) + eventCallback (b, EM_CONNECTION_NOTIFY_WRITABLE, null); + } + else { + try { + if (!ec.writeOutboundData()) + UnboundConnections.add (b); + } catch (IOException e) { + UnboundConnections.add (b); + } + } + } + + void isConnectable (SelectionKey k) { + EventableSocketChannel ec = (EventableSocketChannel) k.attachment(); + long b = ec.getBinding(); + + try { + if (ec.finishConnecting()) + eventCallback (b, EM_CONNECTION_COMPLETED, null); + else + UnboundConnections.add (b); + } catch (IOException e) { + UnboundConnections.add (b); + } + } + + void close() { + try { + if (mySelector != null) + mySelector.close(); + } catch (IOException e) {} + mySelector = null; + + // run down open connections and sockets. + Iterator i = Acceptors.values().iterator(); + while (i.hasNext()) { + try { + i.next().close(); + } catch (IOException e) {} + } + + // 29Sep09: We create an ArrayList of the existing connections, then iterate over + // that to call unbind on them. This is because an unbind can trigger a reconnect, + // which will add to the Connections HashMap, causing a ConcurrentModificationException. + // XXX: The correct behavior here would be to latch the various reactor methods to return + // immediately if the reactor is shutting down. + ArrayList conns = new ArrayList(); + Iterator i2 = Connections.values().iterator(); + while (i2.hasNext()) { + EventableChannel ec = i2.next(); + if (ec != null) { + conns.add (ec); + } + } + Connections.clear(); + + ListIterator i3 = conns.listIterator(0); + while (i3.hasNext()) { + EventableChannel ec = i3.next(); + eventCallback (ec.getBinding(), EM_CONNECTION_UNBOUND, null); + ec.close(); + + EventableSocketChannel sc = (EventableSocketChannel) ec; + if (sc != null && sc.isAttached()) + DetachedConnections.add (sc); + } + + ListIterator i4 = DetachedConnections.listIterator(0); + while (i4.hasNext()) { + EventableSocketChannel ec = i4.next(); + ec.cleanup(); + } + DetachedConnections.clear(); + } + + void runLoopbreaks() { + if (loopBreaker.getAndSet(false)) { + eventCallback (0, EM_LOOPBREAK_SIGNAL, null); + } + } + + public void stop() { + bRunReactor = false; + signalLoopbreak(); + } + + void runTimers() { + long now = new Date().getTime(); + while (!Timers.isEmpty()) { + long k = Timers.firstKey(); + if (k > now) + break; + + ArrayList callbacks = Timers.get(k); + Timers.remove(k); + + // Fire all timers at this timestamp + ListIterator iter = callbacks.listIterator(0); + while (iter.hasNext()) { + eventCallback (0, EM_TIMER_FIRED, null, iter.next().longValue()); + } + } + } + + public long installOneshotTimer (long milliseconds) { + long s = createBinding(); + long deadline = new Date().getTime() + milliseconds; + + if (Timers.containsKey(deadline)) { + Timers.get(deadline).add(s); + } else { + ArrayList callbacks = new ArrayList(); + callbacks.add(s); + Timers.put(deadline, callbacks); + } + + return s; + } + + public long startTcpServer (SocketAddress sa) throws EmReactorException { + try { + ServerSocketChannel server = ServerSocketChannel.open(); + server.configureBlocking(false); + server.socket().bind (sa); + long s = createBinding(); + Acceptors.put(s, server); + server.register(mySelector, SelectionKey.OP_ACCEPT, s); + return s; + } catch (IOException e) { + throw new EmReactorException ("unable to open socket acceptor: " + e.toString()); + } + } + + public long startTcpServer (String address, int port) throws EmReactorException { + return startTcpServer (new InetSocketAddress (address, port)); + } + + public void stopTcpServer (long signature) throws IOException { + ServerSocketChannel server = Acceptors.remove(signature); + if (server != null) + server.close(); + else + throw new RuntimeException ("failed to close unknown acceptor"); + } + + public long openUdpSocket (InetSocketAddress address) throws IOException { + // TODO, don't throw an exception out of here. + DatagramChannel dg = DatagramChannel.open(); + dg.configureBlocking(false); + dg.socket().bind(address); + long b = createBinding(); + EventableChannel ec = new EventableDatagramChannel (dg, b, mySelector); + dg.register(mySelector, SelectionKey.OP_READ, ec); + Connections.put(b, ec); + return b; + } + + public long openUdpSocket (String address, int port) throws IOException { + return openUdpSocket (new InetSocketAddress (address, port)); + } + + public void sendData (long sig, ByteBuffer bb) throws IOException { + Connections.get(sig).scheduleOutboundData( bb ); + } + + public void sendData (long sig, byte[] data) throws IOException { + sendData (sig, ByteBuffer.wrap(data)); + } + + public void setCommInactivityTimeout (long sig, long mills) { + Connections.get(sig).setCommInactivityTimeout (mills); + } + + public void sendDatagram (long sig, byte[] data, int length, String recipAddress, int recipPort) { + sendDatagram (sig, ByteBuffer.wrap(data), recipAddress, recipPort); + } + + public void sendDatagram (long sig, ByteBuffer bb, String recipAddress, int recipPort) { + (Connections.get(sig)).scheduleOutboundDatagram( bb, recipAddress, recipPort); + } + + public long connectTcpServer (String address, int port) { + return connectTcpServer(null, 0, address, port); + } + + public long connectTcpServer (String bindAddr, int bindPort, String address, int port) { + long b = createBinding(); + + try { + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + if (bindAddr != null) + sc.socket().bind(new InetSocketAddress (bindAddr, bindPort)); + + EventableSocketChannel ec = new EventableSocketChannel (sc, b, mySelector); + + if (sc.connect (new InetSocketAddress (address, port))) { + // Connection returned immediately. Can happen with localhost connections. + // WARNING, this code is untested due to lack of available test conditions. + // Ought to be be able to come here from a localhost connection, but that + // doesn't happen on Linux. (Maybe on FreeBSD?) + // The reason for not handling this until we can test it is that we + // really need to return from this function WITHOUT triggering any EM events. + // That's because until the user code has seen the signature we generated here, + // it won't be able to properly dispatch them. The C++ EM deals with this + // by setting pending mode as a flag in ALL eventable descriptors and making + // the descriptor select for writable. Then, it can send UNBOUND and + // CONNECTION_COMPLETED on the next pass through the loop, because writable will + // fire. + throw new RuntimeException ("immediate-connect unimplemented"); + } + else { + ec.setConnectPending(); + Connections.put (b, ec); + NewConnections.add (b); + } + } catch (IOException e) { + // Can theoretically come here if a connect failure can be determined immediately. + // I don't know how to make that happen for testing purposes. + throw new RuntimeException ("immediate-connect unimplemented: " + e.toString()); + } + return b; + } + + public void closeConnection (long sig, boolean afterWriting) { + EventableChannel ec = Connections.get(sig); + if (ec != null) + if (ec.scheduleClose (afterWriting)) + UnboundConnections.add (sig); + } + + long createBinding() { + return ++BindingIndex; + } + + public void signalLoopbreak() { + loopBreaker.set(true); + if (mySelector != null) + mySelector.wakeup(); + } + + public void startTls (long sig) throws NoSuchAlgorithmException, KeyManagementException { + Connections.get(sig).startTls(); + } + + public void setTimerQuantum (int mills) { + if (mills < 5 || mills > 2500) + throw new RuntimeException ("attempt to set invalid timer-quantum value: "+mills); + timerQuantum = mills; + } + + public Object[] getPeerName (long sig) { + EventableChannel channel = Connections.get(sig); + if (channel != null) { + return Connections.get(sig).getPeerName(); + } + else { + ServerSocketChannel acceptor = Acceptors.get(sig); + return new Object[] { acceptor.socket().getLocalPort(), + acceptor.socket().getInetAddress().getHostAddress() }; + } + } + + public Object[] getSockName (long sig) { + EventableChannel channel = Connections.get(sig); + if (channel != null) { + return Connections.get(sig).getSockName(); + } + else { + ServerSocketChannel acceptor = Acceptors.get(sig); + return new Object[] { acceptor.socket().getLocalPort(), + acceptor.socket().getInetAddress().getHostAddress() }; + } + } + + public long attachChannel (SocketChannel sc, boolean watch_mode) { + long b = createBinding(); + + EventableSocketChannel ec = new EventableSocketChannel (sc, b, mySelector); + + ec.setAttached(); + if (watch_mode) + ec.setWatchOnly(); + + Connections.put (b, ec); + NewConnections.add (b); + + return b; + } + + public SocketChannel detachChannel (long sig) { + EventableSocketChannel ec = (EventableSocketChannel) Connections.get (sig); + if (ec != null) { + UnboundConnections.add (sig); + return ec.getChannel(); + } else { + return null; + } + } + + public void setNotifyReadable (long sig, boolean mode) { + ((EventableSocketChannel) Connections.get(sig)).setNotifyReadable(mode); + } + + public void setNotifyWritable (long sig, boolean mode) { + ((EventableSocketChannel) Connections.get(sig)).setNotifyWritable(mode); + } + + public boolean isNotifyReadable (long sig) { + return Connections.get(sig).isNotifyReadable(); + } + + public boolean isNotifyWritable (long sig) { + return Connections.get(sig).isNotifyWritable(); + } + + public boolean pauseConnection (long sig) { + return ((EventableSocketChannel) Connections.get(sig)).pause(); + } + + public boolean resumeConnection (long sig) { + return ((EventableSocketChannel) Connections.get(sig)).resume(); + } + + public boolean isConnectionPaused (long sig) { + return ((EventableSocketChannel) Connections.get(sig)).isPaused(); + } + + public long getOutboundDataSize (long sig) { + return Connections.get(sig).getOutboundDataSize(); + } + + public int getConnectionCount() { + return Connections.size() + Acceptors.size(); + } +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactorException.java b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactorException.java new file mode 100644 index 0000000..69efaa3 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EmReactorException.java @@ -0,0 +1,40 @@ +/** + * $Id$ + * + * Author:: Francis Cianfrocca (gmail: blackhedd) + * Homepage:: http://rubyeventmachine.com + * Date:: 15 Jul 2007 + * + * See EventMachine and EventMachine::Connection for documentation and + * usage examples. + * + * + *---------------------------------------------------------------------------- + * + * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. + * Gmail: blackhedd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of either: 1) the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version; or 2) Ruby's License. + * + * See the file COPYING for complete licensing information. + * + *--------------------------------------------------------------------------- + * + * + */ + +package com.rubyeventmachine; + +/** + * @author francis + * + */ +public class EmReactorException extends Exception { + static final long serialVersionUID = 0; + public EmReactorException (String msg) { + super (msg); + } +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableChannel.java b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableChannel.java new file mode 100644 index 0000000..3d4f5fd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableChannel.java @@ -0,0 +1,72 @@ +/** + * $Id$ + * + * Author:: Francis Cianfrocca (gmail: blackhedd) + * Homepage:: http://rubyeventmachine.com + * Date:: 15 Jul 2007 + * + * See EventMachine and EventMachine::Connection for documentation and + * usage examples. + * + * + *---------------------------------------------------------------------------- + * + * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. + * Gmail: blackhedd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of either: 1) the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version; or 2) Ruby's License. + * + * See the file COPYING for complete licensing information. + * + *--------------------------------------------------------------------------- + * + * + */ + + +package com.rubyeventmachine; + +import java.nio.ByteBuffer; +import java.io.IOException; +import java.nio.channels.ClosedChannelException; + +public interface EventableChannel { + + public void scheduleOutboundData (ByteBuffer bb); + + public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort); + + public boolean scheduleClose (boolean afterWriting); + + public void startTls(); + + public long getBinding(); + + public void readInboundData (ByteBuffer dst) throws IOException; + + public void register() throws ClosedChannelException; + + /** + * This is called by the reactor after it finishes running. + * The idea is to free network resources. + */ + public void close(); + + public boolean writeOutboundData() throws IOException; + + public long getOutboundDataSize(); + + public void setCommInactivityTimeout (long seconds); + + public Object[] getPeerName(); + public Object[] getSockName(); + + public boolean isWatchOnly(); + + public boolean isNotifyReadable(); + public boolean isNotifyWritable(); + +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableDatagramChannel.java b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableDatagramChannel.java new file mode 100644 index 0000000..df1c9fd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableDatagramChannel.java @@ -0,0 +1,201 @@ +/** + * $Id$ + * + * Author:: Francis Cianfrocca (gmail: blackhedd) + * Homepage:: http://rubyeventmachine.com + * Date:: 15 Jul 2007 + * + * See EventMachine and EventMachine::Connection for documentation and + * usage examples. + * + * + *---------------------------------------------------------------------------- + * + * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. + * Gmail: blackhedd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of either: 1) the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version; or 2) Ruby's License. + * + * See the file COPYING for complete licensing information. + * + *--------------------------------------------------------------------------- + * + * + */ + + +package com.rubyeventmachine; + +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.DatagramChannel; +import java.util.LinkedList; +import java.io.*; +import java.net.*; + +public class EventableDatagramChannel implements EventableChannel { + + class Packet { + public ByteBuffer bb; + public SocketAddress recipient; + public Packet (ByteBuffer _bb, SocketAddress _recipient) { + bb = _bb; + recipient = _recipient; + } + } + + DatagramChannel channel; + long binding; + Selector selector; + boolean bCloseScheduled; + LinkedList outboundQ; + long outboundS; + SocketAddress returnAddress; + + + public EventableDatagramChannel (DatagramChannel dc, long _binding, Selector sel) throws ClosedChannelException { + channel = dc; + binding = _binding; + selector = sel; + bCloseScheduled = false; + outboundQ = new LinkedList(); + outboundS = 0; + + dc.register(selector, SelectionKey.OP_READ, this); + } + + public void scheduleOutboundData (ByteBuffer bb) { + try { + if ((!bCloseScheduled) && (bb.remaining() > 0)) { + outboundQ.addLast(new Packet(bb, returnAddress)); + outboundS += bb.remaining(); + channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this); + } + } catch (ClosedChannelException e) { + throw new RuntimeException ("no outbound data"); + } + } + + public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) { + try { + if ((!bCloseScheduled) && (bb.remaining() > 0)) { + outboundQ.addLast(new Packet (bb, new InetSocketAddress (recipAddress, recipPort))); + outboundS += bb.remaining(); + channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this); + } + } catch (ClosedChannelException e) { + throw new RuntimeException ("no outbound data"); + } + } + + public boolean scheduleClose (boolean afterWriting) { + System.out.println ("NOT SCHEDULING CLOSE ON DATAGRAM"); + return false; + } + + public void startTls() { + throw new RuntimeException ("TLS is unimplemented on this Channel"); + } + + public long getBinding() { + return binding; + } + + public void register() throws ClosedChannelException { + // TODO + } + + /** + * Terminate with extreme prejudice. Don't assume there will be another pass through + * the reactor core. + */ + public void close() { + try { + channel.close(); + } catch (IOException e) { + } + } + + public void readInboundData (ByteBuffer dst) { + returnAddress = null; + try { + // If there is no datagram available (we're nonblocking after all), + // then channel.receive returns null. + returnAddress = channel.receive(dst); + } catch (IOException e) { + // probably a no-op. The caller will see the empty (or even partial) buffer + // and presumably do the right thing. + } + } + + public boolean writeOutboundData() { + while (!outboundQ.isEmpty()) { + Packet p = outboundQ.getFirst(); + int written = 0; + try { + // With a datagram socket, it's ok to send an empty buffer. + written = channel.send(p.bb, p.recipient); + outboundS -= written; + } + catch (IOException e) { + return false; + } + + /* Did we consume the whole outbound buffer? If yes, pop it off and + * keep looping. If no, the outbound network buffers are full, so break + * out of here. There's a flaw that affects outbound buffers that are intentionally + * empty. We can tell whether they got sent or not. So we assume they were. + * TODO: As implemented, this ALWAYS discards packets if they were at least + * partially written. This matches the behavior of the C++ EM. My judgment + * is that this is less surprising than fragmenting the data and sending multiple + * packets would be. I could be wrong, so this is subject to change. + */ + + if ((written > 0) || (p.bb.remaining() == 0)) + outboundQ.removeFirst(); + else + break; + } + + if (outboundQ.isEmpty()) { + try { + channel.register(selector, SelectionKey.OP_READ, this); + } catch (ClosedChannelException e) {} + } + + // ALWAYS drain the outbound queue before triggering a connection close. + // If anyone wants to close immediately, they're responsible for clearing + // the outbound queue. + return (bCloseScheduled && outboundQ.isEmpty()) ? false : true; + } + + public void setCommInactivityTimeout (long seconds) { + // TODO + System.out.println ("DATAGRAM: SET COMM INACTIVITY UNIMPLEMENTED " + seconds); + } + + public Object[] getPeerName () { + if (returnAddress != null) { + InetSocketAddress inetAddr = (InetSocketAddress) returnAddress; + return new Object[]{ inetAddr.getPort(), inetAddr.getHostName() }; + } else { + return null; + } + } + + public Object[] getSockName () { + DatagramSocket socket = channel.socket(); + return new Object[]{ socket.getLocalPort(), + socket.getLocalAddress().getHostAddress() }; + } + + public boolean isWatchOnly() { return false; } + public boolean isNotifyReadable() { return false; } + public boolean isNotifyWritable() { return false; } + public long getOutboundDataSize() { return outboundS; } +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableSocketChannel.java b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableSocketChannel.java new file mode 100644 index 0000000..2905ec6 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/java/src/com/rubyeventmachine/EventableSocketChannel.java @@ -0,0 +1,415 @@ +/** + * $Id$ + * + * Author:: Francis Cianfrocca (gmail: blackhedd) + * Homepage:: http://rubyeventmachine.com + * Date:: 15 Jul 2007 + * + * See EventMachine and EventMachine::Connection for documentation and + * usage examples. + * + * + *---------------------------------------------------------------------------- + * + * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. + * Gmail: blackhedd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of either: 1) the GNU General Public License + * as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version; or 2) Ruby's License. + * + * See the file COPYING for complete licensing information. + * + *--------------------------------------------------------------------------- + * + * + */ + +/** + * + */ +package com.rubyeventmachine; + +/** + * @author francis + * + */ + +import java.nio.channels.*; +import java.nio.*; +import java.util.*; +import java.io.*; +import java.net.Socket; +import javax.net.ssl.*; +import javax.net.ssl.SSLEngineResult.*; +import java.lang.reflect.Field; + +import java.security.*; + +public class EventableSocketChannel implements EventableChannel { + Selector selector; + SelectionKey channelKey; + SocketChannel channel; + + long binding; + LinkedList outboundQ; + long outboundS; + + boolean bCloseScheduled; + boolean bConnectPending; + boolean bWatchOnly; + boolean bAttached; + boolean bNotifyReadable; + boolean bNotifyWritable; + boolean bPaused; + + SSLEngine sslEngine; + SSLContext sslContext; + + public EventableSocketChannel (SocketChannel sc, long _binding, Selector sel) { + channel = sc; + binding = _binding; + selector = sel; + bCloseScheduled = false; + bConnectPending = false; + bWatchOnly = false; + bAttached = false; + bNotifyReadable = false; + bNotifyWritable = false; + outboundQ = new LinkedList(); + outboundS = 0; + } + + public long getBinding() { + return binding; + } + + public SocketChannel getChannel() { + return channel; + } + + public void register() throws ClosedChannelException { + if (channelKey == null) { + int events = currentEvents(); + channelKey = channel.register(selector, events, this); + } + } + + /** + * Terminate with extreme prejudice. Don't assume there will be another pass through + * the reactor core. + */ + public void close() { + if (channelKey != null) { + channelKey.cancel(); + channelKey = null; + } + + if (bAttached) { + // attached channels are copies, so reset the file descriptor to prevent java from close()ing it + Field f; + FileDescriptor fd; + + try { + /* do _NOT_ clobber fdVal here, it will break epoll/kqueue on jdk6! + * channelKey.cancel() above does not occur until the next call to select + * and if fdVal is gone, we will continue to get events for this fd. + * + * instead, remove fdVal in cleanup(), which is processed via DetachedConnections, + * after UnboundConnections but before NewConnections. + */ + + f = channel.getClass().getDeclaredField("fd"); + f.setAccessible(true); + fd = (FileDescriptor) f.get(channel); + + f = fd.getClass().getDeclaredField("fd"); + f.setAccessible(true); + f.set(fd, -1); + } catch (java.lang.NoSuchFieldException e) { + e.printStackTrace(); + } catch (java.lang.IllegalAccessException e) { + e.printStackTrace(); + } + + return; + } + + try { + channel.close(); + } catch (IOException e) { + } + } + + public void cleanup() { + if (bAttached) { + Field f; + try { + f = channel.getClass().getDeclaredField("fdVal"); + f.setAccessible(true); + f.set(channel, -1); + } catch (java.lang.NoSuchFieldException e) { + e.printStackTrace(); + } catch (java.lang.IllegalAccessException e) { + e.printStackTrace(); + } + } + + channel = null; + } + + public void scheduleOutboundData (ByteBuffer bb) { + if (!bCloseScheduled && bb.remaining() > 0) { + if (sslEngine != null) { + try { + ByteBuffer b = ByteBuffer.allocate(32*1024); // TODO, preallocate this buffer. + sslEngine.wrap(bb, b); + b.flip(); + outboundQ.addLast(b); + outboundS += b.remaining(); + } catch (SSLException e) { + throw new RuntimeException ("ssl error"); + } + } + else { + outboundQ.addLast(bb); + outboundS += bb.remaining(); + } + + updateEvents(); + } + } + + public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) { + throw new RuntimeException ("datagram sends not supported on this channel"); + } + + /** + * Called by the reactor when we have selected readable. + */ + public void readInboundData (ByteBuffer bb) throws IOException { + if (channel.read(bb) == -1) + throw new IOException ("eof"); + } + + public long getOutboundDataSize() { return outboundS; } + + /** + * Called by the reactor when we have selected writable. + * Return false to indicate an error that should cause the connection to close. + * TODO, VERY IMPORTANT: we're here because we selected writable, but it's always + * possible to become unwritable between the poll and when we get here. The way + * this code is written, we're depending on a nonblocking write NOT TO CONSUME + * the whole outbound buffer in this case, rather than firing an exception. + * We should somehow verify that this is indeed Java's defined behavior. + * @return + */ + public boolean writeOutboundData() throws IOException { + ByteBuffer[] bufs = new ByteBuffer[64]; + int i; + long written, toWrite; + while (!outboundQ.isEmpty()) { + i = 0; + toWrite = 0; + written = 0; + while (i < 64 && !outboundQ.isEmpty()) { + bufs[i] = outboundQ.removeFirst(); + toWrite += bufs[i].remaining(); + i++; + } + if (toWrite > 0) + written = channel.write(bufs, 0, i); + + outboundS -= written; + // Did we consume the whole outbound buffer? If yes, + // pop it off and keep looping. If no, the outbound network + // buffers are full, so break out of here. + if (written < toWrite) { + while (i > 0 && bufs[i-1].remaining() > 0) { + outboundQ.addFirst(bufs[i-1]); + i--; + } + break; + } + } + + if (outboundQ.isEmpty() && !bCloseScheduled) { + updateEvents(); + } + + // ALWAYS drain the outbound queue before triggering a connection close. + // If anyone wants to close immediately, they're responsible for clearing + // the outbound queue. + return (bCloseScheduled && outboundQ.isEmpty()) ? false : true; + } + + public void setConnectPending() { + bConnectPending = true; + updateEvents(); + } + + /** + * Called by the reactor when we have selected connectable. + * Return false to indicate an error that should cause the connection to close. + */ + public boolean finishConnecting() throws IOException { + channel.finishConnect(); + + bConnectPending = false; + updateEvents(); + return true; + } + + public boolean scheduleClose (boolean afterWriting) { + // TODO: What the hell happens here if bConnectPending is set? + if (!afterWriting) { + outboundQ.clear(); + outboundS = 0; + } + + if (outboundQ.isEmpty()) + return true; + else { + updateEvents(); + bCloseScheduled = true; + return false; + } + } + + public void startTls() { + if (sslEngine == null) { + try { + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, null); // TODO, fill in the parameters. + sslEngine = sslContext.createSSLEngine(); // TODO, should use the parameterized version, to get Kerb stuff and session re-use. + sslEngine.setUseClientMode(false); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException ("unable to start TLS"); // TODO, get rid of this. + } catch (KeyManagementException e) { + throw new RuntimeException ("unable to start TLS"); // TODO, get rid of this. + } + } + System.out.println ("Starting TLS"); + } + + public ByteBuffer dispatchInboundData (ByteBuffer bb) throws SSLException { + if (sslEngine != null) { + if (true) throw new RuntimeException ("TLS currently unimplemented"); + System.setProperty("javax.net.debug", "all"); + ByteBuffer w = ByteBuffer.allocate(32*1024); // TODO, WRONG, preallocate this buffer. + SSLEngineResult res = sslEngine.unwrap(bb, w); + if (res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + Runnable r; + while ((r = sslEngine.getDelegatedTask()) != null) { + r.run(); + } + } + System.out.println (bb); + w.flip(); + return w; + } + else + return bb; + } + + public void setCommInactivityTimeout (long seconds) { + // TODO + System.out.println ("SOCKET: SET COMM INACTIVITY UNIMPLEMENTED " + seconds); + } + + public Object[] getPeerName () { + Socket sock = channel.socket(); + return new Object[]{ sock.getPort(), sock.getInetAddress().getHostAddress() }; + } + + public Object[] getSockName () { + Socket sock = channel.socket(); + return new Object[]{ sock.getLocalPort(), + sock.getLocalAddress().getHostAddress() }; + } + + public void setWatchOnly() { + bWatchOnly = true; + updateEvents(); + } + public boolean isWatchOnly() { return bWatchOnly; } + + public void setAttached() { + bAttached = true; + } + public boolean isAttached() { return bAttached; } + + public void setNotifyReadable (boolean mode) { + bNotifyReadable = mode; + updateEvents(); + } + public boolean isNotifyReadable() { return bNotifyReadable; } + + public void setNotifyWritable (boolean mode) { + bNotifyWritable = mode; + updateEvents(); + } + public boolean isNotifyWritable() { return bNotifyWritable; } + + public boolean pause() { + if (bWatchOnly) { + throw new RuntimeException ("cannot pause/resume 'watch only' connections, set notify readable/writable instead"); + } + boolean old = bPaused; + bPaused = true; + updateEvents(); + return !old; + } + + public boolean resume() { + if (bWatchOnly) { + throw new RuntimeException ("cannot pause/resume 'watch only' connections, set notify readable/writable instead"); + } + boolean old = bPaused; + bPaused = false; + updateEvents(); + return old; + } + + public boolean isPaused() { + return bPaused; + } + + private void updateEvents() { + if (channelKey == null) + return; + + int events = currentEvents(); + + if (channelKey.interestOps() != events) { + channelKey.interestOps(events); + } + } + + private int currentEvents() { + int events = 0; + + if (bWatchOnly) + { + if (bNotifyReadable) + events |= SelectionKey.OP_READ; + + if (bNotifyWritable) + events |= SelectionKey.OP_WRITE; + } + else if (!bPaused) + { + if (bConnectPending) + events |= SelectionKey.OP_CONNECT; + else { + events |= SelectionKey.OP_READ; + + if (!outboundQ.isEmpty()) + events |= SelectionKey.OP_WRITE; + } + } + + return events; + } +} diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb new file mode 100644 index 0000000..caf4f77 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb @@ -0,0 +1,59 @@ +# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based +# by default. It allows input to be spoon-fed from some outside source which +# receives arbitrary length datagrams which may-or-may-not contain the token +# by which entities are delimited. In this respect it's ideally paired with +# something like EventMachine (http://rubyeventmachine.com/). +class BufferedTokenizer + # New BufferedTokenizers will operate on lines delimited by a delimiter, + # which is by default the global input delimiter $/ ("\n"). + # + # The input buffer is stored as an array. This is by far the most efficient + # approach given language constraints (in C a linked list would be a more + # appropriate data structure). Segments of input data are stored in a list + # which is only joined when a token is reached, substantially reducing the + # number of objects required for the operation. + def initialize(delimiter = $/) + @delimiter = delimiter + @input = [] + @tail = '' + @trim = @delimiter.length - 1 + end + + # Extract takes an arbitrary string of input data and returns an array of + # tokenized entities, provided there were any available to extract. This + # makes for easy processing of datagrams using a pattern like: + # + # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ... + # + # Using -1 makes split to return "" if the token is at the end of + # the string, meaning the last element is the start of the next chunk. + def extract(data) + if @trim > 0 + tail_end = @tail.slice!(-@trim, @trim) # returns nil if string is too short + data = tail_end + data if tail_end + end + + @input << @tail + entities = data.split(@delimiter, -1) + @tail = entities.shift + + unless entities.empty? + @input << @tail + entities.unshift @input.join + @input.clear + @tail = entities.pop + end + + entities + end + + # Flush the contents of the input buffer, i.e. return the input buffer even though + # a token has not yet been encountered + def flush + @input << @tail + buffer = @input.join + @input.clear + @tail = "" # @tail.clear is slightly faster, but not supported on 1.8.7 + buffer + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb new file mode 100644 index 0000000..4928feb --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb @@ -0,0 +1,58 @@ +module EventMachine + # Utility method for coercing arguments to an object that responds to :call. + # Accepts an object and a method name to send to, or a block, or an object + # that responds to :call. + # + # @example EventMachine.Callback used with a block. Returns that block. + # + # cb = EventMachine.Callback do |msg| + # puts(msg) + # end + # # returned object is a callable + # cb.call('hello world') + # + # + # @example EventMachine.Callback used with an object (to be more specific, class object) and a method name, returns an object that responds to #call + # + # cb = EventMachine.Callback(Object, :puts) + # # returned object is a callable that delegates to Kernel#puts (in this case Object.puts) + # cb.call('hello world') + # + # + # @example EventMachine.Callback used with an object that responds to #call. Returns the argument. + # + # cb = EventMachine.Callback(proc{ |msg| puts(msg) }) + # # returned object is a callable + # cb.call('hello world') + # + # + # @overload Callback(object, method) + # Wraps `method` invocation on `object` into an object that responds to #call that proxies all the arguments to that method + # @param [Object] Object to invoke method on + # @param [Symbol] Method name + # @return [<#call>] An object that responds to #call that takes any number of arguments and invokes method on object with those arguments + # + # @overload Callback(object) + # Returns callable object as is, without any coercion + # @param [<#call>] An object that responds to #call + # @return [<#call>] Its argument + # + # @overload Callback(&block) + # Returns block passed to it without any coercion + # @return [<#call>] Block passed to this method + # + # @raise [ArgumentError] When argument doesn't respond to #call, method name is missing or when invoked without arguments and block isn't given + # + # @return [<#call>] + def self.Callback(object = nil, method = nil, &blk) + if object && method + lambda { |*args| object.__send__ method, *args } + else + if object.respond_to? :call + object + else + blk || raise(ArgumentError) + end # if + end # if + end # self.Callback +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb new file mode 100644 index 0000000..a919adf --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb @@ -0,0 +1,69 @@ +module EventMachine + # Provides a simple thread-safe way to transfer data between (typically) long running + # tasks in {EventMachine.defer} and event loop thread. + # + # @example + # + # channel = EventMachine::Channel.new + # sid = channel.subscribe { |msg| p [:got, msg] } + # + # channel.push('hello world') + # channel.unsubscribe(sid) + # + # + class Channel + def initialize + @subs = {} + @uid = 0 + end + + # Return the number of current subscribers. + def num_subscribers + return @subs.size + end + + # Takes any arguments suitable for EM::Callback() and returns a subscriber + # id for use when unsubscribing. + # + # @return [Integer] Subscribe identifier + # @see #unsubscribe + def subscribe(*a, &b) + name = gen_id + EM.schedule { @subs[name] = EM::Callback(*a, &b) } + + name + end + + # Removes subscriber from the list. + # + # @param [Integer] Subscriber identifier + # @see #subscribe + def unsubscribe(name) + EM.schedule { @subs.delete name } + end + + # Add items to the channel, which are pushed out to all subscribers. + def push(*items) + items = items.dup + EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } } + end + alias << push + + # Fetches one message from the channel. + def pop(*a, &b) + EM.schedule { + name = subscribe do |*args| + unsubscribe(name) + EM::Callback(*a, &b).call(*args) + end + } + end + + private + + # @private + def gen_id + @uid += 1 + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb new file mode 100644 index 0000000..926535f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb @@ -0,0 +1,304 @@ +# = EM::Completion +# +# A completion is a callback container for various states of completion. In +# its most basic form it has a start state and a finish state. +# +# This implementation includes some hold-back from the EM::Deferrable +# interface in order to be compatible - but it has a much cleaner +# implementation. +# +# In general it is preferred that this implementation be used as a state +# callback container than EM::DefaultDeferrable or other classes including +# EM::Deferrable. This is because it is generally more sane to keep this level +# of state in a dedicated state-back container. This generally leads to more +# malleable interfaces and software designs, as well as eradicating nasty bugs +# that result from abstraction leakage. +# +# == Basic Usage +# +# As already mentioned, the basic usage of a Completion is simply for its two +# final states, :succeeded and :failed. +# +# An asynchronous operation will complete at some future point in time, and +# users often want to react to this event. API authors will want to expose +# some common interface to react to these events. +# +# In the following example, the user wants to know when a short lived +# connection has completed its exchange with the remote server. The simple +# protocol just waits for an ack to its message. +# +# class Protocol < EM::Connection +# include EM::P::LineText2 +# +# def initialize(message, completion) +# @message, @completion = message, completion +# @completion.completion { close_connection } +# @completion.timeout(1, :timeout) +# end +# +# def post_init +# send_data(@message) +# end +# +# def receive_line(line) +# case line +# when /ACK/i +# @completion.succeed line +# when /ERR/i +# @completion.fail :error, line +# else +# @completion.fail :unknown, line +# end +# end +# +# def unbind +# @completion.fail :disconnected unless @completion.completed? +# end +# end +# +# class API +# attr_reader :host, :port +# +# def initialize(host = 'example.org', port = 8000) +# @host, @port = host, port +# end +# +# def request(message) +# completion = EM::Deferrable::Completion.new +# EM.connect(host, port, Protocol, message, completion) +# completion +# end +# end +# +# api = API.new +# completion = api.request('stuff') +# completion.callback do |line| +# puts "API responded with: #{line}" +# end +# completion.errback do |type, line| +# case type +# when :error +# puts "API error: #{line}" +# when :unknown +# puts "API returned unknown response: #{line}" +# when :disconnected +# puts "API server disconnected prematurely" +# when :timeout +# puts "API server did not respond in a timely fashion" +# end +# end +# +# == Advanced Usage +# +# This completion implementation also supports more state callbacks and +# arbitrary states (unlike the original Deferrable API). This allows for basic +# stateful process encapsulation. One might use this to setup state callbacks +# for various states in an exchange like in the basic usage example, except +# where the applicaiton could be made to react to "connected" and +# "disconnected" states additionally. +# +# class Protocol < EM::Connection +# def initialize(completion) +# @response = [] +# @completion = completion +# @completion.stateback(:disconnected) do +# @completion.succeed @response.join +# end +# end +# +# def connection_completed +# @host, @port = Socket.unpack_sockaddr_in get_peername +# @completion.change_state(:connected, @host, @port) +# send_data("GET http://example.org/ HTTP/1.0\r\n\r\n") +# end +# +# def receive_data(data) +# @response << data +# end +# +# def unbind +# @completion.change_state(:disconnected, @host, @port) +# end +# end +# +# completion = EM::Deferrable::Completion.new +# completion.stateback(:connected) do |host, port| +# puts "Connected to #{host}:#{port}" +# end +# completion.stateback(:disconnected) do |host, port| +# puts "Disconnected from #{host}:#{port}" +# end +# completion.callback do |response| +# puts response +# end +# +# EM.connect('example.org', 80, Protocol, completion) +# +# == Timeout +# +# The Completion also has a timeout. The timeout is global and is not aware of +# states apart from completion states. The timeout is only engaged if #timeout +# is called, and it will call fail if it is reached. +# +# == Completion states +# +# By default there are two completion states, :succeeded and :failed. These +# states can be modified by subclassing and overrding the #completion_states +# method. Completion states are special, in that callbacks for all completion +# states are explcitly cleared when a completion state is entered. This +# prevents errors that could arise from accidental unterminated timeouts, and +# other such user errors. +# +# == Other notes +# +# Several APIs have been carried over from EM::Deferrable for compatibility +# reasons during a transitionary period. Specifically cancel_errback and +# cancel_callback are implemented, but their usage is to be strongly +# discouraged. Due to the already complex nature of reaction systems, dynamic +# callback deletion only makes the problem much worse. It is always better to +# add correct conditionals to the callback code, or use more states, than to +# address such implementaiton issues with conditional callbacks. + +module EventMachine + + class Completion + # This is totally not used (re-implemented), it's here in case people check + # for kind_of? + include EventMachine::Deferrable + + attr_reader :state, :value + + def initialize + @state = :unknown + @callbacks = Hash.new { |h,k| h[k] = [] } + @value = [] + @timeout_timer = nil + end + + # Enter the :succeeded state, setting the result value if given. + def succeed(*args) + change_state(:succeeded, *args) + end + # The old EM method: + alias set_deferred_success succeed + + # Enter the :failed state, setting the result value if given. + def fail(*args) + change_state(:failed, *args) + end + # The old EM method: + alias set_deferred_failure fail + + # Statebacks are called when you enter (or are in) the named state. + def stateback(state, *a, &b) + # The following is quite unfortunate special casing for :completed + # statebacks, but it's a necessary evil for latent completion + # definitions. + + if :completed == state || !completed? || @state == state + @callbacks[state] << EM::Callback(*a, &b) + end + execute_callbacks + self + end + + # Callbacks are called when you enter (or are in) a :succeeded state. + def callback(*a, &b) + stateback(:succeeded, *a, &b) + end + + # Errbacks are called when you enter (or are in) a :failed state. + def errback(*a, &b) + stateback(:failed, *a, &b) + end + + # Completions are called when you enter (or are in) either a :failed or a + # :succeeded state. They are stored as a special (reserved) state called + # :completed. + def completion(*a, &b) + stateback(:completed, *a, &b) + end + + # Enter a new state, setting the result value if given. If the state is one + # of :succeeded or :failed, then :completed callbacks will also be called. + def change_state(state, *args) + @value = args + @state = state + + EM.schedule { execute_callbacks } + end + + # The old EM method: + alias set_deferred_status change_state + + # Indicates that we've reached some kind of completion state, by default + # this is :succeeded or :failed. Due to these semantics, the :completed + # state is reserved for internal use. + def completed? + completion_states.any? { |s| state == s } + end + + # Completion states simply returns a list of completion states, by default + # this is :succeeded and :failed. + def completion_states + [:succeeded, :failed] + end + + # Schedule a time which if passes before we enter a completion state, this + # deferrable will be failed with the given arguments. + def timeout(time, *args) + cancel_timeout + @timeout_timer = EM::Timer.new(time) do + fail(*args) unless completed? + end + end + + # Disable the timeout + def cancel_timeout + if @timeout_timer + @timeout_timer.cancel + @timeout_timer = nil + end + end + + # Remove an errback. N.B. Some errbacks cannot be deleted. Usage is NOT + # recommended, this is an anti-pattern. + def cancel_errback(*a, &b) + @callbacks[:failed].delete(EM::Callback(*a, &b)) + end + + # Remove a callback. N.B. Some callbacks cannot be deleted. Usage is NOT + # recommended, this is an anti-pattern. + def cancel_callback(*a, &b) + @callbacks[:succeeded].delete(EM::Callback(*a, &b)) + end + + private + # Execute all callbacks for the current state. If in a completed state, then + # call any statebacks associated with the completed state. + def execute_callbacks + execute_state_callbacks(state) + if completed? + execute_state_callbacks(:completed) + clear_dead_callbacks + cancel_timeout + end + end + + # Iterate all callbacks for a given state, and remove then call them. + def execute_state_callbacks(state) + while callback = @callbacks[state].shift + callback.call(*value) + end + end + + # If we enter a completion state, clear other completion states after all + # callback chains are completed. This means that operation specific + # callbacks can't be dual-called, which is most common user error. + def clear_dead_callbacks + completion_states.each do |state| + @callbacks[state].clear + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb new file mode 100644 index 0000000..267aec2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb @@ -0,0 +1,770 @@ +module EventMachine + class FileNotFoundException < Exception + end + + # EventMachine::Connection is a class that is instantiated + # by EventMachine's processing loop whenever a new connection + # is created. (New connections can be either initiated locally + # to a remote server or accepted locally from a remote client.) + # When a Connection object is instantiated, it mixes in + # the functionality contained in the user-defined module + # specified in calls to {EventMachine.connect} or {EventMachine.start_server}. + # User-defined handler modules may redefine any or all of the standard + # methods defined here, as well as add arbitrary additional code + # that will also be mixed in. + # + # EventMachine manages one object inherited from EventMachine::Connection + # (and containing the mixed-in user code) for every network connection + # that is active at any given time. + # The event loop will automatically call methods on EventMachine::Connection + # objects whenever specific events occur on the corresponding connections, + # as described below. + # + # This class is never instantiated by user code, and does not publish an + # initialize method. The instance methods of EventMachine::Connection + # which may be called by the event loop are: + # + # * {#post_init} + # * {#connection_completed} + # * {#receive_data} + # * {#unbind} + # * {#ssl_verify_peer} (if TLS is used) + # * {#ssl_handshake_completed} + # + # All of the other instance methods defined here are called only by user code. + # + # @see file:docs/GettingStarted.md EventMachine tutorial + class Connection + # @private + attr_accessor :signature + + # @private + alias original_method method + + # Override .new so subclasses don't have to call super and can ignore + # connection-specific arguments + # + # @private + def self.new(sig, *args) + allocate.instance_eval do + # Store signature + @signature = sig + # associate_callback_target sig + + # Call a superclass's #initialize if it has one + initialize(*args) + + # post initialize callback + post_init + + self + end + end + + # Stubbed initialize so legacy superclasses can safely call super + # + # @private + def initialize(*args) + end + + # Called by the event loop immediately after the network connection has been established, + # and before resumption of the network loop. + # This method is generally not called by user code, but is called automatically + # by the event loop. The base-class implementation is a no-op. + # This is a very good place to initialize instance variables that will + # be used throughout the lifetime of the network connection. + # + # @see #connection_completed + # @see #unbind + # @see #send_data + # @see #receive_data + def post_init + end + + # Called by the event loop whenever data has been received by the network connection. + # It is never called by user code. {#receive_data} is called with a single parameter, a String containing + # the network protocol data, which may of course be binary. You will + # generally redefine this method to perform your own processing of the incoming data. + # + # Here's a key point which is essential to understanding the event-driven + # programming model: EventMachine knows absolutely nothing about the protocol + # which your code implements. You must not make any assumptions about + # the size of the incoming data packets, or about their alignment on any + # particular intra-message or PDU boundaries (such as line breaks). + # receive_data can and will send you arbitrary chunks of data, with the + # only guarantee being that the data is presented to your code in the order + # it was collected from the network. Don't even assume that the chunks of + # data will correspond to network packets, as EventMachine can and will coalesce + # several incoming packets into one, to improve performance. The implication for your + # code is that you generally will need to implement some kind of a state machine + # in your redefined implementation of receive_data. For a better understanding + # of this, read through the examples of specific protocol handlers in EventMachine::Protocols + # + # The base-class implementation (which will be invoked only if you didn't override it in your protocol handler) + # simply prints incoming data packet size to stdout. + # + # @param [String] data Opaque incoming data. + # @note Depending on the protocol, buffer sizes and OS networking stack configuration, incoming data may or may not be "a complete message". + # It is up to this handler to detect content boundaries to determine whether all the content (for example, full HTTP request) + # has been received and can be processed. + # + # @see #post_init + # @see #connection_completed + # @see #unbind + # @see #send_data + # @see file:docs/GettingStarted.md EventMachine tutorial + def receive_data data + puts "............>>>#{data.length}" + end + + # Called by EventMachine when the SSL/TLS handshake has + # been completed, as a result of calling #start_tls to initiate SSL/TLS on the connection. + # + # This callback exists because {#post_init} and {#connection_completed} are **not** reliable + # for indicating when an SSL/TLS connection is ready to have its certificate queried for. + # + # @see #get_peer_cert + def ssl_handshake_completed + end + + # Called by EventMachine when :verify_peer => true has been passed to {#start_tls}. + # It will be called with each certificate in the certificate chain provided by the remote peer. + # + # The cert will be passed as a String in PEM format, the same as in {#get_peer_cert}. It is up to user defined + # code to perform a check on the certificates. The return value from this callback is used to accept or deny the peer. + # A return value that is not nil or false triggers acceptance. If the peer is not accepted, the connection + # will be subsequently closed. + # + # @example This server always accepts all peers + # + # module AcceptServer + # def post_init + # start_tls(:verify_peer => true) + # end + # + # def ssl_verify_peer(cert) + # true + # end + # + # def ssl_handshake_completed + # $server_handshake_completed = true + # end + # end + # + # + # @example This server never accepts any peers + # + # module DenyServer + # def post_init + # start_tls(:verify_peer => true) + # end + # + # def ssl_verify_peer(cert) + # # Do not accept the peer. This should now cause the connection to shut down + # # without the SSL handshake being completed. + # false + # end + # + # def ssl_handshake_completed + # $server_handshake_completed = true + # end + # end + # + # @see #start_tls + def ssl_verify_peer(cert) + end + + # called by the framework whenever a connection (either a server or client connection) is closed. + # The close can occur because your code intentionally closes it (using {#close_connection} and {#close_connection_after_writing}), + # because the remote peer closed the connection, or because of a network error. + # You may not assume that the network connection is still open and able to send or + # receive data when the callback to unbind is made. This is intended only to give + # you a chance to clean up associations your code may have made to the connection + # object while it was open. + # + # If you want to detect which peer has closed the connection, you can override {#close_connection} in your protocol handler + # and set an @ivar. + # + # @example Overriding Connection#close_connection to distinguish connections closed on our side + # + # class MyProtocolHandler < EventMachine::Connection + # + # # ... + # + # def close_connection(*args) + # @intentionally_closed_connection = true + # super(*args) + # end + # + # def unbind + # if @intentionally_closed_connection + # # ... + # end + # end + # + # # ... + # + # end + # + # @see #post_init + # @see #connection_completed + # @see file:docs/GettingStarted.md EventMachine tutorial + def unbind + end + + # Called by the reactor after attempting to relay incoming data to a descriptor (set as a proxy target descriptor with + # {EventMachine.enable_proxy}) that has already been closed. + # + # @see EventMachine.enable_proxy + def proxy_target_unbound + end + + # called when the reactor finished proxying all + # of the requested bytes. + def proxy_completed + end + + # EventMachine::Connection#proxy_incoming_to is called only by user code. It sets up + # a low-level proxy relay for all data inbound for this connection, to the connection given + # as the argument. This is essentially just a helper method for enable_proxy. + # + # @see EventMachine.enable_proxy + def proxy_incoming_to(conn,bufsize=0) + EventMachine::enable_proxy(self, conn, bufsize) + end + + # A helper method for {EventMachine.disable_proxy} + def stop_proxying + EventMachine::disable_proxy(self) + end + + # The number of bytes proxied to another connection. Reset to zero when + # EventMachine::Connection#proxy_incoming_to is called, and incremented whenever data is proxied. + def get_proxied_bytes + EventMachine::get_proxied_bytes(@signature) + end + + # EventMachine::Connection#close_connection is called only by user code, and never + # by the event loop. You may call this method against a connection object in any + # callback handler, whether or not the callback was made against the connection + # you want to close. close_connection schedules the connection to be closed + # at the next available opportunity within the event loop. You may not assume that + # the connection is closed when close_connection returns. In particular, the framework + # will callback the unbind method for the particular connection at a point shortly + # after you call close_connection. You may assume that the unbind callback will + # take place sometime after your call to close_connection completes. In other words, + # the unbind callback will not re-enter your code "inside" of your call to close_connection. + # However, it's not guaranteed that a future version of EventMachine will not change + # this behavior. + # + # {#close_connection} will *silently discard* any outbound data which you have + # sent to the connection using {EventMachine::Connection#send_data} but which has not + # yet been sent across the network. If you want to avoid this behavior, use + # {EventMachine::Connection#close_connection_after_writing}. + # + def close_connection after_writing = false + EventMachine::close_connection @signature, after_writing + end + + # Removes given connection from the event loop. + # The connection's socket remains open and its file descriptor number is returned. + def detach + EventMachine::detach_fd @signature + end + + def get_sock_opt level, option + EventMachine::get_sock_opt @signature, level, option + end + + def set_sock_opt level, optname, optval + EventMachine::set_sock_opt @signature, level, optname, optval + end + + # A variant of {#close_connection}. + # All of the descriptive comments given for close_connection also apply to + # close_connection_after_writing, *with one exception*: if the connection has + # outbound data sent using send_dat but which has not yet been sent across the network, + # close_connection_after_writing will schedule the connection to be closed *after* + # all of the outbound data has been safely written to the remote peer. + # + # Depending on the amount of outgoing data and the speed of the network, + # considerable time may elapse between your call to close_connection_after_writing + # and the actual closing of the socket (at which time the unbind callback will be called + # by the event loop). During this time, you *may not* call send_data to transmit + # additional data (that is, the connection is closed for further writes). In very + # rare cases, you may experience a receive_data callback after your call to {#close_connection_after_writing}, + # depending on whether incoming data was in the process of being received on the connection + # at the moment when you called {#close_connection_after_writing}. Your protocol handler must + # be prepared to properly deal with such data (probably by ignoring it). + # + # @see #close_connection + # @see #send_data + def close_connection_after_writing + close_connection true + end + + # Call this method to send data to the remote end of the network connection. It takes a single String argument, + # which may contain binary data. Data is buffered to be sent at the end of this event loop tick (cycle). + # + # When used in a method that is event handler (for example, {#post_init} or {#connection_completed}, it will send + # data to the other end of the connection that generated the event. + # You can also call {#send_data} to write to other connections. For more information see The Chat Server Example in the + # {file:docs/GettingStarted.md EventMachine tutorial}. + # + # If you want to send some data and then immediately close the connection, make sure to use {#close_connection_after_writing} + # instead of {#close_connection}. + # + # + # @param [String] data Data to send asynchronously + # + # @see file:docs/GettingStarted.md EventMachine tutorial + # @see Connection#receive_data + # @see Connection#post_init + # @see Connection#unbind + def send_data data + data = data.to_s + size = data.bytesize if data.respond_to?(:bytesize) + size ||= data.size + EventMachine::send_data @signature, data, size + end + + # Returns true if the connection is in an error state, false otherwise. + # + # In general, you can detect the occurrence of communication errors or unexpected + # disconnection by the remote peer by handing the {#unbind} method. In some cases, however, + # it's useful to check the status of the connection using {#error?} before attempting to send data. + # This function is synchronous but it will return immediately without blocking. + # + # @return [Boolean] true if the connection is in an error state, false otherwise + def error? + errno = EventMachine::report_connection_error_status(@signature) + case errno + when 0 + false + when -1 + true + else + EventMachine::ERRNOS[errno] + end + end + + # Called by the event loop when a remote TCP connection attempt completes successfully. + # You can expect to get this notification after calls to {EventMachine.connect}. Remember that EventMachine makes remote connections + # asynchronously, just as with any other kind of network event. This method + # is intended primarily to assist with network diagnostics. For normal protocol + # handling, use #post_init to perform initial work on a new connection (such as sending initial set of data). + # {Connection#post_init} will always be called. This method will only be called in case of a successful completion. + # A connection attempt which fails will result a call to {Connection#unbind} after the failure. + # + # @see Connection#post_init + # @see Connection#unbind + # @see file:docs/GettingStarted.md EventMachine tutorial + def connection_completed + end + + # Call {#start_tls} at any point to initiate TLS encryption on connected streams. + # The method is smart enough to know whether it should perform a server-side + # or a client-side handshake. An appropriate place to call {#start_tls} is in + # your redefined {#post_init} method, or in the {#connection_completed} handler for + # an outbound connection. + # + # + # @option args [String] :cert_chain_file (nil) local path of a readable file that contants a chain of X509 certificates in + # the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail), + # with the most-resolved certificate at the top of the file, successive intermediate + # certs in the middle, and the root (or CA) cert at the bottom. + # + # @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). + # + # @option args [Boolean] :verify_peer (false) indicates whether a server should request a certificate from a peer, to be verified by user code. + # If true, the {#ssl_verify_peer} callback on the {EventMachine::Connection} object is called with each certificate + # in the certificate chain provided by the peer. See documentation on {#ssl_verify_peer} for how to use this. + # + # @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. + # + # + # @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. + # + # @option args [String] :ecdh_curve (nil) The curve for ECDHE ciphers. See available ciphers with 'openssl ecparam -list_curves' + # + # @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' + # + # @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}. + # + # @example Using TLS with EventMachine + # + # require 'rubygems' + # require 'eventmachine' + # + # module Handler + # def post_init + # start_tls(:private_key_file => '/tmp/server.key', :cert_chain_file => '/tmp/server.crt', :verify_peer => false) + # end + # end + # + # EventMachine.run do + # EventMachine.start_server("127.0.0.1", 9999, Handler) + # end + # + # @param [Hash] args + # + # @todo support passing an encryption parameter, which can be string or Proc, to get a passphrase + # for encrypted private keys. + # @todo support passing key material via raw strings or Procs that return strings instead of + # just filenames. + # + # @see #ssl_verify_peer + def start_tls args={} + priv_key = args[:private_key_file] + cert_chain = args[:cert_chain_file] + verify_peer = args[:verify_peer] + sni_hostname = args[:sni_hostname] + cipher_list = args[:cipher_list] + ssl_version = args[:ssl_version] + ecdh_curve = args[:ecdh_curve] + dhparam = args[:dhparam] + fail_if_no_peer_cert = args[:fail_if_no_peer_cert] + + [priv_key, cert_chain].each do |file| + next if file.nil? or file.empty? + raise FileNotFoundException, + "Could not find #{file} for start_tls" unless File.exist? file + end + + protocols_bitmask = 0 + if ssl_version.nil? + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1 + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1 + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2 + else + [ssl_version].flatten.each do |p| + case p.to_s.downcase + when 'sslv2' + protocols_bitmask |= EventMachine::EM_PROTO_SSLv2 + when 'sslv3' + protocols_bitmask |= EventMachine::EM_PROTO_SSLv3 + when 'tlsv1' + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1 + when 'tlsv1_1' + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1 + when 'tlsv1_2' + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2 + else + raise("Unrecognized SSL/TLS Protocol: #{p}") + end + end + end + + 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) + EventMachine::start_tls @signature + end + + # If [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is active on the connection, returns the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509) + # as a string, in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail). This can then be used for arbitrary validation + # of a peer's certificate in your code. + # + # This should be called in/after the {#ssl_handshake_completed} callback, which indicates + # that SSL/TLS is active. Using this callback is important, because the certificate may not + # be available until the time it is executed. Using #post_init or #connection_completed is + # not adequate, because the SSL handshake may still be taking place. + # + # This method will return `nil` if: + # + # * EventMachine is not built with [OpenSSL](http://www.openssl.org) support + # * [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is not active on the connection + # * TLS handshake is not yet complete + # * Remote peer for any other reason has not presented a certificate + # + # + # @example Getting peer TLS certificate information in EventMachine + # + # module Handler + # def post_init + # puts "Starting TLS" + # start_tls + # end + # + # def ssl_handshake_completed + # puts get_peer_cert + # close_connection + # end + # + # def unbind + # EventMachine::stop_event_loop + # end + # end + # + # EventMachine.run do + # EventMachine.connect "mail.google.com", 443, Handler + # end + # + # # Will output: + # # -----BEGIN CERTIFICATE----- + # # MIIDIjCCAougAwIBAgIQbldpChBPqv+BdPg4iwgN8TANBgkqhkiG9w0BAQUFADBM + # # MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg + # # THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wODA1MDIxNjMyNTRaFw0w + # # OTA1MDIxNjMyNTRaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh + # # MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRgw + # # FgYDVQQDEw9tYWlsLmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ + # # AoGBALlkxdh2QXegdElukCSOV2+8PKiONIS+8Tu9K7MQsYpqtLNC860zwOPQ2NLI + # # 3Zp4jwuXVTrtzGuiqf5Jioh35Ig3CqDXtLyZoypjZUQcq4mlLzHlhIQ4EhSjDmA7 + # # Ffw9y3ckSOQgdBQWNLbquHh9AbEUjmhkrYxIqKXeCnRKhv6nAgMBAAGjgecwgeQw + # # KAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwNgYDVR0f + # # BC8wLTAroCmgJ4YlaHR0cDovL2NybC50aGF3dGUuY29tL1RoYXd0ZVNHQ0NBLmNy + # # bDByBggrBgEFBQcBAQRmMGQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0 + # # ZS5jb20wPgYIKwYBBQUHMAKGMmh0dHA6Ly93d3cudGhhd3RlLmNvbS9yZXBvc2l0 + # # b3J5L1RoYXd0ZV9TR0NfQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEF + # # BQADgYEAsRwpLg1dgCR1gYDK185MFGukXMeQFUvhGqF8eT/CjpdvezyKVuz84gSu + # # 6ccMXgcPQZGQN/F4Xug+Q01eccJjRSVfdvR5qwpqCj+6BFl5oiKDBsveSkrmL5dz + # # s2bn7TdTSYKcLeBkjXxDLHGBqLJ6TNCJ3c4/cbbG5JhGvoema94= + # # -----END CERTIFICATE----- + # + # You can do whatever you want with the certificate String, such as load it + # as a certificate object using the OpenSSL library, and check its fields. + # + # @return [String] the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509), in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail), + # if TLS is active on the connection + # + # @see Connection#start_tls + # @see Connection#ssl_handshake_completed + def get_peer_cert + EventMachine::get_peer_cert @signature + end + + def get_cipher_bits + EventMachine::get_cipher_bits @signature + end + + def get_cipher_name + EventMachine::get_cipher_name @signature + end + + def get_cipher_protocol + EventMachine::get_cipher_protocol @signature + end + + def get_sni_hostname + EventMachine::get_sni_hostname @signature + end + + # Sends UDP messages. + # + # This method may be called from any Connection object that refers + # to an open datagram socket (see EventMachine#open_datagram_socket). + # The method sends a UDP (datagram) packet containing the data you specify, + # to a remote peer specified by the IP address and port that you give + # as parameters to the method. + # Observe that you may send a zero-length packet (empty string). + # However, you may not send an arbitrarily-large data packet because + # your operating system will enforce a platform-specific limit on + # the size of the outbound packet. (Your kernel + # will respond in a platform-specific way if you send an overlarge + # packet: some will send a truncated packet, some will complain, and + # some will silently drop your request). + # On LANs, it's usually OK to send datagrams up to about 4000 bytes in length, + # but to be really safe, send messages smaller than the Ethernet-packet + # size (typically about 1400 bytes). Some very restrictive WANs + # will either drop or truncate packets larger than about 500 bytes. + # + # @param [String] data Data to send asynchronously + # @param [String] recipient_address IP address of the recipient + # @param [String] recipient_port Port of the recipient + def send_datagram data, recipient_address, recipient_port + data = data.to_s + size = data.bytesize if data.respond_to?(:bytesize) + size ||= data.size + EventMachine::send_datagram @signature, data, size, recipient_address, Integer(recipient_port) + end + + + # This method is used with stream-connections to obtain the identity + # of the remotely-connected peer. If a peername is available, this method + # returns a sockaddr structure. The method returns nil if no peername is available. + # You can use Socket.unpack_sockaddr_in and its variants to obtain the + # values contained in the peername structure returned from #get_peername. + # + # @example How to get peer IP address and port with EventMachine + # + # require 'socket' + # + # module Handler + # def receive_data data + # port, ip = Socket.unpack_sockaddr_in(get_peername) + # puts "got #{data.inspect} from #{ip}:#{port}" + # end + # end + def get_peername + EventMachine::get_peername @signature + end + + # Used with stream-connections to obtain the identity + # of the local side of the connection. If a local name is available, this method + # returns a sockaddr structure. The method returns nil if no local name is available. + # You can use {Socket.unpack_sockaddr_in} and its variants to obtain the + # values contained in the local-name structure returned from this method. + # + # @example + # + # require 'socket' + # + # module Handler + # def receive_data data + # port, ip = Socket.unpack_sockaddr_in(get_sockname) + # puts "got #{data.inspect}" + # end + # end + def get_sockname + EventMachine::get_sockname @signature + end + + # Returns the PID (kernel process identifier) of a subprocess + # associated with this Connection object. For use with {EventMachine.popen} + # and similar methods. Returns nil when there is no meaningful subprocess. + # + # @return [Integer] + def get_pid + EventMachine::get_subprocess_pid @signature + end + + # Returns a subprocess exit status. Only useful for {EventMachine.popen}. Call it in your + # {#unbind} handler. + # + # @return [Integer] + def get_status + EventMachine::get_subprocess_status @signature + end + + # The number of seconds since the last send/receive activity on this connection. + def get_idle_time + EventMachine::get_idle_time @signature + end + + # comm_inactivity_timeout returns the current value (float in seconds) of the inactivity-timeout + # property of network-connection and datagram-socket objects. A nonzero value + # indicates that the connection or socket will automatically be closed if no read or write + # activity takes place for at least that number of seconds. + # A zero value (the default) specifies that no automatic timeout will take place. + def comm_inactivity_timeout + EventMachine::get_comm_inactivity_timeout @signature + end + + # Allows you to set the inactivity-timeout property for + # a network connection or datagram socket. Specify a non-negative float value in seconds. + # If the value is greater than zero, the connection or socket will automatically be closed + # if no read or write activity takes place for at least that number of seconds. + # Specify a value of zero to indicate that no automatic timeout should take place. + # Zero is the default value. + def comm_inactivity_timeout= value + EventMachine::set_comm_inactivity_timeout @signature, value.to_f + end + alias set_comm_inactivity_timeout comm_inactivity_timeout= + + # The duration after which a TCP connection in the connecting state will fail. + # It is important to distinguish this value from {EventMachine::Connection#comm_inactivity_timeout}, + # which looks at how long since data was passed on an already established connection. + # The value is a float in seconds. + # + # @return [Float] The duration after which a TCP connection in the connecting state will fail, in seconds. + def pending_connect_timeout + EventMachine::get_pending_connect_timeout @signature + end + + # Sets the duration after which a TCP connection in a + # connecting state will fail. + # + # @param [Float, #to_f] value Connection timeout in seconds + def pending_connect_timeout= value + EventMachine::set_pending_connect_timeout @signature, value.to_f + end + alias set_pending_connect_timeout pending_connect_timeout= + + # Reconnect to a given host/port with the current instance + # + # @param [String] server Hostname or IP address + # @param [Integer] port Port to reconnect to + def reconnect server, port + EventMachine::reconnect server, port, self + end + + + # Like {EventMachine::Connection#send_data}, this sends data to the remote end of + # the network connection. {EventMachine::Connection#send_file_data} takes a + # filename as an argument, though, and sends the contents of the file, in one + # chunk. + # + # @param [String] filename Local path of the file to send + # + # @see #send_data + # @author Kirk Haines + def send_file_data filename + EventMachine::send_file_data @signature, filename + end + + # Open a file on the filesystem and send it to the remote peer. This returns an + # object of type {EventMachine::Deferrable}. The object's callbacks will be executed + # on the reactor main thread when the file has been completely scheduled for + # transmission to the remote peer. Its errbacks will be called in case of an error (such as file-not-found). + # This method employs various strategies to achieve the fastest possible performance, + # balanced against minimum consumption of memory. + # + # Warning: this feature has an implicit dependency on an outboard extension, + # evma_fastfilereader. You must install this extension in order to use {#stream_file_data} + # with files larger than a certain size (currently 8192 bytes). + # + # @option args [Boolean] :http_chunks (false) If true, this method will stream the file data in a format + # compatible with the HTTP chunked-transfer encoding + # + # @param [String] filename Local path of the file to stream + # @param [Hash] args Options + # + # @return [EventMachine::Deferrable] + def stream_file_data filename, args={} + EventMachine::FileStreamer.new( self, filename, args ) + end + + # Watches connection for readability. Only possible if the connection was created + # using {EventMachine.attach} and had {EventMachine.notify_readable}/{EventMachine.notify_writable} defined on the handler. + # + # @see #notify_readable? + def notify_readable= mode + EventMachine::set_notify_readable @signature, mode + end + + # @return [Boolean] true if the connection is being watched for readability. + def notify_readable? + EventMachine::is_notify_readable @signature + end + + # Watches connection for writeability. Only possible if the connection was created + # using {EventMachine.attach} and had {EventMachine.notify_readable}/{EventMachine.notify_writable} defined on the handler. + # + # @see #notify_writable? + def notify_writable= mode + EventMachine::set_notify_writable @signature, mode + end + + # Returns true if the connection is being watched for writability. + def notify_writable? + EventMachine::is_notify_writable @signature + end + + # Pause a connection so that {#send_data} and {#receive_data} events are not fired until {#resume} is called. + # @see #resume + def pause + EventMachine::pause_connection @signature + end + + # Resume a connection's {#send_data} and {#receive_data} events. + # @see #pause + def resume + EventMachine::resume_connection @signature + end + + # @return [Boolean] true if the connect was paused using {EventMachine::Connection#pause}. + # @see #pause + # @see #resume + def paused? + EventMachine::connection_paused? @signature + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb new file mode 100644 index 0000000..18a6d31 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb @@ -0,0 +1,210 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 Jul 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Deferrable + autoload :Pool, 'em/deferrable/pool' + + # Specify a block to be executed if and when the Deferrable object receives + # a status of :succeeded. See #set_deferred_status for more information. + # + # Calling this method on a Deferrable object whose status is not yet known + # will cause the callback block to be stored on an internal list. + # If you call this method on a Deferrable whose status is :succeeded, the + # block will be executed immediately, receiving the parameters given to the + # prior #set_deferred_status call. + # + #-- + # If there is no status, add a callback to an internal list. + # If status is succeeded, execute the callback immediately. + # If status is failed, do nothing. + # + def callback &block + return unless block + @deferred_status ||= :unknown + if @deferred_status == :succeeded + block.call(*@deferred_args) + elsif @deferred_status != :failed + @callbacks ||= [] + @callbacks.unshift block # << block + end + self + end + + # Cancels an outstanding callback to &block if any. Undoes the action of #callback. + # + def cancel_callback block + @callbacks ||= [] + @callbacks.delete block + end + + # Specify a block to be executed if and when the Deferrable object receives + # a status of :failed. See #set_deferred_status for more information. + #-- + # If there is no status, add an errback to an internal list. + # If status is failed, execute the errback immediately. + # If status is succeeded, do nothing. + # + def errback &block + return unless block + @deferred_status ||= :unknown + if @deferred_status == :failed + block.call(*@deferred_args) + elsif @deferred_status != :succeeded + @errbacks ||= [] + @errbacks.unshift block # << block + end + self + end + + # Cancels an outstanding errback to &block if any. Undoes the action of #errback. + # + def cancel_errback block + @errbacks ||= [] + @errbacks.delete block + end + + # Sets the "disposition" (status) of the Deferrable object. See also the large set of + # sugarings for this method. + # Note that if you call this method without arguments, + # no arguments will be passed to the callback/errback. + # If the user has coded these with arguments, then the + # user code will throw an argument exception. + # Implementors of deferrable classes must + # document the arguments they will supply to user callbacks. + # + # OBSERVE SOMETHING VERY SPECIAL here: you may call this method even + # on the INSIDE of a callback. This is very useful when a previously-registered + # callback wants to change the parameters that will be passed to subsequently-registered + # ones. + # + # You may give either :succeeded or :failed as the status argument. + # + # If you pass :succeeded, then all of the blocks passed to the object using the #callback + # method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks + # passed to the object using #errback will be discarded. + # + # If you pass :failed, then all of the blocks passed to the object using the #errback + # method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks + # passed to the object using # callback will be discarded. + # + # If you pass any arguments to #set_deferred_status in addition to the status argument, + # they will be passed as arguments to any callbacks or errbacks that are executed. + # It's your responsibility to ensure that the argument lists specified in your callbacks and + # errbacks match the arguments given in calls to #set_deferred_status, otherwise Ruby will raise + # an ArgumentError. + # + #-- + # We're shifting callbacks off and discarding them as we execute them. + # This is valid because by definition callbacks are executed no more than + # once. It also has the magic effect of permitting recursive calls, which + # means that a callback can call #set_deferred_status and change the parameters + # that will be sent to subsequent callbacks down the chain. + # + # Changed @callbacks and @errbacks from push/shift to unshift/pop, per suggestion + # by Kirk Haines, to work around the memory leak bug that still exists in many Ruby + # versions. + # + # Changed 15Sep07: after processing callbacks or errbacks, CLEAR the other set of + # handlers. This gets us a little closer to the behavior of Twisted's "deferred," + # which only allows status to be set once. Prior to making this change, it was possible + # to "succeed" a Deferrable (triggering its callbacks), and then immediately "fail" it, + # triggering its errbacks! That is clearly undesirable, but it's just as undesirable + # to raise an exception is status is set more than once on a Deferrable. The latter + # behavior would invalidate the idiom of resetting arguments by setting status from + # within a callback or errback, but more seriously it would cause spurious errors + # if a Deferrable was timed out and then an attempt was made to succeed it. See the + # comments under the new method #timeout. + # + def set_deferred_status status, *args + cancel_timeout + @errbacks ||= nil + @callbacks ||= nil + @deferred_status = status + @deferred_args = args + case @deferred_status + when :succeeded + if @callbacks + while cb = @callbacks.pop + cb.call(*@deferred_args) + end + end + @errbacks.clear if @errbacks + when :failed + if @errbacks + while eb = @errbacks.pop + eb.call(*@deferred_args) + end + end + @callbacks.clear if @callbacks + end + end + + + # Setting a timeout on a Deferrable causes it to go into the failed state after + # the Timeout expires (passing no arguments to the object's errbacks). + # Setting the status at any time prior to a call to the expiration of the timeout + # will cause the timer to be cancelled. + def timeout seconds, *args + cancel_timeout + me = self + @deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)} + self + end + + # Cancels an outstanding timeout if any. Undoes the action of #timeout. + # + def cancel_timeout + @deferred_timeout ||= nil + if @deferred_timeout + @deferred_timeout.cancel + @deferred_timeout = nil + end + end + + + # Sugar for set_deferred_status(:succeeded, ...) + # + def succeed *args + set_deferred_status :succeeded, *args + end + alias set_deferred_success succeed + + # Sugar for set_deferred_status(:failed, ...) + # + def fail *args + set_deferred_status :failed, *args + end + alias set_deferred_failure fail + end + + + # DefaultDeferrable is an otherwise empty class that includes Deferrable. + # This is very useful when you just need to return a Deferrable object + # as a way of communicating deferred status to some other part of a program. + class DefaultDeferrable + include Deferrable + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb new file mode 100644 index 0000000..3c278ee --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb @@ -0,0 +1,2 @@ +warn "EM::Deferrable::Pool is deprecated, please use EM::Pool" +EM::Deferrable::Pool = EM::Pool \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb new file mode 100644 index 0000000..074ffed --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb @@ -0,0 +1,73 @@ +module EventMachine + # Utility class that is useful for file monitoring. Supported events are + # + # * File is modified + # * File is deleted + # * File is moved + # + # @note On Mac OS X, file watching only works when kqueue is enabled + # + # @see EventMachine.watch_file + class FileWatch < Connection + # @private + Cmodified = 'modified'.freeze + # @private + Cdeleted = 'deleted'.freeze + # @private + Cmoved = 'moved'.freeze + + + # @private + def receive_data(data) + case data + when Cmodified + file_modified + when Cdeleted + file_deleted + when Cmoved + file_moved + end + end + + # Returns the path that is being monitored. + # + # @note Current implementation does not pick up on the new filename after a rename occurs. + # + # @return [String] + # @see EventMachine.watch_file + def path + @path + end + + # Will be called when the file is modified. Supposed to be redefined by subclasses. + # + # @abstract + def file_modified + end + + # Will be called when the file is deleted. Supposed to be redefined by subclasses. + # When the file is deleted, stop_watching will be called after this to make sure everything is + # cleaned up correctly. + # + # @note On Linux (with {http://en.wikipedia.org/wiki/Inotify inotify}), this method will not be called until *all* open file descriptors to + # the file have been closed. + # + # @abstract + def file_deleted + end + + # Will be called when the file is moved or renamed. Supposed to be redefined by subclasses. + # + # @abstract + def file_moved + end + + # Discontinue monitoring of the file. + # + # This involves cleaning up the underlying monitoring details with kqueue/inotify, and in turn firing {EventMachine::Connection#unbind}. + # This will be called automatically when a file is deleted. User code may call it as well. + def stop_watching + EventMachine::unwatch_filename(@signature) + end # stop_watching + end # FileWatch +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb new file mode 100644 index 0000000..4affbf5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb @@ -0,0 +1,61 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 Jul 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +#-- +# This defines EventMachine::Deferrable#future, which requires +# that the rest of EventMachine::Deferrable has already been seen. +# (It's in deferrable.rb.) + +module EventMachine + module Deferrable + + # A future is a sugaring of a typical deferrable usage. + #-- + # Evaluate arg (which may be an expression or a block). + # What's the class of arg? + # If arg is an ordinary expression, then return it. + # If arg is deferrable (responds to :set_deferred_status), + # then look at the arguments. If either callback or errback + # are defined, then use them. If neither are defined, then + # use the supplied block (if any) as the callback. + # Then return arg. + def self.future arg, cb=nil, eb=nil, &blk + arg = arg.call if arg.respond_to?(:call) + + if arg.respond_to?(:set_deferred_status) + if cb || eb + arg.callback(&cb) if cb + arg.errback(&eb) if eb + else + arg.callback(&blk) if blk + end + end + + arg + end + + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb new file mode 100644 index 0000000..a30b9dd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb @@ -0,0 +1,252 @@ +module EventMachine + # A simple iterator for concurrent asynchronous work. + # + # Unlike ruby's built-in iterators, the end of the current iteration cycle is signaled manually, + # instead of happening automatically after the yielded block finishes executing. For example: + # + # (0..10).each{ |num| } + # + # becomes: + # + # EM::Iterator.new(0..10).each{ |num,iter| iter.next } + # + # This is especially useful when doing asynchronous work via reactor libraries and + # functions. For example, given a sync and async http api: + # + # response = sync_http_get(url); ... + # async_http_get(url){ |response| ... } + # + # a synchronous iterator such as: + # + # responses = urls.map{ |url| sync_http_get(url) } + # ... + # puts 'all done!' + # + # could be written as: + # + # EM::Iterator.new(urls).map(proc{ |url,iter| + # async_http_get(url){ |res| + # iter.return(res) + # } + # }, proc{ |responses| + # ... + # puts 'all done!' + # }) + # + # Now, you can take advantage of the asynchronous api to issue requests in parallel. For example, + # to fetch 10 urls at a time, simply pass in a concurrency of 10: + # + # EM::Iterator.new(urls, 10).each do |url,iter| + # async_http_get(url){ iter.next } + # end + # + class Iterator + Stop = "EM::Stop" + # Create a new parallel async iterator with specified concurrency. + # + # i = EM::Iterator.new(1..100, 10) + # + # will create an iterator over the range that processes 10 items at a time. Iteration + # is started via #each, #map or #inject + # + # The list may either be an array-like object, or a proc that returns a new object + # to be processed each time it is called. If a proc is used, it must return + # EventMachine::Iterator::Stop to signal the end of the iterations. + # + def initialize(list, concurrency = 1) + raise ArgumentError, 'concurrency must be bigger than zero' unless (concurrency > 0) + if list.respond_to?(:call) + @list = nil + @list_proc = list + elsif list.respond_to?(:to_a) + @list = list.to_a.dup + @list_proc = nil + else + raise ArgumentError, 'argument must be a proc or an array' + end + @concurrency = concurrency + + @started = false + @ended = false + end + + # Change the concurrency of this iterator. Workers will automatically be spawned or destroyed + # to accomodate the new concurrency level. + # + def concurrency=(val) + old = @concurrency + @concurrency = val + + spawn_workers if val > old and @started and !@ended + end + attr_reader :concurrency + + # Iterate over a set of items using the specified block or proc. + # + # EM::Iterator.new(1..100).each do |num, iter| + # puts num + # iter.next + # end + # + # An optional second proc is invoked after the iteration is complete. + # + # EM::Iterator.new(1..100).each( + # proc{ |num,iter| iter.next }, + # proc{ puts 'all done' } + # ) + # + def each(foreach=nil, after=nil, &blk) + raise ArgumentError, 'proc or block required for iteration' unless foreach ||= blk + raise RuntimeError, 'cannot iterate over an iterator more than once' if @started or @ended + + @started = true + @pending = 0 + @workers = 0 + + all_done = proc{ + after.call if after and @ended and @pending == 0 + } + + @process_next = proc{ + # p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list] + unless @ended or @workers > @concurrency + item = next_item() + if item.equal?(Stop) + @ended = true + @workers -= 1 + all_done.call + else + @pending += 1 + + is_done = false + on_done = proc{ + raise RuntimeError, 'already completed this iteration' if is_done + is_done = true + + @pending -= 1 + + if @ended + all_done.call + else + EM.next_tick(@process_next) + end + } + class << on_done + alias :next :call + end + + foreach.call(item, on_done) + end + else + @workers -= 1 + end + } + + spawn_workers + + self + end + + # Collect the results of an asynchronous iteration into an array. + # + # EM::Iterator.new(%w[ pwd uptime uname date ], 2).map(proc{ |cmd,iter| + # EM.system(cmd){ |output,status| + # iter.return(output) + # } + # }, proc{ |results| + # p results + # }) + # + def map(foreach, after) + index = 0 + + inject([], proc{ |results,item,iter| + i = index + index += 1 + + is_done = false + on_done = proc{ |res| + raise RuntimeError, 'already returned a value for this iteration' if is_done + is_done = true + + results[i] = res + iter.return(results) + } + class << on_done + alias :return :call + def next + raise NoMethodError, 'must call #return on a map iterator' + end + end + + foreach.call(item, on_done) + }, proc{ |results| + after.call(results) + }) + end + + # Inject the results of an asynchronous iteration onto a given object. + # + # EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter| + # EM.system(cmd){ |output,status| + # hash[cmd] = status.exitstatus == 0 ? output.strip : nil + # iter.return(hash) + # } + # }, proc{ |results| + # p results + # }) + # + def inject(obj, foreach, after) + each(proc{ |item,iter| + is_done = false + on_done = proc{ |res| + raise RuntimeError, 'already returned a value for this iteration' if is_done + is_done = true + + obj = res + iter.next + } + class << on_done + alias :return :call + def next + raise NoMethodError, 'must call #return on an inject iterator' + end + end + + foreach.call(obj, item, on_done) + }, proc{ + after.call(obj) + }) + end + + private + + # Spawn workers to consume items from the iterator's enumerator based on the current concurrency level. + # + def spawn_workers + EM.next_tick(start_worker = proc{ + if @workers < @concurrency and !@ended + # p [:spawning_worker, :workers=, @workers, :concurrency=, @concurrency, :ended=, @ended] + @workers += 1 + @process_next.call + EM.next_tick(start_worker) + end + }) + nil + end + + # Return the next item from @list or @list_proc. + # Once items have run out, will return EM::Iterator::Stop. Procs must supply this themselves + def next_item + if @list_proc + @list_proc.call + else + @list.empty? ? Stop : @list.shift + end + end + end +end + +# TODO: pass in one object instead of two? .each{ |iter| puts iter.current; iter.next } +# TODO: support iter.pause/resume/stop/break/continue? +# TODO: create some exceptions instead of using RuntimeError diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb new file mode 100644 index 0000000..9a51c39 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb @@ -0,0 +1,66 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 Jul 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +=begin + +Message Routing in EventMachine. + +The goal here is to enable "routing points," objects that can send and receive +"messages," which are delimited streams of bytes. The boundaries of a message +are preserved as it passes through the reactor system. + +There will be several module methods defined in EventMachine to create route-point +objects (which will probably have a base class of EventMachine::MessageRouter +until someone suggests a better name). + +As with I/O objects, routing objects will receive events by having the router +core call methods on them. And of course user code can and will define handlers +to deal with events of interest. + +The message router base class only really needs a receive_message method. There will +be an EM module-method to send messages, in addition to the module methods to create +the various kinds of message receivers. + +The simplest kind of message receiver object can receive messages by being named +explicitly in a parameter to EM#send_message. More sophisticated receivers can define +pub-sub selectors and message-queue names. And they can also define channels for +route-points in other processes or even on other machines. + +A message is NOT a marshallable entity. Rather, it's a chunk of flat content more like +an Erlang message. Initially, all content submitted for transmission as a message will +have the to_s method called on it. Eventually, we'll be able to transmit certain structured +data types (XML and YAML documents, Structs within limits) and have them reconstructed +on the other end. + +A fundamental goal of the message-routing capability is to interoperate seamlessly with +external systems, including non-Ruby systems like ActiveMQ. We will define various protocol +handlers for things like Stomp and possibly AMQP, but these will be wrapped up and hidden +from the users of the basic routing capability. + +As with Erlang, a critical goal is for programs that are built to use message-passing to work +WITHOUT CHANGE when the code is re-based on a multi-process system. + +=end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb new file mode 100644 index 0000000..2cb3662 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb @@ -0,0 +1,151 @@ +module EventMachine + # A simple async resource pool based on a resource and work queue. Resources + # are enqueued and work waits for resources to become available. + # + # @example + # require 'em-http-request' + # + # EM.run do + # pool = EM::Pool.new + # spawn = lambda { pool.add EM::HttpRequest.new('http://example.org') } + # 10.times { spawn[] } + # done, scheduled = 0, 0 + # + # check = lambda do + # done += 1 + # if done >= scheduled + # EM.stop + # end + # end + # + # pool.on_error { |conn| spawn[] } + # + # 100.times do |i| + # scheduled += 1 + # pool.perform do |conn| + # req = conn.get :path => '/', :keepalive => true + # + # req.callback do + # p [:success, conn.object_id, i, req.response.size] + # check[] + # end + # + # req.errback { check[] } + # + # req + # end + # end + # end + # + # Resources are expected to be controlled by an object responding to a + # deferrable/completion style API with callback and errback blocks. + # + class Pool + + def initialize + @resources = EM::Queue.new + @removed = [] + @contents = [] + @on_error = nil + end + + def add resource + @contents << resource + requeue resource + end + + def remove resource + @contents.delete resource + @removed << resource + end + + # Returns a list for introspection purposes only. You should *NEVER* call + # modification or work oriented methods on objects in this list. A good + # example use case is periodic statistics collection against a set of + # connection resources. + # + # @example + # pool.contents.inject(0) { |sum, connection| connection.num_bytes } + def contents + @contents.dup + end + + # Define a default catch-all for when the deferrables returned by work + # blocks enter a failed state. By default all that happens is that the + # resource is returned to the pool. If on_error is defined, this block is + # responsible for re-adding the resource to the pool if it is still usable. + # In other words, it is generally assumed that on_error blocks explicitly + # handle the rest of the lifetime of the resource. + def on_error *a, &b + @on_error = EM::Callback(*a, &b) + end + + # Perform a given #call-able object or block. The callable object will be + # called with a resource from the pool as soon as one is available, and is + # expected to return a deferrable. + # + # The deferrable will have callback and errback added such that when the + # deferrable enters a finished state, the object is returned to the pool. + # + # If on_error is defined, then objects are not automatically returned to the + # pool. + def perform(*a, &b) + work = EM::Callback(*a, &b) + + @resources.pop do |resource| + if removed? resource + @removed.delete resource + reschedule work + else + process work, resource + end + end + end + alias reschedule perform + + # A peek at the number of enqueued jobs waiting for resources + def num_waiting + @resources.num_waiting + end + + # Removed will show resources in a partial pruned state. Resources in the + # removed list may not appear in the contents list if they are currently in + # use. + def removed? resource + @removed.include? resource + end + + protected + def requeue resource + @resources.push resource + end + + def failure resource + if @on_error + @contents.delete resource + @on_error.call resource + # Prevent users from calling a leak. + @removed.delete resource + else + requeue resource + end + end + + def completion deferrable, resource + deferrable.callback { requeue resource } + deferrable.errback { failure resource } + end + + def process work, resource + deferrable = work.call resource + if deferrable.kind_of?(EM::Deferrable) + completion deferrable, resource + else + raise ArgumentError, "deferrable expected from work" + end + rescue + failure resource + raise + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb new file mode 100644 index 0000000..66e8943 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb @@ -0,0 +1,45 @@ +module EventMachine + + # This is subclassed from EventMachine::Connection for use with the process monitoring API. Read the + # documentation on the instance methods of this class, and for a full explanation see EventMachine.watch_process. + class ProcessWatch < Connection + # @private + Cfork = 'fork'.freeze + # @private + Cexit = 'exit'.freeze + + # @private + def receive_data(data) + case data + when Cfork + process_forked + when Cexit + process_exited + end + end + + # Returns the pid that EventMachine::watch_process was originally called with. + def pid + @pid + end + + # Should be redefined with the user's custom callback that will be fired when the prcess is forked. + # + # There is currently not an easy way to get the pid of the forked child. + def process_forked + end + + # Should be redefined with the user's custom callback that will be fired when the process exits. + # + # stop_watching is called automatically after this callback + def process_exited + end + + # Discontinue monitoring of the process. + # This will be called automatically when a process dies. User code may call it as well. + def stop_watching + EventMachine::unwatch_pid(@signature) + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb new file mode 100644 index 0000000..4bbc14f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb @@ -0,0 +1,123 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 13 Dec 07 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-08 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + + +module EventMachine + + # EM::DeferrableChildProcess is a sugaring of a common use-case + # involving EM::popen. + # Call the #open method on EM::DeferrableChildProcess, passing + # a command-string. #open immediately returns an EM::Deferrable + # object. It also schedules the forking of a child process, which + # will execute the command passed to #open. + # When the forked child terminates, the Deferrable will be signalled + # and execute its callbacks, passing the data that the child process + # wrote to stdout. + # + class DeferrableChildProcess < EventMachine::Connection + include EventMachine::Deferrable + + # @private + def initialize + super + @data = [] + end + + # Sugars a common use-case involving forked child processes. + # #open takes a String argument containing an shell command + # string (including arguments if desired). #open immediately + # returns an EventMachine::Deferrable object, without blocking. + # + # It also invokes EventMachine#popen to run the passed-in + # command in a forked child process. + # + # When the forked child terminates, the Deferrable that + # #open calls its callbacks, passing the data returned + # from the child process. + # + def self.open cmd + EventMachine.popen( cmd, DeferrableChildProcess ) + end + + # @private + def receive_data data + @data << data + end + + # @private + def unbind + succeed( @data.join ) + end + end + + # @private + class SystemCmd < EventMachine::Connection + def initialize cb + @cb = cb + @output = [] + end + def receive_data data + @output << data + end + def unbind + @cb.call @output.join(''), get_status if @cb + end + end + + # EM::system is a simple wrapper for EM::popen. It is similar to Kernel::system, but requires a + # single string argument for the command and performs no shell expansion. + # + # The block or proc passed to EM::system is called with two arguments: the output generated by the command, + # and a Process::Status that contains information about the command's execution. + # + # EM.run{ + # EM.system('ls'){ |output,status| puts output if status.exitstatus == 0 } + # } + # + # You can also supply an additional proc to send some data to the process: + # + # EM.run{ + # EM.system('sh', proc{ |process| + # process.send_data("echo hello\n") + # process.send_data("exit\n") + # }, proc{ |out,status| + # puts(out) + # }) + # } + # + # Like EventMachine.popen, EventMachine.system currently does not work on windows. + # It returns the pid of the spawned process. + def EventMachine::system cmd, *args, &cb + cb ||= args.pop if args.last.is_a? Proc + init = args.pop if args.last.is_a? Proc + + # merge remaining arguments into the command + cmd = [cmd, *args] if args.any? + + EM.get_subprocess_pid(EM.popen(cmd, SystemCmd, cb) do |c| + init[c] if init + end.signature) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb new file mode 100644 index 0000000..0c17906 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb @@ -0,0 +1,37 @@ +module EventMachine + # This module contains various protocol implementations, including: + # - HttpClient and HttpClient2 + # - Stomp + # - Memcache + # - SmtpClient and SmtpServer + # - SASLauth and SASLauthclient + # - LineProtocol, LineAndTextProtocol and LineText2 + # - HeaderAndContentProtocol + # - Postgres3 + # - ObjectProtocol + # + # The protocol implementations live in separate files in the protocols/ subdirectory, + # but are auto-loaded when they are first referenced in your application. + # + # EventMachine::Protocols is also aliased to EM::P for easier usage. + # + module Protocols + # TODO : various autotools are completely useless with the lack of naming + # convention, we need to correct that! + autoload :TcpConnectTester, 'em/protocols/tcptest' + autoload :HttpClient, 'em/protocols/httpclient' + autoload :HttpClient2, 'em/protocols/httpclient2' + autoload :LineAndTextProtocol, 'em/protocols/line_and_text' + autoload :HeaderAndContentProtocol, 'em/protocols/header_and_content' + autoload :LineText2, 'em/protocols/linetext2' + autoload :Stomp, 'em/protocols/stomp' + autoload :SmtpClient, 'em/protocols/smtpclient' + autoload :SmtpServer, 'em/protocols/smtpserver' + autoload :SASLauth, 'em/protocols/saslauth' + autoload :Memcache, 'em/protocols/memcache' + autoload :Postgres3, 'em/protocols/postgres3' + autoload :ObjectProtocol, 'em/protocols/object_protocol' + autoload :Socks4, 'em/protocols/socks4' + autoload :LineProtocol, 'em/protocols/line_protocol' + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb new file mode 100644 index 0000000..b5a465a --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb @@ -0,0 +1,138 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 Nov 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # === Usage + # + # class RequestHandler < EM::P::HeaderAndContentProtocol + # def receive_request headers, content + # p [:request, headers, content] + # end + # end + # + # EM.run{ + # EM.start_server 'localhost', 80, RequestHandler + # } + # + #-- + # Originally, this subclassed LineAndTextProtocol, which in + # turn relies on BufferedTokenizer, which doesn't gracefully + # handle the transitions between lines and binary text. + # Changed 13Sep08 by FCianfrocca. + class HeaderAndContentProtocol < Connection + include LineText2 + + ContentLengthPattern = /Content-length:\s*(\d+)/i + + def initialize *args + super + init_for_request + end + + def receive_line line + case @hc_mode + when :discard_blanks + unless line == "" + @hc_mode = :headers + receive_line line + end + when :headers + if line == "" + raise "unrecognized state" unless @hc_headers.length > 0 + if respond_to?(:receive_headers) + receive_headers @hc_headers + end + # @hc_content_length will be nil, not 0, if there was no content-length header. + if @hc_content_length.to_i > 0 + set_binary_mode @hc_content_length + else + dispatch_request + end + else + @hc_headers << line + if ContentLengthPattern =~ line + # There are some attacks that rely on sending multiple content-length + # headers. This is a crude protection, but needs to become tunable. + raise "extraneous content-length header" if @hc_content_length + @hc_content_length = $1.to_i + end + if @hc_headers.length == 1 and respond_to?(:receive_first_header_line) + receive_first_header_line line + end + end + else + raise "internal error, unsupported mode" + end + end + + def receive_binary_data text + @hc_content = text + dispatch_request + end + + def dispatch_request + if respond_to?(:receive_request) + receive_request @hc_headers, @hc_content + end + init_for_request + end + private :dispatch_request + + def init_for_request + @hc_mode = :discard_blanks + @hc_headers = [] + # originally was @hc_headers ||= []; @hc_headers.clear to get a performance + # boost, but it's counterproductive because a subclassed handler will have to + # call dup to use the header array we pass in receive_headers. + + @hc_content_length = nil + @hc_content = "" + end + private :init_for_request + + # Basically a convenience method. We might create a subclass that does this + # automatically. But it's such a performance killer. + def headers_2_hash hdrs + self.class.headers_2_hash hdrs + end + + class << self + def headers_2_hash hdrs + hash = {} + hdrs.each {|h| + if /\A([^\s:]+)\s*:\s*/ =~ h + tail = $'.dup + hash[ $1.downcase.gsub(/-/,"_").intern ] = tail + end + } + hash + end + end + + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb new file mode 100644 index 0000000..38b175c --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb @@ -0,0 +1,300 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # Note: This class is deprecated and will be removed. Please use EM-HTTP-Request instead. + # + # @example + # EventMachine.run { + # http = EventMachine::Protocols::HttpClient.request( + # :host => server, + # :port => 80, + # :request => "/index.html", + # :query_string => "parm1=value1&parm2=value2" + # ) + # http.callback {|response| + # puts response[:status] + # puts response[:headers] + # puts response[:content] + # } + # } + #-- + # TODO: + # Add streaming so we can support enormous POSTs. Current max is 20meg. + # Timeout for connections that run too long or hang somewhere in the middle. + # Persistent connections (HTTP/1.1), may need a associated delegate object. + # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's + # DNS lookups are unbelievably slow. + # HEAD requests. + # Convenience methods for requests. get, post, url, etc. + # SSL. + # Handle status codes like 304, 100, etc. + # Refactor this code so that protocol errors all get handled one way (an exception?), + # instead of sprinkling set_deferred_status :failed calls everywhere. + class HttpClient < Connection + include EventMachine::Deferrable + + MaxPostContentLength = 20 * 1024 * 1024 + + def initialize + warn "HttpClient is deprecated and will be removed. EM-Http-Request should be used instead." + @connected = false + end + + # @param args [Hash] The request arguments + # @option args [String] :host The host IP/DNS name + # @option args [Integer] :port The port to connect too + # @option args [String] :verb The request type [GET | POST | DELETE | PUT] + # @option args [String] :request The request path + # @option args [Hash] :basic_auth The basic auth credentials (:username and :password) + # @option args [String] :content The request content + # @option args [String] :contenttype The content type (e.g. text/plain) + # @option args [String] :query_string The query string + # @option args [String] :host_header The host header to set + # @option args [String] :cookie Cookies to set + def self.request( args = {} ) + args[:port] ||= 80 + EventMachine.connect( args[:host], args[:port], self ) {|c| + # According to the docs, we will get here AFTER post_init is called. + c.instance_eval {@args = args} + } + end + + def post_init + @start_time = Time.now + @data = "" + @read_state = :base + end + + # We send the request when we get a connection. + # AND, we set an instance variable to indicate we passed through here. + # That allows #unbind to know whether there was a successful connection. + # NB: This naive technique won't work when we have to support multiple + # requests on a single connection. + def connection_completed + @connected = true + send_request @args + end + + def send_request args + args[:verb] ||= args[:method] # Support :method as an alternative to :verb. + args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified? + + verb = args[:verb].to_s.upcase + unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb) + set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type + return # NOTE THE EARLY RETURN, we're not sending any data. + end + + request = args[:request] || "/" + unless request[0,1] == "/" + request = "/" + request + end + + qs = args[:query_string] || "" + if qs.length > 0 and qs[0,1] != '?' + qs = "?" + qs + end + + version = args[:version] || "1.1" + + # Allow an override for the host header if it's not the connect-string. + host = args[:host_header] || args[:host] || "_" + # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default. + port = args[:port].to_i != 80 ? ":#{args[:port]}" : "" + + # POST items. + postcontenttype = args[:contenttype] || "application/octet-stream" + postcontent = args[:content] || "" + raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength + + # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise. + # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption. + req = [ + "#{verb} #{request}#{qs} HTTP/#{version}", + "Host: #{host}#{port}", + "User-agent: Ruby EventMachine", + ] + + if verb == "POST" || verb == "PUT" + req << "Content-type: #{postcontenttype}" + req << "Content-length: #{postcontent.length}" + end + + # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string. + # Eventually we will want to deal intelligently with arrays and hashes. + if args[:cookie] + req << "Cookie: #{args[:cookie]}" + end + + # Allow custom HTTP headers, e.g. SOAPAction + args[:custom_headers].each do |k,v| + req << "#{k}: #{v}" + end if args[:custom_headers] + + # Basic-auth stanza contributed by Matt Murphy. + if args[:basic_auth] + basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'') + req << "Authorization: Basic #{basic_auth_string}" + end + + req << "" + reqstring = req.map {|l| "#{l}\r\n"}.join + send_data reqstring + + if verb == "POST" || verb == "PUT" + send_data postcontent + end + end + + + def receive_data data + while data and data.length > 0 + case @read_state + when :base + # Perform any per-request initialization here and don't consume any data. + @data = "" + @headers = [] + @content_length = nil # not zero + @content = "" + @status = nil + @chunked = false + @chunk_length = nil + @read_state = :header + @connection_close = nil + when :header + ary = data.split( /\r?\n/m, 2 ) + if ary.length == 2 + data = ary.last + if ary.first == "" + if (@content_length and @content_length > 0) || @chunked || @connection_close + @read_state = :content + else + dispatch_response + @read_state = :base + end + else + @headers << ary.first + if @headers.length == 1 + parse_response_line + elsif ary.first =~ /\Acontent-length:\s*/i + # Only take the FIRST content-length header that appears, + # which we can distinguish because @content_length is nil. + # TODO, it's actually a fatal error if there is more than one + # content-length header, because the caller is presumptively + # a bad guy. (There is an exploit that depends on multiple + # content-length headers.) + @content_length ||= $'.to_i + elsif ary.first =~ /\Aconnection:\s*close/i + @connection_close = true + elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i + @chunked = true + end + end + else + @data << data + data = "" + end + when :content + if @chunked && @chunk_length + bytes_needed = @chunk_length - @chunk_read + new_data = data[0, bytes_needed] + @chunk_read += new_data.length + @content += new_data + data = data[bytes_needed..-1] || "" + if @chunk_length == @chunk_read && data[0,2] == "\r\n" + @chunk_length = nil + data = data[2..-1] + end + elsif @chunked + if (m = data.match(/\A(\S*)\r\n/m)) + data = data[m[0].length..-1] + @chunk_length = m[1].to_i(16) + @chunk_read = 0 + if @chunk_length == 0 + dispatch_response + @read_state = :base + end + end + elsif @content_length + # If there was no content-length header, we have to wait until the connection + # closes. Everything we get until that point is content. + # TODO: Must impose a content-size limit, and also must implement chunking. + # Also, must support either temporary files for large content, or calling + # a content-consumer block supplied by the user. + bytes_needed = @content_length - @content.length + @content += data[0, bytes_needed] + data = data[bytes_needed..-1] || "" + if @content_length == @content.length + dispatch_response + @read_state = :base + end + else + @content << data + data = "" + end + end + end + end + + + # We get called here when we have received an HTTP response line. + # It's an opportunity to throw an exception or trigger other exceptional + # handling. + def parse_response_line + if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/ + @status = $1.to_i + else + set_deferred_status :failed, { + :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this. + } + close_connection + end + end + private :parse_response_line + + def dispatch_response + @read_state = :base + set_deferred_status :succeeded, { + :content => @content, + :headers => @headers, + :status => @status + } + # TODO, we close the connection for now, but this is wrong for persistent clients. + close_connection + end + + def unbind + if !@connected + set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error. + elsif (@read_state == :content and @content_length == nil) + dispatch_response + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb new file mode 100644 index 0000000..0fb64e8 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb @@ -0,0 +1,600 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # Note: This class is deprecated and will be removed. Please use EM-HTTP-Request instead. + # + # === Usage + # + # EM.run{ + # conn = EM::Protocols::HttpClient2.connect 'google.com', 80 + # + # req = conn.get('/') + # req.callback{ |response| + # p(response.status) + # p(response.headers) + # p(response.content) + # } + # } + class HttpClient2 < Connection + include LineText2 + + def initialize + warn "HttpClient2 is deprecated and will be removed. EM-Http-Request should be used instead." + + @authorization = nil + @closed = nil + @requests = nil + end + + # @private + class Request + include Deferrable + + attr_reader :version + attr_reader :status + attr_reader :header_lines + attr_reader :headers + attr_reader :content + attr_reader :internal_error + + def initialize conn, args + @conn = conn + @args = args + @header_lines = [] + @headers = {} + @blanks = 0 + @chunk_trailer = nil + @chunking = nil + end + + def send_request + az = @args[:authorization] and az = "Authorization: #{az}\r\n" + + r = [ + "#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n", + "Host: #{@args[:host_header] || "_"}\r\n", + az || "", + "\r\n" + ] + @conn.send_data r.join + end + + + #-- + # + def receive_line ln + if @chunk_trailer + receive_chunk_trailer(ln) + elsif @chunking + receive_chunk_header(ln) + else + receive_header_line(ln) + end + end + + #-- + # + def receive_chunk_trailer ln + if ln.length == 0 + @conn.pop_request + succeed(self) + else + p "Received chunk trailer line" + end + end + + #-- + # Allow up to ten blank lines before we get a real response line. + # Allow no more than 100 lines in the header. + # + def receive_header_line ln + if ln.length == 0 + if @header_lines.length > 0 + process_header + else + @blanks += 1 + if @blanks > 10 + @conn.close_connection + end + end + else + @header_lines << ln + if @header_lines.length > 100 + @internal_error = :bad_header + @conn.close_connection + end + end + end + + #-- + # Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks. + # + def receive_chunk_header ln + if ln.length > 0 + chunksize = ln.to_i(16) + if chunksize > 0 + @conn.set_text_mode(ln.to_i(16)) + else + @content = @content ? @content.join : '' + @chunk_trailer = true + end + else + # We correctly come here after each chunk gets read. + # p "Got A BLANK chunk line" + end + + end + + + #-- + # We get a single chunk. Append it to the incoming content and switch back to line mode. + # + def receive_chunked_text text + # p "RECEIVED #{text.length} CHUNK" + (@content ||= []) << text + end + + + #-- + # TODO, inefficient how we're handling this. Part of it is done so as to + # make sure we don't have problems in detecting chunked-encoding, content-length, + # etc. + # + HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i + ClenRE = /\AContent-length:\s*(\d+)/i + ChunkedRE = /\ATransfer-encoding:\s*chunked/i + ColonRE = /\:\s*/ + + def process_header + unless @header_lines.first =~ HttpResponseRE + @conn.close_connection + @internal_error = :bad_request + end + @version = $1.dup + @status = $2.dup.to_i + + clen = nil + chunks = nil + @header_lines.each_with_index do |e,ix| + if ix > 0 + hdr,val = e.split(ColonRE,2) + (@headers[hdr.downcase] ||= []) << val + end + + if clen == nil and e =~ ClenRE + clen = $1.dup.to_i + end + if e =~ ChunkedRE + chunks = true + end + end + + if clen + # If the content length is zero we should not call set_text_mode, + # because a value of zero will make it wait forever, hanging the + # connection. Just return success instead, with empty content. + if clen == 0 then + @content = "" + @conn.pop_request + succeed(self) + else + @conn.set_text_mode clen + end + elsif chunks + @chunking = true + else + # Chunked transfer, multipart, or end-of-connection. + # For end-of-connection, we need to go the unbind + # method and suppress its desire to fail us. + p "NO CLEN" + p @args[:uri] + p @header_lines + @internal_error = :unsupported_clen + @conn.close_connection + end + end + private :process_header + + + def receive_text text + @chunking ? receive_chunked_text(text) : receive_sized_text(text) + end + + #-- + # At the present time, we only handle contents that have a length + # specified by the content-length header. + # + def receive_sized_text text + @content = text + @conn.pop_request + succeed(self) + end + end + + # Make a connection to a remote HTTP server. + # Can take either a pair of arguments (which will be interpreted as + # a hostname/ip-address and a port), or a hash. + # If the arguments are a hash, then supported values include: + # :host => a hostname or ip-address + # :port => a port number + # :ssl => true to enable ssl + def self.connect *args + if args.length == 2 + args = {:host=>args[0], :port=>args[1]} + else + args = args.first + end + + h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl]) + conn = EM.connect( h, prt, self ) + conn.start_tls if ssl + conn.set_default_host_header( h, prt, ssl ) + conn + end + + # Get a url + # + # req = conn.get(:uri => '/') + # req.callback{|response| puts response.content } + # + def get args + if args.is_a?(String) + args = {:uri=>args} + end + args[:verb] = "GET" + request args + end + + # Post to a url + # + # req = conn.post('/data') + # req.callback{|response| puts response.content } + #-- + # XXX there's no way to supply a POST body.. wtf? + def post args + if args.is_a?(String) + args = {:uri=>args} + end + args[:verb] = "POST" + request args + end + + + #-- + # Compute and remember a string to be used as the host header in HTTP requests + # unless the user overrides it with an argument to #request. + # + # @private + def set_default_host_header host, port, ssl + if (ssl and port != 443) or (!ssl and port != 80) + @host_header = "#{host}:#{port}" + else + @host_header = host + end + end + + + # @private + def post_init + super + @connected = EM::DefaultDeferrable.new + end + + # @private + def connection_completed + super + @connected.succeed + end + + #-- + # All pending requests, if any, must fail. + # We might come here without ever passing through connection_completed + # in case we can't connect to the server. We'll also get here when the + # connection closes (either because the server closes it, or we close it + # due to detecting an internal error or security violation). + # In either case, run down all pending requests, if any, and signal failure + # on them. + # + # Set and remember a flag (@closed) so we can immediately fail any + # subsequent requests. + # + # @private + def unbind + super + @closed = true + (@requests || []).each {|r| r.fail} + end + + # @private + def request args + args[:host_header] = @host_header unless args.has_key?(:host_header) + args[:authorization] = @authorization unless args.has_key?(:authorization) + r = Request.new self, args + if @closed + r.fail + else + (@requests ||= []).unshift r + @connected.callback {r.send_request} + end + r + end + + # @private + def receive_line ln + if req = @requests.last + req.receive_line ln + else + p "??????????" + p ln + end + end + + # @private + def receive_binary_data text + @requests.last.receive_text text + end + + #-- + # Called by a Request object when it completes. + # + # @private + def pop_request + @requests.pop + end + end + + +=begin + class HttpClient2x < Connection + include LineText2 + + # TODO: Make this behave appropriate in case a #connect fails. + # Currently, this produces no errors. + + # Make a connection to a remote HTTP server. + # Can take either a pair of arguments (which will be interpreted as + # a hostname/ip-address and a port), or a hash. + # If the arguments are a hash, then supported values include: + # :host => a hostname or ip-address; + # :port => a port number + #-- + # TODO, support optional encryption arguments like :ssl + def self.connect *args + if args.length == 2 + args = {:host=>args[0], :port=>args[1]} + else + args = args.first + end + + h,prt = args[:host],Integer(args[:port]) + EM.connect( h, prt, self, h, prt ) + end + + + #-- + # Sugars a connection that makes a single request and then + # closes the connection. Matches the behavior and the arguments + # of the original implementation of class HttpClient. + # + # Intended primarily for back compatibility, but the idiom + # is probably useful so it's not deprecated. + # We return a Deferrable, as did the original implementation. + # + # Because we're improving the way we deal with errors and exceptions + # (specifically, HTTP response codes other than 2xx will trigger the + # errback rather than the callback), this may break some existing code. + # + def self.request args + c = connect args + end + + #-- + # Requests can be pipelined. When we get a request, add it to the + # front of a queue as an array. The last element of the @requests + # array is always the oldest request received. Each element of the + # @requests array is a two-element array consisting of a hash with + # the original caller's arguments, and an initially-empty Ostruct + # containing the data we retrieve from the server's response. + # Maintain the instance variable @current_response, which is the response + # of the oldest pending request. That's just to make other code a little + # easier. If the variable doesn't exist when we come here, we're + # obviously the first request being made on the connection. + # + # The reason for keeping this method private (and requiring use of the + # convenience methods #get, #post, #head, etc) is to avoid the small + # performance penalty of canonicalizing the verb. + # + def request args + d = EventMachine::DefaultDeferrable.new + + if @closed + d.fail + return d + end + + o = OpenStruct.new + o.deferrable = d + (@requests ||= []).unshift [args, o] + @current_response ||= @requests.last.last + @connected.callback { + az = args[:authorization] and az = "Authorization: #{az}\r\n" + + r = [ + "#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n", + "Host: #{args[:host_header] || @host_header}\r\n", + az || "", + "\r\n" + ] + p r + send_data r.join + } + o.deferrable + end + private :request + + def get args + if args.is_a?(String) + args = {:uri=>args} + end + args[:verb] = "GET" + request args + end + + def initialize host, port + super + @host_header = "#{host}:#{port}" + end + def post_init + super + @connected = EM::DefaultDeferrable.new + end + + + def connection_completed + super + @connected.succeed + end + + #-- + # Make sure to throw away any leftover incoming data if we've + # been closed due to recognizing an error. + # + # Generate an internal error if we get an unreasonable number of + # header lines. It could be malicious. + # + def receive_line ln + p ln + return if @closed + + if ln.length > 0 + (@current_response.headers ||= []).push ln + abort_connection if @current_response.headers.length > 100 + else + process_received_headers + end + end + + #-- + # We come here when we've seen all the headers for a particular request. + # What we do next depends on the response line (which should be the + # first line in the header set), and whether there is content to read. + # We may transition into a text-reading state to read content, or + # we may abort the connection, or we may go right back into parsing + # responses for the next response in the chain. + # + # We make an ASSUMPTION that the first line is an HTTP response. + # Anything else produces an error that aborts the connection. + # This may not be enough, because it may be that responses to pipelined + # requests will come with a blank-line delimiter. + # + # Any non-2xx response will be treated as a fatal error, and abort the + # connection. We will set up the status and other response parameters. + # TODO: we will want to properly support 1xx responses, which some versions + # of IIS copiously generate. + # TODO: We need to give the option of not aborting the connection with certain + # non-200 responses, in order to work with NTLM and other authentication + # schemes that work at the level of individual connections. + # + # Some error responses will get sugarings. For example, we'll return the + # Location header in the response in case of a 301/302 response. + # + # Possible dispositions here: + # 1) No content to read (either content-length is zero or it's a HEAD request); + # 2) Switch to text mode to read a specific number of bytes; + # 3) Read a chunked or multipart response; + # 4) Read till the server closes the connection. + # + # Our reponse to the client can be either to wait till all the content + # has been read and then to signal caller's deferrable, or else to signal + # it when we finish the processing the headers and then expect the caller + # to have given us a block to call as the content comes in. And of course + # the latter gets stickier with chunks and multiparts. + # + HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i + ClenRE = /\AContent-length:\s*(\d+)/i + def process_received_headers + abort_connection unless @current_response.headers.first =~ HttpResponseRE + @current_response.version = $1.dup + st = $2.dup + @current_response.status = st.to_i + abort_connection unless st[0,1] == "2" + + clen = nil + @current_response.headers.each do |e| + if clen == nil and e =~ ClenRE + clen = $1.dup.to_i + end + end + + if clen + set_text_mode clen + end + end + private :process_received_headers + + + def receive_binary_data text + @current_response.content = text + @current_response.deferrable.succeed @current_response + @requests.pop + @current_response = (@requests.last || []).last + set_line_mode + end + + + + # We've received either a server error or an internal error. + # Close the connection and abort any pending requests. + #-- + # When should we call close_connection? It will cause #unbind + # to be fired. Should the user expect to see #unbind before + # we call #receive_http_error, or the other way around? + # + # Set instance variable @closed. That's used to inhibit further + # processing of any inbound data after an error has been recognized. + # + # We shouldn't have to worry about any leftover outbound data, + # because we call close_connection (not close_connection_after_writing). + # That ensures that any pipelined requests received after an error + # DO NOT get streamed out to the server on this connection. + # Very important. TODO, write a unit-test to establish that behavior. + # + def abort_connection + close_connection + @closed = true + @current_response.deferrable.fail( @current_response ) + end + + + #------------------------ + # Below here are user-overridable methods. + + end +=end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb new file mode 100644 index 0000000..784daf2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb @@ -0,0 +1,125 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + # A protocol that handles line-oriented data with interspersed binary text. + # + # This version is optimized for performance. See EventMachine::Protocols::LineText2 + # for a version which is optimized for correctness with regard to binary text blocks + # that can switch back to line mode. + class LineAndTextProtocol < Connection + MaxBinaryLength = 32*1024*1024 + + def initialize *args + super + lbp_init_line_state + end + + def receive_data data + if @lbp_mode == :lines + begin + @lpb_buffer.extract(data).each do |line| + receive_line(line.chomp) if respond_to?(:receive_line) + end + rescue + receive_error('overlength line') if respond_to?(:receive_error) + close_connection + return + end + else + if @lbp_binary_limit > 0 + wanted = @lbp_binary_limit - @lbp_binary_bytes_received + chunk = nil + if data.length > wanted + chunk = data.slice!(0...wanted) + else + chunk = data + data = "" + end + @lbp_binary_buffer[@lbp_binary_bytes_received...(@lbp_binary_bytes_received+chunk.length)] = chunk + @lbp_binary_bytes_received += chunk.length + if @lbp_binary_bytes_received == @lbp_binary_limit + receive_binary_data(@lbp_binary_buffer) if respond_to?(:receive_binary_data) + lbp_init_line_state + end + receive_data(data) if data.length > 0 + else + receive_binary_data(data) if respond_to?(:receive_binary_data) + data = "" + end + end + end + + def unbind + if @lbp_mode == :binary and @lbp_binary_limit > 0 + if respond_to?(:receive_binary_data) + receive_binary_data( @lbp_binary_buffer[0...@lbp_binary_bytes_received] ) + end + end + end + + # Set up to read the supplied number of binary bytes. + # This recycles all the data currently waiting in the line buffer, if any. + # If the limit is nil, then ALL subsequent data will be treated as binary + # data and passed to the upstream protocol handler as we receive it. + # If a limit is given, we'll hold the incoming binary data and not + # pass it upstream until we've seen it all, or until there is an unbind + # (in which case we'll pass up a partial). + # Specifying nil for the limit (the default) means there is no limit. + # Specifiyng zero for the limit will cause an immediate transition back to line mode. + # + def set_binary_mode size = nil + if @lbp_mode == :lines + if size == 0 + receive_binary_data("") if respond_to?(:receive_binary_data) + # Do no more work here. Stay in line mode and keep consuming data. + else + @lbp_binary_limit = size.to_i # (nil will be stored as zero) + if @lbp_binary_limit > 0 + raise "Overlength" if @lbp_binary_limit > MaxBinaryLength # arbitrary sanity check + @lbp_binary_buffer = "\0" * @lbp_binary_limit + @lbp_binary_bytes_received = 0 + end + + @lbp_mode = :binary + receive_data @lpb_buffer.flush + end + else + raise "invalid operation" + end + end + + #-- + # For internal use, establish protocol baseline for handling lines. + def lbp_init_line_state + @lpb_buffer = BufferedTokenizer.new("\n") + @lbp_mode = :lines + end + private :lbp_init_line_state + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb new file mode 100644 index 0000000..dfddae8 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb @@ -0,0 +1,29 @@ +module EventMachine + module Protocols + # LineProtocol will parse out newline terminated strings from a receive_data stream + # + # module Server + # include EM::P::LineProtocol + # + # def receive_line(line) + # send_data("you said: #{line}") + # end + # end + # + module LineProtocol + # @private + def receive_data data + (@buf ||= '') << data + + while @buf.slice!(/(.*?)\r?\n/) + receive_line($1) + end + end + + # Invoked with lines received over the network + def receive_line(line) + # stub + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb new file mode 100644 index 0000000..9fdf28b --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb @@ -0,0 +1,179 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + # In the grand, time-honored tradition of re-inventing the wheel, we offer + # here YET ANOTHER protocol that handles line-oriented data with interspersed + # binary text. This one trades away some of the performance optimizations of + # EventMachine::Protocols::LineAndTextProtocol in order to get better correctness + # with regard to binary text blocks that can switch back to line mode. It also + # permits the line-delimiter to change in midstream. + # This was originally written to support Stomp. + module LineText2 + # TODO! We're not enforcing the limits on header lengths and text-lengths. + # When we get around to that, call #receive_error if the user defined it, otherwise + # throw exceptions. + + MaxBinaryLength = 32*1024*1024 + + #-- + # Will loop internally until there's no data left to read. + # That way the user-defined handlers we call can modify the + # handling characteristics on a per-token basis. + # + def receive_data data + return unless (data and data.length > 0) + + # Do this stuff in lieu of a constructor. + @lt2_mode ||= :lines + @lt2_delimiter ||= "\n" + @lt2_linebuffer ||= [] + + remaining_data = data + + while remaining_data.length > 0 + if @lt2_mode == :lines + delimiter_string = case @lt2_delimiter + when Regexp + remaining_data.slice(@lt2_delimiter) + else + @lt2_delimiter + end + ix = remaining_data.index(delimiter_string) if delimiter_string + if ix + @lt2_linebuffer << remaining_data[0...ix] + ln = @lt2_linebuffer.join + @lt2_linebuffer.clear + if @lt2_delimiter == "\n" + ln.chomp! + end + receive_line ln + remaining_data = remaining_data[(ix+delimiter_string.length)..-1] + else + @lt2_linebuffer << remaining_data + remaining_data = "" + end + elsif @lt2_mode == :text + if @lt2_textsize + needed = @lt2_textsize - @lt2_textpos + will_take = if remaining_data.length > needed + needed + else + remaining_data.length + end + + @lt2_textbuffer << remaining_data[0...will_take] + tail = remaining_data[will_take..-1] + + @lt2_textpos += will_take + if @lt2_textpos >= @lt2_textsize + # Reset line mode (the default behavior) BEFORE calling the + # receive_binary_data. This makes it possible for user code + # to call set_text_mode, enabling chains of text blocks + # (which can possibly be of different sizes). + set_line_mode + receive_binary_data @lt2_textbuffer.join + receive_end_of_binary_data + end + + remaining_data = tail + else + receive_binary_data remaining_data + remaining_data = "" + end + end + end + end + + # The line delimiter may be a regular expression or a string. Anything + # passed to set_delimiter other than a regular expression will be + # converted to a string. + def set_delimiter delim + @lt2_delimiter = case delim + when Regexp + delim + else + delim.to_s + end + end + + # Called internally but also exposed to user code, for the case in which + # processing of binary data creates a need to transition back to line mode. + # We support an optional parameter to "throw back" some data, which might + # be an umprocessed chunk of the transmitted binary data, or something else + # entirely. + def set_line_mode data="" + @lt2_mode = :lines + (@lt2_linebuffer ||= []).clear + receive_data data.to_s + end + + def set_text_mode size=nil + if size == 0 + set_line_mode + else + @lt2_mode = :text + (@lt2_textbuffer ||= []).clear + @lt2_textsize = size # which can be nil, signifying no limit + @lt2_textpos = 0 + end + end + + # Alias for #set_text_mode, added for back-compatibility with LineAndTextProtocol. + def set_binary_mode size=nil + set_text_mode size + end + + # In case of a dropped connection, we'll send a partial buffer to user code + # when in sized text mode. User overrides of #receive_binary_data need to + # be aware that they may get a short buffer. + def unbind + @lt2_mode ||= nil + if @lt2_mode == :text and @lt2_textpos > 0 + receive_binary_data @lt2_textbuffer.join + end + end + + # Stub. Should be subclassed by user code. + def receive_line ln + # no-op + end + + # Stub. Should be subclassed by user code. + def receive_binary_data data + # no-op + end + + # Stub. Should be subclassed by user code. + # This is called when transitioning internally from text mode + # back to line mode. Useful when client code doesn't want + # to keep track of how much data it's received. + def receive_end_of_binary_data + # no-op + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb new file mode 100644 index 0000000..1f81aaf --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb @@ -0,0 +1,331 @@ +module EventMachine + module Protocols + # Implements the Memcache protocol (http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt). + # Requires memcached >= 1.2.4 w/ noreply support + # + # == Usage example + # + # EM.run{ + # cache = EM::P::Memcache.connect 'localhost', 11211 + # + # cache.set :a, 'hello' + # cache.set :b, 'hi' + # cache.set :c, 'how are you?' + # cache.set :d, '' + # + # cache.get(:a){ |v| p v } + # cache.get_hash(:a, :b, :c, :d){ |v| p v } + # cache.get(:a,:b,:c,:d){ |a,b,c,d| p [a,b,c,d] } + # + # cache.get(:a,:z,:b,:y,:d){ |a,z,b,y,d| p [a,z,b,y,d] } + # + # cache.get(:missing){ |m| p [:missing=, m] } + # cache.set(:missing, 'abc'){ p :stored } + # cache.get(:missing){ |m| p [:missing=, m] } + # cache.del(:missing){ p :deleted } + # cache.get(:missing){ |m| p [:missing=, m] } + # } + # + module Memcache + include EM::Deferrable + + ## + # constants + + unless defined? Cempty + # @private + Cstored = 'STORED'.freeze + # @private + Cend = 'END'.freeze + # @private + Cdeleted = 'DELETED'.freeze + # @private + Cunknown = 'NOT_FOUND'.freeze + # @private + Cerror = 'ERROR'.freeze + + # @private + Cempty = ''.freeze + # @private + Cdelimiter = "\r\n".freeze + end + + ## + # commands + + # Get the value associated with one or multiple keys + # + # cache.get(:a){ |v| p v } + # cache.get(:a,:b,:c,:d){ |a,b,c,d| p [a,b,c,d] } + # + def get *keys + raise ArgumentError unless block_given? + + callback{ + keys = keys.map{|k| k.to_s.gsub(/\s/,'_') } + send_data "get #{keys.join(' ')}\r\n" + @get_cbs << [keys, proc{ |values| + yield *keys.map{ |k| values[k] } + }] + } + end + + # Set the value for a given key + # + # cache.set :a, 'hello' + # cache.set(:missing, 'abc'){ puts "stored the value!" } + # + def set key, val, exptime = 0, &cb + callback{ + val = val.to_s + send_cmd :set, key, 0, exptime, val.respond_to?(:bytesize) ? val.bytesize : val.size, !block_given? + send_data val + send_data Cdelimiter + @set_cbs << cb if cb + } + end + + # Gets multiple values as a hash + # + # cache.get_hash(:a, :b, :c, :d){ |h| puts h[:a] } + # + def get_hash *keys + raise ArgumentError unless block_given? + + get *keys do |*values| + yield keys.inject({}){ |hash, k| hash.update k => values[keys.index(k)] } + end + end + + # Delete the value associated with a key + # + # cache.del :a + # cache.del(:b){ puts "deleted the value!" } + # + def delete key, expires = 0, &cb + callback{ + send_data "delete #{key} #{expires}#{cb ? '' : ' noreply'}\r\n" + @del_cbs << cb if cb + } + end + alias del delete + + # Connect to a memcached server (must support NOREPLY, memcached >= 1.2.4) + def self.connect host = 'localhost', port = 11211 + EM.connect host, port, self, host, port + end + + def send_cmd cmd, key, flags = 0, exptime = 0, bytes = 0, noreply = false + send_data "#{cmd} #{key} #{flags} #{exptime} #{bytes}#{noreply ? ' noreply' : ''}\r\n" + end + private :send_cmd + + ## + # errors + + # @private + class ParserError < StandardError + end + + ## + # em hooks + + # @private + def initialize host, port = 11211 + @host, @port = host, port + end + + # @private + def connection_completed + @get_cbs = [] + @set_cbs = [] + @del_cbs = [] + + @values = {} + + @reconnecting = false + @connected = true + succeed + # set_delimiter "\r\n" + # set_line_mode + end + + #-- + # 19Feb09 Switched to a custom parser, LineText2 is recursive and can cause + # stack overflows when there is too much data. + # include EM::P::LineText2 + # @private + def receive_data data + (@buffer||='') << data + + while index = @buffer.index(Cdelimiter) + begin + line = @buffer.slice!(0,index+2) + process_cmd line + rescue ParserError + @buffer[0...0] = line + break + end + end + end + + #-- + # def receive_line line + # @private + def process_cmd line + case line.strip + when /^VALUE\s+(.+?)\s+(\d+)\s+(\d+)/ # VALUE + bytes = Integer($3) + # set_binary_mode bytes+2 + # @cur_key = $1 + if @buffer.size >= bytes + 2 + @values[$1] = @buffer.slice!(0,bytes) + @buffer.slice!(0,2) # \r\n + else + raise ParserError + end + + when Cend # END + if entry = @get_cbs.shift + keys, cb = entry + cb.call(@values) + end + @values = {} + + when Cstored # STORED + if cb = @set_cbs.shift + cb.call(true) + end + + when Cdeleted # DELETED + if cb = @del_cbs.shift + cb.call(true) + end + + when Cunknown # NOT_FOUND + if cb = @del_cbs.shift + cb.call(false) + end + + else + p [:MEMCACHE_UNKNOWN, line] + end + end + + #-- + # def receive_binary_data data + # @values[@cur_key] = data[0..-3] + # end + + # @private + def unbind + if @connected or @reconnecting + EM.add_timer(1){ reconnect @host, @port } + @connected = false + @reconnecting = true + @deferred_status = nil + else + raise 'Unable to connect to memcached server' + end + end + end + end +end + +if __FILE__ == $0 + # ruby -I ext:lib -r eventmachine -rubygems lib/protocols/memcache.rb + require 'em/spec' + + # @private + class TestConnection + include EM::P::Memcache + def send_data data + sent_data << data + end + def sent_data + @sent_data ||= '' + end + + def initialize + connection_completed + end + end + + EM.describe EM::Protocols::Memcache do + + before{ + @c = TestConnection.new + } + + should 'send get requests' do + @c.get('a'){} + @c.sent_data.should == "get a\r\n" + done + end + + should 'send set requests' do + @c.set('a', 1){} + @c.sent_data.should == "set a 0 0 1\r\n1\r\n" + done + end + + should 'use noreply on set without block' do + @c.set('a', 1) + @c.sent_data.should == "set a 0 0 1 noreply\r\n1\r\n" + done + end + + should 'send delete requests' do + @c.del('a') + @c.sent_data.should == "delete a 0 noreply\r\n" + done + end + + should 'work when get returns no values' do + @c.get('a'){ |a| + a.should.be.nil + done + } + + @c.receive_data "END\r\n" + end + + should 'invoke block on set' do + @c.set('a', 1){ + done + } + + @c.receive_data "STORED\r\n" + end + + should 'invoke block on delete' do + @c.delete('a'){ |found| + found.should.be.false + } + @c.delete('b'){ |found| + found.should.be.true + done + } + + @c.receive_data "NOT_FOUND\r\n" + @c.receive_data "DELETED\r\n" + end + + should 'parse split responses' do + @c.get('a'){ |a| + a.should == 'abc' + done + } + + @c.receive_data "VAL" + @c.receive_data "UE a 0 " + @c.receive_data "3\r\n" + @c.receive_data "ab" + @c.receive_data "c" + @c.receive_data "\r\n" + @c.receive_data "EN" + @c.receive_data "D\r\n" + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb new file mode 100644 index 0000000..ec79cb4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb @@ -0,0 +1,46 @@ +module EventMachine + module Protocols + # ObjectProtocol allows for easy communication using marshaled ruby objects + # + # module RubyServer + # include EM::P::ObjectProtocol + # + # def receive_object obj + # send_object({'you said' => obj}) + # end + # end + # + module ObjectProtocol + # By default returns Marshal, override to return JSON or YAML, or any + # other serializer/deserializer responding to #dump and #load. + def serializer + Marshal + end + + # @private + def receive_data data + (@buf ||= '') << data + + while @buf.size >= 4 + if @buf.size >= 4+(size=@buf.unpack('N').first) + @buf.slice!(0,4) + receive_object serializer.load(@buf.slice!(0,size)) + else + break + end + end + end + + # Invoked with ruby objects received over the network + def receive_object obj + # stub + end + + # Sends a ruby object over the network + def send_object obj + data = serializer.dump(obj) + send_data [data.respond_to?(:bytesize) ? data.bytesize : data.size, data].pack('Na*') + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb new file mode 100644 index 0000000..7d87505 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb @@ -0,0 +1,246 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-08 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +require 'postgres-pr/message' +require 'postgres-pr/connection' +require 'stringio' + +# @private +class StringIO + # Reads exactly +n+ bytes. + # + # If the data read is nil an EOFError is raised. + # + # If the data read is too short an IOError is raised + def readbytes(n) + str = read(n) + if str == nil + raise EOFError, "End of file reached" + end + if str.size < n + raise IOError, "data truncated" + end + str + end + alias read_exactly_n_bytes readbytes +end + + +module EventMachine + module Protocols + # PROVISIONAL IMPLEMENTATION of an evented Postgres client. + # This implements version 3 of the Postgres wire protocol, which will work + # with any Postgres version from roughly 7.4 onward. + # + # Objective: we want to access Postgres databases without requiring threads. + # Until now this has been a problem because the Postgres client implementations + # have all made use of blocking I/O calls, which is incompatible with a + # thread-free evented model. + # + # But rather than re-implement the Postgres Wire3 protocol, we're taking advantage + # of the existing postgres-pr library, which was originally written by Michael + # Neumann but (at this writing) appears to be no longer maintained. Still, it's + # in basically a production-ready state, and the wire protocol isn't that complicated + # anyway. + # + # We're tucking in a bunch of require statements that may not be present in garden-variety + # EM installations. Until we find a good way to only require these if a program + # requires postgres, this file will need to be required explicitly. + # + # We need to monkeypatch StringIO because it lacks the #readbytes method needed + # by postgres-pr. + # The StringIO monkeypatch is lifted from the standard library readbytes.rb, + # which adds method #readbytes directly to class IO. But StringIO is not a subclass of IO. + # It is modified to raise an IOError instead of TruncatedDataException since the exception is unused. + # + # We cloned the handling of postgres messages from lib/postgres-pr/connection.rb + # in the postgres-pr library, and modified it for event-handling. + # + # TODO: The password handling in dispatch_conn_message is totally incomplete. + # + # + # We return Deferrables from the user-level operations surfaced by this interface. + # Experimentally, we're using the pattern of always returning a boolean value as the + # first argument of a deferrable callback to indicate success or failure. This is + # instead of the traditional pattern of calling Deferrable#succeed or #fail, and + # requiring the user to define both a callback and an errback function. + # + # === Usage + # EM.run { + # db = EM.connect_unix_domain( "/tmp/.s.PGSQL.5432", EM::P::Postgres3 ) + # db.connect( dbname, username, psw ).callback do |status| + # if status + # db.query( "select * from some_table" ).callback do |status, result, errors| + # if status + # result.rows.each do |row| + # p row + # end + # end + # end + # end + # end + # } + class Postgres3 < EventMachine::Connection + include PostgresPR + + def initialize + @data = "" + @params = {} + end + + def connect db, user, psw=nil + d = EM::DefaultDeferrable.new + d.timeout 15 + + if @pending_query || @pending_conn + d.succeed false, "Operation already in progress" + else + @pending_conn = d + prms = {"user"=>user, "database"=>db} + @user = user + if psw + @password = psw + #prms["password"] = psw + end + send_data PostgresPR::StartupMessage.new( 3 << 16, prms ).dump + end + + d + end + + def query sql + d = EM::DefaultDeferrable.new + d.timeout 15 + + if @pending_query || @pending_conn + d.succeed false, "Operation already in progress" + else + @r = PostgresPR::Connection::Result.new + @e = [] + @pending_query = d + send_data PostgresPR::Query.dump(sql) + end + + d + end + + + def receive_data data + @data << data + while @data.length >= 5 + pktlen = @data[1...5].unpack("N").first + if @data.length >= (1 + pktlen) + pkt = @data.slice!(0...(1+pktlen)) + m = StringIO.open( pkt, "r" ) {|io| PostgresPR::Message.read( io ) } + if @pending_conn + dispatch_conn_message m + elsif @pending_query + dispatch_query_message m + else + raise "Unexpected message from database" + end + else + break # very important, break out of the while + end + end + end + + + def unbind + if o = (@pending_query || @pending_conn) + o.succeed false, "lost connection" + end + end + + # Cloned and modified from the postgres-pr. + def dispatch_conn_message msg + case msg + when AuthentificationClearTextPassword + raise ArgumentError, "no password specified" if @password.nil? + send_data PasswordMessage.new(@password).dump + + when AuthentificationCryptPassword + raise ArgumentError, "no password specified" if @password.nil? + send_data PasswordMessage.new(@password.crypt(msg.salt)).dump + + when AuthentificationMD5Password + raise ArgumentError, "no password specified" if @password.nil? + require 'digest/md5' + + m = Digest::MD5.hexdigest(@password + @user) + m = Digest::MD5.hexdigest(m + msg.salt) + m = 'md5' + m + send_data PasswordMessage.new(m).dump + + when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential + raise "unsupported authentification" + + when AuthentificationOk + when ErrorResponse + raise msg.field_values.join("\t") + when NoticeResponse + @notice_processor.call(msg) if @notice_processor + when ParameterStatus + @params[msg.key] = msg.value + when BackendKeyData + # TODO + #p msg + when ReadyForQuery + # TODO: use transaction status + pc,@pending_conn = @pending_conn,nil + pc.succeed true + else + raise "unhandled message type" + end + end + + # Cloned and modified from the postgres-pr. + def dispatch_query_message msg + case msg + when DataRow + @r.rows << msg.columns + when CommandComplete + @r.cmd_tag = msg.cmd_tag + when ReadyForQuery + pq,@pending_query = @pending_query,nil + pq.succeed true, @r, @e + when RowDescription + @r.fields = msg.fields + when CopyInResponse + when CopyOutResponse + when EmptyQueryResponse + when ErrorResponse + # TODO + @e << msg + when NoticeResponse + @notice_processor.call(msg) if @notice_processor + else + # TODO + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb new file mode 100644 index 0000000..9cabc51 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb @@ -0,0 +1,175 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + + # Implements SASL authd. + # This is a very, very simple protocol that mimics the one used + # by saslauthd and pwcheck, two outboard daemons included in the + # standard SASL library distro. + # The only thing this is really suitable for is SASL PLAIN + # (user+password) authentication, but the SASL libs that are + # linked into standard servers (like imapd and sendmail) implement + # the other ones. + # + # SASL-auth is intended for reasonably fast operation inside a + # single machine, so it has no transport-security (although there + # have been multi-machine extensions incorporating transport-layer + # encryption). + # + # The standard saslauthd module generally runs privileged and does + # its work by referring to the system-account files. + # + # This feature was added to EventMachine to enable the development + # of custom authentication/authorization engines for standard servers. + # + # To use SASLauth, include it in a class that subclasses EM::Connection, + # and reimplement the validate method. + # + # The typical way to incorporate this module into an authentication + # daemon would be to set it as the handler for a UNIX-domain socket. + # The code might look like this: + # + # EM.start_unix_domain_server( "/var/run/saslauthd/mux", MyHandler ) + # File.chmod( 0777, "/var/run/saslauthd/mux") + # + # The chmod is probably needed to ensure that unprivileged clients can + # access the UNIX-domain socket. + # + # It's also a very good idea to drop superuser privileges (if any), after + # the UNIX-domain socket has been opened. + #-- + # Implementation details: assume the client can send us pipelined requests, + # and that the client will close the connection. + # + # The client sends us four values, each encoded as a two-byte length field in + # network order followed by the specified number of octets. + # The fields specify the username, password, service name (such as imap), + # and the "realm" name. We send back the barest minimum reply, a single + # field also encoded as a two-octet length in network order, followed by + # either "NO" or "OK" - simplicity itself. + # + # We enforce a maximum field size just as a sanity check. + # We do NOT automatically time out the connection. + # + # The code we use to parse out the values is ugly and probably slow. + # Improvements welcome. + # + module SASLauth + + MaxFieldSize = 128*1024 + def post_init + super + @sasl_data = "" + @sasl_values = [] + end + + def receive_data data + @sasl_data << data + while @sasl_data.length >= 2 + len = (@sasl_data[0,2].unpack("n")).first + raise "SASL Max Field Length exceeded" if len > MaxFieldSize + if @sasl_data.length >= (len + 2) + @sasl_values << @sasl_data[2,len] + @sasl_data.slice!(0...(2+len)) + if @sasl_values.length == 4 + send_data( validate(*@sasl_values) ? "\0\002OK" : "\0\002NO" ) + @sasl_values.clear + end + else + break + end + end + end + + def validate username, psw, sysname, realm + p username + p psw + p sysname + p realm + true + end + end + + # Implements the SASL authd client protocol. + # This is a very, very simple protocol that mimics the one used + # by saslauthd and pwcheck, two outboard daemons included in the + # standard SASL library distro. + # The only thing this is really suitable for is SASL PLAIN + # (user+password) authentication, but the SASL libs that are + # linked into standard servers (like imapd and sendmail) implement + # the other ones. + # + # You can use this module directly as a handler for EM Connections, + # or include it in a module or handler class of your own. + # + # First connect to a SASL server (it's probably a TCP server, or more + # likely a Unix-domain socket). Then call the #validate? method, + # passing at least a username and a password. #validate? returns + # a Deferrable which will either succeed or fail, depending + # on the status of the authentication operation. + # + module SASLauthclient + MaxFieldSize = 128*1024 + + def validate? username, psw, sysname=nil, realm=nil + + str = [username, psw, sysname, realm].map {|m| + [(m || "").length, (m || "")] + }.flatten.pack( "nA*" * 4 ) + send_data str + + d = EM::DefaultDeferrable.new + @queries.unshift d + d + end + + def post_init + @sasl_data = "" + @queries = [] + end + + def receive_data data + @sasl_data << data + + while @sasl_data.length > 2 + len = (@sasl_data[0,2].unpack("n")).first + raise "SASL Max Field Length exceeded" if len > MaxFieldSize + if @sasl_data.length >= (len + 2) + val = @sasl_data[2,len] + @sasl_data.slice!(0...(2+len)) + q = @queries.pop + (val == "NO") ? q.fail : q.succeed + else + break + end + end + end + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb new file mode 100644 index 0000000..25ad17a --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb @@ -0,0 +1,394 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +require 'ostruct' + +module EventMachine + module Protocols + + # Simple SMTP client + # + # @example + # email = EM::Protocols::SmtpClient.send( + # :domain=>"example.com", + # :host=>'localhost', + # :port=>25, # optional, defaults 25 + # :starttls=>true, # use ssl + # :from=>"sender@example.com", + # :to=> ["to_1@example.com", "to_2@example.com"], + # :header=> {"Subject" => "This is a subject line"}, + # :body=> "This is the body of the email" + # ) + # email.callback{ + # puts 'Email sent!' + # } + # email.errback{ |e| + # puts 'Email failed!' + # } + # + # Sending generated emails (using Mail) + # + # mail = Mail.new do + # from 'alice@example.com' + # to 'bob@example.com' + # subject 'This is a test email' + # body 'Hello, world!' + # end + # + # email = EM::P::SmtpClient.send( + # :domain=>'example.com', + # :from=>mail.from.first, + # :to=>mail.to, + # :message=>mail.to_s + # ) + # + class SmtpClient < Connection + include EventMachine::Deferrable + include EventMachine::Protocols::LineText2 + + def initialize + @succeeded = nil + @responder = nil + @code = nil + @msg = nil + end + + # :host => required String + # a string containing the IP address or host name of the SMTP server to connect to. + # :port => optional + # defaults to 25. + # :domain => required String + # This is passed as the argument to the EHLO command. + # :starttls => optional Boolean + # If it evaluates true, then the client will initiate STARTTLS with + # the server, and abort the connection if the negotiation doesn't succeed. + # TODO, need to be able to pass certificate parameters with this option. + # :auth => optional Hash of auth parameters + # If not given, then no auth will be attempted. + # (In that case, the connection will be aborted if the server requires auth.) + # Specify the hash value :type to determine the auth type, along with additional parameters + # depending on the type. + # Currently only :type => :plain is supported. Pass additional parameters :username (String), + # and :password (either a String or a Proc that will be called at auth-time). + # + # @example + # :auth => {:type=>:plain, :username=>"mickey@disney.com", :password=>"mouse"} + # + # :from => required String + # Specifies the sender of the message. Will be passed as the argument + # to the MAIL FROM. Do NOT enclose the argument in angle-bracket (<>) characters. + # The connection will abort if the server rejects the value. + # :to => required String or Array of Strings + # The recipient(s) of the message. Do NOT enclose + # any of the values in angle-brackets (<>) characters. It's NOT a fatal error if one or more + # recipients are rejected by the server. (Of course, if ALL of them are, the server will most + # likely trigger an error when we try to send data.) An array of codes containing the status + # of each requested recipient is available after the call completes. TODO, we should define + # an overridable stub that will be called on rejection of a recipient or a sender, giving + # user code the chance to try again or abort the connection. + # + # One of either :message, :content, or :header and :body is required: + # + # :message => String + # A valid RFC2822 Internet Message. + # :content => String + # Raw data which MUST be in correct SMTP body format, with escaped leading dots and a trailing + # dot line. + # :header => String or Hash of values to be transmitted in the header of the message. + # The hash keys are the names of the headers (do NOT append a trailing colon), and the values + # are strings containing the header values. TODO, support Arrays of header values, which would + # cause us to send that specific header line more than once. + # + # @example + # :header => {"Subject" => "Bogus", "CC" => "myboss@example.com"} + # + # :body => Optional String or Array of Strings, defaults blank. + # This will be passed as the body of the email message. + # TODO, this needs to be significantly beefed up. As currently written, this requires the caller + # to properly format the input into CRLF-delimited lines of 7-bit characters in the standard + # SMTP transmission format. We need to be able to automatically convert binary data, and add + # correct line-breaks to text data. + # + # :verbose => Optional. + # If true, will cause a lot of information (including the server-side of the + # conversation) to be dumped to $>. + # + def self.send args={} + args[:port] ||= 25 + args[:body] ||= "" + +=begin + (I don't think it's possible for EM#connect to throw an exception under normal + circumstances, so this original code is stubbed out. A connect-failure will result + in the #unbind method being called without calling #connection_completed.) + begin + EventMachine.connect( args[:host], args[:port], self) {|c| + # According to the EM docs, we will get here AFTER post_init is called. + c.args = args + c.set_comm_inactivity_timeout 60 + } + rescue + # We'll get here on a connect error. This code mimics the effect + # of a call to invoke_internal_error. Would be great to DRY this up. + # (Actually, it may be that we never get here, if EM#connect catches + # its errors internally.) + d = EM::DefaultDeferrable.new + d.set_deferred_status(:failed, {:error=>[:connect, 500, "unable to connect to server"]}) + d + end +=end + EventMachine.connect( args[:host], args[:port], self) {|c| + # According to the EM docs, we will get here AFTER post_init is called. + c.args = args + c.set_comm_inactivity_timeout 60 + } + end + + attr_writer :args + + # @private + def post_init + @return_values = OpenStruct.new + @return_values.start_time = Time.now + end + + # @private + def connection_completed + @responder = :receive_signon + @msg = [] + end + + # We can get here in a variety of ways, all of them being failures unless + # the @succeeded flag is set. If a protocol success was recorded, then don't + # set a deferred success because the caller will already have done it + # (no need to wait until the connection closes to invoke the callbacks). + # + # @private + def unbind + unless @succeeded + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = @code + @return_values.message = @msg + set_deferred_status(:failed, @return_values) + end + end + + # @private + def receive_line ln + $>.puts ln if @args[:verbose] + @range = ln[0...1].to_i + @code = ln[0...3].to_i + @msg << ln[4..-1] + unless ln[3...4] == '-' + $>.puts @responder if @args[:verbose] + send @responder + @msg.clear + end + end + + private + + # We encountered an error from the server and will close the connection. + # Use the error and message the server returned. + # + def invoke_error + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = @code + @return_values.message = @msg + set_deferred_status :failed, @return_values + send_data "QUIT\r\n" + close_connection_after_writing + end + + # We encountered an error on our side of the protocol and will close the connection. + # Use an extra-protocol error code (900) and use the message from the caller. + # + def invoke_internal_error msg = "???" + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = 900 + @return_values.message = msg + set_deferred_status :failed, @return_values + send_data "QUIT\r\n" + close_connection_after_writing + end + + def send_ehlo + send_data "EHLO #{@args[:domain]}\r\n" + end + + def receive_signon + return invoke_error unless @range == 2 + send_ehlo + @responder = :receive_ehlo_response + end + def receive_ehlo_response + return invoke_error unless @range == 2 + @server_caps = @msg + invoke_starttls + end + + def invoke_starttls + if @args[:starttls] + # It would be more sociable to first ask if @server_caps contains + # the string "STARTTLS" before we invoke it, but hey, life's too short. + send_data "STARTTLS\r\n" + @responder = :receive_starttls_response + else + invoke_auth + end + end + def receive_starttls_response + return invoke_error unless @range == 2 + start_tls + invoke_ehlo_over_tls + end + + def invoke_ehlo_over_tls + send_ehlo + @responder = :receive_ehlo_over_tls_response + end + def receive_ehlo_over_tls_response + return invoke_error unless @range == 2 + invoke_auth + end + + # Perform an authentication. If the caller didn't request one, then fall through + # to the mail-from state. + def invoke_auth + if @args[:auth] + if @args[:auth][:type] == :plain + psw = @args[:auth][:password] + if psw.respond_to?(:call) + psw = psw.call + end + #str = Base64::encode64("\0#{@args[:auth][:username]}\0#{psw}").chomp + str = ["\0#{@args[:auth][:username]}\0#{psw}"].pack("m").gsub(/\n/, '') + send_data "AUTH PLAIN #{str}\r\n" + @responder = :receive_auth_response + else + return invoke_internal_error("unsupported auth type") + end + else + invoke_mail_from + end + end + def receive_auth_response + return invoke_error unless @range == 2 + invoke_mail_from + end + + def invoke_mail_from + send_data "MAIL FROM: <#{@args[:from]}>\r\n" + @responder = :receive_mail_from_response + end + def receive_mail_from_response + return invoke_error unless @range == 2 + invoke_rcpt_to + end + + def invoke_rcpt_to + @rcpt_responses ||= [] + l = @rcpt_responses.length + to = @args[:to].is_a?(Array) ? @args[:to] : [@args[:to].to_s] + if l < to.length + send_data "RCPT TO: <#{to[l]}>\r\n" + @responder = :receive_rcpt_to_response + else + e = @rcpt_responses.select {|rr| rr.last == 2} + if e and e.length > 0 + invoke_data + else + invoke_error + end + end + end + def receive_rcpt_to_response + @rcpt_responses << [@code, @msg, @range] + invoke_rcpt_to + end + + def escape_leading_dots(s) + s.gsub(/^\./, '..') + end + + def invoke_data + send_data "DATA\r\n" + @responder = :receive_data_response + end + def receive_data_response + return invoke_error unless @range == 3 + + # The data to send can be given in either @args[:message], @args[:content], or the + # combination of @args[:header] and @args[:body]. + # + # - @args[:message] (String) MUST be a valid RFC2822 Internet Message + # + # - @args[:content] (String) MUST be in correct SMTP body format, with escaped + # leading dots and a trailing dot line + # + # - @args[:header] (Hash or String) + # - @args[:body] (Array or String) + if @args[:message] + send_data escape_leading_dots(@args[:message].to_s) + send_data "\r\n.\r\n" + elsif @args[:content] + send_data @args[:content].to_s + else + # The header can be a hash or an array. + if @args[:header].is_a?(Hash) + (@args[:header] || {}).each {|k,v| send_data escape_leading_dots("#{k}: #{v}\r\n") } + else + send_data escape_leading_dots(@args[:header].to_s) + end + send_data "\r\n" + + if @args[:body].is_a?(Array) + @args[:body].each {|e| send_data escape_leading_dots(e)} + else + send_data escape_leading_dots(@args[:body].to_s) + end + + send_data "\r\n.\r\n" + end + + @responder = :receive_message_response + end + def receive_message_response + return invoke_error unless @range == 2 + send_data "QUIT\r\n" + close_connection_after_writing + @succeeded = true + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = @code + @return_values.message = @msg + set_deferred_status :succeeded, @return_values + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb new file mode 100644 index 0000000..e0f1f05 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb @@ -0,0 +1,666 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # This is a protocol handler for the server side of SMTP. + # It's NOT a complete SMTP server obeying all the semantics of servers conforming to + # RFC2821. Rather, it uses overridable method stubs to communicate protocol states + # and data to user code. User code is responsible for doing the right things with the + # data in order to get complete and correct SMTP server behavior. + # + # Simple SMTP server example: + # + # class EmailServer < EM::P::SmtpServer + # def receive_plain_auth(user, pass) + # true + # end + # + # def get_server_domain + # "mock.smtp.server.local" + # end + # + # def get_server_greeting + # "mock smtp server greets you with impunity" + # end + # + # def receive_sender(sender) + # current.sender = sender + # true + # end + # + # def receive_recipient(recipient) + # current.recipient = recipient + # true + # end + # + # def receive_message + # current.received = true + # current.completed_at = Time.now + # + # p [:received_email, current] + # @current = OpenStruct.new + # true + # end + # + # def receive_ehlo_domain(domain) + # @ehlo_domain = domain + # true + # end + # + # def receive_data_command + # current.data = "" + # true + # end + # + # def receive_data_chunk(data) + # current.data << data.join("\n") + # true + # end + # + # def receive_transaction + # if @ehlo_domain + # current.ehlo_domain = @ehlo_domain + # @ehlo_domain = nil + # end + # true + # end + # + # def current + # @current ||= OpenStruct.new + # end + # + # def self.start(host = 'localhost', port = 1025) + # require 'ostruct' + # @server = EM.start_server host, port, self + # end + # + # def self.stop + # if @server + # EM.stop_server @server + # @server = nil + # end + # end + # + # def self.running? + # !!@server + # end + # end + # + # EM.run{ EmailServer.start } + # + #-- + # Useful paragraphs in RFC-2821: + # 4.3.2: Concise list of command-reply sequences, in essence a text representation + # of the command state-machine. + # + # STARTTLS is defined in RFC2487. + # Observe that there are important rules governing whether a publicly-referenced server + # (meaning one whose Internet address appears in public MX records) may require the + # non-optional use of TLS. + # Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS. + class SmtpServer < EventMachine::Connection + include Protocols::LineText2 + + HeloRegex = /\AHELO\s*/i + EhloRegex = /\AEHLO\s*/i + QuitRegex = /\AQUIT/i + MailFromRegex = /\AMAIL FROM:\s*/i + RcptToRegex = /\ARCPT TO:\s*/i + DataRegex = /\ADATA/i + NoopRegex = /\ANOOP/i + RsetRegex = /\ARSET/i + VrfyRegex = /\AVRFY\s+/i + ExpnRegex = /\AEXPN\s+/i + HelpRegex = /\AHELP/i + StarttlsRegex = /\ASTARTTLS/i + AuthRegex = /\AAUTH\s+/i + + + # Class variable containing default parameters that can be overridden + # in application code. + # Individual objects of this class will make an instance-local copy of + # the class variable, so that they can be reconfigured on a per-instance + # basis. + # + # Chunksize is the number of data lines we'll buffer before + # sending them to the application. TODO, make this user-configurable. + # + @@parms = { + :chunksize => 4000, + :verbose => false + } + def self.parms= parms={} + @@parms.merge!(parms) + end + + + + def initialize *args + super + @parms = @@parms + init_protocol_state + end + + def parms= parms={} + @parms.merge!(parms) + end + + # In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM, + # #post_init will execute BEFORE the block passed to #start_server, for any + # given accepted connection. Since in this class we'll probably be getting + # a lot of initialization parameters, we want the guts of post_init to + # run AFTER the application has initialized the connection object. So we + # use a spawn to schedule the post_init to run later. + # It's a little weird, I admit. A reasonable alternative would be to set + # parameters as a class variable and to do that before accepting any connections. + # + # OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration. + # + def post_init + #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL) + #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self) + (EM.spawn {|x| x.send_server_greeting}).notify(self) + end + + def send_server_greeting + send_data "220 #{get_server_greeting}\r\n" + end + + def receive_line ln + @@parms[:verbose] and $>.puts ">>> #{ln}" + + return process_data_line(ln) if @state.include?(:data) + return process_auth_line(ln) if @state.include?(:auth_incomplete) + + case ln + when EhloRegex + process_ehlo $'.dup + when HeloRegex + process_helo $'.dup + when MailFromRegex + process_mail_from $'.dup + when RcptToRegex + process_rcpt_to $'.dup + when DataRegex + process_data + when RsetRegex + process_rset + when VrfyRegex + process_vrfy + when ExpnRegex + process_expn + when HelpRegex + process_help + when NoopRegex + process_noop + when QuitRegex + process_quit + when StarttlsRegex + process_starttls + when AuthRegex + process_auth $'.dup + else + process_unknown + end + end + + # TODO - implement this properly, the implementation is a stub! + def process_help + send_data "250 Ok, but unimplemented\r\n" + end + + # RFC2821, 3.5.3 Meaning of VRFY or EXPN Success Response: + # A server MUST NOT return a 250 code in response to a VRFY or EXPN + # command unless it has actually verified the address. In particular, + # a server MUST NOT return 250 if all it has done is to verify that the + # syntax given is valid. In that case, 502 (Command not implemented) + # or 500 (Syntax error, command unrecognized) SHOULD be returned. + # + # TODO - implement this properly, the implementation is a stub! + def process_vrfy + send_data "502 Command not implemented\r\n" + end + # TODO - implement this properly, the implementation is a stub! + def process_expn + send_data "502 Command not implemented\r\n" + end + + #-- + # This is called at several points to restore the protocol state + # to a pre-transaction state. In essence, we "forget" having seen + # any valid command except EHLO and STARTTLS. + # We also have to callback user code, in case they're keeping track + # of senders, recipients, and whatnot. + # + # We try to follow the convention of avoiding the verb "receive" for + # internal method names except receive_line (which we inherit), and + # using only receive_xxx for user-overridable stubs. + # + # init_protocol_state is called when we initialize the connection as + # well as during reset_protocol_state. It does NOT call the user + # override method. This enables us to promise the users that they + # won't see the overridable fire except after EHLO and RSET, and + # after a message has been received. Although the latter may be wrong. + # The standard may allow multiple DATA segments with the same set of + # senders and recipients. + # + def reset_protocol_state + init_protocol_state + s,@state = @state,[] + @state << :starttls if s.include?(:starttls) + @state << :ehlo if s.include?(:ehlo) + receive_transaction + end + def init_protocol_state + @state ||= [] + end + + + #-- + # EHLO/HELO is always legal, per the standard. On success + # it always clears buffers and initiates a mail "transaction." + # Which means that a MAIL FROM must follow. + # + # Per the standard, an EHLO/HELO or a RSET "initiates" an email + # transaction. Thereafter, MAIL FROM must be received before + # RCPT TO, before DATA. Not sure what this specific ordering + # achieves semantically, but it does make it easier to + # implement. We also support user-specified requirements for + # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM + # without fulfilling tls and/or auth, if the user specified either + # or both as required. We need to check the extension standard + # for auth to see if a credential is discarded after a RSET along + # with all the rest of the state. We'll behave as if it is. + # Now clearly, we can't discard tls after its been negotiated + # without dropping the connection, so that flag doesn't get cleared. + # + def process_ehlo domain + if receive_ehlo_domain domain + send_data "250-#{get_server_domain}\r\n" + if @@parms[:starttls] + send_data "250-STARTTLS\r\n" + end + if @@parms[:auth] + send_data "250-AUTH PLAIN\r\n" + end + send_data "250-NO-SOLICITING\r\n" + # TODO, size needs to be configurable. + send_data "250 SIZE 20000000\r\n" + reset_protocol_state + @state << :ehlo + else + send_data "550 Requested action not taken\r\n" + end + end + + def process_helo domain + if receive_ehlo_domain domain.dup + send_data "250 #{get_server_domain}\r\n" + reset_protocol_state + @state << :ehlo + else + send_data "550 Requested action not taken\r\n" + end + end + + def process_quit + send_data "221 Ok\r\n" + close_connection_after_writing + end + + def process_noop + send_data "250 Ok\r\n" + end + + def process_unknown + send_data "500 Unknown command\r\n" + end + + #-- + # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well. + # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx + # response and a continuation of the auth conversation. + # + def process_auth str + if @state.include?(:auth) + send_data "503 auth already issued\r\n" + elsif str =~ /\APLAIN\s?/i + if $'.length == 0 + # we got a partial response, so let the client know to send the rest + @state << :auth_incomplete + send_data("334 \r\n") + else + # we got the initial response, so go ahead & process it + process_auth_line($') + end + #elsif str =~ /\ALOGIN\s+/i + else + send_data "504 auth mechanism not available\r\n" + end + end + + def process_auth_line(line) + plain = line.unpack("m").first + _,user,psw = plain.split("\000") + + succeeded = proc { + send_data "235 authentication ok\r\n" + @state << :auth + } + failed = proc { + send_data "535 invalid authentication\r\n" + } + auth = receive_plain_auth user,psw + + if auth.respond_to?(:callback) + auth.callback(&succeeded) + auth.errback(&failed) + else + (auth ? succeeded : failed).call + end + + @state.delete :auth_incomplete + end + + #-- + # Unusually, we can deal with a Deferrable returned from the user application. + # This was added to deal with a special case in a particular application, but + # it would be a nice idea to add it to the other user-code callbacks. + # + def process_data + unless @state.include?(:rcpt) + send_data "503 Operation sequence error\r\n" + else + succeeded = proc { + send_data "354 Send it\r\n" + @state << :data + @databuffer = [] + } + failed = proc { + send_data "550 Operation failed\r\n" + } + + d = receive_data_command + + if d.respond_to?(:callback) + d.callback(&succeeded) + d.errback(&failed) + else + (d ? succeeded : failed).call + end + end + end + + def process_rset + reset_protocol_state + receive_reset + send_data "250 Ok\r\n" + end + + def unbind + connection_ended + end + + #-- + # STARTTLS may not be issued before EHLO, or unless the user has chosen + # to support it. + # + # If :starttls_options is present and :starttls is set in the parms + # pass the options in :starttls_options to start_tls. Do this if you want to use + # your own certificate + # e.g. {:cert_chain_file => "/etc/ssl/cert.pem", :private_key_file => "/etc/ssl/private/cert.key"} + + def process_starttls + if @@parms[:starttls] + if @state.include?(:starttls) + send_data "503 TLS Already negotiated\r\n" + elsif ! @state.include?(:ehlo) + send_data "503 EHLO required before STARTTLS\r\n" + else + send_data "220 Start TLS negotiation\r\n" + start_tls(@@parms[:starttls_options] || {}) + @state << :starttls + end + else + process_unknown + end + end + + + #-- + # Requiring TLS is touchy, cf RFC2784. + # Requiring AUTH seems to be much more reasonable. + # We don't currently support any notion of deriving an authentication from the TLS + # negotiation, although that would certainly be reasonable. + # We DON'T allow MAIL FROM to be given twice. + # We DON'T enforce all the various rules for validating the sender or + # the reverse-path (like whether it should be null), and notifying the reverse + # path in case of delivery problems. All of that is left to the calling application. + # + def process_mail_from sender + if (@@parms[:starttls]==:required and !@state.include?(:starttls)) + send_data "550 This server requires STARTTLS before MAIL FROM\r\n" + elsif (@@parms[:auth]==:required and !@state.include?(:auth)) + send_data "550 This server requires authentication before MAIL FROM\r\n" + elsif @state.include?(:mail_from) + send_data "503 MAIL already given\r\n" + else + unless receive_sender sender + send_data "550 sender is unacceptable\r\n" + else + send_data "250 Ok\r\n" + @state << :mail_from + end + end + end + + #-- + # Since we require :mail_from to have been seen before we process RCPT TO, + # we don't need to repeat the tests for TLS and AUTH. + # Note that we don't remember or do anything else with the recipients. + # All of that is on the user code. + # TODO: we should enforce user-definable limits on the total number of + # recipients per transaction. + # We might want to make sure that a given recipient is only seen once, but + # for now we'll let that be the user's problem. + # + # User-written code can return a deferrable from receive_recipient. + # + def process_rcpt_to rcpt + unless @state.include?(:mail_from) + send_data "503 MAIL is required before RCPT\r\n" + else + succeeded = proc { + send_data "250 Ok\r\n" + @state << :rcpt unless @state.include?(:rcpt) + } + failed = proc { + send_data "550 recipient is unacceptable\r\n" + } + + d = receive_recipient rcpt + + if d.respond_to?(:set_deferred_status) + d.callback(&succeeded) + d.errback(&failed) + else + (d ? succeeded : failed).call + end + +=begin + unless receive_recipient rcpt + send_data "550 recipient is unacceptable\r\n" + else + send_data "250 Ok\r\n" + @state << :rcpt unless @state.include?(:rcpt) + end +=end + end + end + + + # Send the incoming data to the application one chunk at a time, rather than + # one line at a time. That lets the application be a little more flexible about + # storing to disk, etc. + # Since we clear the chunk array every time we submit it, the caller needs to be + # aware to do things like dup it if he wants to keep it around across calls. + # + # Resets the transaction upon disposition of the incoming message. + # RFC5321 says this about the MAIL FROM command: + # "This command tells the SMTP-receiver that a new mail transaction is + # starting and to reset all its state tables and buffers, including any + # recipients or mail data." + # + # Equivalent behaviour is implemented by resetting after a completed transaction. + # + # User-written code can return a Deferrable as a response from receive_message. + # + def process_data_line ln + if ln == "." + if @databuffer.length > 0 + receive_data_chunk @databuffer + @databuffer.clear + end + + + succeeded = proc { + send_data "250 Message accepted\r\n" + reset_protocol_state + } + failed = proc { + send_data "550 Message rejected\r\n" + reset_protocol_state + } + d = receive_message + + if d.respond_to?(:set_deferred_status) + d.callback(&succeeded) + d.errback(&failed) + else + (d ? succeeded : failed).call + end + + @state.delete :data + else + # slice off leading . if any + ln.slice!(0...1) if ln[0] == ?. + @databuffer << ln + if @databuffer.length > @@parms[:chunksize] + receive_data_chunk @databuffer + @databuffer.clear + end + end + end + + + #------------------------------------------ + # Everything from here on can be overridden in user code. + + # The greeting returned in the initial connection message to the client. + def get_server_greeting + "EventMachine SMTP Server" + end + # The domain name returned in the first line of the response to a + # successful EHLO or HELO command. + def get_server_domain + "Ok EventMachine SMTP Server" + end + + # A false response from this user-overridable method will cause a + # 550 error to be returned to the remote client. + # + def receive_ehlo_domain domain + true + end + + # Return true or false to indicate that the authentication is acceptable. + def receive_plain_auth user, password + true + end + + # Receives the argument of the MAIL FROM command. Return false to + # indicate to the remote client that the sender is not accepted. + # This can only be successfully called once per transaction. + # + def receive_sender sender + true + end + + # Receives the argument of a RCPT TO command. Can be given multiple + # times per transaction. Return false to reject the recipient. + # + def receive_recipient rcpt + true + end + + # Sent when the remote peer issues the RSET command. + # Since RSET is not allowed to fail (according to the protocol), + # we ignore any return value from user overrides of this method. + # + def receive_reset + end + + # Sent when the remote peer has ended the connection. + # + def connection_ended + end + + # Called when the remote peer sends the DATA command. + # Returning false will cause us to send a 550 error to the peer. + # This can be useful for dealing with problems that arise from processing + # the whole set of sender and recipients. + # + def receive_data_command + true + end + + # Sent when data from the remote peer is available. The size can be controlled + # by setting the :chunksize parameter. This call can be made multiple times. + # The goal is to strike a balance between sending the data to the application one + # line at a time, and holding all of a very large message in memory. + # + def receive_data_chunk data + @smtps_msg_size ||= 0 + @smtps_msg_size += data.join.length + STDERR.write "<#{@smtps_msg_size}>" + end + + # Sent after a message has been completely received. User code + # must return true or false to indicate whether the message has + # been accepted for delivery. + def receive_message + @@parms[:verbose] and $>.puts "Received complete message" + true + end + + # This is called when the protocol state is reset. It happens + # when the remote client calls EHLO/HELO or RSET. + def receive_transaction + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb new file mode 100644 index 0000000..132f320 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb @@ -0,0 +1,66 @@ +module EventMachine + module Protocols + # Basic SOCKS v4 client implementation + # + # Use as you would any regular connection: + # + # class MyConn < EM::P::Socks4 + # def post_init + # send_data("sup") + # end + # + # def receive_data(data) + # send_data("you said: #{data}") + # end + # end + # + # EM.connect socks_host, socks_port, MyConn, host, port + # + class Socks4 < Connection + def initialize(host, port) + @host = Socket.gethostbyname(host).last + @port = port + @socks_error_code = nil + @buffer = '' + setup_methods + end + + def setup_methods + class << self + def post_init; socks_post_init; end + def receive_data(*a); socks_receive_data(*a); end + end + end + + def restore_methods + class << self + remove_method :post_init + remove_method :receive_data + end + end + + def socks_post_init + header = [4, 1, @port, @host, 0].flatten.pack("CCnA4C") + send_data(header) + end + + def socks_receive_data(data) + @buffer << data + return if @buffer.size < 8 + + header_resp = @buffer.slice! 0, 8 + _, r = header_resp.unpack("cc") + if r != 90 + @socks_error_code = r + close_connection + return + end + + restore_methods + + post_init + receive_data(@buffer) unless @buffer.empty? + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb new file mode 100644 index 0000000..ca6f078 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb @@ -0,0 +1,205 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + + # Implements Stomp (http://docs.codehaus.org/display/STOMP/Protocol). + # + # == Usage example + # + # module StompClient + # include EM::Protocols::Stomp + # + # def connection_completed + # connect :login => 'guest', :passcode => 'guest' + # end + # + # def receive_msg msg + # if msg.command == "CONNECTED" + # subscribe '/some/topic' + # else + # p ['got a message', msg] + # puts msg.body + # end + # end + # end + # + # EM.run{ + # EM.connect 'localhost', 61613, StompClient + # } + # + module Stomp + include LineText2 + + class Message + # The command associated with the message, usually 'CONNECTED' or 'MESSAGE' + attr_accessor :command + # Hash containing headers such as destination and message-id + attr_accessor :header + alias :headers :header + # Body of the message + attr_accessor :body + + # @private + def initialize + @header = {} + @state = :precommand + @content_length = nil + end + # @private + def consume_line line + if @state == :precommand + unless line =~ /\A\s*\Z/ + @command = line + @state = :headers + end + elsif @state == :headers + if line == "" + if @content_length + yield( [:sized_text, @content_length+1] ) + else + @state = :body + yield( [:unsized_text] ) + end + elsif line =~ /\A([^:]+):(.+)\Z/ + k = $1.dup.strip + v = $2.dup.strip + @header[k] = v + if k == "content-length" + @content_length = v.to_i + end + else + # This is a protocol error. How to signal it? + end + elsif @state == :body + @body = line + yield( [:dispatch] ) + end + end + end + + # @private + def send_frame verb, headers={}, body="" + body = body.to_s + ary = [verb, "\n"] + body_bytesize = body.bytesize if body.respond_to? :bytesize + body_bytesize ||= body.size + headers.each {|k,v| ary << "#{k}:#{v}\n" } + ary << "content-length: #{body_bytesize}\n" + ary << "content-type: text/plain; charset=UTF-8\n" unless headers.has_key? 'content-type' + ary << "\n" + ary << body + ary << "\0" + send_data ary.join + end + + # @private + def receive_line line + @stomp_initialized || init_message_reader + @stomp_message.consume_line(line) {|outcome| + if outcome.first == :sized_text + set_text_mode outcome[1] + elsif outcome.first == :unsized_text + set_delimiter "\0" + elsif outcome.first == :dispatch + receive_msg(@stomp_message) if respond_to?(:receive_msg) + init_message_reader + end + } + end + + # @private + def receive_binary_data data + @stomp_message.body = data[0..-2] + receive_msg(@stomp_message) if respond_to?(:receive_msg) + init_message_reader + end + + # @private + def init_message_reader + @stomp_initialized = true + set_delimiter "\n" + set_line_mode + @stomp_message = Message.new + end + + # Invoked with an incoming Stomp::Message received from the STOMP server + def receive_msg msg + # stub, overwrite this in your handler + end + + # CONNECT command, for authentication + # + # connect :login => 'guest', :passcode => 'guest' + # + def connect parms={} + send_frame "CONNECT", parms + end + + # SEND command, for publishing messages to a topic + # + # send '/topic/name', 'some message here' + # + def send destination, body, parms={} + send_frame "SEND", parms.merge( :destination=>destination ), body.to_s + end + + # SUBSCRIBE command, for subscribing to topics + # + # subscribe '/topic/name', false + # + def subscribe dest, ack=false + send_frame "SUBSCRIBE", {:destination=>dest, :ack=>(ack ? "client" : "auto")} + end + + # ACK command, for acknowledging receipt of messages + # + # module StompClient + # include EM::P::Stomp + # + # def connection_completed + # connect :login => 'guest', :passcode => 'guest' + # # subscribe with ack mode + # subscribe '/some/topic', true + # end + # + # def receive_msg msg + # if msg.command == "MESSAGE" + # ack msg.headers['message-id'] + # puts msg.body + # end + # end + # end + # + def ack msgid + send_frame "ACK", 'message-id'=> msgid + end + + end + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb new file mode 100644 index 0000000..3187893 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb @@ -0,0 +1,54 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + + # @private + class TcpConnectTester < Connection + include EventMachine::Deferrable + + def self.test( host, port ) + EventMachine.connect( host, port, self ) + end + + def post_init + @start_time = Time.now + end + + def connection_completed + @completed = true + set_deferred_status :succeeded, (Time.now - @start_time) + close_connection + end + + def unbind + set_deferred_status :failed, (Time.now - @start_time) unless @completed + end + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb new file mode 100644 index 0000000..94fd175 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb @@ -0,0 +1,1284 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 8 Apr 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#------------------------------------------------------------------- +# +# + +# TODO List: +# TCP-connects currently assume non-blocking connect is available- need to +# degrade automatically on versions of Ruby prior to June 2006. +# + +require 'singleton' +require 'forwardable' +require 'socket' +require 'fcntl' +require 'set' +require 'openssl' + +module EventMachine + # @private + class Error < Exception; end + # @private + class UnknownTimerFired < RuntimeError; end + # @private + class Unsupported < RuntimeError; end + # @private + class ConnectionError < RuntimeError; end + # @private + class ConnectionNotBound < RuntimeError; end + + # Older versions of Ruby may not provide the SSLErrorWaitReadable + # OpenSSL class. Create an error class to act as a "proxy". + if defined?(OpenSSL::SSL::SSLErrorWaitReadable) + SSLConnectionWaitReadable = OpenSSL::SSL::SSLErrorWaitReadable + else + SSLConnectionWaitReadable = IO::WaitReadable + end + + # Older versions of Ruby may not provide the SSLErrorWaitWritable + # OpenSSL class. Create an error class to act as a "proxy". + if defined?(OpenSSL::SSL::SSLErrorWaitWritable) + SSLConnectionWaitWritable = OpenSSL::SSL::SSLErrorWaitWritable + else + SSLConnectionWaitWritable = IO::WaitWritable + end +end + +module EventMachine + class CertificateCreator + attr_reader :cert, :key + + def initialize + @key = OpenSSL::PKey::RSA.new(1024) + public_key = @key.public_key + subject = "/C=EventMachine/O=EventMachine/OU=EventMachine/CN=EventMachine" + @cert = OpenSSL::X509::Certificate.new + @cert.subject = @cert.issuer = OpenSSL::X509::Name.parse(subject) + @cert.not_before = Time.now + @cert.not_after = Time.now + 365 * 24 * 60 * 60 + @cert.public_key = public_key + @cert.serial = 0x0 + @cert.version = 2 + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = @cert + factory.issuer_certificate = @cert + @cert.extensions = [ + factory.create_extension("basicConstraints","CA:TRUE", true), + factory.create_extension("subjectKeyIdentifier", "hash") + ] + @cert.add_extension factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") + @cert.sign(@key, OpenSSL::Digest::SHA1.new) + end + end + + # @private + DefaultCertificate = CertificateCreator.new + + # @private + DefaultDHKey1024 = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ +AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR +T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC +-----END DH PARAMETERS----- + _end_of_pem_ + + # @private + DefaultDHKey2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY +JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab +VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 +YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 +1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD +7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== +-----END DH PARAMETERS----- + _end_of_pem_ +end + +# @private +module EventMachine + class << self + # This is mostly useful for automated tests. + # Return a distinctive symbol so the caller knows whether he's dealing + # with an extension or with a pure-Ruby library. + # @private + def library_type + :pure_ruby + end + + # @private + def initialize_event_machine + Reactor.instance.initialize_for_run + end + + # Changed 04Oct06: intervals from the caller are now in milliseconds, but our native-ruby + # processor still wants them in seconds. + # @private + def add_oneshot_timer interval + Reactor.instance.install_oneshot_timer(interval / 1000) + end + + # @private + def run_machine + Reactor.instance.run + end + + # @private + def release_machine + end + + + def stopping? + return Reactor.instance.stop_scheduled + end + + # @private + def stop + Reactor.instance.stop + end + + # @private + def connect_server host, port + bind_connect_server nil, nil, host, port + end + + # @private + def bind_connect_server bind_addr, bind_port, host, port + EvmaTCPClient.connect(bind_addr, bind_port, host, port).uuid + end + + # @private + def send_data target, data, datalength + selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target" + selectable.send_data data + end + + # @private + def close_connection target, after_writing + selectable = Reactor.instance.get_selectable( target ) + selectable.schedule_close after_writing if selectable + end + + # @private + def start_tcp_server host, port + (s = EvmaTCPServer.start_server host, port) or raise "no acceptor" + s.uuid + end + + # @private + def stop_tcp_server sig + s = Reactor.instance.get_selectable(sig) + s.schedule_close + end + + # @private + def start_unix_server chain + (s = EvmaUNIXServer.start_server chain) or raise "no acceptor" + s.uuid + end + + # @private + def connect_unix_server chain + EvmaUNIXClient.connect(chain).uuid + end + + # @private + def signal_loopbreak + Reactor.instance.signal_loopbreak + end + + # @private + def get_peername sig + selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_peername target" + selectable.get_peername + end + + # @private + def get_sockname sig + selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_sockname target" + selectable.get_sockname + end + + # @private + def open_udp_socket host, port + EvmaUDPSocket.create(host, port).uuid + end + + # This is currently only for UDP! + # We need to make it work with unix-domain sockets as well. + # @private + def send_datagram target, data, datalength, host, port + selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target" + selectable.send_datagram data, Socket::pack_sockaddr_in(port, host) + end + + + # Sets reactor quantum in milliseconds. The underlying Reactor function wants a (possibly + # fractional) number of seconds. + # @private + def set_timer_quantum interval + Reactor.instance.set_timer_quantum(( 1.0 * interval) / 1000.0) + end + + # This method is a harmless no-op in the pure-Ruby implementation. This is intended to ensure + # that user code behaves properly across different EM implementations. + # @private + def epoll + end + + # @private + def ssl? + true + end + + def tls_parm_set?(parm) + !(parm.nil? || parm.empty?) + end + + # This method takes a series of positional arguments for specifying such + # things as private keys and certificate chains. It's expected that the + # parameter list will grow as we add more supported features. ALL of these + # parameters are optional, and can be specified as empty or nil strings. + # @private + def set_tls_parms signature, priv_key, cert_chain, verify_peer, fail_if_no_peer_cert, sni_hostname, cipher_list, ecdh_curve, dhparam, protocols_bitmask + bitmask = protocols_bitmask + ssl_options = OpenSSL::SSL::OP_ALL + ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) && EM_PROTO_SSLv2 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) && EM_PROTO_SSLv3 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1) && EM_PROTO_TLSv1 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) && EM_PROTO_TLSv1_1 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_2 if defined?(OpenSSL::SSL::OP_NO_TLSv1_2) && EM_PROTO_TLSv1_2 & bitmask == 0 + @tls_parms ||= {} + @tls_parms[signature] = { + :verify_peer => verify_peer, + :fail_if_no_peer_cert => fail_if_no_peer_cert, + :ssl_options => ssl_options + } + @tls_parms[signature][:priv_key] = File.read(priv_key) if tls_parm_set?(priv_key) + @tls_parms[signature][:cert_chain] = File.read(cert_chain) if tls_parm_set?(cert_chain) + @tls_parms[signature][:sni_hostname] = sni_hostname if tls_parm_set?(sni_hostname) + @tls_parms[signature][:cipher_list] = cipher_list.gsub(/,\s*/, ':') if tls_parm_set?(cipher_list) + @tls_parms[signature][:dhparam] = File.read(dhparam) if tls_parm_set?(dhparam) + @tls_parms[signature][:ecdh_curve] = ecdh_curve if tls_parm_set?(ecdh_curve) + end + + def start_tls signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown io selectable for start_tls" + tls_parms = @tls_parms[signature] + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = tls_parms[:ssl_options] + ctx.cert = DefaultCertificate.cert + ctx.key = DefaultCertificate.key + ctx.cert_store = OpenSSL::X509::Store.new + ctx.cert_store.set_default_paths + ctx.cert = OpenSSL::X509::Certificate.new(tls_parms[:cert_chain]) if tls_parms[:cert_chain] + ctx.key = OpenSSL::PKey::RSA.new(tls_parms[:priv_key]) if tls_parms[:priv_key] + verify_mode = OpenSSL::SSL::VERIFY_NONE + if tls_parms[:verify_peer] + verify_mode |= OpenSSL::SSL::VERIFY_PEER + end + if tls_parms[:fail_if_no_peer_cert] + verify_mode |= OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + end + ctx.verify_mode = verify_mode + ctx.servername_cb = Proc.new do |_, server_name| + tls_parms[:server_name] = server_name + nil + end + ctx.ciphers = tls_parms[:cipher_list] if tls_parms[:cipher_list] + if selectable.is_server + ctx.tmp_dh_callback = Proc.new do |_, _, key_length| + if tls_parms[:dhparam] + OpenSSL::PKey::DH.new(tls_parms[:dhparam]) + else + case key_length + when 1024 then DefaultDHKey1024 + when 2048 then DefaultDHKey2048 + else + nil + end + end + end + if tls_parms[:ecdh_curve] && ctx.respond_to?(:tmp_ecdh_callback) + ctx.tmp_ecdh_callback = Proc.new do + OpenSSL::PKey::EC.new(tls_parms[:ecdh_curve]) + end + end + end + ssl_io = OpenSSL::SSL::SSLSocket.new(selectable, ctx) + ssl_io.sync_close = true + if tls_parms[:sni_hostname] + ssl_io.hostname = tls_parms[:sni_hostname] if ssl_io.respond_to?(:hostname=) + end + begin + selectable.is_server ? ssl_io.accept_nonblock : ssl_io.connect_nonblock + rescue; end + selectable.io = ssl_io + end + + def get_peer_cert signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_peer_cert target" + if selectable.io.respond_to?(:peer_cert) && selectable.io.peer_cert + selectable.io.peer_cert.to_pem + else + nil + end + end + + def get_cipher_name signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_name target" + selectable.io.respond_to?(:cipher) ? selectable.io.cipher[0] : nil + end + + def get_cipher_protocol signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_protocol target" + selectable.io.respond_to?(:cipher) ? selectable.io.cipher[1] : nil + end + + def get_cipher_bits signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_bits target" + selectable.io.respond_to?(:cipher) ? selectable.io.cipher[2] : nil + end + + def get_sni_hostname signature + @tls_parms ||= {} + if @tls_parms[signature] + @tls_parms[signature][:server_name] + else + nil + end + end + + # This method is a no-op in the pure-Ruby implementation. We simply return Ruby's built-in + # per-process file-descriptor limit. + # @private + def set_rlimit_nofile n + 1024 + end + + # This method is a harmless no-op in pure Ruby, which doesn't have a built-in limit + # on the number of available timers. + # @private + def set_max_timer_count n + end + + # @private + def get_sock_opt signature, level, optname + selectable = Reactor.instance.get_selectable( signature ) or raise "unknown get_sock_opt target" + selectable.getsockopt level, optname + end + + # @private + def set_sock_opt signature, level, optname, optval + selectable = Reactor.instance.get_selectable( signature ) or raise "unknown set_sock_opt target" + selectable.setsockopt level, optname, optval + end + + # @private + def send_file_data sig, filename + sz = File.size(filename) + raise "file too large" if sz > 32*1024 + data = + begin + File.read filename + rescue + "" + end + send_data sig, data, data.length + end + + # @private + def get_outbound_data_size sig + r = Reactor.instance.get_selectable( sig ) or raise "unknown get_outbound_data_size target" + r.get_outbound_data_size + end + + # @private + def read_keyboard + EvmaKeyboard.open.uuid + end + + # @private + def set_comm_inactivity_timeout sig, tm + r = Reactor.instance.get_selectable( sig ) or raise "unknown set_comm_inactivity_timeout target" + r.set_inactivity_timeout tm + end + + # @private + def set_pending_connect_timeout sig, tm + # Needs to be implemented. Currently a no-op stub to allow + # certain software to operate with the EM pure-ruby. + end + + # @private + def report_connection_error_status signature + get_sock_opt(signature, Socket::SOL_SOCKET, Socket::SO_ERROR).int + end + end +end + +module EventMachine + # @private + class Connection + # @private + def get_outbound_data_size + EventMachine::get_outbound_data_size @signature + end + end +end + +module EventMachine + + # Factored out so we can substitute other implementations + # here if desired, such as the one in ActiveRBAC. + # @private + module UuidGenerator + def self.generate + @ix ||= 0 + @ix += 1 + end + end +end + + +module EventMachine + # @private + TimerFired = 100 + # @private + ConnectionData = 101 + # @private + ConnectionUnbound = 102 + # @private + ConnectionAccepted = 103 + # @private + ConnectionCompleted = 104 + # @private + LoopbreakSignalled = 105 + # @private + ConnectionNotifyReadable = 106 + # @private + ConnectionNotifyWritable = 107 + # @private + SslHandshakeCompleted = 108 + # @private + SslVerify = 109 + # @private + EM_PROTO_SSLv2 = 2 + # @private + EM_PROTO_SSLv3 = 4 + # @private + EM_PROTO_TLSv1 = 8 + # @private + EM_PROTO_TLSv1_1 = 16 + # @private + EM_PROTO_TLSv1_2 = 32 +end + +module EventMachine + # @private + class Reactor + include Singleton + + HeartbeatInterval = 2 + + attr_reader :current_loop_time, :stop_scheduled + + def initialize + initialize_for_run + end + + def install_oneshot_timer interval + uuid = UuidGenerator::generate + #@timers << [Time.now + interval, uuid] + #@timers.sort! {|a,b| a.first <=> b.first} + @timers.add([Time.now + interval, uuid]) + uuid + end + + # Called before run, this is a good place to clear out arrays + # with cruft that may be left over from a previous run. + # @private + def initialize_for_run + @running = false + @stop_scheduled = false + @selectables ||= {}; @selectables.clear + @timers = SortedSet.new # [] + set_timer_quantum(0.1) + @current_loop_time = Time.now + @next_heartbeat = @current_loop_time + HeartbeatInterval + end + + def add_selectable io + @selectables[io.uuid] = io + end + + def get_selectable uuid + @selectables[uuid] + end + + def run + raise Error.new( "already running" ) if @running + @running = true + + begin + open_loopbreaker + + loop { + @current_loop_time = Time.now + + break if @stop_scheduled + run_timers + break if @stop_scheduled + crank_selectables + break if @stop_scheduled + run_heartbeats + } + ensure + close_loopbreaker + @selectables.each {|k, io| io.close} + @selectables.clear + + @running = false + end + + end + + def run_timers + @timers.each {|t| + if t.first <= @current_loop_time + @timers.delete t + EventMachine::event_callback "", TimerFired, t.last + else + break + end + } + #while @timers.length > 0 and @timers.first.first <= now + # t = @timers.shift + # EventMachine::event_callback "", TimerFired, t.last + #end + end + + def run_heartbeats + if @next_heartbeat <= @current_loop_time + @next_heartbeat = @current_loop_time + HeartbeatInterval + @selectables.each {|k,io| io.heartbeat} + end + end + + def crank_selectables + #$stderr.write 'R' + + readers = @selectables.values.select {|io| io.select_for_reading?} + writers = @selectables.values.select {|io| io.select_for_writing?} + + s = select( readers, writers, nil, @timer_quantum) + + s and s[1] and s[1].each {|w| w.eventable_write } + s and s[0] and s[0].each {|r| r.eventable_read } + + @selectables.delete_if {|k,io| + if io.close_scheduled? + io.close + begin + EventMachine::event_callback io.uuid, ConnectionUnbound, nil + rescue ConnectionNotBound; end + true + end + } + end + + # #stop + def stop + raise Error.new( "not running") unless @running + @stop_scheduled = true + end + + def open_loopbreaker + # Can't use an IO.pipe because they can't be set nonselectable in Windows. + # Pick a random localhost UDP port. + #@loopbreak_writer.close if @loopbreak_writer + #rd,@loopbreak_writer = IO.pipe + @loopbreak_reader = UDPSocket.new + @loopbreak_writer = UDPSocket.new + bound = false + 100.times { + @loopbreak_port = rand(10000) + 40000 + begin + @loopbreak_reader.bind "127.0.0.1", @loopbreak_port + bound = true + break + rescue + end + } + raise "Unable to bind Loopbreaker" unless bound + LoopbreakReader.new(@loopbreak_reader) + end + + def close_loopbreaker + @loopbreak_writer.close + @loopbreak_writer = nil + end + + def signal_loopbreak + begin + @loopbreak_writer.send('+',0,"127.0.0.1",@loopbreak_port) if @loopbreak_writer + rescue IOError; end + end + + def set_timer_quantum interval_in_seconds + @timer_quantum = interval_in_seconds + end + + end + +end + +# @private +class IO + extend Forwardable + def_delegator :@my_selectable, :close_scheduled? + def_delegator :@my_selectable, :select_for_reading? + def_delegator :@my_selectable, :select_for_writing? + def_delegator :@my_selectable, :eventable_read + def_delegator :@my_selectable, :eventable_write + def_delegator :@my_selectable, :uuid + def_delegator :@my_selectable, :is_server + def_delegator :@my_selectable, :is_server= + def_delegator :@my_selectable, :send_data + def_delegator :@my_selectable, :schedule_close + def_delegator :@my_selectable, :get_peername + def_delegator :@my_selectable, :get_sockname + def_delegator :@my_selectable, :send_datagram + def_delegator :@my_selectable, :get_outbound_data_size + def_delegator :@my_selectable, :set_inactivity_timeout + def_delegator :@my_selectable, :heartbeat + def_delegator :@my_selectable, :io + def_delegator :@my_selectable, :io= +end + +module EventMachine + # @private + class Selectable + + attr_accessor :io, :is_server + attr_reader :uuid + + def initialize io + @io = io + @uuid = UuidGenerator.generate + @is_server = false + @last_activity = Reactor.instance.current_loop_time + + if defined?(Fcntl::F_GETFL) + m = @io.fcntl(Fcntl::F_GETFL, 0) + @io.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | m) + else + # Windows doesn't define F_GETFL. + # It's not very reliable about setting descriptors nonblocking either. + begin + s = Socket.for_fd(@io.fileno) + s.fcntl( Fcntl::F_SETFL, Fcntl::O_NONBLOCK ) + rescue Errno::EINVAL, Errno::EBADF + warn "Serious error: unable to set descriptor non-blocking" + end + end + # TODO, should set CLOEXEC on Unix? + + @close_scheduled = false + @close_requested = false + + se = self; @io.instance_eval { @my_selectable = se } + Reactor.instance.add_selectable @io + end + + def close_scheduled? + @close_scheduled + end + + def select_for_reading? + false + end + + def select_for_writing? + false + end + + def get_peername + nil + end + + def get_sockname + nil + end + + def set_inactivity_timeout tm + @inactivity_timeout = tm + end + + def heartbeat + end + + def schedule_close(after_writing=false) + if after_writing + @close_requested = true + else + @close_scheduled = true + end + end + end + +end + +module EventMachine + # @private + class StreamObject < Selectable + def initialize io + super io + @outbound_q = [] + end + + # If we have to close, or a close-after-writing has been requested, + # then don't read any more data. + def select_for_reading? + true unless (@close_scheduled || @close_requested) + end + + # If we have to close, don't select for writing. + # Otherwise, see if the protocol is ready to close. + # If not, see if he has data to send. + # If a close-after-writing has been requested and the outbound queue + # is empty, convert the status to close_scheduled. + def select_for_writing? + unless @close_scheduled + if @outbound_q.empty? + @close_scheduled = true if @close_requested + false + else + true + end + end + end + + # Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006. + # If we have it, then we can read multiple times safely to improve + # performance. + # The last-activity clock ASSUMES that we only come here when we + # have selected readable. + # TODO, coalesce multiple reads into a single event. + # TODO, do the function check somewhere else and cache it. + def eventable_read + @last_activity = Reactor.instance.current_loop_time + begin + if io.respond_to?(:read_nonblock) + 10.times { + data = io.read_nonblock(4096) + EventMachine::event_callback uuid, ConnectionData, data + } + else + data = io.sysread(4096) + EventMachine::event_callback uuid, ConnectionData, data + end + rescue Errno::EAGAIN, Errno::EWOULDBLOCK, SSLConnectionWaitReadable + # no-op + rescue Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Errno::EPIPE, OpenSSL::SSL::SSLError + @close_scheduled = true + EventMachine::event_callback uuid, ConnectionUnbound, nil + end + + end + + # Provisional implementation. Will be re-implemented in subclasses. + # TODO: Complete this implementation. As it stands, this only writes + # a single packet per cycle. Highly inefficient, but required unless + # we're running on a Ruby with proper nonblocking I/O (Ruby 1.8.4 + # built from sources from May 25, 2006 or newer). + # We need to improve the loop so it writes multiple times, however + # not more than a certain number of bytes per cycle, otherwise + # one busy connection could hog output buffers and slow down other + # connections. Also we should coalesce small writes. + # URGENT TODO: Coalesce small writes. They are a performance killer. + # The last-activity recorder ASSUMES we'll only come here if we've + # selected writable. + def eventable_write + # coalesce the outbound array here, perhaps + @last_activity = Reactor.instance.current_loop_time + while data = @outbound_q.shift do + begin + data = data.to_s + w = if io.respond_to?(:write_nonblock) + io.write_nonblock data + else + io.syswrite data + end + + if w < data.length + @outbound_q.unshift data[w..-1] + break + end + rescue Errno::EAGAIN, SSLConnectionWaitReadable, SSLConnectionWaitWritable + @outbound_q.unshift data + break + rescue EOFError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EPIPE, OpenSSL::SSL::SSLError + @close_scheduled = true + @outbound_q.clear + end + end + + end + + # #send_data + def send_data data + # TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last? + unless @close_scheduled or @close_requested or !data or data.length <= 0 + @outbound_q << data.to_s + end + end + + # #get_peername + # This is defined in the normal way on connected stream objects. + # Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants. + # We could also use a convenience method that did the unpacking automatically. + def get_peername + io.getpeername + end + + # #get_sockname + # This is defined in the normal way on connected stream objects. + # Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants. + # We could also use a convenience method that did the unpacking automatically. + def get_sockname + io.getsockname + end + + # #get_outbound_data_size + def get_outbound_data_size + @outbound_q.inject(0) {|memo,obj| memo += (obj || "").length} + end + + def heartbeat + if @inactivity_timeout and @inactivity_timeout > 0 and (@last_activity + @inactivity_timeout) < Reactor.instance.current_loop_time + schedule_close true + end + end + end + + +end + + +#-------------------------------------------------------------- + + + +module EventMachine + # @private + class EvmaTCPClient < StreamObject + + def self.connect bind_addr, bind_port, host, port + sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) + sd.bind( Socket.pack_sockaddr_in( bind_port, bind_addr )) if bind_addr + + begin + # TODO, this assumes a current Ruby snapshot. + # We need to degrade to a nonblocking connect otherwise. + sd.connect_nonblock( Socket.pack_sockaddr_in( port, host )) + rescue Errno::ECONNREFUSED, Errno::EINPROGRESS + end + EvmaTCPClient.new sd + end + + def initialize io + super + @pending = true + @handshake_complete = false + end + + def ready? + if RUBY_PLATFORM =~ /linux/ + io.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO).unpack("i").first == 1 # TCP_ESTABLISHED + else + io.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).unpack("i").first == 0 # NO ERROR + end + end + + def handshake_complete? + if !@handshake_complete && io.respond_to?(:state) + if io.state =~ /^SSLOK/ + @handshake_complete = true + EventMachine::event_callback uuid, SslHandshakeCompleted, "" + EventMachine::event_callback uuid, SslVerify, io.peer_cert.to_pem if io.peer_cert + end + else + @handshake_complete = true + end + @handshake_complete + end + + def pending? + handshake_complete? + if @pending + if ready? + @pending = false + EventMachine::event_callback uuid, ConnectionCompleted, "" + end + end + @pending + end + + def select_for_writing? + pending? + super + end + + def select_for_reading? + pending? + super + end + end +end + + + +module EventMachine + # @private + class EvmaKeyboard < StreamObject + + def self.open + EvmaKeyboard.new STDIN + end + + + def initialize io + super + end + + + def select_for_writing? + false + end + + def select_for_reading? + true + end + + + end +end + + + +module EventMachine + # @private + class EvmaUNIXClient < StreamObject + + def self.connect chain + sd = Socket.new( Socket::AF_LOCAL, Socket::SOCK_STREAM, 0 ) + begin + # TODO, this assumes a current Ruby snapshot. + # We need to degrade to a nonblocking connect otherwise. + sd.connect_nonblock( Socket.pack_sockaddr_un( chain )) + rescue Errno::EINPROGRESS + end + EvmaUNIXClient.new sd + end + + + def initialize io + super + @pending = true + end + + + def select_for_writing? + @pending ? true : super + end + + def select_for_reading? + @pending ? false : super + end + + def eventable_write + if @pending + @pending = false + if 0 == io.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).unpack("i").first + EventMachine::event_callback uuid, ConnectionCompleted, "" + end + else + super + end + end + + + + end +end + + +#-------------------------------------------------------------- + +module EventMachine + # @private + class EvmaTCPServer < Selectable + + # TODO, refactor and unify with EvmaUNIXServer. + + class << self + # Versions of ruby 1.8.4 later than May 26 2006 will work properly + # with an object of type TCPServer. Prior versions won't so we + # play it safe and just build a socket. + # + def start_server host, port + sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) + sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true ) + sd.bind( Socket.pack_sockaddr_in( port, host )) + sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough. + EvmaTCPServer.new sd + end + end + + def initialize io + super io + end + + + def select_for_reading? + true + end + + #-- + # accept_nonblock returns an array consisting of the accepted + # socket and a sockaddr_in which names the peer. + # Don't accept more than 10 at a time. + def eventable_read + begin + 10.times { + descriptor,peername = io.accept_nonblock + sd = EvmaTCPClient.new descriptor + sd.is_server = true + EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid + } + rescue Errno::EWOULDBLOCK, Errno::EAGAIN + end + end + + #-- + # + def schedule_close + @close_scheduled = true + end + + end +end + + +#-------------------------------------------------------------- + +module EventMachine + # @private + class EvmaUNIXServer < Selectable + + # TODO, refactor and unify with EvmaTCPServer. + + class << self + # Versions of ruby 1.8.4 later than May 26 2006 will work properly + # with an object of type TCPServer. Prior versions won't so we + # play it safe and just build a socket. + # + def start_server chain + sd = Socket.new( Socket::AF_LOCAL, Socket::SOCK_STREAM, 0 ) + sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true ) + sd.bind( Socket.pack_sockaddr_un( chain )) + sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough. + EvmaUNIXServer.new sd + end + end + + def initialize io + super io + end + + + def select_for_reading? + true + end + + #-- + # accept_nonblock returns an array consisting of the accepted + # socket and a sockaddr_in which names the peer. + # Don't accept more than 10 at a time. + def eventable_read + begin + 10.times { + descriptor,peername = io.accept_nonblock + sd = StreamObject.new descriptor + EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid + } + rescue Errno::EWOULDBLOCK, Errno::EAGAIN + end + end + + #-- + # + def schedule_close + @close_scheduled = true + end + + end +end + + + +#-------------------------------------------------------------- + +module EventMachine + # @private + class LoopbreakReader < Selectable + + def select_for_reading? + true + end + + def eventable_read + io.sysread(128) + EventMachine::event_callback "", LoopbreakSignalled, "" + end + + end +end + + + +# @private +module EventMachine + # @private + class DatagramObject < Selectable + def initialize io + super io + @outbound_q = [] + end + + # #send_datagram + def send_datagram data, target + # TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last? + unless @close_scheduled or @close_requested + @outbound_q << [data.to_s, target] + end + end + + # #select_for_writing? + def select_for_writing? + unless @close_scheduled + if @outbound_q.empty? + @close_scheduled = true if @close_requested + false + else + true + end + end + end + + # #select_for_reading? + def select_for_reading? + true + end + + # #get_outbound_data_size + def get_outbound_data_size + @outbound_q.inject(0) {|memo,obj| memo += (obj || "").length} + end + + + end + + +end + + +module EventMachine + # @private + class EvmaUDPSocket < DatagramObject + + class << self + def create host, port + sd = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 ) + sd.bind Socket::pack_sockaddr_in( port, host ) + EvmaUDPSocket.new sd + end + end + + # #eventable_write + # This really belongs in DatagramObject, but there is some UDP-specific stuff. + def eventable_write + 40.times { + break if @outbound_q.empty? + begin + data,target = @outbound_q.first + + # This damn better be nonblocking. + io.send data.to_s, 0, target + + @outbound_q.shift + rescue Errno::EAGAIN + # It's not been observed in testing that we ever get here. + # True to the definition, packets will be accepted and quietly dropped + # if the system is under pressure. + break + rescue EOFError, Errno::ECONNRESET + @close_scheduled = true + @outbound_q.clear + end + } + end + + # Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006. + # If we have it, then we can read multiple times safely to improve + # performance. + def eventable_read + begin + if io.respond_to?(:recvfrom_nonblock) + 40.times { + data,@return_address = io.recvfrom_nonblock(16384) + EventMachine::event_callback uuid, ConnectionData, data + @return_address = nil + } + else + raise "unimplemented datagram-read operation on this Ruby" + end + rescue Errno::EAGAIN + # no-op + rescue Errno::ECONNRESET, EOFError + @close_scheduled = true + EventMachine::event_callback uuid, ConnectionUnbound, nil + end + end + + def send_data data + send_datagram data, @return_address + end + end +end + +# load base EM api on top, now that we have the underlying pure ruby +# implementation defined +require 'eventmachine' diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb new file mode 100644 index 0000000..9096ed0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb @@ -0,0 +1,80 @@ +module EventMachine + # A cross thread, reactor scheduled, linear queue. + # + # This class provides a simple queue abstraction on top of the reactor + # scheduler. It services two primary purposes: + # + # * API sugar for stateful protocols + # * Pushing processing onto the reactor thread + # + # @example + # + # q = EM::Queue.new + # q.push('one', 'two', 'three') + # 3.times do + # q.pop { |msg| puts(msg) } + # end + # + class Queue + def initialize + @sink = [] + @drain = [] + @popq = [] + end + + # Pop items off the queue, running the block on the reactor thread. The pop + # will not happen immediately, but at some point in the future, either in + # the next tick, if the queue has data, or when the queue is populated. + # + # @return [NilClass] nil + def pop(*a, &b) + cb = EM::Callback(*a, &b) + EM.schedule do + if @drain.empty? + @drain = @sink + @sink = [] + end + if @drain.empty? + @popq << cb + else + cb.call @drain.shift + end + end + nil # Always returns nil + end + + # Push items onto the queue in the reactor thread. The items will not appear + # in the queue immediately, but will be scheduled for addition during the + # next reactor tick. + def push(*items) + EM.schedule do + @sink.push(*items) + unless @popq.empty? + @drain = @sink + @sink = [] + @popq.shift.call @drain.shift until @drain.empty? || @popq.empty? + end + end + end + alias :<< :push + + # @return [Boolean] + # @note This is a peek, it's not thread safe, and may only tend toward accuracy. + def empty? + @drain.empty? && @sink.empty? + end + + # @return [Integer] Queue size + # @note This is a peek, it's not thread safe, and may only tend toward accuracy. + def size + @drain.size + @sink.size + end + + # @return [Integer] Waiting size + # @note This is a peek at the number of jobs that are currently waiting on the Queue + def num_waiting + @popq.size + end + + end # Queue +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb new file mode 100644 index 0000000..1d2d7aa --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb @@ -0,0 +1,232 @@ +module EventMachine + module DNS + class Resolver + + def self.windows? + if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/ + require 'win32/resolv' + true + else + false + end + end + + HOSTS_FILE = windows? ? Win32::Resolv.get_hosts_path : '/etc/hosts' + + @hosts = nil + @nameservers = nil + @socket = nil + + def self.resolve(hostname) + Request.new(socket, hostname) + end + + def self.socket + if @socket && @socket.error? + @socket = Socket.open + else + @socket ||= Socket.open + end + end + + def self.nameservers=(ns) + @nameservers = ns + end + + def self.nameservers + return @nameservers if @nameservers + + if windows? + _, ns = Win32::Resolv.get_resolv_info + return @nameservers = ns || [] + end + + @nameservers = [] + IO.readlines('/etc/resolv.conf').each do |line| + if line =~ /^nameserver (.+)$/ + @nameservers << $1.split(/\s+/).first + end + end + + @nameservers + rescue + @nameservers = [] + end + + def self.nameserver + nameservers.shuffle.first + end + + def self.hosts + return @hosts if @hosts + + @hosts = {} + IO.readlines(HOSTS_FILE).each do |line| + next if line =~ /^#/ + addr, host = line.split(/\s+/) + + next unless addr && host + @hosts[host] ||= [] + @hosts[host] << addr + end + + @hosts + rescue + @hosts = {} + end + end + + class RequestIdAlreadyUsed < RuntimeError; end + + class Socket < EventMachine::Connection + def self.open + EventMachine::open_datagram_socket('0.0.0.0', 0, self) + end + + def initialize + @nameserver = nil + end + + def post_init + @requests = {} + end + + def start_timer + @timer ||= EM.add_periodic_timer(0.1, &method(:tick)) + end + + def stop_timer + EM.cancel_timer(@timer) + @timer = nil + end + + def unbind + end + + def tick + @requests.each do |id,req| + req.tick + end + end + + def register_request(id, req) + if @requests.has_key?(id) + raise RequestIdAlreadyUsed + else + @requests[id] = req + end + + start_timer + end + + def deregister_request(id, req) + @requests.delete(id) + stop_timer if @requests.length == 0 + end + + def send_packet(pkt) + send_datagram(pkt, nameserver, 53) + end + + def nameserver=(ns) + @nameserver = ns + end + + def nameserver + @nameserver || Resolver.nameserver + end + + # Decodes the packet, looks for the request and passes the + # response over to the requester + def receive_data(data) + msg = nil + begin + msg = Resolv::DNS::Message.decode data + rescue + else + req = @requests[msg.id] + if req + @requests.delete(msg.id) + stop_timer if @requests.length == 0 + req.receive_answer(msg) + end + end + end + end + + class Request + include Deferrable + attr_accessor :retry_interval, :max_tries + + def initialize(socket, hostname) + @socket = socket + @hostname = hostname + @tries = 0 + @last_send = Time.at(0) + @retry_interval = 3 + @max_tries = 5 + + if addrs = Resolver.hosts[hostname] + succeed addrs + else + EM.next_tick { tick } + end + end + + def tick + # Break early if nothing to do + return if @last_send + @retry_interval > Time.now + if @tries < @max_tries + send + else + @socket.deregister_request(@id, self) + fail 'retries exceeded' + end + end + + def receive_answer(msg) + addrs = [] + msg.each_answer do |name,ttl,data| + if data.kind_of?(Resolv::DNS::Resource::IN::A) || + data.kind_of?(Resolv::DNS::Resource::IN::AAAA) + addrs << data.address.to_s + end + end + + if addrs.empty? + fail "rcode=#{msg.rcode}" + else + succeed addrs + end + end + + private + + def send + @tries += 1 + @last_send = Time.now + @socket.send_packet(packet.encode) + end + + def id + begin + @id = rand(65535) + @socket.register_request(@id, self) + rescue RequestIdAlreadyUsed + retry + end unless defined?(@id) + + @id + end + + def packet + msg = Resolv::DNS::Message.new + msg.id = id + msg.rd = 1 + msg.add_question @hostname, Resolv::DNS::Resource::IN::A + msg + end + + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb new file mode 100644 index 0000000..35c087d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb @@ -0,0 +1,84 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 25 Aug 2007 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + # Support for Erlang-style processes. + # + class SpawnedProcess + # Send a message to the spawned process + def notify *x + me = self + EM.next_tick { + # A notification executes in the context of this + # SpawnedProcess object. That makes self and notify + # work as one would expect. + # + y = me.call(*x) + if y and y.respond_to?(:pull_out_yield_block) + a,b = y.pull_out_yield_block + set_receiver a + self.notify if b + end + } + end + alias_method :resume, :notify + alias_method :run, :notify # for formulations like (EM.spawn {xxx}).run + + def set_receiver blk + (class << self ; self ; end).class_eval do + remove_method :call if method_defined? :call + define_method :call, blk + end + end + + end + + # @private + class YieldBlockFromSpawnedProcess + def initialize block, notify + @block = [block,notify] + end + def pull_out_yield_block + @block + end + end + + # Spawn an erlang-style process + def self.spawn &block + s = SpawnedProcess.new + s.set_receiver block + s + end + + # @private + def self.yield &block + return YieldBlockFromSpawnedProcess.new( block, false ) + end + + # @private + def self.yield_and_notify &block + return YieldBlockFromSpawnedProcess.new( block, true ) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb new file mode 100644 index 0000000..cf49c88 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb @@ -0,0 +1,118 @@ +module EventMachine + # Streams a file over a given connection. Streaming begins once the object is + # instantiated. Typically FileStreamer instances are not reused. + # + # Streaming uses buffering for files larger than 16K and uses so-called fast file reader (a C++ extension) + # if available (it is part of eventmachine gem itself). + # + # @example + # + # module FileSender + # def post_init + # streamer = EventMachine::FileStreamer.new(self, '/tmp/bigfile.tar') + # streamer.callback{ + # # file was sent successfully + # close_connection_after_writing + # } + # end + # end + # + # + # @author Francis Cianfrocca + class FileStreamer + include Deferrable + + # Use mapped streamer for files bigger than 16k + MappingThreshold = 16384 + # Wait until next tick to send more data when 50k is still in the outgoing buffer + BackpressureLevel = 50000 + # Send 16k chunks at a time + ChunkSize = 16384 + + # @param [EventMachine::Connection] connection + # @param [String] filename File path + # + # @option args [Boolean] :http_chunks (false) Use HTTP 1.1 style chunked-encoding semantics. + def initialize connection, filename, args = {} + @connection = connection + @http_chunks = args[:http_chunks] + + if File.exist?(filename) + @size = File.size(filename) + if @size <= MappingThreshold + stream_without_mapping filename + else + stream_with_mapping filename + end + else + fail "file not found" + end + end + + # @private + def stream_without_mapping filename + if @http_chunks + @connection.send_data "#{@size.to_s(16)}\r\n" + @connection.send_file_data filename + @connection.send_data "\r\n0\r\n\r\n" + else + @connection.send_file_data filename + end + succeed + end + private :stream_without_mapping + + # @private + def stream_with_mapping filename + ensure_mapping_extension_is_present + + @position = 0 + @mapping = EventMachine::FastFileReader::Mapper.new filename + stream_one_chunk + end + private :stream_with_mapping + + # Used internally to stream one chunk at a time over multiple reactor ticks + # @private + def stream_one_chunk + loop { + if @position < @size + if @connection.get_outbound_data_size > BackpressureLevel + EventMachine::next_tick {stream_one_chunk} + break + else + len = @size - @position + len = ChunkSize if (len > ChunkSize) + + @connection.send_data( "#{len.to_s(16)}\r\n" ) if @http_chunks + @connection.send_data( @mapping.get_chunk( @position, len )) + @connection.send_data("\r\n") if @http_chunks + + @position += len + end + else + @connection.send_data "0\r\n\r\n" if @http_chunks + @mapping.close + succeed + break + end + } + end + + # + # We use an outboard extension class to get memory-mapped files. + # It's outboard to avoid polluting the core distro, but that means + # there's a "hidden" dependency on it. The first time we get here in + # any run, try to load up the dependency extension. User code will see + # a LoadError if it's not available, but code that doesn't require + # mapped files will work fine without it. This is a somewhat difficult + # compromise between usability and proper modularization. + # + # @private + def ensure_mapping_extension_is_present + @@fastfilereader ||= (require 'fastfilereaderext') + end + private :ensure_mapping_extension_is_present + + end # FileStreamer +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb new file mode 100644 index 0000000..87704d5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb @@ -0,0 +1,90 @@ +module EventMachine + # = EventMachine::ThreadedResource + # + # A threaded resource is a "quick and dirty" wrapper around the concept of + # wiring up synchronous code into a standard EM::Pool. This is useful to keep + # interfaces coherent and provide a simple approach at "making an interface + # async-ish". + # + # General usage is to wrap libraries that do not support EventMachine, or to + # have a specific number of dedicated high-cpu worker resources. + # + # == Basic Usage example + # + # This example requires the cassandra gem. The cassandra gem contains an + # EventMachine interface, but it's sadly Fiber based and thus only works on + # 1.9. It also requires (potentially) complex stack switching logic to reach + # completion of nested operations. By contrast this approach provides a block + # in which normal synchronous code can occur, but makes no attempt to wire the + # IO into EventMachines C++ IO implementations, instead relying on the reactor + # pattern in rb_thread_select. + # + # cassandra_dispatcher = ThreadedResource.new do + # Cassandra.new('allthethings', '127.0.0.1:9160') + # end + # + # pool = EM::Pool.new + # + # pool.add cassandra_dispatcher + # + # # If we don't care about the result: + # pool.perform do |dispatcher| + # # The following block executes inside a dedicated thread, and should not + # # access EventMachine things: + # dispatcher.dispatch do |cassandra| + # cassandra.insert(:Things, '10', 'stuff' => 'things') + # end + # end + # + # # Example where we care about the result: + # pool.perform do |dispatcher| + # # The dispatch block is executed in the resources thread. + # completion = dispatcher.dispatch do |cassandra| + # cassandra.get(:Things, '10', 'stuff') + # end + # + # # This block will be yielded on the EM thread: + # completion.callback do |result| + # EM.do_something_with(result) + # end + # + # completion + # end + class ThreadedResource + + # The block should return the resource that will be yielded in a dispatch. + def initialize + @resource = yield + + @running = true + @queue = ::Queue.new + @thread = Thread.new do + @queue.pop.call while @running + end + end + + # Called on the EM thread, generally in a perform block to return a + # completion for the work. + def dispatch + completion = EM::Completion.new + @queue << lambda do + begin + result = yield @resource + completion.succeed result + rescue => e + completion.fail e + end + end + completion + end + + # Kill the internal thread. should only be used to cleanup - generally + # only required for tests. + def shutdown + @running = false + @queue << lambda {} + @thread.join + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb new file mode 100644 index 0000000..a95d516 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb @@ -0,0 +1,85 @@ +module EventMachine + # Creates and immediately starts an EventMachine::TickLoop + def self.tick_loop(*a, &b) + TickLoop.new(*a, &b).start + end + + # A TickLoop is useful when one needs to distribute amounts of work + # throughout ticks in order to maintain response times. It is also useful for + # simple repeated checks and metrics. + # @example + # # Here we run through an array one item per tick until it is empty, + # # printing each element. + # # When the array is empty, we return :stop from the callback, and the + # # loop will terminate. + # # When the loop terminates, the on_stop callbacks will be called. + # EM.run do + # array = (1..100).to_a + # + # tickloop = EM.tick_loop do + # if array.empty? + # :stop + # else + # puts array.shift + # end + # end + # + # tickloop.on_stop { EM.stop } + # end + # + class TickLoop + + # Arguments: A callback (EM::Callback) to call each tick. If the call + # returns +:stop+ then the loop will be stopped. Any other value is + # ignored. + def initialize(*a, &b) + @work = EM::Callback(*a, &b) + @stops = [] + @stopped = true + end + + # Arguments: A callback (EM::Callback) to call once on the next stop (or + # immediately if already stopped). + def on_stop(*a, &b) + if @stopped + EM::Callback(*a, &b).call + else + @stops << EM::Callback(*a, &b) + end + end + + # Stop the tick loop immediately, and call it's on_stop callbacks. + def stop + @stopped = true + until @stops.empty? + @stops.shift.call + end + end + + # Query if the loop is stopped. + def stopped? + @stopped + end + + # Start the tick loop, will raise argument error if the loop is already + # running. + def start + raise ArgumentError, "double start" unless @stopped + @stopped = false + schedule + end + + private + def schedule + EM.next_tick do + next if @stopped + if @work.call == :stop + stop + else + schedule + end + end + self + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb new file mode 100644 index 0000000..41cd959 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb @@ -0,0 +1,61 @@ +module EventMachine + # Creates a one-time timer + # + # timer = EventMachine::Timer.new(5) do + # # this will never fire because we cancel it + # end + # timer.cancel + # + class Timer + # Create a new timer that fires after a given number of seconds + def initialize interval, callback=nil, &block + @signature = EventMachine::add_timer(interval, callback || block) + end + + # Cancel the timer + def cancel + EventMachine.send :cancel_timer, @signature + end + end + + # Creates a periodic timer + # + # @example + # n = 0 + # timer = EventMachine::PeriodicTimer.new(5) do + # puts "the time is #{Time.now}" + # timer.cancel if (n+=1) > 5 + # end + # + class PeriodicTimer + # Create a new periodic timer that executes every interval seconds + def initialize interval, callback=nil, &block + @interval = interval + @code = callback || block + @cancelled = false + @work = method(:fire) + schedule + end + + # Cancel the periodic timer + def cancel + @cancelled = true + end + + # Fire the timer every interval seconds + attr_accessor :interval + + # @private + def schedule + EventMachine::add_timer @interval, @work + end + + # @private + def fire + unless @cancelled + @code.call + schedule + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb new file mode 100644 index 0000000..ffc4310 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb @@ -0,0 +1,3 @@ +module EventMachine + VERSION = "1.2.7" +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/eventmachine.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/eventmachine.rb new file mode 100644 index 0000000..0873747 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/eventmachine.rb @@ -0,0 +1,1601 @@ +if defined?(EventMachine.library_type) and EventMachine.library_type == :pure_ruby + # assume 'em/pure_ruby' was loaded already +elsif RUBY_PLATFORM =~ /java/ + require 'java' + require 'jeventmachine' +else + begin + require 'rubyeventmachine' + rescue LoadError + warn "Unable to load the EventMachine C extension; To use the pure-ruby reactor, require 'em/pure_ruby'" + raise + end +end + +require 'em/version' +require 'em/pool' +require 'em/deferrable' +require 'em/future' +require 'em/streamer' +require 'em/spawnable' +require 'em/processes' +require 'em/iterator' +require 'em/buftok' +require 'em/timers' +require 'em/protocols' +require 'em/connection' +require 'em/callback' +require 'em/queue' +require 'em/channel' +require 'em/file_watch' +require 'em/process_watch' +require 'em/tick_loop' +require 'em/resolver' +require 'em/completion' +require 'em/threaded_resource' + +require 'shellwords' +require 'thread' +require 'resolv' + +# Top-level EventMachine namespace. If you are looking for EventMachine examples, see {file:docs/GettingStarted.md EventMachine tutorial}. +# +# ## Key methods ## +# ### Starting and stopping the event loop ### +# +# * {EventMachine.run} +# * {EventMachine.stop_event_loop} +# +# ### Implementing clients ### +# +# * {EventMachine.connect} +# +# ### Implementing servers ### +# +# * {EventMachine.start_server} +# +# ### Working with timers ### +# +# * {EventMachine.add_timer} +# * {EventMachine.add_periodic_timer} +# * {EventMachine.cancel_timer} +# +# ### Working with blocking tasks ### +# +# * {EventMachine.defer} +# * {EventMachine.next_tick} +# +# ### Efficient proxying ### +# +# * {EventMachine.enable_proxy} +# * {EventMachine.disable_proxy} +module EventMachine + class << self + # Exposed to allow joining on the thread, when run in a multithreaded + # environment. Performing other actions on the thread has undefined + # semantics (read: a dangerous endevor). + # + # @return [Thread] + attr_reader :reactor_thread + end + @next_tick_mutex = Mutex.new + @reactor_running = false + @next_tick_queue = [] + @tails = [] + @threadpool = @threadqueue = @resultqueue = nil + @all_threads_spawned = false + + # System errnos + # @private + ERRNOS = Errno::constants.grep(/^E/).inject(Hash.new(:unknown)) { |hash, name| + errno = Errno.__send__(:const_get, name) + hash[errno::Errno] = errno + hash + } + + # Initializes and runs an event loop. This method only returns if code inside the block passed to this method + # calls {EventMachine.stop_event_loop}. The block is executed after initializing its internal event loop but *before* running the loop, + # therefore this block is the right place to call any code that needs event loop to run, for example, {EventMachine.start_server}, + # {EventMachine.connect} or similar methods of libraries that use EventMachine under the hood + # (like `EventMachine::HttpRequest.new` or `AMQP.start`). + # + # Programs that are run for long periods of time (e.g. servers) usually start event loop by calling {EventMachine.run}, and let it + # run "forever". It's also possible to use {EventMachine.run} to make a single client-connection to a remote server, + # process the data flow from that single connection, and then call {EventMachine.stop_event_loop} to stop, in other words, + # to run event loop for a short period of time (necessary to complete some operation) and then shut it down. + # + # Once event loop is running, it is perfectly possible to start multiple servers and clients simultaneously: content-aware + # proxies like [Proxymachine](https://github.com/mojombo/proxymachine) do just that. + # + # ## Using EventMachine with Ruby on Rails and other Web application frameworks ## + # + # Standalone applications often run event loop on the main thread, thus blocking for their entire lifespan. In case of Web applications, + # if you are running an EventMachine-based app server such as [Thin](http://code.macournoyer.com/thin/) or [Goliath](https://github.com/postrank-labs/goliath/), + # they start event loop for you. Servers like Unicorn, Apache Passenger or Mongrel occupy main Ruby thread to serve HTTP(S) requests. This means + # that calling {EventMachine.run} on the same thread is not an option (it will result in Web server never binding to the socket). + # In that case, start event loop in a separate thread as demonstrated below. + # + # + # @example Starting EventMachine event loop in the current thread to run the "Hello, world"-like Echo server example + # + # #!/usr/bin/env ruby + # + # require 'rubygems' # or use Bundler.setup + # require 'eventmachine' + # + # class EchoServer < EM::Connection + # def receive_data(data) + # send_data(data) + # end + # end + # + # EventMachine.run do + # EventMachine.start_server("0.0.0.0", 10000, EchoServer) + # end + # + # + # @example Starting EventMachine event loop in a separate thread + # + # # doesn't block current thread, can be used with Ruby on Rails, Sinatra, Merb, Rack + # # and any other application server that occupies main Ruby thread. + # Thread.new { EventMachine.run } + # + # + # @note This method blocks calling thread. If you need to start EventMachine event loop from a Web app + # running on a non event-driven server (Unicorn, Apache Passenger, Mongrel), do it in a separate thread like demonstrated + # in one of the examples. + # @see file:docs/GettingStarted.md Getting started with EventMachine + # @see EventMachine.stop_event_loop + def self.run blk=nil, tail=nil, &block + # Obsoleted the use_threads mechanism. + # 25Nov06: Added the begin/ensure block. We need to be sure that release_machine + # gets called even if an exception gets thrown within any of the user code + # that the event loop runs. The best way to see this is to run a unit + # test with two functions, each of which calls {EventMachine.run} and each of + # which throws something inside of #run. Without the ensure, the second test + # will start without release_machine being called and will immediately throw + + # + if @reactor_running and @reactor_pid != Process.pid + # Reactor was started in a different parent, meaning we have forked. + # Clean up reactor state so a new reactor boots up in this child. + stop_event_loop + release_machine + cleanup_machine + @reactor_running = false + end + + tail and @tails.unshift(tail) + + if reactor_running? + (b = blk || block) and b.call # next_tick(b) + else + @conns = {} + @acceptors = {} + @timers = {} + @wrapped_exception = nil + @next_tick_queue ||= [] + @tails ||= [] + begin + initialize_event_machine + @reactor_pid = Process.pid + @reactor_thread = Thread.current + @reactor_running = true + + (b = blk || block) and add_timer(0, b) + if @next_tick_queue && !@next_tick_queue.empty? + add_timer(0) { signal_loopbreak } + end + + # Rubinius needs to come back into "Ruby space" for GC to work, + # so we'll crank the machine here. + if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx" + while run_machine_once; end + else + run_machine + end + + ensure + until @tails.empty? + @tails.pop.call + end + + release_machine + cleanup_machine + @reactor_running = false + @reactor_thread = nil + end + + raise @wrapped_exception if @wrapped_exception + end + end + + # Sugars a common use case. Will pass the given block to #run, but will terminate + # the reactor loop and exit the function as soon as the code in the block completes. + # (Normally, {EventMachine.run} keeps running indefinitely, even after the block supplied to it + # finishes running, until user code calls {EventMachine.stop}) + # + def self.run_block &block + pr = proc { + block.call + EventMachine::stop + } + run(&pr) + end + + # @return [Boolean] true if the calling thread is the same thread as the reactor. + def self.reactor_thread? + Thread.current == @reactor_thread + end + + # Runs the given callback on the reactor thread, or immediately if called + # from the reactor thread. Accepts the same arguments as {EventMachine::Callback} + def self.schedule(*a, &b) + cb = Callback(*a, &b) + if reactor_running? && reactor_thread? + cb.call + else + next_tick { cb.call } + end + end + + # Forks a new process, properly stops the reactor and then calls {EventMachine.run} inside of it again, passing your block. + def self.fork_reactor &block + # This implementation is subject to change, especially if we clean up the relationship + # of EM#run to @reactor_running. + # Original patch by Aman Gupta. + # + Kernel.fork do + if reactor_running? + stop_event_loop + release_machine + cleanup_machine + @reactor_running = false + @reactor_thread = nil + end + run block + end + end + + # Clean up Ruby space following a release_machine + def self.cleanup_machine + if @threadpool && !@threadpool.empty? + # Tell the threads to stop + @threadpool.each { |t| t.exit } + # Join the threads or bump the stragglers one more time + @threadpool.each { |t| t.join 0.01 || t.exit } + end + @threadpool = nil + @threadqueue = nil + @resultqueue = nil + @all_threads_spawned = false + @next_tick_queue = [] + end + + # Adds a block to call as the reactor is shutting down. + # + # These callbacks are called in the _reverse_ order to which they are added. + # + # @example Scheduling operations to be run when EventMachine event loop is stopped + # + # EventMachine.run do + # EventMachine.add_shutdown_hook { puts "b" } + # EventMachine.add_shutdown_hook { puts "a" } + # EventMachine.stop + # end + # + # # Outputs: + # # a + # # b + # + def self.add_shutdown_hook &block + @tails << block + end + + # Adds a one-shot timer to the event loop. + # Call it with one or two parameters. The first parameters is a delay-time + # expressed in *seconds* (not milliseconds). The second parameter, if + # present, must be an object that responds to :call. If 2nd parameter is not given, then you + # can also simply pass a block to the method call. + # + # This method may be called from the block passed to {EventMachine.run} + # or from any callback method. It schedules execution of the proc or block + # passed to it, after the passage of an interval of time equal to + # *at least* the number of seconds specified in the first parameter to + # the call. + # + # {EventMachine.add_timer} is a non-blocking method. Callbacks can and will + # be called during the interval of time that the timer is in effect. + # There is no built-in limit to the number of timers that can be outstanding at + # any given time. + # + # @example Setting a one-shot timer with EventMachine + # + # EventMachine.run { + # puts "Starting the run now: #{Time.now}" + # EventMachine.add_timer 5, proc { puts "Executing timer event: #{Time.now}" } + # EventMachine.add_timer(10) { puts "Executing timer event: #{Time.now}" } + # } + # + # @param [Integer] delay Delay in seconds + # @see EventMachine::Timer + # @see EventMachine.add_periodic_timer + def self.add_timer *args, &block + interval = args.shift + code = args.shift || block + if code + # check too many timers! + s = add_oneshot_timer((interval.to_f * 1000).to_i) + @timers[s] = code + s + end + end + + # Adds a periodic timer to the event loop. + # It takes the same parameters as the one-shot timer method, {EventMachine.add_timer}. + # This method schedules execution of the given block repeatedly, at intervals + # of time *at least* as great as the number of seconds given in the first + # parameter to the call. + # + # @example Write a dollar-sign to stderr every five seconds, without blocking + # + # EventMachine.run { + # EventMachine.add_periodic_timer( 5 ) { $stderr.write "$" } + # } + # + # @param [Integer] delay Delay in seconds + # + # @see EventMachine::PeriodicTimer + # @see EventMachine.add_timer + # + def self.add_periodic_timer *args, &block + interval = args.shift + code = args.shift || block + + EventMachine::PeriodicTimer.new(interval, code) + end + + + # Cancel a timer (can be a callback or an {EventMachine::Timer} instance). + # + # @param [#cancel, #call] timer_or_sig A timer to cancel + # @see EventMachine::Timer#cancel + def self.cancel_timer timer_or_sig + if timer_or_sig.respond_to? :cancel + timer_or_sig.cancel + else + @timers[timer_or_sig] = false if @timers.has_key?(timer_or_sig) + end + end + + + # Causes the processing loop to stop executing, which will cause all open connections and accepting servers + # to be run down and closed. Connection termination callbacks added using {EventMachine.add_shutdown_hook} + # will be called as part of running this method. + # + # When all of this processing is complete, the call to {EventMachine.run} which started the processing loop + # will return and program flow will resume from the statement following {EventMachine.run} call. + # + # @example Stopping a running EventMachine event loop + # + # require 'rubygems' + # require 'eventmachine' + # + # module Redmond + # def post_init + # puts "We're sending a dumb HTTP request to the remote peer." + # send_data "GET / HTTP/1.1\r\nHost: www.microsoft.com\r\n\r\n" + # end + # + # def receive_data data + # puts "We received #{data.length} bytes from the remote peer." + # puts "We're going to stop the event loop now." + # EventMachine::stop_event_loop + # end + # + # def unbind + # puts "A connection has terminated." + # end + # end + # + # puts "We're starting the event loop now." + # EventMachine.run { + # EventMachine.connect "www.microsoft.com", 80, Redmond + # } + # puts "The event loop has stopped." + # + # # This program will produce approximately the following output: + # # + # # We're starting the event loop now. + # # We're sending a dumb HTTP request to the remote peer. + # # We received 1440 bytes from the remote peer. + # # We're going to stop the event loop now. + # # A connection has terminated. + # # The event loop has stopped. + # + # + def self.stop_event_loop + EventMachine::stop + end + + # Initiates a TCP server (socket acceptor) on the specified IP address and port. + # + # The IP address must be valid on the machine where the program + # runs, and the process must be privileged enough to listen + # on the specified port (on Unix-like systems, superuser privileges + # are usually required to listen on any port lower than 1024). + # Only one listener may be running on any given address/port + # combination. start_server will fail if the given address and port + # are already listening on the machine, either because of a prior call + # to {.start_server} or some unrelated process running on the machine. + # If {.start_server} succeeds, the new network listener becomes active + # immediately and starts accepting connections from remote peers, + # and these connections generate callback events that are processed + # by the code specified in the handler parameter to {.start_server}. + # + # The optional handler which is passed to this method is the key + # to EventMachine's ability to handle particular network protocols. + # The handler parameter passed to start_server must be a Ruby Module + # that you must define. When the network server that is started by + # start_server accepts a new connection, it instantiates a new + # object of an anonymous class that is inherited from {EventMachine::Connection}, + # *into which your handler module have been included*. Arguments passed into start_server + # after the class name are passed into the constructor during the instantiation. + # + # Your handler module may override any of the methods in {EventMachine::Connection}, + # such as {EventMachine::Connection#receive_data}, in order to implement the specific behavior + # of the network protocol. + # + # Callbacks invoked in response to network events *always* take place + # within the execution context of the object derived from {EventMachine::Connection} + # extended by your handler module. There is one object per connection, and + # all of the callbacks invoked for a particular connection take the form + # of instance methods called against the corresponding {EventMachine::Connection} + # object. Therefore, you are free to define whatever instance variables you + # wish, in order to contain the per-connection state required by the network protocol you are + # implementing. + # + # {EventMachine.start_server} is usually called inside the block passed to {EventMachine.run}, + # but it can be called from any EventMachine callback. {EventMachine.start_server} will fail + # unless the EventMachine event loop is currently running (which is why + # it's often called in the block suppled to {EventMachine.run}). + # + # You may call start_server any number of times to start up network + # listeners on different address/port combinations. The servers will + # all run simultaneously. More interestingly, each individual call to start_server + # can specify a different handler module and thus implement a different + # network protocol from all the others. + # + # @example + # + # require 'rubygems' + # require 'eventmachine' + # + # # Here is an example of a server that counts lines of input from the remote + # # peer and sends back the total number of lines received, after each line. + # # Try the example with more than one client connection opened via telnet, + # # and you will see that the line count increments independently on each + # # of the client connections. Also very important to note, is that the + # # handler for the receive_data function, which our handler redefines, may + # # not assume that the data it receives observes any kind of message boundaries. + # # Also, to use this example, be sure to change the server and port parameters + # # to the start_server call to values appropriate for your environment. + # module LineCounter + # MaxLinesPerConnection = 10 + # + # def post_init + # puts "Received a new connection" + # @data_received = "" + # @line_count = 0 + # end + # + # def receive_data data + # @data_received << data + # while @data_received.slice!( /^[^\n]*[\n]/m ) + # @line_count += 1 + # send_data "received #{@line_count} lines so far\r\n" + # @line_count == MaxLinesPerConnection and close_connection_after_writing + # end + # end + # end + # + # EventMachine.run { + # host, port = "192.168.0.100", 8090 + # EventMachine.start_server host, port, LineCounter + # puts "Now accepting connections on address #{host}, port #{port}..." + # EventMachine.add_periodic_timer(10) { $stderr.write "*" } + # } + # + # @param [String] server Host to bind to. + # @param [Integer] port Port to bind to. + # @param [Module, Class] handler A module or class that implements connection callbacks + # + # @note Don't forget that in order to bind to ports < 1024 on Linux, *BSD and Mac OS X your process must have superuser privileges. + # + # @see file:docs/GettingStarted.md EventMachine tutorial + # @see EventMachine.stop_server + def self.start_server server, port=nil, handler=nil, *args, &block + begin + port = Integer(port) + rescue ArgumentError, TypeError + # there was no port, so server must be a unix domain socket + # the port argument is actually the handler, and the handler is one of the args + args.unshift handler if handler + handler = port + port = nil + end if port + + klass = klass_from_handler(Connection, handler, *args) + + s = if port + start_tcp_server server, port + else + start_unix_server server + end + @acceptors[s] = [klass,args,block] + s + end + + # Attach to an existing socket's file descriptor. The socket may have been + # started with {EventMachine.start_server}. + def self.attach_server sock, handler=nil, *args, &block + klass = klass_from_handler(Connection, handler, *args) + sd = sock.respond_to?(:fileno) ? sock.fileno : sock + s = attach_sd(sd) + @acceptors[s] = [klass,args,block,sock] + s + end + + # Stop a TCP server socket that was started with {EventMachine.start_server}. + # @see EventMachine.start_server + def self.stop_server signature + EventMachine::stop_tcp_server signature + end + + # Start a Unix-domain server. + # + # Note that this is an alias for {EventMachine.start_server}, which can be used to start both + # TCP and Unix-domain servers. + # + # @see EventMachine.start_server + def self.start_unix_domain_server filename, *args, &block + start_server filename, *args, &block + end + + # Initiates a TCP connection to a remote server and sets up event handling for the connection. + # {EventMachine.connect} requires event loop to be running (see {EventMachine.run}). + # + # {EventMachine.connect} takes the IP address (or hostname) and + # port of the remote server you want to connect to. + # It also takes an optional handler (a module or a subclass of {EventMachine::Connection}) which you must define, that + # contains the callbacks that will be invoked by the event loop on behalf of the connection. + # + # Learn more about connection lifecycle callbacks in the {file:docs/GettingStarted.md EventMachine tutorial} and + # {file:docs/ConnectionLifecycleCallbacks.md Connection lifecycle guide}. + # + # + # @example + # + # # Here's a program which connects to a web server, sends a naive + # # request, parses the HTTP header of the response, and then + # # (antisocially) ends the event loop, which automatically drops the connection + # # (and incidentally calls the connection's unbind method). + # module DumbHttpClient + # def post_init + # send_data "GET / HTTP/1.1\r\nHost: _\r\n\r\n" + # @data = "" + # @parsed = false + # end + # + # def receive_data data + # @data << data + # if !@parsed and @data =~ /[\n][\r]*[\n]/m + # @parsed = true + # puts "RECEIVED HTTP HEADER:" + # $`.each {|line| puts ">>> #{line}" } + # + # puts "Now we'll terminate the loop, which will also close the connection" + # EventMachine::stop_event_loop + # end + # end + # + # def unbind + # puts "A connection has terminated" + # end + # end + # + # EventMachine.run { + # EventMachine.connect "www.bayshorenetworks.com", 80, DumbHttpClient + # } + # puts "The event loop has ended" + # + # + # @example Defining protocol handler as a class + # + # class MyProtocolHandler < EventMachine::Connection + # def initialize *args + # super + # # whatever else you want to do here + # end + # + # # ... + # end + # + # + # @param [String] server Host to connect to + # @param [Integer] port Port to connect to + # @param [Module, Class] handler A module or class that implements connection lifecycle callbacks + # + # @see EventMachine.start_server + # @see file:docs/GettingStarted.md EventMachine tutorial + def self.connect server, port=nil, handler=nil, *args, &blk + # EventMachine::connect initiates a TCP connection to a remote + # server and sets up event-handling for the connection. + # It internally creates an object that should not be handled + # by the caller. HOWEVER, it's often convenient to get the + # object to set up interfacing to other objects in the system. + # We return the newly-created anonymous-class object to the caller. + # It's expected that a considerable amount of code will depend + # on this behavior, so don't change it. + # + # Ok, added support for a user-defined block, 13Apr06. + # This leads us to an interesting choice because of the + # presence of the post_init call, which happens in the + # initialize method of the new object. We call the user's + # block and pass the new object to it. This is a great + # way to do protocol-specific initiation. It happens + # AFTER post_init has been called on the object, which I + # certainly hope is the right choice. + # Don't change this lightly, because accepted connections + # are different from connected ones and we don't want + # to have them behave differently with respect to post_init + # if at all possible. + + bind_connect nil, nil, server, port, handler, *args, &blk + end + + # This method is like {EventMachine.connect}, but allows for a local address/port + # to bind the connection to. + # + # @see EventMachine.connect + def self.bind_connect bind_addr, bind_port, server, port=nil, handler=nil, *args + begin + port = Integer(port) + rescue ArgumentError, TypeError + # there was no port, so server must be a unix domain socket + # the port argument is actually the handler, and the handler is one of the args + args.unshift handler if handler + handler = port + port = nil + end if port + + klass = klass_from_handler(Connection, handler, *args) + + s = if port + if bind_addr + bind_connect_server bind_addr, bind_port.to_i, server, port + else + connect_server server, port + end + else + connect_unix_server server + end + + c = klass.new s, *args + @conns[s] = c + block_given? and yield c + c + end + + # {EventMachine.watch} registers a given file descriptor or IO object with the eventloop. The + # file descriptor will not be modified (it will remain blocking or non-blocking). + # + # The eventloop can be used to process readable and writable events on the file descriptor, using + # {EventMachine::Connection#notify_readable=} and {EventMachine::Connection#notify_writable=} + # + # {EventMachine::Connection#notify_readable?} and {EventMachine::Connection#notify_writable?} can be used + # to check what events are enabled on the connection. + # + # To detach the file descriptor, use {EventMachine::Connection#detach} + # + # @example + # + # module SimpleHttpClient + # def notify_readable + # header = @io.readline + # + # if header == "\r\n" + # # detach returns the file descriptor number (fd == @io.fileno) + # fd = detach + # end + # rescue EOFError + # detach + # end + # + # def unbind + # EM.next_tick do + # # socket is detached from the eventloop, but still open + # data = @io.read + # end + # end + # end + # + # EventMachine.run { + # sock = TCPSocket.new('site.com', 80) + # sock.write("GET / HTTP/1.0\r\n\r\n") + # conn = EventMachine.watch(sock, SimpleHttpClient) + # conn.notify_readable = true + # } + # + # @author Riham Aldakkak (eSpace Technologies) + def EventMachine::watch io, handler=nil, *args, &blk + attach_io io, true, handler, *args, &blk + end + + # Attaches an IO object or file descriptor to the eventloop as a regular connection. + # The file descriptor will be set as non-blocking, and EventMachine will process + # receive_data and send_data events on it as it would for any other connection. + # + # To watch a fd instead, use {EventMachine.watch}, which will not alter the state of the socket + # and fire notify_readable and notify_writable events instead. + def EventMachine::attach io, handler=nil, *args, &blk + attach_io io, false, handler, *args, &blk + end + + # @private + def EventMachine::attach_io io, watch_mode, handler=nil, *args + klass = klass_from_handler(Connection, handler, *args) + + if !watch_mode and klass.public_instance_methods.any?{|m| [:notify_readable, :notify_writable].include? m.to_sym } + raise ArgumentError, "notify_readable/writable with EM.attach is not supported. Use EM.watch(io){ |c| c.notify_readable = true }" + end + + if io.respond_to?(:fileno) + # getDescriptorByFileno deprecated in JRuby 1.7.x, removed in JRuby 9000 + if defined?(JRuby) && JRuby.runtime.respond_to?(:getDescriptorByFileno) + fd = JRuby.runtime.getDescriptorByFileno(io.fileno).getChannel + else + fd = io.fileno + end + else + fd = io + end + + s = attach_fd fd, watch_mode + c = klass.new s, *args + + c.instance_variable_set(:@io, io) + c.instance_variable_set(:@watch_mode, watch_mode) + c.instance_variable_set(:@fd, fd) + + @conns[s] = c + block_given? and yield c + c + end + + + # Connect to a given host/port and re-use the provided {EventMachine::Connection} instance. + # Consider also {EventMachine::Connection#reconnect}. + # + # @see EventMachine::Connection#reconnect + def self.reconnect server, port, handler + # Observe, the test for already-connected FAILS if we call a reconnect inside post_init, + # because we haven't set up the connection in @conns by that point. + # RESIST THE TEMPTATION to "fix" this problem by redefining the behavior of post_init. + # + # Changed 22Nov06: if called on an already-connected handler, just return the + # handler and do nothing more. Originally this condition raised an exception. + # We may want to change it yet again and call the block, if any. + + raise "invalid handler" unless handler.respond_to?(:connection_completed) + #raise "still connected" if @conns.has_key?(handler.signature) + return handler if @conns.has_key?(handler.signature) + + s = if port + connect_server server, port + else + connect_unix_server server + end + handler.signature = s + @conns[s] = handler + block_given? and yield handler + handler + end + + + # Make a connection to a Unix-domain socket. This method is simply an alias for {.connect}, + # which can connect to both TCP and Unix-domain sockets. Make sure that your process has sufficient + # permissions to open the socket it is given. + # + # @param [String] socketname Unix domain socket (local fully-qualified path) you want to connect to. + # + # @note UNIX sockets, as the name suggests, are not available on Microsoft Windows. + def self.connect_unix_domain socketname, *args, &blk + connect socketname, *args, &blk + end + + + # Used for UDP-based protocols. Its usage is similar to that of {EventMachine.start_server}. + # + # This method will create a new UDP (datagram) socket and + # bind it to the address and port that you specify. + # The normal callbacks (see {EventMachine.start_server}) will + # be called as events of interest occur on the newly-created + # socket, but there are some differences in how they behave. + # + # {Connection#receive_data} will be called when a datagram packet + # is received on the socket, but unlike TCP sockets, the message + # boundaries of the received data will be respected. In other words, + # if the remote peer sent you a datagram of a particular size, + # you may rely on {Connection#receive_data} to give you the + # exact data in the packet, with the original data length. + # Also observe that Connection#receive_data may be called with a + # *zero-length* data payload, since empty datagrams are permitted in UDP. + # + # {Connection#send_data} is available with UDP packets as with TCP, + # but there is an important difference. Because UDP communications + # are *connectionless*, there is no implicit recipient for the packets you + # send. Ordinarily you must specify the recipient for each packet you send. + # However, EventMachine provides for the typical pattern of receiving a UDP datagram + # from a remote peer, performing some operation, and then sending + # one or more packets in response to the same remote peer. + # To support this model easily, just use {Connection#send_data} + # in the code that you supply for {Connection#receive_data}. + # + # EventMachine will provide an implicit return address for any messages sent to + # {Connection#send_data} within the context of a {Connection#receive_data} callback, + # and your response will automatically go to the correct remote peer. + # + # Observe that the port number that you supply to {EventMachine.open_datagram_socket} + # may be zero. In this case, EventMachine will create a UDP socket + # that is bound to an [ephemeral port](http://en.wikipedia.org/wiki/Ephemeral_port). + # This is not appropriate for servers that must publish a well-known + # port to which remote peers may send datagrams. But it can be useful + # for clients that send datagrams to other servers. + # If you do this, you will receive any responses from the remote + # servers through the normal {Connection#receive_data} callback. + # Observe that you will probably have issues with firewalls blocking + # the ephemeral port numbers, so this technique is most appropriate for LANs. + # + # If you wish to send datagrams to arbitrary remote peers (not + # necessarily ones that have sent data to which you are responding), + # then see {Connection#send_datagram}. + # + # DO NOT call send_data from a datagram socket outside of a {Connection#receive_data} method. Use {Connection#send_datagram}. + # If you do use {Connection#send_data} outside of a {Connection#receive_data} method, you'll get a confusing error + # because there is no "peer," as #send_data requires (inside of {EventMachine::Connection#receive_data}, + # {EventMachine::Connection#send_data} "fakes" the peer as described above). + # + # @param [String] address IP address + # @param [String] port Port + # @param [Class, Module] handler A class or a module that implements connection lifecycle callbacks. + def self.open_datagram_socket address, port, handler=nil, *args + # Replaced the implementation on 01Oct06. Thanks to Tobias Gustafsson for pointing + # out that this originally did not take a class but only a module. + + + klass = klass_from_handler(Connection, handler, *args) + s = open_udp_socket address, port.to_i + c = klass.new s, *args + @conns[s] = c + block_given? and yield c + c + end + + + # For advanced users. This function sets the default timer granularity, which by default is + # slightly smaller than 100 milliseconds. Call this function to set a higher or lower granularity. + # The function affects the behavior of {EventMachine.add_timer} and {EventMachine.add_periodic_timer}. + # Most applications will not need to call this function. + # + # Avoid setting the quantum to very low values because that may reduce performance under some extreme conditions. + # We recommend that you not use values lower than 10. + # + # This method only can be used if event loop is running. + # + # @param [Integer] mills New timer granularity, in milliseconds + # + # @see EventMachine.add_timer + # @see EventMachine.add_periodic_timer + # @see EventMachine::Timer + # @see EventMachine.run + def self.set_quantum mills + set_timer_quantum mills.to_i + end + + # Sets the maximum number of timers and periodic timers that may be outstanding at any + # given time. You only need to call {.set_max_timers} if you need more than the default + # number of timers, which on most platforms is 1000. + # + # @note This method has to be used *before* event loop is started. + # + # @param [Integer] ct Maximum number of timers that may be outstanding at any given time + # + # @see EventMachine.add_timer + # @see EventMachine.add_periodic_timer + # @see EventMachine::Timer + def self.set_max_timers ct + set_max_timer_count ct + end + + # Gets the current maximum number of allowed timers + # + # @return [Integer] Maximum number of timers that may be outstanding at any given time + def self.get_max_timers + get_max_timer_count + end + + # Returns the total number of connections (file descriptors) currently held by the reactor. + # Note that a tick must pass after the 'initiation' of a connection for this number to increment. + # It's usually accurate, but don't rely on the exact precision of this number unless you really know EM internals. + # + # @example + # + # EventMachine.run { + # EventMachine.connect("rubyeventmachine.com", 80) + # # count will be 0 in this case, because connection is not + # # established yet + # count = EventMachine.connection_count + # } + # + # + # @example + # + # EventMachine.run { + # EventMachine.connect("rubyeventmachine.com", 80) + # + # EventMachine.next_tick { + # # In this example, count will be 1 since the connection has been established in + # # the next loop of the reactor. + # count = EventMachine.connection_count + # } + # } + # + # @return [Integer] Number of connections currently held by the reactor. + def self.connection_count + self.get_connection_count + end + + # The is the responder for the loopback-signalled event. + # It can be fired either by code running on a separate thread ({EventMachine.defer}) or on + # the main thread ({EventMachine.next_tick}). + # It will often happen that a next_tick handler will reschedule itself. We + # consume a copy of the tick queue so that tick events scheduled by tick events + # have to wait for the next pass through the reactor core. + # + # @private + def self.run_deferred_callbacks + until (@resultqueue ||= []).empty? + result,cback = @resultqueue.pop + cback.call result if cback + end + + # Capture the size at the start of this tick... + size = @next_tick_mutex.synchronize { @next_tick_queue.size } + size.times do |i| + callback = @next_tick_mutex.synchronize { @next_tick_queue.shift } + begin + callback.call + rescue + exception_raised = true + raise + ensure + # This is a little nasty. The problem is, if an exception occurs during + # the callback, then we need to send a signal to the reactor to actually + # do some work during the next_tick. The only mechanism we have from the + # ruby side is next_tick itself, although ideally, we'd just drop a byte + # on the loopback descriptor. + next_tick {} if exception_raised + end + end + end + + + # EventMachine.defer is used for integrating blocking operations into EventMachine's control flow. + # The action of {.defer} is to take the block specified in the first parameter (the "operation") + # and schedule it for asynchronous execution on an internal thread pool maintained by EventMachine. + # When the operation completes, it will pass the result computed by the block (if any) back to the + # EventMachine reactor. Then, EventMachine calls the block specified in the second parameter to + # {.defer} (the "callback"), as part of its normal event handling loop. The result computed by the + # operation block is passed as a parameter to the callback. You may omit the callback parameter if + # you don't need to execute any code after the operation completes. If the operation raises an + # unhandled exception, the exception will be passed to the third parameter to {.defer} (the + # "errback"), as part of its normal event handling loop. If no errback is provided, the exception + # will be allowed to blow through to the main thread immediately. + # + # ## Caveats ## + # + # Note carefully that the code in your deferred operation will be executed on a separate + # thread from the main EventMachine processing and all other Ruby threads that may exist in + # your program. Also, multiple deferred operations may be running at once! Therefore, you + # are responsible for ensuring that your operation code is threadsafe. + # + # Don't write a deferred operation that will block forever. If so, the current implementation will + # not detect the problem, and the thread will never be returned to the pool. EventMachine limits + # the number of threads in its pool, so if you do this enough times, your subsequent deferred + # operations won't get a chance to run. + # + # The threads within the EventMachine's thread pool have abort_on_exception set to true. As a result, + # if an unhandled exception is raised by the deferred operation and an errback is not provided, it + # will blow through to the main thread immediately. If the main thread is within an indiscriminate + # rescue block at that time, the exception could be handled improperly by the main thread. + # + # @example + # + # operation = proc { + # # perform a long-running operation here, such as a database query. + # "result" # as usual, the last expression evaluated in the block will be the return value. + # } + # callback = proc {|result| + # # do something with result here, such as send it back to a network client. + # } + # errback = proc {|error| + # # do something with error here, such as re-raising or logging. + # } + # + # EventMachine.defer(operation, callback, errback) + # + # @param [#call] op An operation you want to offload to EventMachine thread pool + # @param [#call] callback A callback that will be run on the event loop thread after `operation` finishes. + # @param [#call] errback An errback that will be run on the event loop thread after `operation` raises an exception. + # + # @see EventMachine.threadpool_size + def self.defer op = nil, callback = nil, errback = nil, &blk + # OBSERVE that #next_tick hacks into this mechanism, so don't make any changes here + # without syncing there. + # + # Running with $VERBOSE set to true gives a warning unless all ivars are defined when + # they appear in rvalues. But we DON'T ever want to initialize @threadqueue unless we + # need it, because the Ruby threads are so heavyweight. We end up with this bizarre + # way of initializing @threadqueue because EventMachine is a Module, not a Class, and + # has no constructor. + + unless @threadpool + @threadpool = [] + @threadqueue = ::Queue.new + @resultqueue = ::Queue.new + spawn_threadpool + end + + @threadqueue << [op||blk,callback,errback] + end + + + # @private + def self.spawn_threadpool + until @threadpool.size == @threadpool_size.to_i + thread = Thread.new do + Thread.current.abort_on_exception = true + while true + begin + op, cback, eback = *@threadqueue.pop + rescue ThreadError + $stderr.puts $!.message + break # Ruby 2.0 may fail at Queue.pop + end + begin + result = op.call + @resultqueue << [result, cback] + rescue Exception => error + raise error unless eback + @resultqueue << [error, eback] + end + signal_loopbreak + end + end + @threadpool << thread + end + @all_threads_spawned = true + end + + ## + # Returns +true+ if all deferred actions are done executing and their + # callbacks have been fired. + # + def self.defers_finished? + return false if @threadpool and !@all_threads_spawned + return false if @threadqueue and not @threadqueue.empty? + return false if @resultqueue and not @resultqueue.empty? + return false if @threadpool and @threadqueue.num_waiting != @threadpool.size + return true + end + + class << self + # @private + attr_reader :threadpool + + # Size of the EventMachine.defer threadpool (defaults to 20) + # @return [Number] + attr_accessor :threadpool_size + EventMachine.threadpool_size = 20 + end + + # Schedules a proc for execution immediately after the next "turn" through the reactor + # core. An advanced technique, this can be useful for improving memory management and/or + # application responsiveness, especially when scheduling large amounts of data for + # writing to a network connection. + # + # This method takes either a single argument (which must be a callable object) or a block. + # + # @param [#call] pr A callable object to run + def self.next_tick pr=nil, &block + # This works by adding to the @resultqueue that's used for #defer. + # The general idea is that next_tick is used when we want to give the reactor a chance + # to let other operations run, either to balance the load out more evenly, or to let + # outbound network buffers drain, or both. So we probably do NOT want to block, and + # we probably do NOT want to be spinning any threads. A program that uses next_tick + # but not #defer shouldn't suffer the penalty of having Ruby threads running. They're + # extremely expensive even if they're just sleeping. + + raise ArgumentError, "no proc or block given" unless ((pr && pr.respond_to?(:call)) or block) + @next_tick_mutex.synchronize do + @next_tick_queue << ( pr || block ) + end + signal_loopbreak if reactor_running? + end + + # A wrapper over the setuid system call. Particularly useful when opening a network + # server on a privileged port because you can use this call to drop privileges + # after opening the port. Also very useful after a call to {.set_descriptor_table_size}, + # which generally requires that you start your process with root privileges. + # + # This method is intended for use in enforcing security requirements, consequently + # it will throw a fatal error and end your program if it fails. + # + # @param [String] username The effective name of the user whose privilege-level your process should attain. + # + # @note This method has no effective implementation on Windows or in the pure-Ruby + # implementation of EventMachine + def self.set_effective_user username + EventMachine::setuid_string username + end + + + # Sets the maximum number of file or socket descriptors that your process may open. + # If you call this method with no arguments, it will simply return + # the current size of the descriptor table without attempting to change it. + # + # The new limit on open descriptors **only** applies to sockets and other descriptors + # that belong to EventMachine. It has **no effect** on the number of descriptors + # you can create in ordinary Ruby code. + # + # Not available on all platforms. Increasing the number of descriptors beyond its + # default limit usually requires superuser privileges. (See {.set_effective_user} + # for a way to drop superuser privileges while your program is running.) + # + # @param [Integer] n_descriptors The maximum number of file or socket descriptors that your process may open + # @return [Integer] The new descriptor table size. + def self.set_descriptor_table_size n_descriptors=nil + EventMachine::set_rlimit_nofile n_descriptors + end + + + + # Runs an external process. + # + # @example + # + # module RubyCounter + # def post_init + # # count up to 5 + # send_data "5\n" + # end + # def receive_data data + # puts "ruby sent me: #{data}" + # end + # def unbind + # puts "ruby died with exit status: #{get_status.exitstatus}" + # end + # end + # + # EventMachine.run { + # EventMachine.popen("ruby -e' $stdout.sync = true; gets.to_i.times{ |i| puts i+1; sleep 1 } '", RubyCounter) + # } + # + # @note This method is not supported on Microsoft Windows + # @see EventMachine::DeferrableChildProcess + # @see EventMachine.system + def self.popen cmd, handler=nil, *args + # At this moment, it's only available on Unix. + # Perhaps misnamed since the underlying function uses socketpair and is full-duplex. + + klass = klass_from_handler(Connection, handler, *args) + w = case cmd + when Array + cmd + when String + Shellwords::shellwords( cmd ) + end + w.unshift( w.first ) if w.first + s = invoke_popen( w ) + c = klass.new s, *args + @conns[s] = c + yield(c) if block_given? + c + end + + + # Tells you whether the EventMachine reactor loop is currently running. + # + # Useful when writing libraries that want to run event-driven code, but may + # be running in programs that are already event-driven. In such cases, if {EventMachine.reactor_running?} + # returns false, your code can invoke {EventMachine.run} and run your application code inside + # the block passed to that method. If this method returns true, just + # execute your event-aware code. + # + # @return [Boolean] true if the EventMachine reactor loop is currently running + def self.reactor_running? + @reactor_running && Process.pid == @reactor_pid + end + + + # (Experimental) + # + # @private + def self.open_keyboard handler=nil, *args + klass = klass_from_handler(Connection, handler, *args) + + s = read_keyboard + c = klass.new s, *args + @conns[s] = c + block_given? and yield c + c + end + + # EventMachine's file monitoring API. Currently supported are the following events + # on individual files, using inotify on Linux systems, and kqueue for *BSD and Mac OS X: + # + # * File modified (written to) + # * File moved/renamed + # * File deleted + # + # EventMachine::watch_file takes a filename and a handler Module containing your custom callback methods. + # This will setup the low level monitoring on the specified file, and create a new EventMachine::FileWatch + # object with your Module mixed in. FileWatch is a subclass of {EventMachine::Connection}, so callbacks on this object + # work in the familiar way. The callbacks that will be fired by EventMachine are: + # + # * file_modified + # * file_moved + # * file_deleted + # + # You can access the filename being monitored from within this object using {FileWatch#path}. + # + # When a file is deleted, {FileWatch#stop_watching} will be called after your file_deleted callback, + # to clean up the underlying monitoring and remove EventMachine's reference to the now-useless {FileWatch} instance. + # This will in turn call unbind, if you wish to use it. + # + # The corresponding system-level Errno will be raised when attempting to monitor non-existent files, + # files with wrong permissions, or if an error occurs dealing with inotify/kqueue. + # + # @example + # + # # Before running this example, make sure we have a file to monitor: + # # $ echo "bar" > /tmp/foo + # + # module Handler + # def file_modified + # puts "#{path} modified" + # end + # + # def file_moved + # puts "#{path} moved" + # end + # + # def file_deleted + # puts "#{path} deleted" + # end + # + # def unbind + # puts "#{path} monitoring ceased" + # end + # end + # + # # for efficient file watching, use kqueue on Mac OS X + # EventMachine.kqueue = true if EventMachine.kqueue? + # + # EventMachine.run { + # EventMachine.watch_file("/tmp/foo", Handler) + # } + # + # # $ echo "baz" >> /tmp/foo => "/tmp/foo modified" + # # $ mv /tmp/foo /tmp/oof => "/tmp/foo moved" + # # $ rm /tmp/oof => "/tmp/foo deleted" + # + # @note The ability to pick up on the new filename after a rename is not yet supported. + # Calling #path will always return the filename you originally used. + # + # @param [String] filename Local path to the file to watch. + # @param [Class, Module] handler A class or module that implements event handlers associated with the file. + def self.watch_file(filename, handler=nil, *args) + klass = klass_from_handler(FileWatch, handler, *args) + + s = EM::watch_filename(filename) + c = klass.new s, *args + # we have to set the path like this because of how Connection.new works + c.instance_variable_set("@path", filename) + @conns[s] = c + block_given? and yield c + c + end + + # EventMachine's process monitoring API. On Mac OS X and *BSD this method is implemented using kqueue. + # + # @example + # + # module ProcessWatcher + # def process_exited + # put 'the forked child died!' + # end + # end + # + # pid = fork{ sleep } + # + # EventMachine.run { + # EventMachine.watch_process(pid, ProcessWatcher) + # EventMachine.add_timer(1){ Process.kill('TERM', pid) } + # } + # + # @param [Integer] pid PID of the process to watch. + # @param [Class, Module] handler A class or module that implements event handlers associated with the file. + def self.watch_process(pid, handler=nil, *args) + pid = pid.to_i + + klass = klass_from_handler(ProcessWatch, handler, *args) + + s = EM::watch_pid(pid) + c = klass.new s, *args + # we have to set the path like this because of how Connection.new works + c.instance_variable_set("@pid", pid) + @conns[s] = c + block_given? and yield c + c + end + + # Catch-all for errors raised during event loop callbacks. + # + # @example + # + # EventMachine.error_handler{ |e| + # puts "Error raised during event loop: #{e.message}" + # } + # + # @param [#call] cb Global catch-all errback + def self.error_handler cb = nil, &blk + if cb or blk + @error_handler = cb || blk + elsif instance_variable_defined? :@error_handler + remove_instance_variable :@error_handler + end + end + + # This method allows for direct writing of incoming data back out to another descriptor, at the C++ level in the reactor. + # This is very efficient and especially useful for proxies where high performance is required. Propogating data from a server response + # all the way up to Ruby, and then back down to the reactor to be sent back to the client, is often unnecessary and + # incurs a significant performance decrease. + # + # The two arguments are instance of {EventMachine::Connection} subclasses, 'from' and 'to'. 'from' is the connection whose inbound data you want + # relayed back out. 'to' is the connection to write it to. + # + # Once you call this method, the 'from' connection will no longer get receive_data callbacks from the reactor, + # except in the case that 'to' connection has already closed when attempting to write to it. You can see + # in the example, that proxy_target_unbound will be called when this occurs. After that, further incoming + # data will be passed into receive_data as normal. + # + # Note also that this feature supports different types of descriptors: TCP, UDP, and pipes. You can relay + # data from one kind to another, for example, feed a pipe from a UDP stream. + # + # @example + # + # module ProxyConnection + # def initialize(client, request) + # @client, @request = client, request + # end + # + # def post_init + # EM::enable_proxy(self, @client) + # end + # + # def connection_completed + # send_data @request + # end + # + # def proxy_target_unbound + # close_connection + # end + # + # def unbind + # @client.close_connection_after_writing + # end + # end + # + # module ProxyServer + # def receive_data(data) + # (@buf ||= "") << data + # if @buf =~ /\r\n\r\n/ # all http headers received + # EventMachine.connect("10.0.0.15", 80, ProxyConnection, self, data) + # end + # end + # end + # + # EventMachine.run { + # EventMachine.start_server("127.0.0.1", 8080, ProxyServer) + # } + # + # @param [EventMachine::Connection] from Source of data to be proxies/streamed. + # @param [EventMachine::Connection] to Destination of data to be proxies/streamed. + # @param [Integer] bufsize Buffer size to use + # @param [Integer] length Maximum number of bytes to proxy. + # + # @see EventMachine.disable_proxy + def self.enable_proxy(from, to, bufsize=0, length=0) + EM::start_proxy(from.signature, to.signature, bufsize, length) + end + + # Takes just one argument, a {Connection} that has proxying enabled via {EventMachine.enable_proxy}. + # Calling this method will remove that functionality and your connection will begin receiving + # data via {Connection#receive_data} again. + # + # @param [EventMachine::Connection] from Source of data that is being proxied + # @see EventMachine.enable_proxy + def self.disable_proxy(from) + EM::stop_proxy(from.signature) + end + + # Retrieve the heartbeat interval. This is how often EventMachine will check for dead connections + # that have had an inactivity timeout set via {Connection#set_comm_inactivity_timeout}. + # Default is 2 seconds. + # + # @return [Integer] Heartbeat interval, in seconds + def self.heartbeat_interval + EM::get_heartbeat_interval + end + + # Set the heartbeat interval. This is how often EventMachine will check for dead connections + # that have had an inactivity timeout set via {Connection#set_comm_inactivity_timeout}. + # Takes a Numeric number of seconds. Default is 2. + # + # @param [Integer] time Heartbeat interval, in seconds + def self.heartbeat_interval=(time) + EM::set_heartbeat_interval time.to_f + end + + # @private + def self.event_callback conn_binding, opcode, data + # + # Changed 27Dec07: Eliminated the hookable error handling. + # No one was using it, and it degraded performance significantly. + # It's in original_event_callback, which is dead code. + # + # Changed 25Jul08: Added a partial solution to the problem of exceptions + # raised in user-written event-handlers. If such exceptions are not caught, + # we must cause the reactor to stop, and then re-raise the exception. + # Otherwise, the reactor doesn't stop and it's left on the call stack. + # This is partial because we only added it to #unbind, where it's critical + # (to keep unbind handlers from being re-entered when a stopping reactor + # runs down open connections). It should go on the other calls to user + # code, but the performance impact may be too large. + # + if opcode == ConnectionUnbound + if c = @conns.delete( conn_binding ) + begin + if c.original_method(:unbind).arity != 0 + c.unbind(data == 0 ? nil : EventMachine::ERRNOS[data]) + else + c.unbind + end + # If this is an attached (but not watched) connection, close the underlying io object. + if c.instance_variable_defined?(:@io) and !c.instance_variable_get(:@watch_mode) + io = c.instance_variable_get(:@io) + begin + io.close + rescue Errno::EBADF, IOError + end + end + # As noted above, unbind absolutely must not raise an exception or the reactor will crash. + # If there is no EM.error_handler, or if the error_handler retrows, then stop the reactor, + # stash the exception in $wrapped_exception, and the exception will be raised after the + # reactor is cleaned up (see the last line of self.run). + rescue Exception => error + if instance_variable_defined? :@error_handler + begin + @error_handler.call error + # No need to stop unless error_handler rethrows + rescue Exception => error + @wrapped_exception = error + stop + end + else + @wrapped_exception = error + stop + end + end + elsif c = @acceptors.delete( conn_binding ) + # no-op + else + if $! # Bubble user generated errors. + @wrapped_exception = $! + stop + else + raise ConnectionNotBound, "received ConnectionUnbound for an unknown signature: #{conn_binding}" + end + end + elsif opcode == ConnectionAccepted + accep,args,blk = @acceptors[conn_binding] + raise NoHandlerForAcceptedConnection unless accep + c = accep.new data, *args + @conns[data] = c + blk and blk.call(c) + c # (needed?) + ## + # The remaining code is a fallback for the pure ruby and java reactors. + # In the C++ reactor, these events are handled in the C event_callback() in rubymain.cpp + elsif opcode == ConnectionCompleted + c = @conns[conn_binding] or raise ConnectionNotBound, "received ConnectionCompleted for unknown signature: #{conn_binding}" + c.connection_completed + elsif opcode == SslHandshakeCompleted + c = @conns[conn_binding] or raise ConnectionNotBound, "received SslHandshakeCompleted for unknown signature: #{conn_binding}" + c.ssl_handshake_completed + elsif opcode == SslVerify + c = @conns[conn_binding] or raise ConnectionNotBound, "received SslVerify for unknown signature: #{conn_binding}" + c.close_connection if c.ssl_verify_peer(data) == false + elsif opcode == TimerFired + t = @timers.delete( data ) + return if t == false # timer cancelled + t or raise UnknownTimerFired, "timer data: #{data}" + t.call + elsif opcode == ConnectionData + c = @conns[conn_binding] or raise ConnectionNotBound, "received data #{data} for unknown signature: #{conn_binding}" + c.receive_data data + elsif opcode == LoopbreakSignalled + run_deferred_callbacks + elsif opcode == ConnectionNotifyReadable + c = @conns[conn_binding] or raise ConnectionNotBound + c.notify_readable + elsif opcode == ConnectionNotifyWritable + c = @conns[conn_binding] or raise ConnectionNotBound + c.notify_writable + end + end + + # + # + # @private + def self._open_file_for_writing filename, handler=nil + klass = klass_from_handler(Connection, handler) + + s = _write_file filename + c = klass.new s + @conns[s] = c + block_given? and yield c + c + end + + # @private + def self.klass_from_handler(klass = Connection, handler = nil, *args) + klass = if handler and handler.is_a?(Class) + raise ArgumentError, "must provide module or subclass of #{klass.name}" unless klass >= handler + handler + elsif handler + if defined?(handler::EM_CONNECTION_CLASS) + handler::EM_CONNECTION_CLASS + else + handler::const_set(:EM_CONNECTION_CLASS, Class.new(klass) {include handler}) + end + else + klass + end + + arity = klass.instance_method(:initialize).arity + expected = arity >= 0 ? arity : -(arity + 1) + if (arity >= 0 and args.size != expected) or (arity < 0 and args.size < expected) + raise ArgumentError, "wrong number of arguments for #{klass}#initialize (#{args.size} for #{expected})" + end + + klass + end +end # module EventMachine + +# Alias for {EventMachine} +EM = EventMachine +# Alias for {EventMachine::Protocols} +EM::P = EventMachine::Protocols diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/fastfilereaderext.bundle b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/fastfilereaderext.bundle new file mode 100755 index 0000000..2cf4e83 Binary files /dev/null and b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/fastfilereaderext.bundle differ diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/jeventmachine.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/jeventmachine.rb new file mode 100644 index 0000000..21a267d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/jeventmachine.rb @@ -0,0 +1,316 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 8 Apr 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +# This module provides "glue" for the Java version of the EventMachine reactor core. +# For C++ EventMachines, the analogous functionality is found in ext/rubymain.cpp, +# which is a garden-variety Ruby-extension glue module. + +require 'java' +require 'rubyeventmachine' +require 'socket' + +java_import java.io.FileDescriptor +java_import java.nio.channels.SocketChannel +java_import java.lang.reflect.Field + +module JavaFields + def set_field(key, value) + field = getClass.getDeclaredField(key) + field.setAccessible(true) + if field.getType.toString == 'int' + field.setInt(self, value) + else + field.set(self, value) + end + end + + def get_field(key) + field = getClass.getDeclaredField(key) + field.setAccessible(true) + field.get(self) + end +end + +FileDescriptor.send :include, JavaFields +SocketChannel.send :include, JavaFields + +module EventMachine + # TODO: These event numbers are defined in way too many places. + # DRY them up. + # @private + TimerFired = 100 + # @private + ConnectionData = 101 + # @private + ConnectionUnbound = 102 + # @private + ConnectionAccepted = 103 + # @private + ConnectionCompleted = 104 + # @private + LoopbreakSignalled = 105 + # @private + ConnectionNotifyReadable = 106 + # @private + ConnectionNotifyWritable = 107 + # @private + SslHandshakeCompleted = 108 + # @private + SslVerify = 109 + + # @private + EM_PROTO_SSLv2 = 2 + # @private + EM_PROTO_SSLv3 = 4 + # @private + EM_PROTO_TLSv1 = 8 + # @private + EM_PROTO_TLSv1_1 = 16 + # @private + EM_PROTO_TLSv1_2 = 32 + + # Exceptions that are defined in rubymain.cpp + class ConnectionError < RuntimeError; end + class ConnectionNotBound < RuntimeError; end + class UnknownTimerFired < RuntimeError; end + class Unsupported < RuntimeError; end + + # This thunk class used to be called EM, but that caused conflicts with + # the alias "EM" for module EventMachine. (FC, 20Jun08) + class JEM < com.rubyeventmachine.EmReactor + def eventCallback a1, a2, a3, a4 + s = String.from_java_bytes(a3.array[a3.position...a3.limit]) if a3 + EventMachine::event_callback a1, a2, s || a4 + nil + end + end + # class Connection < com.rubyeventmachine.Connection + # def associate_callback_target sig + # # No-op for the time being. + # end + # end + def self.initialize_event_machine + @em = JEM.new + end + def self.release_machine + @em = nil + end + def self.add_oneshot_timer interval + @em.installOneshotTimer interval + end + def self.run_machine + @em.run + end + def self.stop + @em.stop + end + def self.start_tcp_server server, port + @em.startTcpServer server, port + end + def self.stop_tcp_server sig + @em.stopTcpServer sig + end + def self.start_unix_server filename + # TEMPORARILY unsupported until someone figures out how to do it. + raise "unsupported on this platform" + end + def self.send_data sig, data, length + @em.sendData sig, data.to_java_bytes + rescue java.lang.NullPointerException + 0 + end + def self.send_datagram sig, data, length, address, port + @em.sendDatagram sig, data.to_java_bytes, length, address, port + end + def self.connect_server server, port + bind_connect_server nil, nil, server, port + end + def self.bind_connect_server bind_addr, bind_port, server, port + @em.connectTcpServer bind_addr, bind_port.to_i, server, port + end + def self.close_connection sig, after_writing + @em.closeConnection sig, after_writing + end + def self.set_comm_inactivity_timeout sig, interval + @em.setCommInactivityTimeout sig, interval + end + def self.set_pending_connect_timeout sig, val + end + def self.set_heartbeat_interval val + end + def self.start_tls sig + @em.startTls sig + end + def self.ssl? + false + end + def self.signal_loopbreak + @em.signalLoopbreak + end + def self.set_timer_quantum q + @em.setTimerQuantum q + end + def self.epoll + # Epoll is a no-op for Java. + # The latest Java versions run epoll when possible in NIO. + end + def self.epoll= val + end + def self.kqueue + end + def self.kqueue= val + end + def self.epoll? + false + end + def self.kqueue? + false + end + def self.set_rlimit_nofile n_descriptors + # Currently a no-op for Java. + end + def self.open_udp_socket server, port + @em.openUdpSocket server, port + end + def self.invoke_popen cmd + # TEMPORARILY unsupported until someone figures out how to do it. + raise "unsupported on this platform" + end + def self.read_keyboard + # TEMPORARILY unsupported until someone figures out how to do it. + raise "temporarily unsupported on this platform" + end + def self.set_max_timer_count num + # harmless no-op in Java. There's no built-in timer limit. + @max_timer_count = num + end + def self.get_max_timer_count + # harmless no-op in Java. There's no built-in timer limit. + @max_timer_count || 100_000 + end + def self.library_type + :java + end + def self.get_peername sig + if peer = @em.getPeerName(sig) + Socket.pack_sockaddr_in(*peer) + end + end + def self.get_sockname sig + if sockName = @em.getSockName(sig) + Socket.pack_sockaddr_in(*sockName) + end + end + # @private + def self.attach_fd fileno, watch_mode + # 3Aug09: We could pass in the actual SocketChannel, but then it would be modified (set as non-blocking), and + # we would need some logic to make sure detach_fd below didn't clobber it. For now, we just always make a new + # SocketChannel for the underlying file descriptor + # if fileno.java_kind_of? SocketChannel + # ch = fileno + # ch.configureBlocking(false) + # fileno = nil + # elsif fileno.java_kind_of? java.nio.channels.Channel + + if fileno.java_kind_of? java.nio.channels.Channel + field = fileno.getClass.getDeclaredField('fdVal') + field.setAccessible(true) + fileno = field.get(fileno) + else + raise ArgumentError, 'attach_fd requires Java Channel or POSIX fileno' unless fileno.is_a? Integer + end + + if fileno == 0 + raise "can't open STDIN as selectable in Java =(" + elsif fileno.is_a? Integer + # 8Aug09: The following code is specific to the sun jvm's SocketChannelImpl. Is there a cross-platform + # way of implementing this? If so, also remember to update EventableSocketChannel#close and #cleanup + fd = FileDescriptor.new + fd.set_field 'fd', fileno + + ch = SocketChannel.open + ch.configureBlocking(false) + ch.kill + ch.set_field 'fd', fd + ch.set_field 'fdVal', fileno + ch.set_field 'state', ch.get_field('ST_CONNECTED') + end + + @em.attachChannel(ch,watch_mode) + end + def self.detach_fd sig + if ch = @em.detachChannel(sig) + ch.get_field 'fdVal' + end + end + + def self.set_notify_readable sig, mode + @em.setNotifyReadable(sig, mode) + end + def self.set_notify_writable sig, mode + @em.setNotifyWritable(sig, mode) + end + + def self.is_notify_readable sig + @em.isNotifyReadable(sig) + end + def self.is_notify_writable sig + @em.isNotifyWritable(sig) + end + def self.get_connection_count + @em.getConnectionCount + end + + def self.pause_connection(sig) + @em.pauseConnection(sig) + end + def self.resume_connection(sig) + @em.resumeConnection(sig) + end + def self.connection_paused?(sig) + @em.isConnectionPaused(sig) + end + def self._get_outbound_data_size(sig) + @em.getOutboundDataSize(sig) + end + + + def self.set_tls_parms(sig, params) + end + def self.start_tls(sig) + end + def self.send_file_data(sig, filename) + end + + class Connection + def associate_callback_target sig + # No-op for the time being + end + def get_outbound_data_size + EM._get_outbound_data_size @signature + end + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/rubyeventmachine.bundle b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/rubyeventmachine.bundle new file mode 100755 index 0000000..3967d9a Binary files /dev/null and b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/rubyeventmachine.bundle differ diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/package.rake b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/package.rake new file mode 100644 index 0000000..00419d0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/package.rake @@ -0,0 +1,120 @@ +require 'rubygems' +require 'rubygems/package_task' + +begin + require 'rake/extensiontask' + require 'rake/javaextensiontask' +rescue LoadError => e + puts <<-MSG +rake-compiler gem seems to be missing. Please install it with + + gem install rake-compiler + +(add sudo if necessary). + MSG +end + +Gem::PackageTask.new(GEMSPEC) do |pkg| +end + +if RUBY_PLATFORM =~ /java/ + Rake::JavaExtensionTask.new("rubyeventmachine", GEMSPEC) do |ext| + ext.ext_dir = 'java/src' + end +else + def setup_cross_compilation(ext) + unless RUBY_PLATFORM =~ /mswin|mingw/ + ext.cross_compile = true + ext.cross_platform = ['x86-mingw32', 'x64-mingw32'] + end + end + def hack_cross_compilation(ext) + # inject 1.8/1.9 pure-ruby entry point + # HACK: add these dependencies to the task instead of using cross_compiling + if ext.cross_platform.is_a?(Array) + ext.cross_platform.each do |platform| + task = "native:#{GEMSPEC.name}:#{platform}" + if Rake::Task.task_defined?(task) + Rake::Task[task].prerequisites.unshift "lib/#{ext.name}.rb" + end + end + end + end + + em = Rake::ExtensionTask.new("rubyeventmachine", GEMSPEC) do |ext| + ext.ext_dir = 'ext' + ext.source_pattern = '*.{h,c,cpp}' + setup_cross_compilation(ext) + end + hack_cross_compilation em + + ff = Rake::ExtensionTask.new("fastfilereaderext", GEMSPEC) do |ext| + ext.ext_dir = 'ext/fastfilereader' + ext.source_pattern = '*.{h,c,cpp}' + setup_cross_compilation(ext) + end + hack_cross_compilation ff +end + +# Setup shim files that require 1.8 vs 1.9 extensions in win32 bin gems +%w[ rubyeventmachine fastfilereaderext ].each do |filename| + file("lib/#{filename}.rb") do |t| + File.open(t.name, 'wb') do |f| + f.write <<-eoruby + RUBY_VERSION =~ /(\\d+.\\d+)/ + require "\#{$1}/#{File.basename(t.name, '.rb')}" + eoruby + end + at_exit{ FileUtils.rm t.name if File.exist?(t.name) } + end +end + +task :cross_cxx do + ENV['CROSS_COMPILING'] = 'yes' + require 'rake/extensioncompiler' + ENV['CXX'] = "#{Rake::ExtensionCompiler.mingw_host}-g++" +end + +if Rake::Task.task_defined?(:cross) + task :cross => 'lib/rubyeventmachine.rb' + task :cross => 'lib/fastfilereaderext.rb' + task :cross => :cross_cxx +end + +def windows?; RUBY_PLATFORM =~ /mswin|mingw/; end +def sudo(cmd) + if windows? || (require 'etc'; Etc.getpwuid.uid == 0) + sh cmd + else + sh "sudo #{cmd}" + end +end +def gem_cmd(action, name, *args) + rb = Gem.ruby rescue nil + rb ||= (require 'rbconfig'; File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])) + sudo "#{rb} -r rubygems -e 'require %{rubygems/gem_runner}; Gem::GemRunner.new.run(%w{#{action} #{name} #{args.join(' ')}})'" +end + +Rake::Task[:clean].enhance [:clobber_package] + +# DevKit task following the example of Luis Lavena's test-ruby-c-extension +task :devkit do + begin + require "devkit" + rescue LoadError => e + abort "Failed to activate RubyInstaller's DevKit required for compilation." + end +end + +if RUBY_PLATFORM =~ /mingw|mswin/ + Rake::Task['compile'].prerequisites.unshift 'devkit' +end + +desc "Build binary gems for Windows with rake-compiler-dock" +task 'gem:windows' do + require 'rake_compiler_dock' + RakeCompilerDock.sh <<-EOT + RUBY_CC_VERSION="${RUBY_CC_VERSION//1.8.7/}" + bundle && rake cross native gem + EOT +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test.rake b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test.rake new file mode 100644 index 0000000..1185ac7 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test.rake @@ -0,0 +1,8 @@ +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << "tests" + t.libs << "lib" + t.pattern = 'tests/**/test_*.rb' + t.warning = true +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test_pure.rake b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test_pure.rake new file mode 100644 index 0000000..5a84ded --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/rakelib/test_pure.rake @@ -0,0 +1,13 @@ +require 'rake/testtask' + +Rake::TestTask.new(:test_pure) do |t| + t.libs << 'tests' + t.libs << 'lib' + t.test_files = Dir.glob('tests/**/test_pure*.rb') + Dir.glob('tests/**/test_ssl*.rb') + t.warning = true +end + +task :test_em_pure_ruby do + ENV['EM_PURE_RUBY'] = 'true' + Rake::Task['test_pure'].execute +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.crt b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.crt new file mode 100644 index 0000000..1919d97 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFRDCCAywCAQEwDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCRU0xFTATBgNV +BAgTDEV2ZW50TWFjaGluZTEVMBMGA1UEChMMRXZlbnRNYWNoaW5lMRQwEgYDVQQL +EwtEZXZlbG9wbWVudDEVMBMGA1UEAxMMRXZlbnRNYWNoaW5lMB4XDTA5MDMyOTAy +MzE0NloXDTEwMDMyOTAyMzE0NlowaDELMAkGA1UEBhMCRU0xFTATBgNVBAgTDEV2 +ZW50TWFjaGluZTEVMBMGA1UEChMMRXZlbnRNYWNoaW5lMRQwEgYDVQQLEwtEZXZl +bG9wbWVudDEVMBMGA1UEAxMMRXZlbnRNYWNoaW5lMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAv1FSOIX1z7CQtVBFlrB0A3/V29T+22STKKmiRWYkKL5b ++hkrp9IZ5J4phZHgUVM2VDPOO2Oc2PU6dlGGZISg+UPERunTogxQKezCV0vcE9cK +OwzxCFDRvv5rK8aKMscfBLbNKocAXywuRRQmdxPiVRzbyPrl+qCr/EDLXAX3D77l +S8n2AwDg19VyI+IgFUE+Dy5e1eLoY6nV+Mq+vNXdn3ttF3t+ngac5pj5Q9h+pD5p +67baDHSnf/7cy2fa/LKrLolVHQR9G2K6cEfeM99NtcsMbkoPs4iI3FA05OVTQHXg +C8C8cRxrb9APl95I/ep65OIaCJgcdYxJ3QD3qOtQo6/NQsGnjbyiUxaEpjfqyT1N +uzWD81Q8uXGNS8yD6dDynt/lseBjyp2nfC3uQ5fY18VdIcu0MJ9pezBUKrNuhlsy +XXEZ2DXj4sY8QOvIcBqSB/zmS1nGEK55xrtkaiaNrY8fe8wRVpcPLxy+P225NFw+ +B69FJRA0Lj6Jt9BM4hV/3MSIEWwTVhuw4E02ywDYTzz1wq3ITf0tsbIPn0hXQMxD +ohhAoKioM6u+yHtqsxD0eYaAWmHTVn5oDvOSGpvCpBfWHyA7FP5UQak0fKABEAgK +iQYEnb294AXwXymJttfGTIV/Ne4tLN5dIpNma8UO8rlThlcr6xnTQDbR3gkTDRsC +AwEAATANBgkqhkiG9w0BAQUFAAOCAgEAj7J8fy1LUWoVWnrXDAC9jwJ1nI/YjoSU +6ywke3o04+nZC5S+dPnuVy+HAwsU940CoNvP6RStI/bH6JL+NIqEFmwM3M8xIEWV +MYVPkfvQUxxGvDnaY7vv93u+6Q77HV3qlhAQBHChyuXyO7TG3+WzsiT9AnBNtAP0 +4jClt5kCAQXLO/p0SFEZQ8Ru9SM8d1i73Z0VDVzs8jYWlBhiherSgbw1xK4wBOpJ +43XmjZsBSrDpiAXd07Ak3UL2GjfT7eStgebL3UIe39ThE/s/+l43bh0M6WbOBvyQ +i/rZ50kd1GvN0xnZhtv07hIJWO85FGWi7Oet8AzdUZJ17v1Md/f2vdhPVTFN9q+w +mQ6LxjackqCvaJaQfBEbqsn2Tklxk4tZuDioiQbOElT2e6vljQVJWIfNx38Ny2LM +aiXQPQu+4CI7meAh5gXM5nyJGbZvRPsxj89CqYzyHCYs5HBP3AsviBvn26ziOF+c +544VmHd9HkIv8UTC29hh+R64RlgMQQQdaXFaUrFPTs/do0k8n/c2bPc0iTdfi5Q2 +gq6Vi8q6Ay5wGgTtRRbn/mWKuCFjEh94z6pF9Xr06NX0PuEOdf+Ls9vI5vz6G0w6 +0Li7devEN7EKBY+7Mcjg918yq9i5tEiMkUgT68788t3fTC+4iUQ5fDtdrHsaOlIR +8bs/XQVNE/s= +-----END CERTIFICATE----- diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.key b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.key new file mode 100644 index 0000000..87a2531 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAv1FSOIX1z7CQtVBFlrB0A3/V29T+22STKKmiRWYkKL5b+hkr +p9IZ5J4phZHgUVM2VDPOO2Oc2PU6dlGGZISg+UPERunTogxQKezCV0vcE9cKOwzx +CFDRvv5rK8aKMscfBLbNKocAXywuRRQmdxPiVRzbyPrl+qCr/EDLXAX3D77lS8n2 +AwDg19VyI+IgFUE+Dy5e1eLoY6nV+Mq+vNXdn3ttF3t+ngac5pj5Q9h+pD5p67ba +DHSnf/7cy2fa/LKrLolVHQR9G2K6cEfeM99NtcsMbkoPs4iI3FA05OVTQHXgC8C8 +cRxrb9APl95I/ep65OIaCJgcdYxJ3QD3qOtQo6/NQsGnjbyiUxaEpjfqyT1NuzWD +81Q8uXGNS8yD6dDynt/lseBjyp2nfC3uQ5fY18VdIcu0MJ9pezBUKrNuhlsyXXEZ +2DXj4sY8QOvIcBqSB/zmS1nGEK55xrtkaiaNrY8fe8wRVpcPLxy+P225NFw+B69F +JRA0Lj6Jt9BM4hV/3MSIEWwTVhuw4E02ywDYTzz1wq3ITf0tsbIPn0hXQMxDohhA +oKioM6u+yHtqsxD0eYaAWmHTVn5oDvOSGpvCpBfWHyA7FP5UQak0fKABEAgKiQYE +nb294AXwXymJttfGTIV/Ne4tLN5dIpNma8UO8rlThlcr6xnTQDbR3gkTDRsCAwEA +AQKCAgB495RDRQB9x6hX3F+DviI8rDGug+h5FAiwJ0IBG2o1kNdbNVsTC5dvpEmg +uPHaugCaEP+PMZbU34mNklKlb+7QbPbH18UGqz5so9TlmYOXz9oaKD6nAWL9nqRo +02pCXQDR3DuxbhbgFnFTIECJ/jqXkl2toGaVp83W+6kZkHP8srkMyLASihWgosc+ +xRWAGvaAZtNz7br+eT5fxuH/SEKPOl1qAZ23kXrXm1XQfizk8MnMTptkUMYv+hfl +TM98BASUsiTs6g+opy43HFn09naOQcqkWZO/8s6Gbvhi2lVfZqi5Ba6g3lVYJ3gU +kGoako4N9qB7WqJz+LYjVR9C4TbkkJ9OD6ArwGAx5IIzC3XKSxCyY/pUn4YumPhY +fjvY/km54TBtx/isS1TAgjSgDUxbzrfbkh7afOXSOniy9bWJMgNqHF61dqxWxmUg +F5Tch9zH3qFFVkXpYzDU/R8ZV+CRouCvhn0eZYDh8IqIAwjH0VjkxjPyQtrdrMd3 +gDKMVKoY31EOMLZzv8a0prjpr15A+uw30tT336qb3fofks4pZKUJw8ru9jJVir2p ++RML6iUHCmIeceF7/N1meooSMLPJe0xgKeMb9M4Wtd/et2UNVtP8nCDG622rf2a0 +F/EudXuFgc3FB8nXRw9TCkw9xKQff38edG5xPFUEgqObbVl5YQKCAQEA5DDKGOmp +EO5Zuf/kZfG6/AMMYwAuv1HrYTV2w/HnI3tyQ34Xkeqo+I/OqmRk68Ztxw4Kx1So +SRavkotrlWhhDpl2+Yn1BjkHktSoOdf9gJ9z9llkLmbOkBjmupig1NUB7fq/4y2k +MdqJXDy3uVKHJ97gxdIheMTyHiKuMJPnuT5lZtlT210Ig82P7sLQb/sgCfKVFTr0 +Z3haQ5/tBNKjq+igT4nMBWupOTD1q2GeZLIZACnmnUIhvu+3/bm0l+wiCB0DqF0T +Wy9tlL3fqQSCqzevL7/k5Lg6tJTaP/XYePB73TsOtAXgIaoltXgRBsBUeE1eaODx +kMT6E1PPtn7EqQKCAQEA1qImmTWGqhKICrwje40awPufFtZ/qXKVCN/V+zYsrJV1 +EnZpUDM+zfitlQCugnrQVHSpgfekI6mmVkmogO3fkNjUFTq+neg7IHOUHnqotx+3 +NMqIsyFInGstu9mfPd26fzZjUtx5wKF38LDTIJJAEJ83U3UpPBfpwKmiOGDXOa54 +2i4em/bb/hrQR6JySruZYLi0fXnGI5ZOfpkHgC/KOFkKNKAg2oh4B9qo7ACyiSNk +yojb2mmn6g1OLPxi7wGUSrkS1HQq4an6RZ+eUO0HXVWag0QStdQ91M9IrIHgSBBG +0e86Ar6jtD579gqsbz4ySpI/FqEI9obTC+E1/b0aIwKCAQAGz334qGCnZLXA22ZR +tJlEFEM2YTcD9snzqMjWqE2hvXl3kjfZ3wsUABbG9yAb+VwlaMHhmSE8rTSoRwj6 ++JaM/P+UCw4JFYKoWzh6IXwrbpbjb1+SEvdvTY71WsDSGVlpZOZ9PUt9QWyAGD/T +hCcMhZZn0RG2rQoc5CQWxxNPcBFOtIXQMkKizGvTUHUwImqeYWMZsxzASdNH2WoV +jsPbyaGfPhmcv83ZKyDp8IvtrXMZkiaT4vlm3Xi8VeKR9jY9z7/gMob1XcEDg3c9 +cCkGOy87WZrXSLhX02mAJzJCycqom66gqNw7pPxjIiY/8VWUEZsTvkL3cymTkhjM +9ZOhAoIBAGUaNqJe01NTrV+ZJgGyAxM6s8LXQYV5IvjuL2bJKxwUvvP2cT9FFGWD +qYiRrKJr5ayS07IUC+58oIzu33/0DSa27JgfduD9HrT3nKMK1mSEfRFSAjiXChQc +bIubRGapBoub/AdxMazqoovvT1R9b84kobQfcVAMV6DYh0CVZWyXYfgsV2DSVOiK +iufjfoDzg5lLCEI+1XW3/LunrB/W4yPN1X/amf8234ublYyt+2ucD4NUGnP05xLa +N6P7M0MwdEEKkvMe0YBBSFH5kWK/dIOjqkgBDes20fVnuuz/tL1dZW7IiIP4dzaV +ZGEOwBEatCfqYetv6b/u3IUxDfS7Wg8CggEBALoOwkn5LGdQg+bpdZAKJspGnJWL +Kyr9Al2tvgc69rxfpZqS5eDLkYYCzWPpspSt0Axm1O7xOUDQDt42luaLNGJzHZ2Q +Hn0ZNMhyHpe8d8mIQngRjD+nuLI/uFUglPzabDOCOln2aycjg1mA6ecXP1XMEVbu +0RB/0IE36XTMfZ+u9+TRjkBLpmUaX1FdIQQWfwUou/LfaXotoQlhSGAcprLrncuJ +T44UATYEgO/q9pMM33bdE3eBYZHoT9mSvqoLCN4s0LuwOYItIxLKUj0GulL0VQOI +SZi+0A1c8cVDXgApkBrWPDQIR9JS4de0gW4hnDoUvHtUc2TYPRnz6N9MtFY= +-----END RSA PRIVATE KEY----- diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/dhparam.pem b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/dhparam.pem new file mode 100644 index 0000000..b6464ab --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/dhparam.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEAikiatXa5aAteOtd6hOO33npjCvJByD3dwuM8rWzz0DFZdUH9nFJi +b0VvTVweVECb6XZBsrDNLqGQykCrm43swSk5D9XQCGJLxFERD6yk3b90xaeCm3/a +b0Ek5ZVvV73Cc/YbVmpBiOHoTFpUFJLZ7pLMQUSn8y3qUlNcY9/88HuwFi1s1lRM +ovihSRyZMYAuYWOD4yuOuIcroKVjD6gWFrsW9XrALWny6vUXQrhk8Q3rj+wM6ZtE +5afcB0b6ZJtphrDfk3dFjOVG/zVT37VWgrY8GABrpo2ey0W0WIQJ7rDKLaPaI4kc +voOgC2K8Z3kSARZK+jULnwmBeYECz4EH/FF6FEp3GOKtkL4mqEkvh1n5EAesDOGl +iiX+RZXcUrZliSeifSXBTMJWWFVC0fkGIMb9PTZfZHyAC54lpuxzVki0HIyQG9Fs +41zBJ5e8eEoXXlfUYtduUC35YGy2IxSzYLAJE76rctAZSWghha9xLOCDFoLjMr8h +FosKeHKJcBQ0bc8ymOpRIfrYLWhc0Pz2zkpJ/4eYw9t7NYg7S+jP19IE0gUnuM9v +SpoYMtS28tP9nEdokdwuBKD0D3bJEBBefDlHgfXoMgvy9Hivc9PBGGNTNpyFPpwF +sWVAkfhoNMJMC5V7LZsze+lftiDtzVoLSPDa9bO4BK7b/MgwCxfOhGsCAQI= +-----END DH PARAMETERS----- diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/em_test_helper.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/em_test_helper.rb new file mode 100644 index 0000000..20a3e59 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/em_test_helper.rb @@ -0,0 +1,154 @@ +require 'em/pure_ruby' if ENV['EM_PURE_RUBY'] +require 'eventmachine' +require 'test/unit' +require 'rbconfig' +require 'socket' + +puts "EM Library Type: #{EM.library_type}" + +class Test::Unit::TestCase + class EMTestTimeout < StandardError ; end + + def setup_timeout(timeout = TIMEOUT_INTERVAL) + EM.schedule { + EM.add_timer(timeout) { + raise EMTestTimeout, "Test was cancelled after #{timeout} seconds." + } + } + end + + def port_in_use?(port, host="127.0.0.1") + s = TCPSocket.new(host, port) + s.close + s + rescue Errno::ECONNREFUSED + false + end + + def next_port + @@port ||= 9000 + begin + @@port += 1 + end while port_in_use?(@@port) + + @@port + end + + # Returns true if the host have a localhost 127.0.0.1 IPv4. + def self.local_ipv4? + return @@has_local_ipv4 if defined?(@@has_local_ipv4) + begin + get_my_ipv4_address "127.0.0.1" + @@has_local_ipv4 = true + rescue + @@has_local_ipv4 = false + end + end + + # Returns true if the host have a public IPv4 and stores it in + # @@public_ipv4. + def self.public_ipv4? + return @@has_public_ipv4 if defined?(@@has_public_ipv4) + begin + @@public_ipv4 = get_my_ipv4_address "1.2.3.4" + @@has_public_ipv4 = true + rescue + @@has_public_ipv4 = false + end + end + + # Returns true if the host have a localhost ::1 IPv6. + def self.local_ipv6? + return @@has_local_ipv6 if defined?(@@has_local_ipv6) + begin + get_my_ipv6_address "::1" + @@has_local_ipv6 = true + rescue + @@has_local_ipv6 = false + end + end + + # Returns true if the host have a public IPv6 and stores it in + # @@public_ipv6. + def self.public_ipv6? + return @@has_public_ipv6 if defined?(@@has_public_ipv6) + begin + @@public_ipv6 = get_my_ipv6_address "2001::1" + @@has_public_ipv6 = true + rescue + @@has_public_ipv6 = false + end + end + + # Returns an array with the localhost addresses (IPv4 and/or IPv6). + def local_ips + return @@local_ips if defined?(@@local_ips) + @@local_ips = [] + @@local_ips << "127.0.0.1" if self.class.local_ipv4? + @@local_ips << "::1" if self.class.local_ipv6? + @@local_ips + end + + def exception_class + jruby? ? NativeException : RuntimeError + end + + module PlatformHelper + # http://blog.emptyway.com/2009/11/03/proper-way-to-detect-windows-platform-in-ruby/ + def windows? + RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + end + + def solaris? + RUBY_PLATFORM =~ /solaris/ + end + + # http://stackoverflow.com/questions/1342535/how-can-i-tell-if-im-running-from-jruby-vs-ruby/1685970#1685970 + def jruby? + defined? JRUBY_VERSION + end + + def rbx? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' + end + end + + include PlatformHelper + extend PlatformHelper + + # Tests run significantly slower on windows. YMMV + TIMEOUT_INTERVAL = windows? ? 1 : 0.25 + + def silent + backup, $VERBOSE = $VERBOSE, nil + begin + yield + ensure + $VERBOSE = backup + end + end + + + private + + def self.get_my_ipv4_address ip + orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily + UDPSocket.open(Socket::AF_INET) do |s| + s.connect ip, 1 + s.addr.last + end + ensure + Socket.do_not_reverse_lookup = orig + end + + def self.get_my_ipv6_address ip + orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily + UDPSocket.open(Socket::AF_INET6) do |s| + s.connect ip, 1 + s.addr.last + end + ensure + Socket.do_not_reverse_lookup = orig + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_attach.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_attach.rb new file mode 100644 index 0000000..4a55017 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_attach.rb @@ -0,0 +1,151 @@ +require 'em_test_helper' +require 'socket' + +class TestAttach < Test::Unit::TestCase + class EchoServer < EM::Connection + def receive_data data + $received_data << data + send_data data + end + end + + class EchoClient < EM::Connection + def initialize socket + self.notify_readable = true + @socket = socket + @socket.write("abc\n") + end + + def notify_readable + $read = @socket.readline + $fd = detach + end + + def unbind + EM.next_tick do + @socket.write("def\n") + EM.add_timer(0.1) { EM.stop } + end + end + end + + def setup + @port = next_port + $read, $r, $w, $fd = nil + $received_data = "" + end + + def teardown + [$r, $w].each do |io| + io.close rescue nil + end + $received_data = nil + end + + def test_attach + socket = nil + + EM.run { + EM.start_server "127.0.0.1", @port, EchoServer + socket = TCPSocket.new "127.0.0.1", @port + EM.watch socket, EchoClient, socket + } + + assert_equal $read, "abc\n" + unless jruby? # jruby filenos are not real + assert_equal $fd, socket.fileno + end + assert_equal false, socket.closed? + assert_equal socket.readline, "def\n" + end + + module PipeWatch + def notify_readable + $read = $r.readline + EM.stop + end + end + + def test_attach_server + omit_if(jruby?) + $before = TCPServer.new("127.0.0.1", @port) + sig = nil + EM.run { + sig = EM.attach_server $before, EchoServer + + handler = Class.new(EM::Connection) do + def initialize + send_data "hello world" + close_connection_after_writing + EM.add_timer(0.1) { EM.stop } + end + end + EM.connect("127.0.0.1", @port, handler) + } + + assert_equal false, $before.closed? + assert_equal "hello world", $received_data + assert sig.is_a?(Integer) + end + + def test_attach_pipe + EM.run{ + $r, $w = IO.pipe + EM.watch $r, PipeWatch do |c| + c.notify_readable = true + end + $w.write("ghi\n") + } + + assert_equal $read, "ghi\n" + end + + def test_set_readable + before, after = nil + + EM.run{ + $r, $w = IO.pipe + c = EM.watch $r, PipeWatch do |con| + con.notify_readable = false + end + + EM.next_tick{ + before = c.notify_readable? + c.notify_readable = true + after = c.notify_readable? + } + + $w.write("jkl\n") + } + + assert !before + assert after + assert_equal $read, "jkl\n" + end + + def test_read_write_pipe + result = nil + + pipe_reader = Module.new do + define_method :receive_data do |data| + result = data + EM.stop + end + end + + r,w = IO.pipe + + EM.run { + EM.attach r, pipe_reader + writer = EM.attach(w) + writer.send_data 'ghi' + + # XXX: Process will hang in Windows without this line + writer.close_connection_after_writing + } + + assert_equal "ghi", result + ensure + [r,w].each {|io| io.close rescue nil } + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_basic.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_basic.rb new file mode 100644 index 0000000..4d72524 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_basic.rb @@ -0,0 +1,321 @@ +require 'em_test_helper' +require 'socket' + +class TestBasic < Test::Unit::TestCase + def setup + @port = next_port + end + + def test_connection_class_cache + mod = Module.new + a, b = nil, nil + EM.run { + EM.start_server '127.0.0.1', @port, mod + a = EM.connect '127.0.0.1', @port, mod + b = EM.connect '127.0.0.1', @port, mod + EM.stop + } + assert_equal a.class, b.class + assert_kind_of EM::Connection, a + end + + #------------------------------------- + + + def test_em + assert_nothing_raised do + EM.run { + setup_timeout + EM.add_timer 0 do + EM.stop + end + } + end + end + + #------------------------------------- + + def test_timer + assert_nothing_raised do + EM.run { + setup_timeout + n = 0 + EM.add_periodic_timer(0.1) { + n += 1 + EM.stop if n == 2 + } + } + end + end + + #------------------------------------- + + # This test once threw an already-running exception. + module Trivial + def post_init + EM.stop + end + end + + def test_server + assert_nothing_raised do + EM.run { + setup_timeout + EM.start_server "127.0.0.1", @port, Trivial + EM.connect "127.0.0.1", @port + } + end + end + + #-------------------------------------- + + # EM#run_block starts the reactor loop, runs the supplied block, and then STOPS + # the loop automatically. Contrast with EM#run, which keeps running the reactor + # even after the supplied block completes. + def test_run_block + assert !EM.reactor_running? + a = nil + EM.run_block { a = "Worked" } + assert a + assert !EM.reactor_running? + end + + class UnbindError < EM::Connection + ERR = Class.new(StandardError) + def initialize *args + super + end + def connection_completed + close_connection_after_writing + end + def unbind + raise ERR + end + end + + def test_unbind_error_during_stop + assert_raises( UnbindError::ERR ) { + EM.run { + EM.start_server "127.0.0.1", @port + EM.connect "127.0.0.1", @port, UnbindError do + EM.stop + end + } + } + end + + def test_unbind_error + EM.run { + EM.error_handler do |e| + assert(e.is_a?(UnbindError::ERR)) + EM.stop + end + EM.start_server "127.0.0.1", @port + EM.connect "127.0.0.1", @port, UnbindError + } + + # Remove the error handler before the next test + EM.error_handler(nil) + end + + module BrsTestSrv + def receive_data data + $received << data + end + def unbind + EM.stop + end + end + module BrsTestCli + def post_init + send_data $sent + close_connection_after_writing + end + end + + # From ticket #50 + def test_byte_range_send + $received = '' + $sent = (0..255).to_a.pack('C*') + EM::run { + EM::start_server "127.0.0.1", @port, BrsTestSrv + EM::connect "127.0.0.1", @port, BrsTestCli + + setup_timeout + } + assert_equal($sent, $received) + end + + def test_bind_connect + pend('FIXME: this test is broken on Windows') if windows? + + local_ip = UDPSocket.open {|s| s.connect('localhost', 80); s.addr.last } + + bind_port = next_port + + port, ip = nil + bound_server = Module.new do + define_method :post_init do + begin + port, ip = Socket.unpack_sockaddr_in(get_peername) + ensure + EM.stop + end + end + end + + EM.run do + setup_timeout + EM.start_server "127.0.0.1", @port, bound_server + EM.bind_connect local_ip, bind_port, "127.0.0.1", @port + end + + assert_equal bind_port, port + assert_equal local_ip, ip + end + + def test_invalid_address_bind_connect_dst + e = nil + EM.run do + begin + EM.bind_connect('localhost', nil, 'invalid.invalid', 80) + rescue Exception => e + # capture the exception + ensure + EM.stop + end + end + + assert_kind_of(EventMachine::ConnectionError, e) + assert_match(/unable to resolve address:.*not known/, e.message) + end + + def test_invalid_address_bind_connect_src + e = nil + EM.run do + begin + EM.bind_connect('invalid.invalid', nil, 'localhost', 80) + rescue Exception => e + # capture the exception + ensure + EM.stop + end + end + + assert_kind_of(EventMachine::ConnectionError, e) + assert_match(/invalid bind address:.*not known/, e.message) + end + + def test_reactor_thread? + assert !EM.reactor_thread? + EM.run { assert EM.reactor_thread?; EM.stop } + assert !EM.reactor_thread? + end + + def test_schedule_on_reactor_thread + x = false + EM.run do + EM.schedule { x = true } + EM.stop + end + assert x + end + + def test_schedule_from_thread + x = false + EM.run do + Thread.new { EM.schedule { x = true; EM.stop } }.join + end + assert x + end + + def test_set_heartbeat_interval + omit_if(jruby?) + interval = 0.5 + EM.run { + EM.set_heartbeat_interval interval + $interval = EM.get_heartbeat_interval + EM.stop + } + assert_equal(interval, $interval) + end + + module PostInitRaiser + ERR = Class.new(StandardError) + def post_init + raise ERR + end + end + + def test_bubble_errors_from_post_init + assert_raises(PostInitRaiser::ERR) do + EM.run do + EM.start_server "127.0.0.1", @port + EM.connect "127.0.0.1", @port, PostInitRaiser + end + end + end + + module InitializeRaiser + ERR = Class.new(StandardError) + def initialize + raise ERR + end + end + + def test_bubble_errors_from_initialize + assert_raises(InitializeRaiser::ERR) do + EM.run do + EM.start_server "127.0.0.1", @port + EM.connect "127.0.0.1", @port, InitializeRaiser + end + end + end + + def test_schedule_close + omit_if(jruby?) + localhost, port = '127.0.0.1', 9000 + timer_ran = false + num_close_scheduled = nil + EM.run do + assert_equal 0, EM.num_close_scheduled + EM.add_timer(1) { timer_ran = true; EM.stop } + EM.start_server localhost, port do |s| + s.close_connection + num_close_scheduled = EM.num_close_scheduled + end + EM.connect localhost, port do |c| + def c.unbind + EM.stop + end + end + end + assert !timer_ran + assert_equal 1, num_close_scheduled + end + + def test_error_handler_idempotent # issue 185 + errors = [] + ticks = [] + EM.error_handler do |e| + errors << e + end + + EM.run do + EM.next_tick do + ticks << :first + raise + end + EM.next_tick do + ticks << :second + end + EM.add_timer(0.001) { EM.stop } + end + + # Remove the error handler before the next test + EM.error_handler(nil) + + assert_equal 1, errors.size + assert_equal [:first, :second], ticks + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_channel.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_channel.rb new file mode 100644 index 0000000..c54bf1d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_channel.rb @@ -0,0 +1,75 @@ +require 'em_test_helper' + +class TestEMChannel < Test::Unit::TestCase + def test_channel_subscribe + s = 0 + EM.run do + c = EM::Channel.new + c.subscribe { |v| s = v; EM.stop } + c << 1 + end + assert_equal 1, s + end + + def test_channel_unsubscribe + s = 0 + EM.run do + c = EM::Channel.new + subscription = c.subscribe { |v| s = v } + c.unsubscribe(subscription) + c << 1 + EM.next_tick { EM.stop } + end + assert_not_equal 1, s + end + + def test_channel_pop + s = 0 + EM.run do + c = EM::Channel.new + c.pop{ |v| s = v } + c.push(1,2,3) + c << 4 + c << 5 + EM.next_tick { EM.stop } + end + assert_equal 1, s + end + + def test_channel_reactor_thread_push + out = [] + c = EM::Channel.new + c.subscribe { |v| out << v } + Thread.new { c.push(1,2,3) }.join + assert out.empty? + + EM.run { EM.next_tick { EM.stop } } + + assert_equal [1,2,3], out + end + + def test_channel_reactor_thread_callback + out = [] + c = EM::Channel.new + Thread.new { c.subscribe { |v| out << v } }.join + c.push(1,2,3) + assert out.empty? + + EM.run { EM.next_tick { EM.stop } } + + assert_equal [1,2,3], out + end + + def test_channel_num_subscribers + subs = 0 + EM.run do + c = EM::Channel.new + c.subscribe { |v| s = v } + c.subscribe { |v| s = v } + EM.next_tick { EM.stop } + subs = c.num_subscribers + end + + assert_equal subs, 2 + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_completion.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_completion.rb new file mode 100644 index 0000000..1bd9a8f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_completion.rb @@ -0,0 +1,178 @@ +require 'em_test_helper' +require 'em/completion' + +class TestCompletion < Test::Unit::TestCase + def completion + @completion ||= EM::Completion.new + end + + def crank + # This is a slow solution, but this just executes the next tick queue + # once. It's the easiest way for now. + EM.run { EM.stop } + end + + def results + @results ||= [] + end + + def test_state + assert_equal :unknown, completion.state + end + + def test_succeed + completion.callback { |val| results << val } + completion.succeed :object + crank + assert_equal :succeeded, completion.state + assert_equal [:object], results + end + + def test_fail + completion.errback { |val| results << val } + completion.fail :object + crank + assert_equal :failed, completion.state + assert_equal [:object], results + end + + def test_callback + completion.callback { results << :callback } + completion.errback { results << :errback } + completion.succeed + crank + assert_equal [:callback], results + end + + def test_errback + completion.callback { results << :callback } + completion.errback { results << :errback } + completion.fail + crank + assert_equal [:errback], results + end + + def test_stateback + completion.stateback(:magic) { results << :stateback } + completion.change_state(:magic) + crank + assert_equal [:stateback], results + end + + def test_does_not_enqueue_when_completed + completion.callback { results << :callback } + completion.succeed + completion.errback { results << :errback } + completion.fail + crank + assert_equal [:callback], results + end + + def test_completed + assert_equal false, completion.completed? + completion.succeed + assert_equal true, completion.completed? + completion.fail + assert_equal true, completion.completed? + completion.change_state :magic + assert_equal false, completion.completed? + end + + def test_recursive_callbacks + completion.callback do |val| + results << val + completion.succeed :two + end + completion.callback do |val| + results << val + completion.succeed :three + end + completion.callback do |val| + results << val + end + completion.succeed :one + crank + assert_equal [:one, :two, :three], results + end + + def test_late_defined_callbacks + completion.callback { results << :one } + completion.succeed + crank + assert_equal [:one], results + completion.callback { results << :two } + crank + assert_equal [:one, :two], results + end + + def test_cleared_completions + completion.callback { results << :callback } + completion.errback { results << :errback } + + completion.succeed + crank + completion.fail + crank + completion.succeed + crank + + assert_equal [:callback], results + end + + def test_skip_completed_callbacks + completion.callback { results << :callback } + completion.succeed + crank + + completion.errback { results << :errback } + completion.fail + crank + + assert_equal [:callback], results + end + + def test_completions + completion.completion { results << :completion } + completion.succeed + crank + assert_equal [:completion], results + + completion.change_state(:unknown) + results.clear + + completion.completion { results << :completion } + completion.fail + crank + assert_equal [:completion], results + end + + def test_latent_completion + completion.completion { results << :completion } + completion.succeed + crank + completion.completion { results << :completion } + crank + assert_equal [:completion, :completion], results + end + + def test_timeout + args = [1, 2, 3] + EM.run do + completion.timeout(0.0001, *args) + completion.errback { |*errargs| results << errargs } + completion.completion { EM.stop } + EM.add_timer(0.1) { flunk 'test timed out' } + end + assert_equal [[1,2,3]], results + end + + def test_timeout_gets_cancelled + EM.run do + completion.timeout(0.0001, :timeout) + completion.errback { results << :errback } + completion.succeed + EM.add_timer(0.0002) { EM.stop } + end + assert_equal [], results + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_count.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_count.rb new file mode 100644 index 0000000..350c417 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_count.rb @@ -0,0 +1,83 @@ +require 'em_test_helper' + +class TestConnectionCount < Test::Unit::TestCase + def teardown + EM.epoll = false + EM.kqueue = false + end + + def test_idle_connection_count + count = nil + EM.run { + count = EM.connection_count + EM.stop_event_loop + } + assert_equal(0, count) + end + + # Run this again with epoll enabled (if available) + def test_idle_connection_count_epoll + EM.epoll if EM.epoll? + + count = nil + EM.run { + count = EM.connection_count + EM.stop_event_loop + } + assert_equal(0, count) + end + + # Run this again with kqueue enabled (if available) + def test_idle_connection_count_kqueue + EM.kqueue if EM.kqueue? + + count = nil + EM.run { + count = EM.connection_count + EM.stop_event_loop + } + assert_equal(0, count) + end + + module Client + def connection_completed + $client_conns += 1 + EM.stop if $client_conns == 3 + end + end + + def test_with_some_connections + EM.run { + $client_conns = 0 + $initial_conns = EM.connection_count + EM.start_server("127.0.0.1", 9999) + $server_conns = EM.connection_count + 3.times { EM.connect("127.0.0.1", 9999, Client) } + } + + assert_equal(0, $initial_conns) + assert_equal(1, $server_conns) + assert_equal(4, $client_conns + $server_conns) + end + + module DoubleCloseClient + def unbind + close_connection + $num_close_scheduled_1 = EM.num_close_scheduled + EM.next_tick do + $num_close_scheduled_2 = EM.num_close_scheduled + EM.stop + end + end + end + + def test_num_close_scheduled + omit_if(jruby?) + EM.run { + assert_equal(0, EM.num_close_scheduled) + EM.connect("127.0.0.1", 9999, DoubleCloseClient) # nothing listening on 9999 + } + assert_equal(1, $num_close_scheduled_1) + assert_equal(0, $num_close_scheduled_2) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_write.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_write.rb new file mode 100644 index 0000000..35533b5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_connection_write.rb @@ -0,0 +1,35 @@ +require 'em_test_helper' + +class TestConnectionWrite < Test::Unit::TestCase + + # This test takes advantage of the fact that EM::_RunSelectOnce iterates over the connections twice: + # - once to determine which ones to call Write() on + # - and once to call Write() on each of them. + # + # But state may change in the meantime before Write() is finally called. + # And that is what we try to exploit to get Write() to be called when bWatchOnly is true, and bNotifyWritable is false, + # to cause an assertion failure. + + module SimpleClient + def notify_writable + $conn2.notify_writable = false # Being naughty in callback + # If this doesn't crash anything, the test passed! + end + end + + def test_with_naughty_callback + EM.run do + r1, _ = IO.pipe + r2, _ = IO.pipe + + # Adding EM.watches + $conn1 = EM.watch(r1, SimpleClient) + $conn2 = EM.watch(r2, SimpleClient) + + $conn1.notify_writable = true + $conn2.notify_writable = true + + EM.stop + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_defer.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_defer.rb new file mode 100644 index 0000000..aeca127 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_defer.rb @@ -0,0 +1,35 @@ +require 'em_test_helper' + +class TestDefer < Test::Unit::TestCase + + def test_defers + n = 0 + n_times = 20 + EM.run { + n_times.times { + work_proc = proc { n += 1 } + callback = proc { EM.stop if n == n_times } + EM.defer work_proc, callback + } + } + assert_equal( n, n_times ) + end + + def test_errbacks + iterations = 20 + callback_parameter = rand(100) + callback_parameters = [] + callback_op = proc { callback_parameter } + callback = proc { |result| callback_parameters << result } + errback_parameter = Exception.new + errback_parameters = [] + errback_op = proc { raise errback_parameter } + errback = proc { |error| errback_parameters << error } + EventMachine.run do + (1..iterations).each { |index| EventMachine.defer(index.even? ? callback_op : errback_op, callback, errback) } + EventMachine.add_periodic_timer(0.1) { EventMachine.stop if EventMachine.defers_finished? } + end + assert_equal(callback_parameters.select { |parameter| parameter == callback_parameter }.length, iterations * 0.5) + assert_equal(errback_parameters.select{ |parameter| parameter == errback_parameter }.length, iterations * 0.5) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_deferrable.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_deferrable.rb new file mode 100644 index 0000000..5f286a7 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_deferrable.rb @@ -0,0 +1,35 @@ +require 'em_test_helper' + +class TestDeferrable < Test::Unit::TestCase + class Later + include EM::Deferrable + end + + def test_timeout_without_args + assert_nothing_raised do + EM.run { + df = Later.new + df.timeout(0) + df.errback { EM.stop } + EM.add_timer(0.01) { flunk "Deferrable was not timed out." } + } + end + end + + def test_timeout_with_args + args = nil + + EM.run { + df = Later.new + df.timeout(0, :timeout, :foo) + df.errback do |type, name| + args = [type, name] + EM.stop + end + + EM.add_timer(0.01) { flunk "Deferrable was not timed out." } + } + + assert_equal [:timeout, :foo], args + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_epoll.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_epoll.rb new file mode 100644 index 0000000..36f5609 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_epoll.rb @@ -0,0 +1,142 @@ +require 'em_test_helper' + + +class TestEpoll < Test::Unit::TestCase + + module TestEchoServer + def receive_data data + send_data data + close_connection_after_writing + end + end + + module TestEchoClient + def connection_completed + send_data "ABCDE" + $max += 1 + end + def receive_data data + raise "bad response" unless data == "ABCDE" + end + def unbind + $n -= 1 + EM.stop if $n == 0 + end + end + + + # We can set the rlimit/nofile of a process but we can only set it + # higher if we're running as root. + # On most systems, the default value is 1024. + def test_rlimit + omit_if(windows? || jruby?) + unless EM.set_descriptor_table_size >= 1024 + a = EM.set_descriptor_table_size + assert( a <= 1024 ) + a = EM.set_descriptor_table_size( 1024 ) + assert( a == 1024 ) + end + end + + # Run a high-volume version of this test by kicking the number of connections + # up past 512. (Each connection uses two sockets, a client and a server.) + # (Will require running the test as root) + # This test exercises TCP clients and servers. + # + # XXX this test causes all sort of weird issues on OSX (when run as part of the suite) + def _test_descriptors + EM.epoll + EM.set_descriptor_table_size 60000 + EM.run { + EM.start_server "127.0.0.1", 9800, TestEchoServer + $n = 0 + $max = 0 + 100.times { + EM.connect("127.0.0.1", 9800, TestEchoClient) {$n += 1} + } + } + assert_equal(0, $n) + assert_equal(100, $max) + end + + def setup + @port = next_port + end + + module TestDatagramServer + def receive_data dgm + $in = dgm + send_data "abcdefghij" + end + end + module TestDatagramClient + def initialize port + @port = port + end + + def post_init + send_datagram "1234567890", "127.0.0.1", @port + end + + def receive_data dgm + $out = dgm + EM.stop + end + end + + def test_datagrams + $in = $out = "" + EM.run { + EM.open_datagram_socket "127.0.0.1", @port, TestDatagramServer + EM.open_datagram_socket "127.0.0.1", 0, TestDatagramClient, @port + } + assert_equal( "1234567890", $in ) + assert_equal( "abcdefghij", $out ) + end + + # XXX this test fails randomly... + def _test_unix_domain + fn = "/tmp/xxx.chain" + EM.epoll + EM.set_descriptor_table_size 60000 + EM.run { + # The pure-Ruby version won't let us open the socket if the node already exists. + # Not sure, that actually may be correct and the compiled version is wrong. + # Pure Ruby also oddly won't let us make that many connections. This test used + # to run 100 times. Not sure where that lower connection-limit is coming from in + # pure Ruby. + # Let's not sweat the Unix-ness of the filename, since this test can't possibly + # work on Windows anyway. + # + File.unlink(fn) if File.exist?(fn) + EM.start_unix_domain_server fn, TestEchoServer + $n = 0 + $max = 0 + 50.times { + EM.connect_unix_domain(fn, TestEchoClient) {$n += 1} + } + EM::add_timer(1) { $stderr.puts("test_unix_domain timed out!"); EM::stop } + } + assert_equal(0, $n) + assert_equal(50, $max) + ensure + File.unlink(fn) if File.exist?(fn) + end + + def test_attach_detach + EM.epoll + EM.run { + EM.add_timer(0.01) { EM.stop } + + r, _ = IO.pipe + + # This tests a regression where detach in the same tick as attach crashes EM + EM.watch(r) do |connection| + connection.detach + end + } + + assert true + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_error_handler.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_error_handler.rb new file mode 100644 index 0000000..23c23f7 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_error_handler.rb @@ -0,0 +1,38 @@ +require 'em_test_helper' + +class TestErrorHandler < Test::Unit::TestCase + def setup + @exception = Class.new(StandardError) + end + + def test_error_handler + error = nil + + EM.error_handler{ |e| + error = e + EM.error_handler(nil) + EM.stop + } + + assert_nothing_raised do + EM.run{ + EM.add_timer(0){ + raise @exception, 'test' + } + } + end + + assert_equal error.class, @exception + assert_equal error.message, 'test' + end + + def test_without_error_handler + assert_raise @exception do + EM.run{ + EM.add_timer(0){ + raise @exception, 'test' + } + } + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_exc.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_exc.rb new file mode 100644 index 0000000..d9c860a --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_exc.rb @@ -0,0 +1,43 @@ +require 'em_test_helper' + +class TestSomeExceptions < Test::Unit::TestCase + class DoomedConnectionError < StandardError + end + class DoomedConnection < EventMachine::Connection + def unbind + raise DoomedConnectionError + end + end + + # Read the commentary in EM#run. + # This test exercises the ensure block in #run that makes sure + # EM#release_machine gets called even if an exception is + # thrown within the user code. Without the ensured call to release_machine, + # the second call to EM#run will fail with a C++ exception + # because the machine wasn't cleaned up properly. + + def test_a + assert_raises(RuntimeError) { + EM.run { + raise "some exception" + } + } + end + + def test_b + assert_raises(RuntimeError) { + EM.run { + raise "some exception" + } + } + end + + def test_exception_on_unbind + assert_raises(DoomedConnectionError) { + EM.run { + EM.connect("localhost", 8888, DoomedConnection) + } + } + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_file_watch.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_file_watch.rb new file mode 100644 index 0000000..5602071 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_file_watch.rb @@ -0,0 +1,85 @@ +require 'em_test_helper' +require 'tempfile' + +class TestFileWatch < Test::Unit::TestCase + if windows? + def test_watch_file_raises_unsupported_error + assert_raises(EM::Unsupported) do + EM.run do + file = Tempfile.new("fake_file") + EM.watch_file(file.path) + end + end + end + elsif EM.respond_to? :watch_filename + module FileWatcher + def file_modified + $modified = true + end + def file_deleted + $deleted = true + end + def unbind + $unbind = true + EM.stop + end + end + + def setup + EM.kqueue = true if EM.kqueue? + end + + def teardown + EM.kqueue = false if EM.kqueue? + end + + def test_events + omit_if(solaris?) + EM.run{ + file = Tempfile.new('em-watch') + $tmp_path = file.path + + # watch it + watch = EM.watch_file(file.path, FileWatcher) + $path = watch.path + + # modify it + File.open(file.path, 'w'){ |f| f.puts 'hi' } + + # delete it + EM.add_timer(0.01){ file.close; file.delete } + } + + assert_equal($path, $tmp_path) + assert($modified) + assert($deleted) + assert($unbind) + end + + # Refer: https://github.com/eventmachine/eventmachine/issues/512 + def test_invalid_signature + # This works fine with kqueue, only fails with linux inotify. + omit_if(EM.kqueue?) + + EM.run { + file = Tempfile.new('foo') + + w1 = EventMachine.watch_file(file.path) + w2 = EventMachine.watch_file(file.path) + + assert_raise EventMachine::InvalidSignature do + w2.stop_watching + end + + EM.stop + } + end + else + warn "EM.watch_file not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_watch_file_unsupported + assert true + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_fork.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_fork.rb new file mode 100644 index 0000000..8b15bb5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_fork.rb @@ -0,0 +1,75 @@ +require 'em_test_helper' + +class TestFork < Test::Unit::TestCase + + def test_fork_safe + omit_if(jruby?) + omit_if(windows?) + + fork_pid = nil + read, write = IO.pipe + EM.run do + fork_pid = fork do + write.puts "forked" + EM.run do + EM.next_tick do + write.puts "EM ran" + EM.stop + end + end + end + EM.stop + end + + sleep 0.1 + begin + Timeout::timeout 1 do + assert_equal "forked\n", read.readline + assert_equal "EM ran\n", read.readline + end + rescue Timeout::Error + Process.kill 'TERM', fork_pid + flunk "Timeout waiting for next_tick in new fork reactor" + end + ensure + read.close rescue nil + write.close rescue nil + end + + def test_fork_reactor + omit_if(jruby?) + omit_if(windows?) + + fork_pid = nil + read, write = IO.pipe + EM.run do + EM.defer do + write.puts Process.pid + EM.defer do + EM.stop + end + end + fork_pid = EM.fork_reactor do + EM.defer do + write.puts Process.pid + EM.stop + end + end + end + + sleep 0.1 + begin + Timeout::timeout 1 do + assert_equal Process.pid.to_s, read.readline.chomp + assert_equal fork_pid.to_s, read.readline.chomp + end + rescue Timeout::Error + Process.kill 'TERM', fork_pid + flunk "Timeout waiting for deferred block in fork_reactor" + end + ensure + read.close rescue nil + write.close rescue nil + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_futures.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_futures.rb new file mode 100644 index 0000000..b494856 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_futures.rb @@ -0,0 +1,170 @@ +require 'em_test_helper' + +class TestFutures < Test::Unit::TestCase + + def setup + end + + def teardown + end + + def test_future + assert_equal(100, EM::Deferrable.future(100) ) + + p1 = proc { 100 + 1 } + assert_equal(101, EM::Deferrable.future(p1) ) + end + + class MyFuture + include EM::Deferrable + def initialize *args + super + set_deferred_status :succeeded, 40 + end + end + + class MyErrorFuture + include EM::Deferrable + def initialize *args + super + set_deferred_status :failed, 41 + end + end + + + def test_future_1 + # Call future with one additional argument and it will be treated as a callback. + def my_future + MyFuture.new + end + + value = nil + EM::Deferrable.future my_future, proc {|v| value=v} + assert_equal( 40, value ) + end + + + def test_future_2 + # Call future with two additional arguments and they will be treated as a callback + # and an errback. + value = nil + EM::Deferrable.future MyErrorFuture.new, nil, proc {|v| value=v} + assert_equal( 41, value ) + end + + + def test_future_3 + # Call future with no additional arguments but with a block, and the block will be + # treated as a callback. + value = nil + EM::Deferrable.future MyFuture.new do |v| + value=v + end + assert_equal( 40, value ) + end + + + class RecursiveCallback + include EM::Deferrable + end + + # A Deferrable callback can call #set_deferred_status to change the values + # passed to subsequent callbacks. + # + def test_recursive_callbacks + n = 0 # counter assures that all the tests actually run. + rc = RecursiveCallback.new + rc.callback {|a| + assert_equal(100, a) + n += 1 + rc.set_deferred_status :succeeded, 101, 101 + } + rc.callback {|a,b| + assert_equal(101, a) + assert_equal(101, b) + n += 1 + rc.set_deferred_status :succeeded, 102, 102, 102 + } + rc.callback {|a,b,c| + assert_equal(102, a) + assert_equal(102, b) + assert_equal(102, c) + n += 1 + } + rc.set_deferred_status :succeeded, 100 + assert_equal(3, n) + end + + def test_syntactic_sugar + rc = RecursiveCallback.new + rc.set_deferred_success 100 + rc.set_deferred_failure 200 + end + + # It doesn't raise an error to set deferred status more than once. + # In fact, this is a desired and useful idiom when it happens INSIDE + # a callback or errback. + # However, it's less useful otherwise, and in fact would generally be + # indicative of a programming error. However, we would like to be resistant + # to such errors. So whenever we set deferred status, we also clear BOTH + # stacks of handlers. + # + def test_double_calls + s = 0 + e = 0 + + d = EM::DefaultDeferrable.new + d.callback {s += 1} + d.errback {e += 1} + + d.succeed # We expect the callback to be called, and the errback to be DISCARDED. + d.fail # Presumably an error. We expect the errback NOT to be called. + d.succeed # We expect the callback to have been discarded and NOT to be called again. + + assert_equal(1, s) + assert_equal(0, e) + end + + # Adding a callback to a Deferrable that is already in a success state executes the callback + # immediately. The same applies to a an errback added to an already-failed Deferrable. + # HOWEVER, we expect NOT to be able to add errbacks to succeeded Deferrables, or callbacks + # to failed ones. + # + # We illustrate this with a rather contrived test. The test calls #fail after #succeed, + # which ordinarily would not happen in a real program. + # + # What we're NOT attempting to specify is what happens if a Deferrable is succeeded and then + # failed (or vice-versa). Should we then be able to add callbacks/errbacks of the appropriate + # type for immediate execution? For now at least, the official answer is "don't do that." + # + def test_delayed_callbacks + s1 = 0 + s2 = 0 + e = 0 + + d = EM::DefaultDeferrable.new + d.callback {s1 += 1} + + d.succeed # Triggers and discards the callback. + + d.callback {s2 += 1} # This callback is executed immediately and discarded. + + d.errback {e += 1} # This errback should be DISCARDED and never execute. + d.fail # To prove it, fail and assert e is 0 + + assert_equal( [1,1], [s1,s2] ) + assert_equal( 0, e ) + end + + def test_timeout + n = 0 + EM.run { + d = EM::DefaultDeferrable.new + d.callback {n = 1; EM.stop} + d.errback {n = 2; EM.stop} + d.timeout(0.01) + } + assert_equal( 2, n ) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_handler_check.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_handler_check.rb new file mode 100644 index 0000000..c417694 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_handler_check.rb @@ -0,0 +1,35 @@ +require 'em_test_helper' + +class TestHandlerCheck < Test::Unit::TestCase + + class Foo < EM::Connection; end; + module TestModule; end; + + def test_with_correct_class + assert_nothing_raised do + EM.run { + EM.connect("127.0.0.1", 80, Foo) + EM.stop_event_loop + } + end + end + + def test_with_incorrect_class + assert_raise(ArgumentError) do + EM.run { + EM.connect("127.0.0.1", 80, String) + EM.stop_event_loop + } + end + end + + def test_with_module + assert_nothing_raised do + EM.run { + EM.connect("127.0.0.1", 80, TestModule) + EM.stop_event_loop + } + end + end + +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_hc.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_hc.rb new file mode 100644 index 0000000..28e32c9 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_hc.rb @@ -0,0 +1,155 @@ +require 'em_test_helper' + +class TestHeaderAndContentProtocol < Test::Unit::TestCase + + class SimpleTest < EM::P::HeaderAndContentProtocol + attr_reader :first_header, :my_headers, :request + + def receive_first_header_line hdr + @first_header ||= [] + @first_header << hdr + end + def receive_headers hdrs + @my_headers ||= [] + @my_headers << hdrs + end + def receive_request hdrs, content + @request ||= [] + @request << [hdrs, content] + end + end + + class StopOnUnbind < EM::Connection + def unbind + EM.add_timer(0.01) { EM.stop } + end + end + + def setup + @port = next_port + end + + def test_no_content + the_connection = nil + EM.run { + EM.start_server( "127.0.0.1", @port, SimpleTest ) do |conn| + the_connection = conn + end + setup_timeout + + EM.connect "127.0.0.1", @port, StopOnUnbind do |c| + c.send_data [ "aaa\n", "bbb\r\n", "ccc\n", "\n" ].join + c.close_connection_after_writing + end + } + assert_equal( ["aaa"], the_connection.first_header ) + assert_equal( [%w(aaa bbb ccc)], the_connection.my_headers ) + assert_equal( [[%w(aaa bbb ccc), ""]], the_connection.request ) + end + + def test_content + the_connection = nil + content = "A" * 50 + headers = ["aaa", "bbb", "Content-length: #{content.length}", "ccc"] + EM.run { + EM.start_server( "127.0.0.1", @port, SimpleTest ) do |conn| + the_connection = conn + end + setup_timeout + + EM.connect "127.0.0.1", @port, StopOnUnbind do |c| + headers.each { |h| c.send_data "#{h}\r\n" } + c.send_data "\n" + c.send_data content + c.close_connection_after_writing + end + } + assert_equal( ["aaa"], the_connection.first_header ) + assert_equal( [headers], the_connection.my_headers ) + assert_equal( [[headers, content]], the_connection.request ) + end + + def test_several_requests + the_connection = nil + content = "A" * 50 + headers = ["aaa", "bbb", "Content-length: #{content.length}", "ccc"] + EM.run { + EM.start_server( "127.0.0.1", @port, SimpleTest ) do |conn| + the_connection = conn + end + setup_timeout + + EM.connect( "127.0.0.1", @port, StopOnUnbind ) do |c| + 5.times do + headers.each { |h| c.send_data "#{h}\r\n" } + c.send_data "\n" + c.send_data content + end + c.close_connection_after_writing + end + } + assert_equal( ["aaa"] * 5, the_connection.first_header ) + assert_equal( [headers] * 5, the_connection.my_headers ) + assert_equal( [[headers, content]] * 5, the_connection.request ) + end + + + # def x_test_multiple_content_length_headers + # # This is supposed to throw a RuntimeError but it throws a C++ exception instead. + # the_connection = nil + # content = "A" * 50 + # headers = ["aaa", "bbb", ["Content-length: #{content.length}"]*2, "ccc"].flatten + # EM.run { + # EM.start_server( "127.0.0.1", @port, SimpleTest ) do |conn| + # the_connection = conn + # end + # EM.add_timer(4) {raise "test timed out"} + # test_proc = proc { + # t = TCPSocket.new "127.0.0.1", @port + # headers.each {|h| t.write "#{h}\r\n" } + # t.write "\n" + # t.write content + # t.close + # } + # EM.defer test_proc, proc { + # EM.stop + # } + # } + # end + + def test_interpret_headers + the_connection = nil + content = "A" * 50 + headers = [ + "GET / HTTP/1.0", + "Accept: aaa", + "User-Agent: bbb", + "Host: ccc", + "x-tempest-header:ddd" + ] + + EM.run { + EM.start_server( "127.0.0.1", @port, SimpleTest ) do |conn| + the_connection = conn + end + setup_timeout + + EM.connect( "127.0.0.1", @port, StopOnUnbind ) do |c| + headers.each { |h| c.send_data "#{h}\r\n" } + c.send_data "\n" + c.send_data content + c.close_connection_after_writing + end + } + + hsh = the_connection.headers_2_hash( the_connection.my_headers.shift ) + expect = { + :accept => "aaa", + :user_agent => "bbb", + :host => "ccc", + :x_tempest_header => "ddd" + } + assert_equal(expect, hsh) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient.rb new file mode 100644 index 0000000..572a802 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient.rb @@ -0,0 +1,238 @@ +require 'em_test_helper' + +class TestHttpClient < Test::Unit::TestCase + + def setup + @port = next_port + end + + #------------------------------------- + + def test_http_client + ok = false + EM.run { + c = silent { EM::P::HttpClient.send :request, :host => "www.google.com", :port => 80 } + c.callback { + ok = true + c.close_connection + EM.stop + } + c.errback {EM.stop} # necessary, otherwise a failure blocks the test suite forever. + } + assert ok + end + + #------------------------------------- + + def test_http_client_1 + ok = false + EM.run { + c = silent { EM::P::HttpClient.send :request, :host => "www.google.com", :port => 80 } + c.callback { + ok = true + c.close_connection + EM.stop + } + c.errback {EM.stop} + } + assert ok + end + + #------------------------------------- + + def test_http_client_2 + ok = false + EM.run { + c = silent { EM::P::HttpClient.send :request, :host => "www.google.com", :port => 80 } + c.callback { + ok = true + c.close_connection + EM.stop + } + c.errback {EM.stop} + } + assert ok + end + + + #----------------------------------------- + + # Test a server that returns a page with a zero content-length. + # This caused an early version of the HTTP client not to generate a response, + # causing this test to hang. Observe, there was no problem with responses + # lacking a content-length, just when the content-length was zero. + # + class EmptyContent < EM::Connection + def initialize *args + super + end + def receive_data data + send_data "HTTP/1.0 404 ...\r\nContent-length: 0\r\n\r\n" + close_connection_after_writing + end + end + + def test_http_empty_content + ok = false + EM.run { + EM.start_server "127.0.0.1", @port, EmptyContent + c = silent { EM::P::HttpClient.send :request, :host => "127.0.0.1", :port => @port } + c.callback { + ok = true + c.close_connection + EM.stop + } + } + assert ok + end + + + #--------------------------------------- + + class PostContent < EM::P::LineAndTextProtocol + def initialize *args + super + @lines = [] + end + def receive_line line + if line.length > 0 + @lines << line + else + process_headers + end + end + def receive_binary_data data + @post_content = data + send_response + end + def process_headers + if @lines.first =~ /\APOST ([^\s]+) HTTP\/1.1\Z/ + @uri = $1.dup + else + raise "bad request" + end + + @lines.each {|line| + if line =~ /\AContent-length:\s*(\d+)\Z/i + @content_length = $1.dup.to_i + elsif line =~ /\AContent-type:\s*(\d+)\Z/i + @content_type = $1.dup + end + } + + raise "invalid content length" unless @content_length + set_binary_mode @content_length + end + def send_response + send_data "HTTP/1.1 200 ...\r\nConnection: close\r\nContent-length: 10\r\nContent-type: text/html\r\n\r\n0123456789" + close_connection_after_writing + end + end + + # TODO, this is WRONG. The handler is asserting an HTTP 1.1 request, but the client + # is sending a 1.0 request. Gotta fix the client + def test_post + response = nil + EM.run { + EM.start_server '127.0.0.1', @port, PostContent + setup_timeout(2) + c = silent { EM::P::HttpClient.request( + :host => '127.0.0.1', + :port => @port, + :method => :post, + :request => "/aaa", + :content => "XYZ", + :content_type => "text/plain" + )} + c.callback {|r| + response = r + EM.stop + } + } + + assert_equal( 200, response[:status] ) + assert_equal( "0123456789", response[:content] ) + end + + + # TODO, need a more intelligent cookie tester. + # In fact, this whole test-harness needs a beefier server implementation. + def test_cookie + ok = false + EM.run { + c = silent { EM::Protocols::HttpClient.send :request, :host => "www.google.com", :port => 80, :cookie=>"aaa=bbb" } + c.callback { + ok = true + c.close_connection + EM.stop + } + c.errback {EM.stop} + } + assert ok + end + + # We can tell the client to send an HTTP/1.0 request (default is 1.1). + # This is useful for suppressing chunked responses until those are working. + def test_version_1_0 + ok = false + EM.run { + c = silent { EM::P::HttpClient.request( + :host => "www.google.com", + :port => 80, + :version => "1.0" + )} + c.callback { + ok = true + c.close_connection + EM.stop + } + c.errback {EM.stop} + } + assert ok + end + + #----------------------------------------- + + # Test a server that returns chunked encoding + # + class ChunkedEncodingContent < EventMachine::Connection + def initialize *args + super + end + def receive_data data + send_data ["HTTP/1.1 200 OK", + "Server: nginx/0.7.67", + "Date: Sat, 23 Oct 2010 16:41:32 GMT", + "Content-Type: application/json", + "Transfer-Encoding: chunked", + "Connection: keep-alive", + "", + "1800", + "chunk1" * 1024, + "5a", + "chunk2" * 15, + "0", + ""].join("\r\n") + close_connection_after_writing + end + end + + def test_http_chunked_encoding_content + ok = false + EM.run { + EM.start_server "127.0.0.1", @port, ChunkedEncodingContent + c = silent { EM::P::HttpClient.send :request, :host => "127.0.0.1", :port => @port } + c.callback { |result| + if result[:content] == "chunk1" * 1024 + "chunk2" * 15 + ok = true + end + c.close_connection + EM.stop + } + } + assert ok + end + +end + + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient2.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient2.rb new file mode 100644 index 0000000..a00fcbc --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_httpclient2.rb @@ -0,0 +1,128 @@ +require 'em_test_helper' + +class TestHttpClient2 < Test::Unit::TestCase + class TestServer < EM::Connection + end + + def setup + @port = next_port + end + + # #connect returns an object which has made a connection to an HTTP server + # and exposes methods for making HTTP requests on that connection. + # #connect can take either a pair of parameters (a host and a port), + # or a single parameter which is a Hash. + # + def test_connect + EM.run { + setup_timeout(1) + EM.start_server '127.0.0.1', @port, TestServer + silent do + EM::P::HttpClient2.connect '127.0.0.1', @port + EM::P::HttpClient2.connect( :host=>'127.0.0.1', :port=>@port ) + end + EM.stop + } + end + + def test_bad_port + EM.run { + setup_timeout(1) + EM.start_server '127.0.0.1', @port, TestServer + assert_raises( ArgumentError ) { + silent { EM::P::HttpClient2.connect '127.0.0.1', "xxx" } + } + EM.stop + } + end + + def test_bad_server + err = nil + EM.run { + setup_timeout(1) + http = silent { EM::P::HttpClient2.connect '127.0.0.1', 9999 } + d = http.get "/" + d.errback { err = true; d.internal_error; EM.stop } + } + assert(err) + end + + def test_get + content = nil + EM.run { + setup_timeout(1) + http = silent { EM::P::HttpClient2.connect :host => "google.com", :port => 80, :version => '1.0' } + d = http.get "/" + d.callback { + content = d.content + EM.stop + } + } + assert(content) + end + + # Not a pipelined request because we wait for one response before we request the next. + # XXX this test is broken because it sends the second request to the first connection + # XXX right before the connection closes + def _test_get_multiple + content = nil + EM.run { + setup_timeout(1) + http = silent { EM::P::HttpClient2.connect "google.com", :version => '1.0' } + d = http.get "/" + d.callback { + e = http.get "/" + e.callback { + content = e.content + EM.stop + } + } + } + assert(content) + end + + def test_get_pipeline + headers, headers2 = nil, nil + EM.run { + setup_timeout(1) + http = silent { EM::P::HttpClient2.connect "google.com", 80 } + d = http.get("/") + d.callback { + headers = d.headers + } + e = http.get("/") + e.callback { + headers2 = e.headers + } + EM.tick_loop { EM.stop if headers && headers2 } + EM.add_timer(1) { EM.stop } + } + assert(headers) + assert(headers2) + end + + def test_authheader + EM.run { + setup_timeout(1) + EM.start_server '127.0.0.1', @port, TestServer + http = silent { EM::P::HttpClient2.connect '127.0.0.1', 18842 } + d = http.get :url=>"/", :authorization=>"Basic xxx" + d.callback {EM.stop} + d.errback {EM.stop} + } + end + + def test_https_get + omit_unless(EM.ssl?) + d = nil + EM.run { + setup_timeout(1) + http = silent { EM::P::HttpClient2.connect :host => 'www.google.com', :port => 443, :ssl => true, :version => '1.0' } + d = http.get "/" + d.callback {EM.stop} + d.errback {EM.stop} + } + assert_equal(200, d.status) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_idle_connection.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_idle_connection.rb new file mode 100644 index 0000000..bfc57cd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_idle_connection.rb @@ -0,0 +1,31 @@ +require 'em_test_helper' + +class TestIdleConnection < Test::Unit::TestCase + def setup + @port = next_port + end + + def test_idle_time + omit_if(!EM.respond_to?(:get_idle_time)) + + a, b = nil, nil + EM.run do + EM.start_server '127.0.0.1', @port, Module.new + conn = EM.connect '127.0.0.1', @port + EM.add_timer(0.3) do + a = conn.get_idle_time + conn.send_data 'a' + EM.next_tick do + EM.next_tick do + b = conn.get_idle_time + conn.close_connection + EM.stop + end + end + end + end + + assert_in_delta 0.3, a, 0.1 + assert_in_delta 0, b, 0.1 + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_inactivity_timeout.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_inactivity_timeout.rb new file mode 100644 index 0000000..15e39a2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_inactivity_timeout.rb @@ -0,0 +1,54 @@ +require 'em_test_helper' + +class TestInactivityTimeout < Test::Unit::TestCase + + if EM.respond_to? :get_comm_inactivity_timeout + def test_default + EM.run { + c = EM.connect("127.0.0.1", 54321) + assert_equal 0.0, c.comm_inactivity_timeout + EM.stop + } + end + + def test_set_and_get + EM.run { + c = EM.connect("127.0.0.1", 54321) + c.comm_inactivity_timeout = 2.5 + assert_equal 2.5, c.comm_inactivity_timeout + EM.stop + } + end + + def test_for_real + start, finish = nil + + timeout_handler = Module.new do + define_method :unbind do + finish = Time.now + EM.stop + end + end + + EM.run { + setup_timeout + EM.heartbeat_interval = 0.01 + EM.start_server("127.0.0.1", 12345) + EM.add_timer(0.01) { + start = Time.now + c = EM.connect("127.0.0.1", 12345, timeout_handler) + c.comm_inactivity_timeout = 0.02 + } + } + + assert_in_delta(0.02, (finish - start), 0.02) + end + else + warn "EM.comm_inactivity_timeout not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_comm_inactivity_timeout_not_implemented + assert true + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv4.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv4.rb new file mode 100644 index 0000000..bd11bbf --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv4.rb @@ -0,0 +1,95 @@ +require 'em_test_helper' + +class TestIPv4 < Test::Unit::TestCase + # Runs a TCP server in the local IPv4 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv4_tcp_local_server + omit_if(!Test::Unit::TestCase.public_ipv4?) + + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM::start_server(@@public_ipv4, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM::connect(@@public_ipv4, @local_port) do |c| + c.send_data "ipv4/tcp" + end + end + + assert_equal "ipv4/tcp", @@received_data + end + + # Runs a UDP server in the local IPv4 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv4_udp_local_server + omit_if(!Test::Unit::TestCase.public_ipv4?) + + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM::open_datagram_socket(@@public_ipv4, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM::open_datagram_socket(@@public_ipv4, next_port) do |c| + c.send_datagram "ipv4/udp", @@public_ipv4, @local_port + end + end + + assert_equal "ipv4/udp", @@received_data + end + + # Try to connect via TCP to an invalid IPv4. EM.connect should raise + # EM::ConnectionError. + def test_tcp_connect_to_invalid_ipv4 + omit_if(!Test::Unit::TestCase.public_ipv4?) + + invalid_ipv4 = "9.9:9" + + EM.run do + begin + error = nil + EM.connect(invalid_ipv4, 1234) + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + # Try to send a UDP datagram to an invalid IPv4. EM.send_datagram should raise + # EM::ConnectionError. + def test_udp_send_datagram_to_invalid_ipv4 + omit_if(!Test::Unit::TestCase.public_ipv4?) + + invalid_ipv4 = "9.9:9" + + EM.run do + begin + error = nil + EM.open_datagram_socket(@@public_ipv4, next_port) do |c| + c.send_datagram "hello", invalid_ipv4, 1234 + end + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv6.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv6.rb new file mode 100644 index 0000000..b52fef1 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ipv6.rb @@ -0,0 +1,107 @@ +require 'em_test_helper' + +class TestIPv6 < Test::Unit::TestCase + + if Test::Unit::TestCase.public_ipv6? + + # Runs a TCP server in the local IPv6 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv6_tcp_local_server + @@received_data = nil + @local_port = next_port + setup_timeout(2) + + EM.run do + EM.start_server(@@public_ipv6, @local_port) do |s| + def s.receive_data data + @@received_data = data + EM.stop + end + end + + EM::connect(@@public_ipv6, @local_port) do |c| + def c.unbind(reason) + warn "unbind: #{reason.inspect}" if reason # XXX at least find out why it failed + end + c.send_data "ipv6/tcp" + end + end + + assert_equal "ipv6/tcp", @@received_data + end + + # Runs a UDP server in the local IPv6 address, connects to it and sends a specific data. + # Timeout in 2 seconds. + def test_ipv6_udp_local_server + @@received_data = nil + @local_port = next_port + @@remote_ip = nil + setup_timeout(2) + + EM.run do + EM.open_datagram_socket(@@public_ipv6, @local_port) do |s| + def s.receive_data data + _port, @@remote_ip = Socket.unpack_sockaddr_in(get_peername) + @@received_data = data + EM.stop + end + end + + EM.open_datagram_socket(@@public_ipv6, next_port) do |c| + c.send_datagram "ipv6/udp", @@public_ipv6, @local_port + end + end + assert_equal @@remote_ip, @@public_ipv6 + assert_equal "ipv6/udp", @@received_data + end + + # Try to connect via TCP to an invalid IPv6. EM.connect should raise + # EM::ConnectionError. + def test_tcp_connect_to_invalid_ipv6 + invalid_ipv6 = "1:A" + + EM.run do + begin + error = nil + EM.connect(invalid_ipv6, 1234) + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + # Try to send a UDP datagram to an invalid IPv6. EM.send_datagram should raise + # EM::ConnectionError. + def test_udp_send_datagram_to_invalid_ipv6 + invalid_ipv6 = "1:A" + + EM.run do + begin + error = nil + EM.open_datagram_socket(@@public_ipv6, next_port) do |c| + c.send_datagram "hello", invalid_ipv6, 1234 + end + rescue => e + error = e + ensure + EM.stop + assert_equal EM::ConnectionError, (error && error.class) + end + end + end + + + else + warn "no IPv6 in this host, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests. + def test_ipv6_unavailable + assert true + end + + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_iterator.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_iterator.rb new file mode 100644 index 0000000..6ec4e46 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_iterator.rb @@ -0,0 +1,118 @@ +require 'em_test_helper' + +class TestIterator < Test::Unit::TestCase + + # By default, format the time with tenths-of-seconds. + # Some tests should ask for extra decimal places to ensure + # that delays between iterations will receive a changed time. + def get_time(n=1) + time = EM.current_time + time.strftime('%H:%M:%S.') + time.tv_usec.to_s[0, n] + end + + def test_default_concurrency + items = {} + list = 1..10 + EM.run { + EM::Iterator.new(list).each( proc {|num,iter| + time = get_time(3) + items[time] ||= [] + items[time] << num + EM::Timer.new(0.02) {iter.next} + }, proc {EM.stop}) + } + assert_equal(10, items.keys.size) + assert_equal(list.to_a.sort, items.values.flatten.sort) + end + + def test_default_concurrency_with_a_proc + items = {} + list = (1..10).to_a + original_list = list.dup + EM.run { + EM::Iterator.new(proc{list.pop || EM::Iterator::Stop}).each( proc {|num,iter| + time = get_time(3) + items[time] ||= [] + items[time] << num + EM::Timer.new(0.02) {iter.next} + }, proc {EM.stop}) + } + assert_equal(10, items.keys.size) + assert_equal(original_list.to_a.sort, items.values.flatten.sort) + end + + def test_concurrency_bigger_than_list_size + items = {} + list = [1,2,3] + EM.run { + EM::Iterator.new(list,10).each(proc {|num,iter| + time = get_time + items[time] ||= [] + items[time] << num + EM::Timer.new(1) {iter.next} + }, proc {EM.stop}) + } + assert_equal(1, items.keys.size) + assert_equal(list.to_a.sort, items.values.flatten.sort) + end + + def test_changing_concurrency_affects_active_iteration + items = {} + list = 1..25 + seen = 0 + EM.run { + i = EM::Iterator.new(list,1) + i.each(proc {|num,iter| + time = get_time + items[time] ||= [] + items[time] << num + if (seen += 1) == 5 + # The first 5 items will be distinct times + # The next 20 items will happen in 2 bursts + i.concurrency = 10 + end + EM::Timer.new(0.2) {iter.next} + }, proc {EM.stop}) + } + assert_in_delta(7, items.keys.size, 1) + assert_equal(list.to_a.sort, items.values.flatten.sort) + end + + def test_map + list = 100..150 + EM.run { + EM::Iterator.new(list).map(proc{ |num,iter| + EM.add_timer(0.01){ iter.return(num) } + }, proc{ |results| + assert_equal(list.to_a.size, results.size) + EM.stop + }) + } + end + + def test_inject + omit_if(windows?) + + list = %w[ pwd uptime uname date ] + EM.run { + EM::Iterator.new(list, 2).inject({}, proc{ |hash,cmd,iter| + EM.system(cmd){ |output,status| + hash[cmd] = status.exitstatus == 0 ? output.strip : nil + iter.return(hash) + } + }, proc{ |results| + assert_equal(results.keys.sort, list.sort) + EM.stop + }) + } + end + + def test_concurrency_is_0 + EM.run { + assert_raise ArgumentError do + EM::Iterator.new(1..5,0) + end + EM.stop + } + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_kb.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_kb.rb new file mode 100644 index 0000000..9c31f5f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_kb.rb @@ -0,0 +1,28 @@ +require 'em_test_helper' + +class TestKeyboardEvents < Test::Unit::TestCase + + module KbHandler + include EM::Protocols::LineText2 + def receive_line d + EM::stop if d == "STOP" + end + end + + # This test doesn't actually do anything useful but is here to + # illustrate the usage. If you removed the timer and ran this test + # by itself on a console, and then typed into the console, it would + # work. + # I don't know how to get the test harness to simulate actual keystrokes. + # When someone figures that out, then we can make this a real test. + # + def test_kb + omit_if(jruby?) + omit_if(!$stdout.tty?) # don't run the test unless it stands a chance of validity. + EM.run do + EM.open_keyboard KbHandler + EM::Timer.new(1) { EM.stop } + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_line_protocol.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_line_protocol.rb new file mode 100644 index 0000000..2067a72 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_line_protocol.rb @@ -0,0 +1,33 @@ +require 'em_test_helper' + +class TestLineProtocol < Test::Unit::TestCase + class LineProtocolTestClass + include EM::Protocols::LineProtocol + + def lines + @lines ||= [] + end + + def receive_line(line) + lines << line + end + end + + def setup + @proto = LineProtocolTestClass.new + end + + def test_simple_split_line + @proto.receive_data("this is") + assert_equal([], @proto.lines) + + @proto.receive_data(" a test\n") + assert_equal(["this is a test"], @proto.lines) + end + + def test_simple_lines + @proto.receive_data("aaa\nbbb\r\nccc\nddd") + assert_equal(%w(aaa bbb ccc), @proto.lines) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp.rb new file mode 100644 index 0000000..06c2685 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp.rb @@ -0,0 +1,155 @@ +require 'em_test_helper' + +class TestLineAndTextProtocol < Test::Unit::TestCase + + class TLP_LineBuffer < EM::P::LineAndTextProtocol + attr_reader :line_buffer + + def initialize + super + @line_buffer = [] + end + + def receive_line line + @line_buffer << line + end + end + + module StopClient + def set_receive_data(&blk) + @rdb = blk + end + + def receive_data data + @rdb.call(data) if @rdb + end + + def unbind + EM.add_timer(0.1) { EM.stop } + end + end + + def setup + @port = next_port + end + + def test_simple_lines + conn = nil + EM.run { + EM.start_server( "127.0.0.1", @port, TLP_LineBuffer ) do |c| + conn = c + end + setup_timeout + + EM.connect "127.0.0.1", @port, StopClient do |c| + c.send_data "aaa\nbbb\r\nccc\n" + c.close_connection_after_writing + end + } + assert_equal( %w(aaa bbb ccc), conn.line_buffer) + end + + #-------------------------------------------------------------------- + + class TLP_ErrorMessage < EM::P::LineAndTextProtocol + attr_reader :error_message + + def initialize + super + @error_message = [] + end + + def receive_line text + raise + end + + def receive_error text + @error_message << text + end + end + + def test_overlength_lines + conn = nil + EM.run { + EM.start_server( "127.0.0.1", @port, TLP_ErrorMessage ) do |c| + conn = c + end + setup_timeout + EM.connect "127.0.0.1", @port, StopClient do |c| + c.send_data "a" * (16*1024 + 1) + c.send_data "\n" + c.close_connection_after_writing + end + + } + assert_equal( ["overlength line"], conn.error_message ) + end + + + #-------------------------------------------------------------------- + + class LineAndTextTest < EM::P::LineAndTextProtocol + def receive_line line + if line =~ /content-length:\s*(\d+)/i + @content_length = $1.to_i + elsif line.length == 0 + set_binary_mode @content_length + end + end + def receive_binary_data text + send_data "received #{text.length} bytes" + close_connection_after_writing + end + end + + def test_lines_and_text + output = '' + EM.run { + EM.start_server( "127.0.0.1", @port, LineAndTextTest ) + setup_timeout + + EM.connect "127.0.0.1", @port, StopClient do |c| + c.set_receive_data { |data| output << data } + c.send_data "Content-length: 400\n" + c.send_data "\n" + c.send_data "A" * 400 + EM.add_timer(0.1) { c.close_connection_after_writing } + end + } + assert_equal( "received 400 bytes", output ) + end + + #-------------------------------------------------------------------- + + + class BinaryTextTest < EM::P::LineAndTextProtocol + def receive_line line + if line =~ /content-length:\s*(\d+)/i + set_binary_mode $1.to_i + else + raise "protocol error" + end + end + def receive_binary_data text + send_data "received #{text.length} bytes" + close_connection_after_writing + end + end + + def test_binary_text + output = '' + EM.run { + EM.start_server( "127.0.0.1", @port, BinaryTextTest ) + setup_timeout + + EM.connect "127.0.0.1", @port, StopClient do |c| + c.set_receive_data { |data| output << data } + c.send_data "Content-length: 10000\n" + c.send_data "A" * 10000 + EM.add_timer(0.1) { c.close_connection_after_writing } + end + } + assert_equal( "received 10000 bytes", output ) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp2.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp2.rb new file mode 100644 index 0000000..220fcbe --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ltp2.rb @@ -0,0 +1,332 @@ +require 'em_test_helper' + +# TODO!!! Need tests for overlength headers and text bodies. + +class TestLineText2 < Test::Unit::TestCase + + # Run each of these tests two ways: passing in the whole test-dataset in one chunk, + # and passing it in one character at a time. + + class Basic + include EM::Protocols::LineText2 + attr_reader :lines + def receive_line line + (@lines ||= []) << line + end + end + def test_basic + testdata = "Line 1\nLine 2\r\nLine 3\n" + + a = Basic.new + a.receive_data testdata + assert_equal( ["Line 1", "Line 2", "Line 3"], a.lines ) + + a = Basic.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( ["Line 1", "Line 2", "Line 3"], a.lines ) + end + + # The basic test above shows that extra newlines are chomped + # This test shows that newlines are preserved if the delimiter isn't \n + class PreserveNewlines + include EM::Protocols::LineText2 + attr_reader :lines + def initialize *args + super + @delim = "|" + set_delimiter @delim + end + def receive_line line + (@lines ||= []) << line + end + end + def test_preserve_newlines + a = PreserveNewlines.new + a.receive_data "aaa|bbb|ccc|\n|\r\n| \t ||" + assert_equal( ["aaa", "bbb", "ccc", "\n", "\r\n", " \t ", ""], a.lines ) + end + + class ChangeDelimiter + include EM::Protocols::LineText2 + attr_reader :lines + def initialize *args + super + @delim = "A" + set_delimiter @delim + end + def receive_line line + (@lines ||= []) << line + set_delimiter( @delim.succ! ) + end + end + + def test_change_delimiter + testdata = %Q(LineaALinebBLinecCLinedD) + + a = ChangeDelimiter.new + a.receive_data testdata + assert_equal( ["Linea", "Lineb", "Linec", "Lined"], a.lines ) + + a = ChangeDelimiter.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( ["Linea", "Lineb", "Linec", "Lined"], a.lines ) + end + + class RegexDelimiter + include EM::Protocols::LineText2 + attr_reader :lines + def initialize *args + super + @delim = /[A-D]/ + set_delimiter @delim + end + def receive_line line + (@lines ||= []) << line + end + end + + def test_regex_delimiter + testdata = %Q(LineaALinebBLinecCLinedD) + + a = RegexDelimiter.new + a.receive_data testdata + assert_equal( ["Linea", "Lineb", "Linec", "Lined"], a.lines ) + + a = RegexDelimiter.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( ["Linea", "Lineb", "Linec", "Lined"], a.lines ) + end + + #-- + # Test two lines followed by an empty line, ten bytes of binary data, then + # two more lines. + + class Binary + include EM::Protocols::LineText2 + attr_reader :lines, :body + def initialize *args + super + @lines = [] + @body = nil + end + def receive_line ln + if ln == "" + set_text_mode 10 + else + @lines << ln + end + end + def receive_binary_data data + @body = data + end + end + + def test_binary + testdata = %Q(Line 1 +Line 2 + +0000000000Line 3 +Line 4 +) + + a = Binary.new + a.receive_data testdata + assert_equal( ["Line 1", "Line 2", "Line 3", "Line 4"], a.lines) + assert_equal( "0000000000", a.body ) + + a = Binary.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( ["Line 1", "Line 2", "Line 3", "Line 4"], a.lines) + assert_equal( "0000000000", a.body ) + end + + + # Test unsized binary data. The expectation is that each chunk of it + # will be passed to us as it it received. + class UnsizedBinary + include EM::Protocols::LineText2 + attr_reader :n_calls, :body + def initialize *args + super + set_text_mode + end + def receive_binary_data data + @n_calls ||= 0 + @n_calls += 1 + (@body ||= "") << data + end + end + + def test_unsized_binary + testdata = "X\0" * 1000 + + a = UnsizedBinary.new + a.receive_data testdata + assert_equal( 1, a.n_calls ) + assert_equal( testdata, a.body ) + + a = UnsizedBinary.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( 2000, a.n_calls ) + assert_equal( testdata, a.body ) + end + + + # Test binary data with a "throw back" into line-mode. + class ThrowBack + include EM::Protocols::LineText2 + attr_reader :headers + def initialize *args + super + @headers = [] + @n_bytes = 0 + set_text_mode + end + def receive_binary_data data + wanted = 25 - @n_bytes + will_take = if data.length > wanted + data.length - wanted + else + data.length + end + @n_bytes += will_take + + if @n_bytes == 25 + set_line_mode( data[will_take..-1] ) + end + end + def receive_line ln + @headers << ln + end + end + def test_throw_back + testdata = "Line\n" * 10 + + a = ThrowBack.new + a.receive_data testdata + assert_equal( ["Line"] * 5, a.headers ) + + a = ThrowBack.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( ["Line"] * 5, a.headers ) + end + + # Test multi-character line delimiters. + # Also note that the test data has a "tail" with no delimiter, that will be + # discarded, but cf. the BinaryTail test. + # TODO!!! This test doesn't work in the byte-by-byte case. + class Multichar + include EM::Protocols::LineText2 + attr_reader :lines + def initialize *args + super + @lines = [] + set_delimiter "012" + end + def receive_line ln + @lines << ln + end + end + def test_multichar + testdata = "Line012Line012Line012Line" + + a = Multichar.new + a.receive_data testdata + assert_equal( ["Line"]*3, a.lines ) + + a = Multichar.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + # DOESN'T WORK in this case. Multi-character delimiters are broken. + #assert_equal( ["Line"]*3, a.lines ) + end + + # Test a binary "tail," when a sized binary transfer doesn't complete because + # of an unbind. We get a partial result. + class BinaryTail + include EM::Protocols::LineText2 + attr_reader :data + def initialize *args + super + @data = "" + set_text_mode 1000 + end + def receive_binary_data data + # we expect to get all the data in one chunk, even in the byte-by-byte case, + # because sized transfers by definition give us exactly one call to + # #receive_binary_data. + @data = data + end + end + def test_binary_tail + testdata = "0" * 500 + + a = BinaryTail.new + a.receive_data testdata + a.unbind + assert_equal( "0" * 500, a.data ) + + a = BinaryTail.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + a.unbind + assert_equal( "0" * 500, a.data ) + end + + + # Test an end-of-binary call. Arrange to receive binary data but don't bother counting it + # as it comes. Rely on getting receive_end_of_binary_data to signal the transition back to + # line mode. + # At the present time, this isn't strictly necessary with sized binary chunks because by + # definition we accumulate them and make exactly one call to receive_binary_data, but + # we may want to support a mode in the future that would break up large chunks into multiple + # calls. + class LazyBinary + include EM::Protocols::LineText2 + attr_reader :data, :end + def initialize *args + super + @data = "" + set_text_mode 1000 + end + def receive_binary_data data + # we expect to get all the data in one chunk, even in the byte-by-byte case, + # because sized transfers by definition give us exactly one call to + # #receive_binary_data. + @data = data + end + def receive_end_of_binary_data + @end = true + end + end + def test_receive_end_of_binary_data + testdata = "_" * 1000 + a = LazyBinary.new + testdata.length.times {|i| a.receive_data( testdata[i...i+1] ) } + assert_equal( "_" * 1000, a.data ) + assert( a.end ) + end + + + # This tests a bug fix in which calling set_text_mode failed when called + # inside receive_binary_data. + # + class BinaryPair + include EM::Protocols::LineText2 + attr_reader :sizes + def initialize *args + super + set_text_mode 1 + @sizes = [] + end + def receive_binary_data dt + @sizes << dt.length + set_text_mode( (dt.length == 1) ? 2 : 1 ) + end + end + def test_binary_pairs + test_data = "123" * 5 + a = BinaryPair.new + a.receive_data test_data + assert_equal( [1,2,1,2,1,2,1,2,1,2], a.sizes ) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_many_fds.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_many_fds.rb new file mode 100644 index 0000000..7c126dc --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_many_fds.rb @@ -0,0 +1,22 @@ +require 'em_test_helper' +require 'socket' + +class TestManyFDs < Test::Unit::TestCase + def setup + @port = next_port + end + + def test_connection_class_cache + mod = Module.new + a = nil + Process.setrlimit(Process::RLIMIT_NOFILE, 4096) rescue nil + EM.run { + EM.start_server '127.0.0.1', @port, mod + 1100.times do + a = EM.connect '127.0.0.1', @port, mod + assert_kind_of EM::Connection, a + end + EM.stop + } + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_next_tick.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_next_tick.rb new file mode 100644 index 0000000..9b60359 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_next_tick.rb @@ -0,0 +1,104 @@ +require 'em_test_helper' + +class TestNextTick < Test::Unit::TestCase + + def test_tick_arg + pr = proc {EM.stop} + EM.run { + EM.next_tick pr + } + assert true + end + + def test_tick_block + EM.run { + EM.next_tick {EM.stop} + } + assert true + end + + # This illustrates the solution to a long-standing problem. + # It's now possible to correctly nest calls to EM#run. + # See the source code commentary for EM#run for more info. + # + def test_run_run + EM.run { + EM.run { + EM.next_tick {EM.stop} + } + } + end + + def test_pre_run_queue + x = false + EM.next_tick { EM.stop; x = true } + EM.run { EM.add_timer(0.01) { EM.stop } } + assert x + end + + def test_cleanup_after_stop + x = true + EM.run{ + EM.next_tick{ + EM.stop + EM.next_tick{ x=false } + } + } + EM.run{ + EM.next_tick{ EM.stop } + } + assert x + end + + # We now support an additional parameter for EM#run. + # You can pass two procs to EM#run now. The first is executed as the normal + # run block. The second (if given) is scheduled for execution after the + # reactor loop completes. + # The reason for supporting this is subtle. There has always been an expectation + # that EM#run doesn't return until after the reactor loop ends. But now it's + # possible to nest calls to EM#run, which means that a nested call WILL + # RETURN. In order to write code that will run correctly either way, it's + # recommended to put any code which must execute after the reactor completes + # in the second parameter. + # + def test_run_run_2 + a = proc {EM.stop} + b = proc {assert true} + EM.run a, b + end + + + # This illustrates that EM#run returns when it's called nested. + # This isn't a feature, rather it's something to be wary of when writing code + # that must run correctly even if EM#run is called while a reactor is already + # running. + def test_run_run_3 + a = [] + EM.run { + EM.run proc {EM.stop}, proc {a << 2} + a << 1 + } + assert_equal( [1,2], a ) + end + + + def test_schedule_on_reactor_thread + x = false + EM.run do + EM.schedule { x = true } + EM.stop + end + assert x + end + + def test_schedule_from_thread + x = false + EM.run do + Thread.new { EM.schedule { x = true } }.join + assert !x + EM.next_tick { EM.stop } + end + assert x + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_object_protocol.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_object_protocol.rb new file mode 100644 index 0000000..b1287ea --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_object_protocol.rb @@ -0,0 +1,36 @@ +require 'em_test_helper' + +class TestObjectProtocol < Test::Unit::TestCase + module Server + include EM::P::ObjectProtocol + def post_init + send_object :hello=>'world' + end + def receive_object obj + $server = obj + EM.stop + end + end + + module Client + include EM::P::ObjectProtocol + def receive_object obj + $client = obj + send_object 'you_said'=>obj + end + end + + def setup + @port = next_port + end + + def test_send_receive + EM.run{ + EM.start_server "127.0.0.1", @port, Server + EM.connect "127.0.0.1", @port, Client + } + + assert($client == {:hello=>'world'}) + assert($server == {'you_said'=>{:hello=>'world'}}) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pause.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pause.rb new file mode 100644 index 0000000..d078a77 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pause.rb @@ -0,0 +1,107 @@ +require 'em_test_helper' + +class TestPause < Test::Unit::TestCase + if EM.respond_to? :pause_connection + def setup + @port = next_port + end + + def teardown + assert(!EM.reactor_running?) + end + + def test_pause_resume + server = nil + + s_rx = c_rx = 0 + + test_server = Module.new do + define_method :post_init do + server = self + end + + define_method :receive_data do |data| + s_rx += 1 + + EM.add_periodic_timer(0.01) { send_data 'hi' } + send_data 'hi' + + # pause server, now no outgoing data will actually + # be sent and no more incoming data will be received + pause + end + end + + test_client = Module.new do + def post_init + EM.add_periodic_timer(0.01) do + send_data 'hello' + end + end + + define_method :receive_data do |data| + c_rx += 1 + end + end + + EM.run do + EM.start_server "127.0.0.1", @port, test_server + EM.connect "127.0.0.1", @port, test_client + + EM.add_timer(0.05) do + assert_equal 1, s_rx + assert_equal 0, c_rx + assert server.paused? + + # resume server, queued outgoing and incoming data will be flushed + server.resume + + assert !server.paused? + + EM.add_timer(0.05) do + assert server.paused? + assert s_rx > 1 + assert c_rx > 0 + EM.stop + end + end + end + end + + def test_pause_in_receive_data + incoming = [] + + test_server = Module.new do + define_method(:receive_data) do |data| + incoming << data + pause + EM.add_timer(0.5){ close_connection } + end + define_method(:unbind) do + EM.stop + end + end + + buf = 'a' * 1024 + + EM.run do + EM.start_server "127.0.0.1", @port, test_server + cli = EM.connect "127.0.0.1", @port + 128.times do + cli.send_data buf + end + end + + assert_equal 1, incoming.size + assert incoming[0].bytesize > buf.bytesize + assert incoming[0].bytesize < buf.bytesize * 128 + end + else + warn "EM.pause_connection not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_pause_connection_not_implemented + assert true + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pending_connect_timeout.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pending_connect_timeout.rb new file mode 100644 index 0000000..a3f7fa4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pending_connect_timeout.rb @@ -0,0 +1,52 @@ +require 'em_test_helper' + +class TestPendingConnectTimeout < Test::Unit::TestCase + + if EM.respond_to? :get_pending_connect_timeout + def test_default + EM.run { + c = EM.connect("127.0.0.1", 54321) + assert_equal 20.0, c.pending_connect_timeout + EM.stop + } + end + + def test_set_and_get + EM.run { + c = EM.connect("127.0.0.1", 54321) + c.pending_connect_timeout = 2.5 + assert_equal 2.5, c.pending_connect_timeout + EM.stop + } + end + + def test_for_real + start, finish = nil + + timeout_handler = Module.new do + define_method :unbind do + finish = EM.current_time + EM.stop + end + end + + EM.run { + setup_timeout + EM.heartbeat_interval = 0.1 + start = EM.current_time + c = EM.connect('192.0.2.0', 54321, timeout_handler) + c.pending_connect_timeout = 0.2 + } + + assert_in_delta(0.2, (finish - start), 0.1) + end + else + warn "EM.pending_connect_timeout not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_pending_connect_timeout_not_implemented + assert true + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pool.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pool.rb new file mode 100644 index 0000000..f859a9d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pool.rb @@ -0,0 +1,196 @@ +require 'em_test_helper' + +class TestPool < Test::Unit::TestCase + def pool + @pool ||= EM::Pool.new + end + + def go + EM.run { yield } + end + + def stop + EM.stop + end + + def deferrable + @deferrable ||= EM::DefaultDeferrable.new + end + + def test_supports_more_work_than_resources + ran = false + go do + pool.perform do + ran = true + deferrable + end + stop + end + assert_equal false, ran + go do + pool.add :resource + stop + end + assert_equal true, ran + end + + def test_reques_resources_on_error + pooled_res, pooled_res2 = nil + pool.add :res + go do + pool.perform do |res| + pooled_res = res + deferrable + end + stop + end + deferrable.fail + go do + pool.perform do |res| + pooled_res2 = res + deferrable + end + stop + end + assert_equal :res, pooled_res + assert_equal pooled_res, pooled_res2 + end + + def test_supports_custom_on_error + eres = nil + pool.on_error do |res| + eres = res + end + performs = [] + pool.add :res + go do + pool.perform do |res| + performs << res + deferrable + end + pool.perform do |res| + performs << res + deferrable + end + deferrable.fail + stop + end + assert_equal :res, eres + # manual requeues required when error handler is installed: + assert_equal 1, performs.size + assert_equal :res, performs.first + end + + def test_catches_successful_deferrables + performs = [] + pool.add :res + go do + pool.perform { |res| performs << res; deferrable } + pool.perform { |res| performs << res; deferrable } + stop + end + assert_equal [:res], performs + deferrable.succeed + go { stop } + assert_equal [:res, :res], performs + end + + def test_prunes_locked_and_removed_resources + performs = [] + pool.add :res + deferrable.succeed + go do + pool.perform { |res| performs << res; pool.remove res; deferrable } + pool.perform { |res| performs << res; pool.remove res; deferrable } + stop + end + assert_equal [:res], performs + end + + # Contents is only to be used for inspection of the pool! + def test_contents + pool.add :res + assert_equal [:res], pool.contents + # Assert that modifying the contents list does not affect the pools + # contents. + pool.contents.delete(:res) + assert_equal [:res], pool.contents + end + + def test_contents_when_perform_errors_and_on_error_is_not_set + pool.add :res + assert_equal [:res], pool.contents + + pool.perform do |r| + d = EM::DefaultDeferrable.new + d.fail + d + end + + EM.run { EM.next_tick { EM.stop } } + + assert_equal [:res], pool.contents + end + + def test_contents_when_perform_errors_and_on_error_is_set + pool.add :res + res = nil + pool.on_error do |r| + res = r + end + assert_equal [:res], pool.contents + + pool.perform do |r| + d = EM::DefaultDeferrable.new + d.fail 'foo' + d + end + + EM.run { EM.next_tick { EM.stop } } + + assert_equal :res, res + assert_equal [], pool.contents + end + + def test_num_waiting + pool.add :res + assert_equal 0, pool.num_waiting + pool.perform { |r| EM::DefaultDeferrable.new } + assert_equal 0, pool.num_waiting + 10.times { pool.perform { |r| EM::DefaultDeferrable.new } } + EM.run { EM.next_tick { EM.stop } } + assert_equal 10, pool.num_waiting + end + + def test_exceptions_in_the_work_block_bubble_up_raise_and_fail_the_resource + pool.add :res + + res = nil + pool.on_error { |r| res = r } + pool.perform { raise 'boom' } + + assert_raises(RuntimeError) do + EM.run { EM.next_tick { EM.stop } } + end + + assert_equal [], pool.contents + assert_equal :res, res + end + + def test_removed_list_does_not_leak_on_errors + pool.add :res + + pool.on_error do |r| + # This is actually the wrong thing to do, and not required, but some users + # might do it. When they do, they would find that @removed would cause a + # slow leak. + pool.remove r + end + + pool.perform { d = EM::DefaultDeferrable.new; d.fail; d } + + EM.run { EM.next_tick { EM.stop } } + assert_equal [], pool.instance_variable_get(:@removed) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_process_watch.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_process_watch.rb new file mode 100644 index 0000000..6e0c49a --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_process_watch.rb @@ -0,0 +1,50 @@ +require 'em_test_helper' + +if EM.kqueue? + class TestProcessWatch < Test::Unit::TestCase + module ParentProcessWatcher + def process_forked + $forked = true + end + end + + module ChildProcessWatcher + def process_exited + $exited = true + end + def unbind + $unbind = true + EM.stop + end + end + + def setup + EM.kqueue = true + end + + def teardown + EM.kqueue = false + end + + def test_events + omit_if(rbx?) + omit_if(jruby?) + EM.run{ + # watch ourselves for a fork notification + EM.watch_process(Process.pid, ParentProcessWatcher) + $fork_pid = fork{ sleep } + child = EM.watch_process($fork_pid, ChildProcessWatcher) + $pid = child.pid + + EM.add_timer(0.2){ + Process.kill('TERM', $fork_pid) + } + } + + assert_equal($pid, $fork_pid) + assert($forked) + assert($exited) + assert($unbind) + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_processes.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_processes.rb new file mode 100644 index 0000000..dd03cf0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_processes.rb @@ -0,0 +1,128 @@ +require 'em_test_helper' + +class TestProcesses < Test::Unit::TestCase + + if !windows? && !jruby? + + # EM::DeferrableChildProcess is a sugaring of a common use-case + # involving EM::popen. + # Call the #open method on EM::DeferrableChildProcess, passing + # a command-string. #open immediately returns an EM::Deferrable + # object. It also schedules the forking of a child process, which + # will execute the command passed to #open. + # When the forked child terminates, the Deferrable will be signalled + # and execute its callbacks, passing the data that the child process + # wrote to stdout. + # + def test_deferrable_child_process + ls = "" + EM.run { + d = EM::DeferrableChildProcess.open( "ls -ltr" ) + d.callback {|data_from_child| + ls = data_from_child + EM.stop + } + } + assert( ls.length > 0) + end + + def setup + $out = nil + $status = nil + end + + def test_em_system + EM.run{ + EM.system('ls'){ |out,status| $out, $status = out, status; EM.stop } + } + + assert( $out.length > 0 ) + assert_equal(0, $status.exitstatus) + assert_kind_of(Process::Status, $status) + end + + def test_em_system_pid + $pids = [] + + EM.run{ + $pids << EM.system('echo hi', proc{ |out,status|$pids << status.pid; EM.stop }) + } + + assert_equal $pids[0], $pids[1] + end + + def test_em_system_with_proc + EM.run{ + EM.system('ls', proc{ |out,status| $out, $status = out, status; EM.stop }) + } + + assert( $out.length > 0 ) + assert_equal(0, $status.exitstatus) + assert_kind_of(Process::Status, $status) + end + + def test_em_system_with_two_procs + EM.run{ + EM.system('sh', proc{ |process| + process.send_data("echo hello\n") + process.send_data("exit\n") + }, proc{ |out,status| + $out = out + $status = status + EM.stop + }) + } + + assert_equal("hello\n", $out) + end + + def test_em_system_cmd_arguments + EM.run{ + EM.system('echo', '1', '2', 'version', proc{ |process| + }, proc{ |out,status| + $out = out + $status = status + EM.stop + }) + } + + assert_match(/1 2 version/i, $out) + end + + def test_em_system_spaced_arguments + EM.run{ + EM.system('ruby', '-e', 'puts "hello"', proc{ |out,status| + $out = out + EM.stop + }) + } + + assert_equal("hello\n", $out) + end + + def test_em_popen_pause_resume + c_rx = 0 + + test_client = Module.new do + define_method :receive_data do |data| + c_rx += 1 + pause + EM.add_timer(0.5) { EM.stop } + end + end + + EM.run do + EM.popen('echo 1', test_client) + end + + assert_equal 1, c_rx + end + else + warn "EM.popen not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_popen_unsupported + assert true + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_proxy_connection.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_proxy_connection.rb new file mode 100644 index 0000000..11c0fb4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_proxy_connection.rb @@ -0,0 +1,180 @@ +require 'em_test_helper' + +class TestProxyConnection < Test::Unit::TestCase + + if EM.respond_to?(:start_proxy) + module ProxyConnection + def initialize(client, request) + @client, @request = client, request + end + + def post_init + EM::enable_proxy(self, @client) + end + + def connection_completed + EM.next_tick { + send_data @request + } + end + + def proxy_target_unbound + $unbound_early = true + EM.stop + end + + def unbind + $proxied_bytes = self.get_proxied_bytes + @client.close_connection_after_writing + end + end + + module PartialProxyConnection + def initialize(client, request, length) + @client, @request, @length = client, request, length + end + + def post_init + EM::enable_proxy(self, @client, 0, @length) + end + + def receive_data(data) + $unproxied_data = data + @client.send_data(data) + end + + def connection_completed + EM.next_tick { + send_data @request + } + end + + def proxy_target_unbound + $unbound_early = true + EM.stop + end + + def proxy_completed + $proxy_completed = true + end + + def unbind + @client.close_connection_after_writing + end + end + + module Client + def connection_completed + send_data "EM rocks!" + end + + def receive_data(data) + $client_data = data + end + + def unbind + EM.stop + end + end + + module Client2 + include Client + def unbind; end + end + + module Server + def receive_data(data) + send_data "I know!" if data == "EM rocks!" + close_connection_after_writing + end + end + + module ProxyServer + def initialize port + @port = port + end + + def receive_data(data) + @proxy = EM.connect("127.0.0.1", @port, ProxyConnection, self, data) + end + end + + module PartialProxyServer + def initialize port + @port = port + end + + def receive_data(data) + EM.connect("127.0.0.1", @port, PartialProxyConnection, self, data, 1) + end + end + + module EarlyClosingProxy + def initialize port + @port = port + end + + def receive_data(data) + EM.connect("127.0.0.1", @port, ProxyConnection, self, data) + close_connection + end + end + + def setup + @port = next_port + @proxy_port = next_port + end + + def test_proxy_connection + EM.run { + EM.start_server("127.0.0.1", @port, Server) + EM.start_server("127.0.0.1", @proxy_port, ProxyServer, @port) + EM.connect("127.0.0.1", @proxy_port, Client) + } + + assert_equal("I know!", $client_data) + end + + def test_proxied_bytes + EM.run { + EM.start_server("127.0.0.1", @port, Server) + EM.start_server("127.0.0.1", @proxy_port, ProxyServer, @port) + EM.connect("127.0.0.1", @proxy_port, Client) + } + + assert_equal("I know!", $client_data) + assert_equal("I know!".bytesize, $proxied_bytes) + end + + def test_partial_proxy_connection + EM.run { + EM.start_server("127.0.0.1", @port, Server) + EM.start_server("127.0.0.1", @proxy_port, PartialProxyServer, @port) + EM.connect("127.0.0.1", @proxy_port, Client) + } + + assert_equal("I know!", $client_data) + assert_equal(" know!", $unproxied_data) + assert($proxy_completed) + end + + def test_early_close + $client_data = nil + EM.run { + EM.start_server("127.0.0.1", @port, Server) + EM.start_server("127.0.0.1", @proxy_port, EarlyClosingProxy, @port) + EM.connect("127.0.0.1", @proxy_port, Client2) + } + + assert($unbound_early) + end + else + warn "EM.start_proxy not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_start_proxy_not_implemented + assert !EM.respond_to?(:start_proxy) + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pure.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pure.rb new file mode 100644 index 0000000..8863a8d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_pure.rb @@ -0,0 +1,139 @@ +require 'em_test_helper' + +class TestPure < Test::Unit::TestCase + + def setup + @port = next_port + end + + # These tests are intended to exercise problems that come up in the + # pure-Ruby implementation. However, we DON'T constrain them such that + # they only run in pure-Ruby. These tests need to work identically in + # any implementation. + + #------------------------------------- + + # The EM reactor needs to run down open connections and release other resources + # when it stops running. Make sure this happens even if user code throws a Ruby + # exception. + # If exception handling is incorrect, the second test will fail with a no-bind error + # because the TCP server opened in the first test will not have been closed. + + def test_exception_handling_releases_resources + exception = Class.new(StandardError) + + 2.times do + assert_raises(exception) do + EM.run do + EM.start_server "127.0.0.1", @port + raise exception + end + end + end + end + + # Under some circumstances, the pure Ruby library would emit an Errno::ECONNREFUSED + # exception on certain kinds of TCP connect-errors. + # It's always been something of an open question whether EM should throw an exception + # in these cases but the defined answer has always been to catch it the unbind method. + # With a connect failure, the latter will always fire, but connection_completed will + # never fire. So even though the point is arguable, it's incorrect for the pure Ruby + # version to throw an exception. + module TestConnrefused + def unbind + EM.stop + end + def connection_completed + raise "should never get here" + end + end + + def test_connrefused + assert_nothing_raised do + EM.run { + setup_timeout(2) + EM.connect "127.0.0.1", @port, TestConnrefused + } + end + end + + # Make sure connection_completed gets called as expected with TCP clients. This is the + # opposite of test_connrefused. + # If the test fails, it will hang because EM.stop never gets called. + # + module TestConnaccepted + def connection_completed + EM.stop + end + end + def test_connaccepted + assert_nothing_raised do + EM.run { + EM.start_server "127.0.0.1", @port + EM.connect "127.0.0.1", @port, TestConnaccepted + setup_timeout(1) + } + end + end + + def test_reactor_running + a = false + EM.run { + a = EM.reactor_running? + EM.next_tick {EM.stop} + } + assert a + end + + module TLSServer + def post_init + start_tls + end + + def ssl_handshake_completed + $server_handshake_completed = true + end + + def receive_data(data) + $server_received_data = data + send_data(data) + end + end + + module TLSClient + def post_init + start_tls + end + + def ssl_handshake_completed + $client_handshake_completed = true + end + + def connection_completed + send_data('Hello World!') + end + + def receive_data(data) + $client_received_data = data + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + def test_start_tls + $client_handshake_completed, $server_handshake_completed = false, false + $client_received_data, $server_received_data = nil, nil + EM.run do + EM.start_server("127.0.0.1", 16789, TLSServer) + EM.connect("127.0.0.1", 16789, TLSClient) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + assert($client_received_data == "Hello World!") + assert($server_received_data == "Hello World!") + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_queue.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_queue.rb new file mode 100644 index 0000000..34278c0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_queue.rb @@ -0,0 +1,64 @@ +require 'em_test_helper' + +class TestEMQueue < Test::Unit::TestCase + def test_queue_push + s = 0 + EM.run do + q = EM::Queue.new + q.push(1) + EM.next_tick { s = q.size; EM.stop } + end + assert_equal 1, s + end + + def test_queue_pop + x,y,z = nil + EM.run do + q = EM::Queue.new + q.push(1,2,3) + q.pop { |v| x = v } + q.pop { |v| y = v } + q.pop { |v| z = v; EM.stop } + end + assert_equal 1, x + assert_equal 2, y + assert_equal 3, z + end + + def test_queue_reactor_thread + q = EM::Queue.new + + Thread.new { q.push(1,2,3) }.join + assert q.empty? + EM.run { EM.next_tick { EM.stop } } + assert_equal 3, q.size + + x = nil + Thread.new { q.pop { |v| x = v } }.join + assert_equal nil, x + EM.run { EM.next_tick { EM.stop } } + assert_equal 1, x + end + + def test_num_waiting + q = EM::Queue.new + many = 3 + many.times { q.pop {} } + EM.run { EM.next_tick { EM.stop } } + assert_equal many, q.num_waiting + end + + def test_big_queue + EM.run do + q = EM::Queue.new + 2000.times do |i| + q.push(*0..1000) + q.pop { |v| assert_equal v, i % 1001 } + end + q.pop do + assert_equal 1_999_999, q.size + EM.stop + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_resolver.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_resolver.rb new file mode 100644 index 0000000..58ed5f5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_resolver.rb @@ -0,0 +1,105 @@ +require 'em_test_helper' + +class TestResolver < Test::Unit::TestCase + def test_nameserver + assert_kind_of(String, EM::DNS::Resolver.nameserver) + end + + def test_nameservers + assert_kind_of(Array, EM::DNS::Resolver.nameservers) + end + + def test_hosts + assert_kind_of(Hash, EM::DNS::Resolver.hosts) + + # Make sure that blank or comment lines are skipped + refute(EM::DNS::Resolver.hosts.include? nil) + end + + def test_a + pend('FIXME: this test is broken on Windows') if windows? + + EM.run { + d = EM::DNS::Resolver.resolve "example.com" + d.errback { assert false } + d.callback { |r| + assert r + EM.stop + } + } + end + + def test_bad_host + EM.run { + d = EM::DNS::Resolver.resolve "asdfasasdf" + d.callback { assert false } + d.errback { assert true; EM.stop } + } + end + + def test_garbage + assert_raises( ArgumentError ) { + EM.run { + EM::DNS::Resolver.resolve 123 + } + } + end + + # There isn't a public DNS entry like 'example.com' with an A rrset + def test_a_pair + pend('FIXME: this test is broken on Windows') if windows? + + EM.run { + d = EM::DNS::Resolver.resolve "yahoo.com" + d.errback { |err| assert false, "failed to resolve yahoo.com: #{err}" } + d.callback { |r| + assert_kind_of(Array, r) + assert r.size > 1, "returned #{r.size} results: #{r.inspect}" + EM.stop + } + } + end + + def test_localhost + pend('FIXME: this test is broken on Windows') if windows? + + EM.run { + d = EM::DNS::Resolver.resolve "localhost" + d.errback { assert false } + d.callback { |r| + assert_include(["127.0.0.1", "::1"], r.first) + assert_kind_of(Array, r) + + EM.stop + } + } + end + + def test_timer_cleanup + pend('FIXME: this test is broken on Windows') if windows? + + EM.run { + d = EM::DNS::Resolver.resolve "example.com" + d.errback { |err| assert false, "failed to resolve example.com: #{err}" } + d.callback { |r| + # This isn't a great test, but it's hard to get more canonical + # confirmation that the timer is cancelled + assert_nil(EM::DNS::Resolver.socket.instance_variable_get(:@timer)) + + EM.stop + } + } + end + + def test_failure_timer_cleanup + EM.run { + d = EM::DNS::Resolver.resolve "asdfasdf" + d.callback { assert false } + d.errback { + assert_nil(EM::DNS::Resolver.socket.instance_variable_get(:@timer)) + + EM.stop + } + } + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_running.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_running.rb new file mode 100644 index 0000000..693b390 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_running.rb @@ -0,0 +1,14 @@ +require 'em_test_helper' + +class TestRunning < Test::Unit::TestCase + def test_running + assert_equal( false, EM::reactor_running? ) + r = false + EM.run { + r = EM::reactor_running? + EM.stop + } + assert_equal( true, r ) + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sasl.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sasl.rb new file mode 100644 index 0000000..e80f657 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sasl.rb @@ -0,0 +1,47 @@ +require 'em_test_helper' + + +class TestSASL < Test::Unit::TestCase + + # SASL authentication is usually done with UNIX-domain sockets, but + # we'll use TCP so this test will work on Windows. As far as the + # protocol handlers are concerned, there's no difference. + + TestUser,TestPsw = "someone", "password" + + class SaslServer < EM::Connection + include EM::Protocols::SASLauth + def validate usr, psw, sys, realm + usr == TestUser and psw == TestPsw + end + end + + class SaslClient < EM::Connection + include EM::Protocols::SASLauthclient + end + + def setup + @port = next_port + end + + def test_sasl + resp = nil + EM.run { + EM.start_server( "127.0.0.1", @port, SaslServer ) + + c = EM.connect( "127.0.0.1", @port, SaslClient ) + d = c.validate?( TestUser, TestPsw ) + d.timeout 1 + d.callback { + resp = true + EM.stop + } + d.errback { + resp = false + EM.stop + } + } + assert_equal( true, resp ) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_send_file.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_send_file.rb new file mode 100644 index 0000000..a784b50 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_send_file.rb @@ -0,0 +1,217 @@ +require 'em_test_helper' +require 'tempfile' + +class TestSendFile < Test::Unit::TestCase + + if EM.respond_to?(:send_file_data) + module TestModule + def initialize filename + @filename = filename + end + + def post_init + send_file_data @filename + close_connection_after_writing + end + end + + module TestClient + def data_to(&blk) + @data_to = blk + end + + def receive_data(data) + @data_to.call(data) if @data_to + end + + def unbind + EM.stop + end + end + + def setup + @file = Tempfile.new("em_test_file") + @filename = @file.path + @port = next_port + end + + def test_send_file + File.open( @filename, "w" ) {|f| + f << ("A" * 5000) + } + + data = '' + + EM.run { + EM.start_server "127.0.0.1", @port, TestModule, @filename + setup_timeout + + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + + assert_equal( "A" * 5000, data ) + end + + # EM::Connection#send_file_data has a strict upper limit on the filesize it will work with. + def test_send_large_file + File.open( @filename, "w" ) {|f| + f << ("A" * 1000000) + } + + data = '' + + assert_raises(RuntimeError) { + EM.run { + EM.start_server "127.0.0.1", @port, TestModule, @filename + setup_timeout + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + } + end + + module StreamTestModule + def initialize filename + @filename = filename + end + + def post_init + EM::Deferrable.future( stream_file_data(@filename)) { + close_connection_after_writing + } + end + end + + module ChunkStreamTestModule + def initialize filename + @filename = filename + end + + def post_init + EM::Deferrable.future( stream_file_data(@filename, :http_chunks=>true)) { + close_connection_after_writing + } + end + end + + def test_stream_file_data + File.open( @filename, "w" ) {|f| + f << ("A" * 1000) + } + + data = '' + + EM.run { + EM.start_server "127.0.0.1", @port, StreamTestModule, @filename + setup_timeout + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + + assert_equal( "A" * 1000, data ) + end + + def test_stream_chunked_file_data + File.open( @filename, "w" ) {|f| + f << ("A" * 1000) + } + + data = '' + + EM.run { + EM.start_server "127.0.0.1", @port, ChunkStreamTestModule, @filename + setup_timeout + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + + assert_equal( "3e8\r\n#{"A" * 1000}\r\n0\r\n\r\n", data ) + end + + module BadFileTestModule + def initialize filename + @filename = filename + end + + def post_init + de = stream_file_data( @filename+".wrong" ) + de.errback {|msg| + send_data msg + close_connection_after_writing + } + end + end + def test_stream_bad_file + data = '' + EM.run { + EM.start_server "127.0.0.1", @port, BadFileTestModule, @filename + setup_timeout(5) + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + + assert_equal( "file not found", data ) + end + else + warn "EM.send_file_data not implemented, skipping tests in #{__FILE__}" + + # Because some rubies will complain if a TestCase class has no tests + def test_em_send_file_data_not_implemented + assert !EM.respond_to?(:send_file_data) + end + end + + begin + require 'fastfilereaderext' + + def test_stream_large_file_data + File.open( @filename, "w" ) {|f| + f << ("A" * 10000) + } + + data = '' + + EM.run { + EM.start_server "127.0.0.1", @port, StreamTestModule, @filename + setup_timeout + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + + assert_equal( "A" * 10000, data ) + end + + def test_stream_large_chunked_file_data + File.open( @filename, "w" ) {|f| + f << ("A" * 100000) + } + + data = '' + + EM.run { + EM.start_server "127.0.0.1", @port, ChunkStreamTestModule, @filename + setup_timeout + EM.connect "127.0.0.1", @port, TestClient do |c| + c.data_to { |d| data << d } + end + } + + expected = [ + "4000\r\n#{"A" * 16384}\r\n" * 6, + "6a0\r\n#{"A" * 0x6a0}\r\n", + "0\r\n\r\n" + ].join + assert_equal( expected, data ) + end + rescue LoadError + warn "require 'fastfilereaderext' failed, skipping tests in #{__FILE__}" + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_servers.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_servers.rb new file mode 100644 index 0000000..2a17050 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_servers.rb @@ -0,0 +1,33 @@ +require 'em_test_helper' +require 'socket' + +class TestServers < Test::Unit::TestCase + + def setup + @port = next_port + end + + def server_alive? + port_in_use?(@port) + end + + def run_test_stop_server + EM.run { + sig = EM.start_server("127.0.0.1", @port) + assert server_alive?, "Server didn't start" + EM.stop_server sig + # Give the server some time to shutdown. + EM.add_timer(0.1) { + assert !server_alive?, "Server didn't stop" + EM.stop + } + } + end + + def test_stop_server + assert !server_alive?, "Port already in use" + 2.times { run_test_stop_server } + assert !server_alive?, "Servers didn't stop" + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_shutdown_hooks.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_shutdown_hooks.rb new file mode 100644 index 0000000..b0e0c5c --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_shutdown_hooks.rb @@ -0,0 +1,23 @@ +require 'em_test_helper' + +class TestShutdownHooks < Test::Unit::TestCase + def test_shutdown_hooks + r = false + EM.run { + EM.add_shutdown_hook { r = true } + EM.stop + } + assert_equal( true, r ) + end + + def test_hook_order + r = [] + EM.run { + EM.add_shutdown_hook { r << 2 } + EM.add_shutdown_hook { r << 1 } + EM.stop + } + assert_equal( [1, 2], r ) + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpclient.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpclient.rb new file mode 100644 index 0000000..71ed584 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpclient.rb @@ -0,0 +1,75 @@ +require 'em_test_helper' + +class TestSmtpClient < Test::Unit::TestCase + + Localhost = "127.0.0.1" + Localport = 9801 + + def setup + end + + def teardown + end + + def test_a + # No real tests until we have a server implementation to test against. + # This is what the call looks like, though: + err = nil + EM.run { + d = EM::Protocols::SmtpClient.send :domain=>"example.com", + :host=>Localhost, + :port=>Localport, # optional, defaults 25 + :starttls=>true, + :from=>"sender@example.com", + :to=> ["to_1@example.com", "to_2@example.com"], + :header=> {"Subject" => "This is a subject line"}, + :body=> "This is the body of the email", + :verbose=>true + d.errback {|e| + err = e + EM.stop + } + } + assert(err) + end + + def test_content + err = nil + EM.run { + d = EM::Protocols::SmtpClient.send :domain=>"example.com", + :host=>Localhost, + :port=>Localport, # optional, defaults 25 + :starttls=>true, + :from=>"sender@example.com", + :to=> ["to_1@example.com", "to_2@example.com"], + :content => ["Subject: xxx\r\n\r\ndata\r\n.\r\n"], + :verbose=>true + d.errback {|e| + err = e + EM.stop + } + } + assert(err) + end + + + EM::Protocols::SmtpClient.__send__(:public, :escape_leading_dots) + + def test_escaping + smtp = EM::Protocols::SmtpClient.new :domain => "example.com" + + expectations = { + "Hello\r\n" => "Hello\r\n", + "\r\n.whatever\r\n" => "\r\n..whatever\r\n", + "\r\n.\r\n" => "\r\n..\r\n", + "\r\n.\r\n." => "\r\n..\r\n..", + ".\r\n.\r\n" => "..\r\n..\r\n", + "..\r\n" => "...\r\n" + } + + expectations.each do |input, output| + assert_equal output, smtp.escape_leading_dots(input) + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpserver.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpserver.rb new file mode 100644 index 0000000..18c50fe --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_smtpserver.rb @@ -0,0 +1,57 @@ +require 'em_test_helper' + +class TestSmtpServer < Test::Unit::TestCase + + # Don't test on port 25. It requires superuser and there's probably + # a mail server already running there anyway. + Localhost = "127.0.0.1" + Localport = 25001 + + # This class is an example of what you need to write in order + # to implement a mail server. You override the methods you are + # interested in. Some, but not all, of these are illustrated here. + # + class Mailserver < EM::Protocols::SmtpServer + + attr_reader :my_msg_body, :my_sender, :my_recipients + + def initialize *args + super + end + def receive_sender sender + @my_sender = sender + #p sender + true + end + def receive_recipient rcpt + @my_recipients ||= [] + @my_recipients << rcpt + true + end + def receive_data_chunk c + @my_msg_body = c.last + end + def connection_ended + EM.stop + end + end + + def test_mail + c = nil + EM.run { + EM.start_server( Localhost, Localport, Mailserver ) {|conn| c = conn} + EM::Timer.new(2) {EM.stop} # prevent hanging the test suite in case of error + EM::Protocols::SmtpClient.send :host=>Localhost, + :port=>Localport, + :domain=>"bogus", + :from=>"me@example.com", + :to=>"you@example.com", + :header=> {"Subject"=>"Email subject line", "Reply-to"=>"me@example.com"}, + :body=>"Not much of interest here." + + } + assert_equal( "Not much of interest here.", c.my_msg_body ) + assert_equal( "", c.my_sender ) + assert_equal( [""], c.my_recipients ) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sock_opt.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sock_opt.rb new file mode 100644 index 0000000..60fba35 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_sock_opt.rb @@ -0,0 +1,54 @@ +require 'em_test_helper' +require 'socket' + +class TestSockOpt < Test::Unit::TestCase + def setup + assert(!EM.reactor_running?) + @port = next_port + end + + def teardown + assert(!EM.reactor_running?) + end + + def test_set_sock_opt + omit_if(windows?) + omit_if(!EM.respond_to?(:set_sock_opt)) + + val = nil + test_module = Module.new do + define_method :post_init do + val = set_sock_opt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true + EM.stop + end + end + + EM.run do + EM.start_server '127.0.0.1', @port + EM.connect '127.0.0.1', @port, test_module + end + + assert_equal 0, val + end + + def test_get_sock_opt + omit_if(windows?) + omit_if(!EM.respond_to?(:set_sock_opt)) + + val = nil + test_module = Module.new do + define_method :connection_completed do + val = get_sock_opt Socket::SOL_SOCKET, Socket::SO_ERROR + EM.stop + end + end + + EM.run do + EM.start_server '127.0.0.1', @port + EM.connect '127.0.0.1', @port, test_module + end + + assert_equal "\0\0\0\0", val + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_spawn.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_spawn.rb new file mode 100644 index 0000000..ab0a92f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_spawn.rb @@ -0,0 +1,293 @@ + +require 'em_test_helper' + + + +class TestSpawn < Test::Unit::TestCase + + # Spawn a process that simply stops the reactor. + # Assert that the notification runs after the block that calls it. + # + def test_stop + x = nil + EM.run { + s = EM.spawn {EM.stop} + s.notify + x = true + } + assert x + end + + + # Pass a parameter to a spawned process. + # + def test_parms + val = 5 + EM.run { + s = EM.spawn {|v| val *= v; EM.stop} + s.notify 3 + } + assert_equal( 15, val ) + end + + # Pass multiple parameters to a spawned process. + # + def test_multiparms + val = 5 + EM.run { + s = EM.spawn {|v1,v2| val *= (v1 + v2); EM.stop} + s.notify 3,4 + } + assert_equal( 35, val ) + end + + + # This test demonstrates that a notification does not happen immediately, + # but rather is scheduled sometime after the current code path completes. + # + def test_race + x = 0 + EM.run { + s = EM.spawn {x *= 2; EM.stop} + s.notify + x = 2 + } + assert_equal( 4, x) + end + + + # Spawn a process and notify it 25 times to run fibonacci + # on a pair of global variables. + # + def test_fibonacci + x = 1 + y = 1 + EM.run { + s = EM.spawn {x,y = y,x+y} + 25.times {s.notify} + + t = EM.spawn {EM.stop} + t.notify + } + assert_equal( 121393, x) + assert_equal( 196418, y) + end + + # This one spawns 25 distinct processes, and notifies each one once, + # rather than notifying a single process 25 times. + # + def test_another_fibonacci + x = 1 + y = 1 + EM.run { + 25.times { + s = EM.spawn {x,y = y,x+y} + s.notify + } + + t = EM.spawn {EM.stop} + t.notify + } + assert_equal( 121393, x) + assert_equal( 196418, y) + end + + + # Make a chain of processes that notify each other in turn + # with intermediate fibonacci results. The final process in + # the chain stops the loop and returns the result. + # + def test_fibonacci_chain + a,b = nil + + EM.run { + nextpid = EM.spawn {|x,y| + a,b = x,y + EM.stop + } + + 25.times { + n = nextpid + nextpid = EM.spawn {|x,y| n.notify( y, x+y )} + } + + nextpid.notify( 1, 1 ) + } + + assert_equal( 121393, a) + assert_equal( 196418, b) + end + + + # EM#yield gives a spawed process to yield control to other processes + # (in other words, to stop running), and to specify a different code block + # that will run on its next notification. + # + def test_yield + a = 0 + EM.run { + n = EM.spawn { + a += 10 + EM.yield { + a += 20 + EM.yield { + a += 30 + EM.stop + } + } + } + n.notify + n.notify + n.notify + } + assert_equal( 60, a ) + end + + # EM#yield_and_notify behaves like EM#yield, except that it also notifies the + # yielding process. This may sound trivial, since the yield block will run very + # shortly after with no action by the program, but this actually can be very useful, + # because it causes the reactor core to execute once before the yielding process + # gets control back. So it can be used to allow heavily-used network connections + # to clear buffers, or allow other processes to process their notifications. + # + # Notice in this test code that only a simple notify is needed at the bottom + # of the initial block. Even so, all of the yielded blocks will execute. + # + def test_yield_and_notify + a = 0 + EM.run { + n = EM.spawn { + a += 10 + EM.yield_and_notify { + a += 20 + EM.yield_and_notify { + a += 30 + EM.stop + } + } + } + n.notify + } + assert_equal( 60, a ) + end + + # resume is an alias for notify. + # + def test_resume + EM.run { + n = EM.spawn {EM.stop} + n.resume + } + assert true + end + + # run is an idiomatic alias for notify. + # + def test_run + EM.run { + (EM.spawn {EM.stop}).run + } + assert true + end + + + # Clones the ping-pong example from the Erlang tutorial, in much less code. + # Illustrates that a spawned block executes in the context of a SpawnableObject. + # (Meaning, we can pass self as a parameter to another process that can then + # notify us.) + # + def test_ping_pong + n_pongs = 0 + EM.run { + pong = EM.spawn {|x, ping| + n_pongs += 1 + ping.notify( x-1 ) + } + ping = EM.spawn {|x| + if x > 0 + pong.notify x, self + else + EM.stop + end + } + ping.notify 3 + } + assert_equal( 3, n_pongs ) + end + + # Illustrates that you can call notify inside a notification, and it will cause + # the currently-executing process to be re-notified. Of course, the new notification + # won't run until sometime after the current one completes. + # + def test_self_notify + n = 0 + EM.run { + pid = EM.spawn {|x| + if x > 0 + n += x + notify( x-1 ) + else + EM.stop + end + } + pid.notify 3 + } + assert_equal( 6, n ) + end + + + # Illustrates that the block passed to #spawn executes in the context of a + # SpawnedProcess object, NOT in the local context. This can often be deceptive. + # + class BlockScopeTest + attr_reader :var + def run + # The following line correctly raises a NameError. + # The problem is that the programmer expected the spawned block to + # execute in the local context, but it doesn't. + # + # (EM.spawn { do_something }).notify ### NO! BAD! + + + + # The following line correctly passes self as a parameter to the + # notified process. + # + (EM.spawn {|obj| obj.do_something }).notify(self) + + + + # Here's another way to do it. This works because "myself" is bound + # in the local scope, unlike "self," so the spawned block sees it. + # + myself = self + (EM.spawn { myself.do_something }).notify + + + + # And we end the loop. + # This is a tangential point, but observe that #notify never blocks. + # It merely appends a message to the internal queue of a spawned process + # and returns. As it turns out, the reactor processes notifications for ALL + # spawned processes in the order that #notify is called. So there is a + # reasonable expectation that the process which stops the reactor will + # execute after the previous ones in this method. HOWEVER, this is NOT + # a documented behavior and is subject to change. + # + (EM.spawn {EM.stop}).notify + end + def do_something + @var ||= 0 + @var += 100 + end + end + + def test_block_scope + bs = BlockScopeTest.new + EM.run { + bs.run + } + assert_equal( 200, bs.var ) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_args.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_args.rb new file mode 100644 index 0000000..d337628 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_args.rb @@ -0,0 +1,78 @@ +require "test/unit" +require 'tempfile' + +require 'em_test_helper' + +module EM + def self._set_mocks + class < priv_file) + end + assert_raises(EM::FileNotFoundException) do + conn.start_tls(:cert_chain_file => cert_file) + end + assert_raises(EM::FileNotFoundException) do + conn.start_tls(:private_key_file => priv_file, :cert_chain_file => cert_file) + end + end + + def test_tls_params_file_does_exist + priv_file = Tempfile.new('em_test') + cert_file = Tempfile.new('em_test') + priv_file_path = priv_file.path + cert_file_path = cert_file.path + conn = EM::Connection.new('foo') + params = {:private_key_file => priv_file_path, :cert_chain_file => cert_file_path} + begin + conn.start_tls params + rescue Object + assert(false, 'should not have raised an exception') + end + end +end if EM.ssl? diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_dhparam.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_dhparam.rb new file mode 100644 index 0000000..85f52d2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_dhparam.rb @@ -0,0 +1,84 @@ +require 'em_test_helper' + +class TestSslDhParam < Test::Unit::TestCase + def setup + $dir = File.dirname(File.expand_path(__FILE__)) + '/' + $dhparam_file = File.join($dir, 'dhparam.pem') + end + + module Client + def post_init + start_tls + end + + def ssl_handshake_completed + $client_handshake_completed = true + $client_cipher_name = get_cipher_name + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + module Server + def post_init + start_tls(:dhparam => $dhparam_file, :cipher_list => "DHE,EDH") + end + + def ssl_handshake_completed + $server_handshake_completed = true + $server_cipher_name = get_cipher_name + end + end + + module NoDhServer + def post_init + start_tls(:cipher_list => "DHE,EDH") + end + + def ssl_handshake_completed + $server_handshake_completed = true + $server_cipher_name = get_cipher_name + end + end + + def test_no_dhparam + omit_unless(EM.ssl?) + omit_if(EM.library_type == :pure_ruby) # DH will work with defaults + omit_if(rbx?) + + $client_handshake_completed, $server_handshake_completed = false, false + $server_cipher_name, $client_cipher_name = nil, nil + + EM.run { + EM.start_server("127.0.0.1", 16784, NoDhServer) + EM.connect("127.0.0.1", 16784, Client) + } + + assert(!$client_handshake_completed) + assert(!$server_handshake_completed) + end + + def test_dhparam + omit_unless(EM.ssl?) + omit_if(rbx?) + + $client_handshake_completed, $server_handshake_completed = false, false + $server_cipher_name, $client_cipher_name = nil, nil + + EM.run { + EM.start_server("127.0.0.1", 16784, Server) + EM.connect("127.0.0.1", 16784, Client) + } + + assert($client_handshake_completed) + assert($server_handshake_completed) + + assert($client_cipher_name.length > 0) + assert_equal($client_cipher_name, $server_cipher_name) + + assert_match(/^(DHE|EDH)/, $client_cipher_name) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_ecdh_curve.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_ecdh_curve.rb new file mode 100644 index 0000000..8dc167b --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_ecdh_curve.rb @@ -0,0 +1,80 @@ +require 'em_test_helper' + +class TestSslEcdhCurve < Test::Unit::TestCase + module Client + def post_init + start_tls + end + + def ssl_handshake_completed + $client_handshake_completed = true + $client_cipher_name = get_cipher_name + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + module Server + def post_init + start_tls(:ecdh_curve => "prime256v1", :cipher_list => "ECDH") + end + + def ssl_handshake_completed + $server_handshake_completed = true + $server_cipher_name = get_cipher_name + end + end + + module NoCurveServer + def post_init + start_tls(:cipher_list => "ECDH") + end + + def ssl_handshake_completed + $server_handshake_completed = true + $server_cipher_name = get_cipher_name + end + end + + def test_no_ecdh_curve + omit_unless(EM.ssl?) + omit_if(rbx?) + + $client_handshake_completed, $server_handshake_completed = false, false + + EM.run { + EM.start_server("127.0.0.1", 16784, NoCurveServer) + EM.connect("127.0.0.1", 16784, Client) + } + + assert(!$client_handshake_completed) + assert(!$server_handshake_completed) + end + + def test_ecdh_curve + omit_unless(EM.ssl?) + omit_if(EM.library_type == :pure_ruby && RUBY_VERSION < "2.3.0") + omit_if(rbx?) + + $client_handshake_completed, $server_handshake_completed = false, false + $server_cipher_name, $client_cipher_name = nil, nil + + EM.run { + EM.start_server("127.0.0.1", 16784, Server) + EM.connect("127.0.0.1", 16784, Client) + } + + assert($client_handshake_completed) + assert($server_handshake_completed) + + assert($client_cipher_name.length > 0) + assert_equal($client_cipher_name, $server_cipher_name) + + assert_match(/^(AECDH|ECDHE)/, $client_cipher_name) + end + + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_extensions.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_extensions.rb new file mode 100644 index 0000000..0610ba8 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_extensions.rb @@ -0,0 +1,49 @@ +require 'em_test_helper' + +require 'socket' +require 'openssl' + +if EM.ssl? + class TestSslExtensions < Test::Unit::TestCase + + module Client + def ssl_handshake_completed + $client_handshake_completed = true + close_connection + end + + def unbind + EM.stop_event_loop + end + + def post_init + start_tls(:ssl_version => :tlsv1, :sni_hostname => 'example.com') + end + end + + module Server + def ssl_handshake_completed + $server_handshake_completed = true + $server_sni_hostname = get_sni_hostname + end + + def post_init + start_tls(:ssl_version => :TLSv1) + end + end + + def test_tlsext_sni_hostname + $server_handshake_completed = false + + EM.run do + EM.start_server("127.0.0.1", 16784, Server) + EM.connect("127.0.0.1", 16784, Client) + end + + assert($server_handshake_completed) + assert_equal('example.com', $server_sni_hostname) + end + end +else + warn "EM built without SSL support, skipping tests in #{__FILE__}" +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_methods.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_methods.rb new file mode 100644 index 0000000..c2e5744 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_methods.rb @@ -0,0 +1,65 @@ +require 'em_test_helper' + +class TestSSLMethods < Test::Unit::TestCase + + module ServerHandler + def post_init + start_tls + end + + def ssl_handshake_completed + $server_called_back = true + $server_cert_value = get_peer_cert + $server_cipher_bits = get_cipher_bits + $server_cipher_name = get_cipher_name + $server_cipher_protocol = get_cipher_protocol + end + end + + module ClientHandler + def post_init + start_tls + end + + def ssl_handshake_completed + $client_called_back = true + $client_cert_value = get_peer_cert + $client_cipher_bits = get_cipher_bits + $client_cipher_name = get_cipher_name + $client_cipher_protocol = get_cipher_protocol + EM.stop_event_loop + end + end + + def test_ssl_methods + omit_unless(EM.ssl?) + omit_if(rbx?) + $server_called_back, $client_called_back = false, false + $server_cert_value, $client_cert_value = nil, nil + $server_cipher_bits, $client_cipher_bits = nil, nil + $server_cipher_name, $client_cipher_name = nil, nil + $server_cipher_protocol, $client_cipher_protocol = nil, nil + + EM.run { + EM.start_server("127.0.0.1", 9999, ServerHandler) + EM.connect("127.0.0.1", 9999, ClientHandler) + } + + assert($server_called_back) + assert($client_called_back) + + assert($server_cert_value.is_a?(NilClass)) + assert($client_cert_value.is_a?(String)) + + assert($client_cipher_bits > 0) + assert_equal($client_cipher_bits, $server_cipher_bits) + + assert($client_cipher_name.length > 0) + assert_match(/AES/, $client_cipher_name) + assert_equal($client_cipher_name, $server_cipher_name) + + assert_match(/TLS/, $client_cipher_protocol) + assert_equal($client_cipher_protocol, $server_cipher_protocol) + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_protocols.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_protocols.rb new file mode 100644 index 0000000..bcb6824 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_protocols.rb @@ -0,0 +1,246 @@ +require 'em_test_helper' + +require 'socket' +require 'openssl' + +if EM.ssl? + class TestSslProtocols < Test::Unit::TestCase + + module Client + def ssl_handshake_completed + $client_handshake_completed = true + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + module Server + def ssl_handshake_completed + $server_handshake_completed = true + end + end + + module ClientAny + include Client + def post_init + start_tls(:ssl_version => %w(sslv2 sslv3 tlsv1 tlsv1_1 tlsv1_2)) + end + end + + module ClientDefault + include Client + def post_init + start_tls + end + end + + module ClientSSLv3 + include Client + def post_init + start_tls(:ssl_version => %w(SSLv3)) + end + end + + module ServerSSLv3 + include Server + def post_init + start_tls(:ssl_version => %w(SSLv3)) + end + end + + module ServerTLSv1CaseInsensitive + include Server + def post_init + start_tls(:ssl_version => %w(tlsv1)) + end + end + + module ServerAny + include Server + def post_init + start_tls(:ssl_version => %w(sslv2 sslv3 tlsv1 tlsv1_1 tlsv1_2)) + end + end + + module ServerDefault + include Server + def post_init + start_tls + end + end + + module InvalidProtocol + include Client + def post_init + start_tls(:ssl_version => %w(tlsv1 badinput)) + end + end + + def test_invalid_ssl_version + assert_raises(RuntimeError, "Unrecognized SSL/TLS Version: badinput") do + EM.run do + EM.start_server("127.0.0.1", 16784, InvalidProtocol) + EM.connect("127.0.0.1", 16784, InvalidProtocol) + end + end + end + + def test_any_to_v3 + $client_handshake_completed, $server_handshake_completed = false, false + EM.run do + EM.start_server("127.0.0.1", 16784, ServerSSLv3) + EM.connect("127.0.0.1", 16784, ClientAny) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_case_insensitivity + $client_handshake_completed, $server_handshake_completed = false, false + EM.run do + EM.start_server("127.0.0.1", 16784, ServerTLSv1CaseInsensitive) + EM.connect("127.0.0.1", 16784, ClientAny) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_v3_to_any + $client_handshake_completed, $server_handshake_completed = false, false + EM.run do + EM.start_server("127.0.0.1", 16784, ServerAny) + EM.connect("127.0.0.1", 16784, ClientSSLv3) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_v3_to_v3 + $client_handshake_completed, $server_handshake_completed = false, false + EM.run do + EM.start_server("127.0.0.1", 16784, ServerSSLv3) + EM.connect("127.0.0.1", 16784, ClientSSLv3) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_any_to_any + $client_handshake_completed, $server_handshake_completed = false, false + EM.run do + EM.start_server("127.0.0.1", 16784, ServerAny) + EM.connect("127.0.0.1", 16784, ClientAny) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_default_to_default + $client_handshake_completed, $server_handshake_completed = false, false + EM.run do + EM.start_server("127.0.0.1", 16784, ServerDefault) + EM.connect("127.0.0.1", 16784, ClientDefault) + end + + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + module ServerV3StopAfterHandshake + def post_init + start_tls(:ssl_version => %w(SSLv3)) + end + + def ssl_handshake_completed + $server_handshake_completed = true + EM.stop_event_loop + end + end + + module ServerTLSv1StopAfterHandshake + def post_init + start_tls(:ssl_version => %w(TLSv1)) + end + + def ssl_handshake_completed + $server_handshake_completed = true + EM.stop_event_loop + end + end + + def test_v3_with_external_client + $server_handshake_completed = false + EM.run do + setup_timeout(2) + EM.start_server("127.0.0.1", 16784, ServerV3StopAfterHandshake) + EM.defer do + sock = TCPSocket.new("127.0.0.1", 16784) + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :SSLv3_client + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.connect + ssl.close rescue nil + sock.close rescue nil + end + end + + assert($server_handshake_completed) + end + + def test_tlsv1_with_external_client + $server_handshake_completed = false + EM.run do + setup_timeout(2) + EM.start_server("127.0.0.1", 16784, ServerTLSv1StopAfterHandshake) + EM.defer do + sock = TCPSocket.new("127.0.0.1", 16784) + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_client + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.connect + ssl.close rescue nil + sock.close rescue nil + end + end + + assert($server_handshake_completed) + end + + def test_tlsv1_required_with_external_client + $server_handshake_completed = false + + EM.run do + n = 0 + EM.add_periodic_timer(0.5) do + n += 1 + (EM.stop rescue nil) if n == 2 + end + EM.start_server("127.0.0.1", 16784, ServerTLSv1StopAfterHandshake) + EM.defer do + sock = TCPSocket.new("127.0.0.1", 16784) + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :SSLv3_client + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + assert_raise OpenSSL::SSL::SSLError do + ssl.connect + end + ssl.close rescue nil + sock.close rescue nil + EM.stop rescue nil + end + end + + assert(!$server_handshake_completed) + end + end +else + warn "EM built without SSL support, skipping tests in #{__FILE__}" +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_verify.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_verify.rb new file mode 100644 index 0000000..a6d8fca --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ssl_verify.rb @@ -0,0 +1,128 @@ +require 'em_test_helper' + +class TestSslVerify < Test::Unit::TestCase + def setup + $dir = File.dirname(File.expand_path(__FILE__)) + '/' + $cert_from_file = File.read($dir+'client.crt') + end + + module ClientNoCert + def connection_completed + start_tls() + end + + def ssl_handshake_completed + $client_handshake_completed = true + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + module Client + def connection_completed + start_tls(:private_key_file => $dir+'client.key', :cert_chain_file => $dir+'client.crt') + end + + def ssl_handshake_completed + $client_handshake_completed = true + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + module AcceptServer + def post_init + start_tls(:verify_peer => true) + end + + def ssl_verify_peer(cert) + $cert_from_server = cert + true + end + + def ssl_handshake_completed + $server_handshake_completed = true + end + end + + module DenyServer + def post_init + start_tls(:verify_peer => true) + end + + def ssl_verify_peer(cert) + $cert_from_server = cert + # Do not accept the peer. This should now cause the connection to shut down without the SSL handshake being completed. + false + end + + def ssl_handshake_completed + $server_handshake_completed = true + end + end + + module FailServerNoPeerCert + def post_init + start_tls(:verify_peer => true, :fail_if_no_peer_cert => true) + end + + def ssl_verify_peer(cert) + raise "Verify peer should not get called for a client without a certificate" + end + + def ssl_handshake_completed + $server_handshake_completed = true + end + end + + def test_fail_no_peer_cert + omit_unless(EM.ssl?) + omit_if(rbx?) + + $client_handshake_completed, $server_handshake_completed = false, false + + EM.run { + EM.start_server("127.0.0.1", 16784, FailServerNoPeerCert) + EM.connect("127.0.0.1", 16784, ClientNoCert) + } + + assert(!$client_handshake_completed) + assert(!$server_handshake_completed) + end + + def test_accept_server + omit_unless(EM.ssl?) + omit_if(EM.library_type == :pure_ruby) # Server has a default cert chain + omit_if(rbx?) + $client_handshake_completed, $server_handshake_completed = false, false + EM.run { + EM.start_server("127.0.0.1", 16784, AcceptServer) + EM.connect("127.0.0.1", 16784, Client).instance_variable_get("@signature") + } + + assert_equal($cert_from_file, $cert_from_server) + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_deny_server + omit_unless(EM.ssl?) + omit_if(EM.library_type == :pure_ruby) # Server has a default cert chain + omit_if(rbx?) + $client_handshake_completed, $server_handshake_completed = false, false + EM.run { + EM.start_server("127.0.0.1", 16784, DenyServer) + EM.connect("127.0.0.1", 16784, Client) + } + + assert_equal($cert_from_file, $cert_from_server) + assert(!$client_handshake_completed) + assert(!$server_handshake_completed) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_stomp.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_stomp.rb new file mode 100644 index 0000000..53c0502 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_stomp.rb @@ -0,0 +1,38 @@ +require 'em_test_helper' + +class TestStomp < Test::Unit::TestCase + CONTENT_LENGTH_REGEX = /^content-length: (\d+)$/ + + def bytesize(str) + str = str.to_s + size = str.bytesize if str.respond_to?(:bytesize) # bytesize added in 1.9 + size || str.size + end + + class TStomp + include EM::P::Stomp + + def last_sent_content_length + @sent && Integer(@sent[CONTENT_LENGTH_REGEX, 1]) + end + + def send_data(string) + @sent = string + end + end + + def test_content_length_in_bytes + connection = TStomp.new + + queue = "queue" + failure_message = "header content-length is not the byte size of last sent body" + + body = "test" + connection.send queue, body + assert_equal bytesize(body), connection.last_sent_content_length, failure_message + + body = "test\u221A" + connection.send queue, body + assert_equal bytesize(body), connection.last_sent_content_length, failure_message + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_system.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_system.rb new file mode 100644 index 0000000..fbbe2c9 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_system.rb @@ -0,0 +1,46 @@ +# coding: utf-8 +require 'em_test_helper' + +class TestSystem < Test::Unit::TestCase + def setup + @filename = File.expand_path("../я манал dump.txt", __FILE__) + @test_data = 'a' * 100 + File.open(@filename, 'w'){|f| f.write(@test_data)} + end + + def test_system + omit_if(windows?) + + result = nil + status = nil + EM.run { + EM.system('cat', @filename){|out, state| + result = out + status = state.exitstatus + EM.stop + } + } + assert_equal(0, status) + assert_equal(@test_data, result) + end + + def test_system_with_string + omit_if(windows?) + + result = nil + status = nil + EM.run { + EM.system("cat '#@filename'"){|out, state| + result = out + status = state.exitstatus + EM.stop + } + } + assert_equal(0, status) + assert_equal(@test_data, result) + end + + def teardown + File.unlink(@filename) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_threaded_resource.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_threaded_resource.rb new file mode 100644 index 0000000..9bb39c6 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_threaded_resource.rb @@ -0,0 +1,63 @@ +require 'em_test_helper' + +class TestThreadedResource < Test::Unit::TestCase + def object + @object ||= {} + end + + def resource + @resource = EM::ThreadedResource.new do + object + end + end + + def teardown + resource.shutdown + end + + def test_dispatch_completion + EM.run do + EM.add_timer(3) do + EM.stop + fail 'Resource dispatch timed out' + end + completion = resource.dispatch do |o| + o[:foo] = :bar + :foo + end + completion.callback do |result| + assert_equal :foo, result + EM.stop + end + completion.errback do |error| + EM.stop + fail "Unexpected error: #{error.message}" + end + end + assert_equal :bar, object[:foo] + end + + def test_dispatch_failure + completion = resource.dispatch do |o| + raise 'boom' + end + completion.errback do |error| + assert_kind_of RuntimeError, error + assert_equal 'boom', error.message + end + end + + def test_dispatch_threading + main = Thread.current + resource.dispatch do |o| + o[:dispatch_thread] = Thread.current + end + assert_not_equal main, object[:dispatch_thread] + end + + def test_shutdown + # This test should get improved sometime. The method returning thread is + # NOT an api that will be maintained. + assert !resource.shutdown.alive? + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_tick_loop.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_tick_loop.rb new file mode 100644 index 0000000..39d86b5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_tick_loop.rb @@ -0,0 +1,59 @@ +require "test/unit" +require 'em_test_helper' + +class TestEmTickLoop < Test::Unit::TestCase + def test_em_tick_loop + i = 0 + EM.tick_loop { i += 1; EM.stop if i == 10 } + EM.run { EM.add_timer(1) { EM.stop } } + assert_equal i, 10 + end + + def test_tick_loop_on_stop + t = nil + tick_loop = EM.tick_loop { :stop } + tick_loop.on_stop { t = true } + EM.run { EM.next_tick { EM.stop } } + assert t + end + + def test_start_twice + i = 0 + s = 0 + tick_loop = EM.tick_loop { i += 1; :stop } + tick_loop.on_stop { s += 1; EM.stop } + EM.run { EM.next_tick { EM.stop } } + assert_equal 1, i + assert_equal 1, s + tick_loop.start + EM.run { EM.next_tick { EM.stop } } + assert_equal 2, i + assert_equal 1, s # stop callbacks are only called once + end + + def test_stop + i, s = 0, 0 + tick_loop = EM.tick_loop { i += 1 } + tick_loop.on_stop { s += 1 } + EM.run { EM.next_tick { tick_loop.stop; EM.next_tick { EM.stop } } } + assert tick_loop.stopped? + assert_equal 1, i + assert_equal 1, s + end + + def test_immediate_stops + s = 0 + tick_loop = EM::TickLoop.new { } + tick_loop.on_stop { s += 1 } + tick_loop.on_stop { s += 1 } + assert_equal 2, s + end + + def test_stopped + tick_loop = EM::TickLoop.new { } + assert tick_loop.stopped? + tick_loop.start + assert !tick_loop.stopped? + end + +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_timers.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_timers.rb new file mode 100644 index 0000000..88b3b78 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_timers.rb @@ -0,0 +1,130 @@ +require 'em_test_helper' + +class TestTimers < Test::Unit::TestCase + + def test_timer_with_block + x = false + EM.run { + EM::Timer.new(0) { + x = true + EM.stop + } + } + assert x + end + + def test_timer_with_proc + x = false + EM.run { + EM::Timer.new(0, proc { + x = true + EM.stop + }) + } + assert x + end + + def test_timer_cancel + assert_nothing_raised do + EM.run { + timer = EM::Timer.new(0.01) { flunk "Timer was not cancelled." } + timer.cancel + + EM.add_timer(0.02) { EM.stop } + } + end + end + + def test_periodic_timer + x = 0 + EM.run { + EM::PeriodicTimer.new(0.01) do + x += 1 + EM.stop if x == 4 + end + } + + assert_equal 4, x + end + + def test_add_periodic_timer + x = 0 + EM.run { + t = EM.add_periodic_timer(0.01) do + x += 1 + EM.stop if x == 4 + end + assert t.respond_to?(:cancel) + } + assert_equal 4, x + end + + def test_periodic_timer_cancel + x = 0 + EM.run { + pt = EM::PeriodicTimer.new(0.01) { x += 1 } + pt.cancel + EM::Timer.new(0.02) { EM.stop } + } + assert_equal 0, x + end + + def test_add_periodic_timer_cancel + x = 0 + EM.run { + pt = EM.add_periodic_timer(0.01) { x += 1 } + EM.cancel_timer(pt) + EM.add_timer(0.02) { EM.stop } + } + assert_equal 0, x + end + + def test_periodic_timer_self_cancel + x = 0 + EM.run { + pt = EM::PeriodicTimer.new(0) { + x += 1 + if x == 4 + pt.cancel + EM.stop + end + } + } + assert_equal 4, x + end + + def test_oneshot_timer_large_future_value + large_value = 11948602000 + EM.run { + EM.add_timer(large_value) { EM.stop } + EM.add_timer(0.02) { EM.stop } + } + end + + # This test is only applicable to compiled versions of the reactor. + # Pure ruby and java versions have no built-in limit on the number of outstanding timers. + unless [:pure_ruby, :java].include? EM.library_type + def test_timer_change_max_outstanding + defaults = EM.get_max_timers + EM.set_max_timers(100) + + one_hundred_one_timers = lambda do + 101.times { EM.add_timer(0.01) {} } + EM.stop + end + + assert_raises(RuntimeError) do + EM.run( &one_hundred_one_timers ) + end + + EM.set_max_timers( 101 ) + + assert_nothing_raised do + EM.run( &one_hundred_one_timers ) + end + ensure + EM.set_max_timers(defaults) + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ud.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ud.rb new file mode 100644 index 0000000..3559756 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_ud.rb @@ -0,0 +1,8 @@ +require 'em_test_helper' + +class TestUserDefinedEvents < Test::Unit::TestCase + + def test_a + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_unbind_reason.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_unbind_reason.rb new file mode 100644 index 0000000..ff9193e --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/tests/test_unbind_reason.rb @@ -0,0 +1,40 @@ +require 'em_test_helper' + +class TestUnbindReason < Test::Unit::TestCase + + class StubConnection < EM::Connection + attr_reader :error + def unbind(reason = nil) + @error = reason + EM.stop + end + end + + # RFC 5737 Address Blocks Reserved for Documentation + def test_connect_timeout + conn = nil + EM.run do + conn = EM.connect '192.0.2.0', 80, StubConnection + conn.pending_connect_timeout = 1 + end + assert_equal Errno::ETIMEDOUT, conn.error + end + + def test_connect_refused + pend('FIXME: this test is broken on Windows') if windows? + conn = nil + EM.run do + conn = EM.connect '127.0.0.1', 12388, StubConnection + end + assert_equal Errno::ECONNREFUSED, conn.error + end + + def test_optional_argument + pend('FIXME: this test is broken on Windows') if windows? + conn = nil + EM.run do + conn = EM.connect '127.0.0.1', 12388, StubConnection + end + assert_equal Errno::ECONNREFUSED, conn.error + end +end -- cgit v1.2.3