eventmachine-1.0.7/0000755000004100000410000000000012511426257014222 5ustar www-datawww-dataeventmachine-1.0.7/Rakefile0000644000004100000410000000106712511426257015673 0ustar www-datawww-datarequire 'rubygems' GEMSPEC = Gem::Specification.load('eventmachine.gemspec') require 'rake/clean' task :clobber => :clean desc "Build eventmachine, then run tests." task :default => [:compile, :test] desc 'Generate documentation' begin require 'yard' YARD::Rake::YardocTask.new do |t| t.files = ['lib/**/*.rb', '-', 'docs/*.md'] t.options = ['--main', 'README.md', '--no-private'] t.options = ['--exclude', 'lib/jeventmachine', '--exclude', 'lib/pr_eventmachine'] end rescue LoadError task :yard do puts "Please install yard first!"; end end eventmachine-1.0.7/Gemfile0000644000004100000410000000004612511426257015515 0ustar www-datawww-datasource 'https://rubygems.org' gemspec eventmachine-1.0.7/examples/0000755000004100000410000000000012511426257016040 5ustar www-datawww-dataeventmachine-1.0.7/examples/guides/0000755000004100000410000000000012511426257017320 5ustar www-datawww-dataeventmachine-1.0.7/examples/guides/getting_started/0000755000004100000410000000000012511426257022507 5ustar www-datawww-dataeventmachine-1.0.7/examples/guides/getting_started/06_simple_chat_server_step_three.rb0000644000004100000410000000323112511426257031440 0ustar www-datawww-data#!/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 eventmachine-1.0.7/examples/guides/getting_started/05_simple_chat_server_step_two.rb0000644000004100000410000000130012511426257031134 0ustar www-datawww-data#!/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 eventmachine-1.0.7/examples/guides/getting_started/07_simple_chat_server_step_four.rb0000644000004100000410000000421112511426257031304 0ustar www-datawww-data#!/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 eventmachine-1.0.7/examples/guides/getting_started/08_simple_chat_server_step_five.rb0000644000004100000410000000611312511426257031266 0ustar www-datawww-data#!/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 ././@LongLink0000000000000000000000000000015700000000000011570 Lustar rootrooteventmachine-1.0.7/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rbeventmachine-1.0.7/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_0000644000004100000410000000070112511426257034423 0ustar www-datawww-data#!/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 eventmachine-1.0.7/examples/guides/getting_started/03_simple_chat_server.rb0000644000004100000410000000640112511426257027215 0ustar www-datawww-data#!/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 eventmachine-1.0.7/examples/guides/getting_started/01_eventmachine_echo_server.rb0000644000004100000410000000056712511426257030376 0ustar www-datawww-data#!/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) endeventmachine-1.0.7/examples/guides/getting_started/04_simple_chat_server_step_one.rb0000644000004100000410000000074412511426257031116 0ustar www-datawww-data#!/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 eventmachine-1.0.7/examples/old/0000755000004100000410000000000012511426257016616 5ustar www-datawww-dataeventmachine-1.0.7/examples/old/ex_queue.rb0000644000004100000410000000005412511426257020762 0ustar www-datawww-datarequire File.dirname(__FILE__) + '/helper' eventmachine-1.0.7/examples/old/ex_tick_loop_counter.rb0000644000004100000410000000101712511426257023360 0ustar www-datawww-datarequire 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 endeventmachine-1.0.7/examples/old/ex_channel.rb0000644000004100000410000000216112511426257021247 0ustar www-datawww-datarequire 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 eventmachine-1.0.7/examples/old/helper.rb0000644000004100000410000000012612511426257020421 0ustar www-datawww-data$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'eventmachine'eventmachine-1.0.7/examples/old/ex_tick_loop_array.rb0000644000004100000410000000033512511426257023021 0ustar www-datawww-datarequire 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 } endeventmachine-1.0.7/tests/0000755000004100000410000000000012511426257015364 5ustar www-datawww-dataeventmachine-1.0.7/tests/test_kb.rb0000644000004100000410000000142112511426257017342 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_servers.rb0000644000004100000410000000123712511426257020444 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_object_protocol.rb0000644000004100000410000000126112511426257022137 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_defer.rb0000644000004100000410000000050712511426257020037 0ustar www-datawww-datarequire '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 end eventmachine-1.0.7/tests/test_tick_loop.rb0000644000004100000410000000253612511426257020741 0ustar www-datawww-datarequire "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 endeventmachine-1.0.7/tests/test_ltp2.rb0000644000004100000410000001627712511426257017646 0ustar www-datawww-datarequire '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 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 #-- # 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 eventmachine-1.0.7/tests/test_unbind_reason.rb0000644000004100000410000000177312511426257021606 0ustar www-datawww-datarequire 'em_test_helper' require 'socket' class TestUnbindReason < Test::Unit::TestCase class StubConnection < EM::Connection attr_reader :error def unbind(reason = nil) @error = reason EM.stop end end def test_connect_timeout error = nil EM.run { conn = EM.connect 'google.com', 81, Module.new{ |m| m.send(:define_method, :unbind) do |reason| error = reason EM.stop end } conn.pending_connect_timeout = 0.1 } assert_equal Errno::ETIMEDOUT, error end def test_connect_refused error = nil EM.run { EM.connect '127.0.0.1', 12388, Module.new{ |m| m.send(:define_method, :unbind) do |reason| error = reason EM.stop end } } assert_equal Errno::ECONNREFUSED, error end def test_optional_argument conn = nil EM.run { conn = EM.connect '127.0.0.1', 12388, StubConnection } assert_equal Errno::ECONNREFUSED, conn.error end end eventmachine-1.0.7/tests/test_next_tick.rb0000644000004100000410000000450012511426257020737 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_pool.rb0000644000004100000410000001021612511426257017721 0ustar www-datawww-datarequire '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_error_handler 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 eventmachine-1.0.7/tests/test_file_watch.rb0000644000004100000410000000256712511426257021067 0ustar www-datawww-datarequire '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 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 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 eventmachine-1.0.7/tests/test_process_watch.rb0000644000004100000410000000170612511426257021620 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_send_file.rb0000644000004100000410000001140712511426257020703 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_proxy_connection.rb0000644000004100000410000000765312511426257022363 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_inactivity_timeout.rb0000644000004100000410000000243012511426257022700 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_handler_check.rb0000644000004100000410000000120412511426257021517 0ustar www-datawww-datarequire '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 endeventmachine-1.0.7/tests/test_idle_connection.rb0000644000004100000410000000117012511426257022103 0ustar www-datawww-datarequire 'em_test_helper' class TestIdleConnection < Test::Unit::TestCase if EM.respond_to?(:get_idle_time) def test_idle_time EM.run{ conn = EM.connect 'www.google.com', 80 EM.add_timer(3){ $idle_time = conn.get_idle_time conn.send_data "GET / HTTP/1.0\r\n\r\n" EM.next_tick{ EM.next_tick{ $idle_time_after_send = conn.get_idle_time conn.close_connection EM.stop } } } } assert_in_delta 3, $idle_time, 0.2 assert_in_delta 0, $idle_time_after_send, 0.1 end end end eventmachine-1.0.7/tests/test_shutdown_hooks.rb0000644000004100000410000000062412511426257022030 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_resolver.rb0000644000004100000410000000344012511426257020612 0ustar www-datawww-datarequire 'em_test_helper' class TestResolver < Test::Unit::TestCase def test_a EM.run { d = EM::DNS::Resolver.resolve "google.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 def test_a_pair 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 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 EM.run { d = EM::DNS::Resolver.resolve "google.com" d.errback { |err| assert false, "failed to resolve google.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 eventmachine-1.0.7/tests/test_futures.rb0000644000004100000410000001072412511426257020451 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_get_sock_opt.rb0000644000004100000410000000152412511426257021432 0ustar www-datawww-datarequire 'em_test_helper' require 'socket' class TestGetSockOpt < Test::Unit::TestCase if EM.respond_to? :get_sock_opt def setup assert(!EM.reactor_running?) end def teardown assert(!EM.reactor_running?) end #------------------------------------- def test_get_sock_opt test = self EM.run do EM.connect 'google.com', 80, Module.new { define_method :connection_completed do val = get_sock_opt Socket::SOL_SOCKET, Socket::SO_ERROR test.assert_equal "\0\0\0\0", val EM.stop end } end end else warn "EM.get_sock_opt not implemented, skipping tests in #{__FILE__}" # Because some rubies will complain if a TestCase class has no tests def test_em_get_sock_opt_unsupported assert true end end end eventmachine-1.0.7/tests/test_epoll.rb0000644000004100000410000000670512511426257020073 0ustar www-datawww-datarequire '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, w = 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 eventmachine-1.0.7/tests/test_pure.rb0000644000004100000410000000465512511426257017735 0ustar www-datawww-datarequire '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 end eventmachine-1.0.7/tests/test_ltp.rb0000644000004100000410000000643412511426257017556 0ustar www-datawww-datarequire 'em_test_helper' class TestLineAndTextProtocol < Test::Unit::TestCase class SimpleLineTest < EM::P::LineAndTextProtocol 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 lines_received = [] EM.run { EM.start_server( "127.0.0.1", @port, SimpleLineTest ) do |conn| conn.instance_eval "@line_buffer = lines_received" 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), lines_received ) end #-------------------------------------------------------------------- class SimpleLineTest < EM::P::LineAndTextProtocol def receive_error text @error_message << text end end def test_overlength_lines lines_received = [] EM.run { EM.start_server( "127.0.0.1", @port, SimpleLineTest ) do |conn| conn.instance_eval "@error_message = lines_received" 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"], lines_received ) 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 eventmachine-1.0.7/tests/test_pause.rb0000644000004100000410000000460212511426257020067 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_timers.rb0000644000004100000410000000441212511426257020254 0ustar www-datawww-datarequire '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 # 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 eventmachine-1.0.7/tests/test_processes.rb0000644000004100000410000000576612511426257020774 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/client.crt0000644000004100000410000000353712511426257017364 0ustar www-datawww-data-----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----- eventmachine-1.0.7/tests/test_spawn.rb0000644000004100000410000001502012511426257020076 0ustar www-datawww-data 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 eventmachine-1.0.7/tests/test_many_fds.rb0000644000004100000410000000070312511426257020550 0ustar www-datawww-datarequire '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); 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 eventmachine-1.0.7/tests/test_pending_connect_timeout.rb0000644000004100000410000000227712511426257023663 0ustar www-datawww-datarequire '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 = Time.now EM.stop end end EM.run { setup_timeout EM.heartbeat_interval = 0.1 start = Time.now c = EM.connect("1.2.3.4", 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 eventmachine-1.0.7/tests/test_sasl.rb0000644000004100000410000000173712511426257017722 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_hc.rb0000644000004100000410000001013112511426257017336 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_attach.rb0000644000004100000410000000563212511426257020222 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_connection_count.rb0000644000004100000410000000236312511426257022323 0ustar www-datawww-datarequire 'em_test_helper' class TestConnectionCount < Test::Unit::TestCase def test_idle_connection_count 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 eventmachine-1.0.7/tests/test_iterator.rb0000644000004100000410000000430012511426257020576 0ustar www-datawww-datarequire 'em_test_helper' class TestIterator < Test::Unit::TestCase def get_time EM.current_time.strftime('%H:%M:%S') end def test_default_concurrency items = {} list = 1..10 EM.run { EM::Iterator.new(list).each( proc {|num,iter| time = get_time items[time] ||= [] items[time] << num EM::Timer.new(1) {iter.next} }, proc {EM.stop}) } assert_equal(10, items.keys.size) assert_equal(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 EM.run { i = EM::Iterator.new(list,5) i.each(proc {|num,iter| time = get_time items[time] ||= [] items[time] << num EM::Timer.new(1) {iter.next} }, proc {EM.stop}) EM.add_timer(1){ i.concurrency = 1 } EM.add_timer(3){ i.concurrency = 3 } } assert_equal(9, items.keys.size) 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 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 eventmachine-1.0.7/tests/test_queue.rb0000644000004100000410000000174712511426257020105 0ustar www-datawww-datarequire '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 end eventmachine-1.0.7/tests/client.key0000644000004100000410000000625312511426257017362 0ustar www-datawww-data-----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----- eventmachine-1.0.7/tests/test_error_handler.rb0000644000004100000410000000122112511426257021572 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_stomp.rb0000644000004100000410000000167112511426257020117 0ustar www-datawww-datarequire '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 def test_content_length_in_bytes connection = Object.new connection.instance_eval do extend EM::P::Stomp def last_sent_content_length @sent && Integer(@sent[CONTENT_LENGTH_REGEX, 1]) end def send_data(string) @sent = string end end 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 eventmachine-1.0.7/tests/test_deferrable.rb0000644000004100000410000000126112511426257021043 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_basic.rb0000644000004100000410000001361612511426257020040 0ustar www-datawww-datarequire '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 assert_raises( UnbindError::ERR ) { EM.run { EM.start_server "127.0.0.1", @port EM.connect "127.0.0.1", @port, UnbindError } } 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 local_ip = UDPSocket.open {|s| s.connect('google.com', 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_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_fork_safe omit_if(jruby?) omit_if(rbx?, 'Omitting test on Rubinius because it hangs for unknown reasons') read, write = IO.pipe EM.run do fork do write.puts "forked" EM.run do EM.next_tick do write.puts "EM ran" EM.stop end end end EM.stop end assert_equal "forked\n", read.readline assert_equal "EM ran\n", read.readline ensure read.close rescue nil write.close rescue nil 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 assert_equal 1, errors.size assert_equal [:first, :second], ticks end end eventmachine-1.0.7/tests/test_line_protocol.rb0000644000004100000410000000117012511426257021617 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_smtpserver.rb0000644000004100000410000000307512511426257021167 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_channel.rb0000644000004100000410000000226012511426257020360 0ustar www-datawww-datarequire '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 endeventmachine-1.0.7/tests/test_httpclient2.rb0000644000004100000410000000561512511426257021217 0ustar www-datawww-datarequire 'em_test_helper' class TestHttpClient2 < Test::Unit::TestCase Localhost = "127.0.0.1" Localport = 9801 def setup end def teardown end class TestServer < EM::Connection 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 { EM.start_server Localhost, Localport, TestServer silent do EM::P::HttpClient2.connect Localhost, Localport EM::P::HttpClient2.connect( :host=>Localhost, :port=>Localport ) end EM.stop } end def test_bad_port EM.run { EM.start_server Localhost, Localport, TestServer assert_raises( ArgumentError ) { silent { EM::P::HttpClient2.connect Localhost, "xxx" } } EM.stop } end def test_bad_server err = nil EM.run { http = silent { EM::P::HttpClient2.connect Localhost, 9999 } d = http.get "/" d.errback { err = true; d.internal_error; EM.stop } } assert(err) end def test_get content = nil EM.run { http = silent { EM::P::HttpClient2.connect "google.com", 80 } 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 { http = silent { EM::P::HttpClient2.connect "google.com", 80 } 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 { 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 { EM.start_server Localhost, Localport, TestServer http = silent { EM::P::HttpClient2.connect Localhost, 18842 } d = http.get :url=>"/", :authorization=>"Basic xxx" d.callback {EM.stop} d.errback {EM.stop} } end def test_https_get d = nil EM.run { http = silent { EM::P::HttpClient2.connect :host => 'www.apple.com', :port => 443, :ssl => true } d = http.get "/" d.callback { EM.stop } } assert_equal(200, d.status) end if EM.ssl? end eventmachine-1.0.7/tests/test_exc.rb0000644000004100000410000000121212511426257017523 0ustar www-datawww-datarequire 'em_test_helper' class TestSomeExceptions < Test::Unit::TestCase # 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 end eventmachine-1.0.7/tests/test_ssl_methods.rb0000644000004100000410000000166212511426257021301 0ustar www-datawww-datarequire '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 end end module ClientHandler def post_init start_tls end def ssl_handshake_completed $client_called_back = true $client_cert_value = get_peer_cert 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 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)) end end eventmachine-1.0.7/tests/test_ssl_verify.rb0000644000004100000410000000370512511426257021142 0ustar www-datawww-datarequire '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 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 def test_accept_server 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, 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(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 eventmachine-1.0.7/tests/test_threaded_resource.rb0000644000004100000410000000244712511426257022446 0ustar www-datawww-dataclass 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 eventmachine-1.0.7/tests/test_system.rb0000644000004100000410000000153312511426257020276 0ustar www-datawww-data# 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 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 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 eventmachine-1.0.7/tests/test_connection_write.rb0000644000004100000410000000177012511426257022326 0ustar www-datawww-datarequire '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, w1 = IO.pipe r2, w2 = 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 eventmachine-1.0.7/tests/test_set_sock_opt.rb0000644000004100000410000000151212511426257021443 0ustar www-datawww-datarequire 'em_test_helper' require 'socket' class TestSetSockOpt < Test::Unit::TestCase if EM.respond_to? :set_sock_opt def setup assert(!EM.reactor_running?) end def teardown assert(!EM.reactor_running?) end #------------------------------------- def test_set_sock_opt test = self EM.run do EM.connect 'google.com', 80, Module.new { define_method :post_init do val = set_sock_opt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true test.assert_equal 0, val EM.stop end } end end else warn "EM.set_sock_opt not implemented, skipping tests in #{__FILE__}" # Because some rubies will complain if a TestCase class has no tests def test_em_set_sock_opt_unsupported assert true end end end eventmachine-1.0.7/tests/test_ssl_args.rb0000644000004100000410000000356212511426257020573 0ustar www-datawww-datarequire "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? eventmachine-1.0.7/tests/em_test_helper.rb0000644000004100000410000000254612511426257020717 0ustar www-datawww-datarequire 'eventmachine' require 'test/unit' require 'rbconfig' require 'socket' 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 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 # 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 end eventmachine-1.0.7/tests/test_completion.rb0000644000004100000410000001006612511426257021124 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_ud.rb0000644000004100000410000000014612511426257017361 0ustar www-datawww-datarequire 'em_test_helper' class TestUserDefinedEvents < Test::Unit::TestCase def test_a end end eventmachine-1.0.7/tests/test_running.rb0000644000004100000410000000037312511426257020433 0ustar www-datawww-datarequire '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 eventmachine-1.0.7/tests/test_httpclient.rb0000644000004100000410000001337212511426257021134 0ustar www-datawww-datarequire 'em_test_helper' class TestHttpClient < Test::Unit::TestCase Localhost = "127.0.0.1" Localport = 9801 def setup end def teardown 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 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; 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 {|result| ok = true; 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", 9701, EmptyContent c = silent { EM::P::HttpClient.send :request, :host => "127.0.0.1", :port => 9701 } c.callback {|result| ok = true 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 Localhost, Localport, PostContent setup_timeout(2) c = silent { EM::P::HttpClient.request( :host=>Localhost, :port=>Localport, :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 {|result| ok = true; 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 {|result| ok = true; 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 EventMachine.run { EventMachine.start_server "127.0.0.1", 9701, ChunkedEncodingContent c = EventMachine::Protocols::HttpClient.send :request, :host => "127.0.0.1", :port => 9701 c.callback {|result| if result[:content] == "chunk1" * 1024 + "chunk2" * 15 ok = true end EventMachine.stop } } assert ok end end eventmachine-1.0.7/tests/test_smtpclient.rb0000644000004100000410000000234412511426257021135 0ustar www-datawww-datarequire '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 end eventmachine-1.0.7/GNU0000644000004100000410000003564212511426257014610 0ustar www-datawww-data. 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. eventmachine-1.0.7/.travis.yml0000644000004100000410000000042512511426257016334 0ustar www-datawww-datascript: bundle exec rake compile test env: global: - TESTOPTS=-v language: ruby sudo: false rvm: - 1.8.7 - 1.9.2 - 1.9.3 - 2.0.0 - 2.1 - 2.2 - rbx - jruby matrix: allow_failures: - rvm: rbx - rvm: jruby include: - rvm: 2.0.0 os: osx eventmachine-1.0.7/lib/0000755000004100000410000000000012511426257014770 5ustar www-datawww-dataeventmachine-1.0.7/lib/jeventmachine.rb0000644000004100000410000001777012511426257020151 0ustar www-datawww-data#-- # # 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 # 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 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? Fixnum end if fileno == 0 raise "can't open STDIN as selectable in Java =(" elsif fileno.is_a? Fixnum # 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 eventmachine-1.0.7/lib/em/0000755000004100000410000000000012511426257015371 5ustar www-datawww-dataeventmachine-1.0.7/lib/em/protocols/0000755000004100000410000000000012511426257017415 5ustar www-datawww-dataeventmachine-1.0.7/lib/em/protocols/linetext2.rb0000644000004100000410000001242312511426257021662 0ustar www-datawww-data#-- # # 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 be called recursively until there's no data 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 ||= [] if @lt2_mode == :lines if ix = data.index( @lt2_delimiter ) @lt2_linebuffer << data[0...ix] ln = @lt2_linebuffer.join @lt2_linebuffer.clear if @lt2_delimiter == "\n" ln.chomp! end receive_line ln receive_data data[(ix+@lt2_delimiter.length)..-1] else @lt2_linebuffer << data end elsif @lt2_mode == :text if @lt2_textsize needed = @lt2_textsize - @lt2_textpos will_take = if data.length > needed needed else data.length end @lt2_textbuffer << data[0...will_take] tail = 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 receive_data tail else receive_binary_data data end end end def set_delimiter delim @lt2_delimiter = delim.to_s 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 eventmachine-1.0.7/lib/em/protocols/line_protocol.rb0000644000004100000410000000115012511426257022607 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/protocols/line_and_text.rb0000644000004100000410000001061412511426257022561 0ustar www-datawww-data#-- # # 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 Exception 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 eventmachine-1.0.7/lib/em/protocols/smtpserver.rb0000644000004100000410000005307312511426257022164 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/stomp.rb0000644000004100000410000001316212511426257021107 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/postgres3.rb0000644000004100000410000001746312511426257021706 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/smtpclient.rb0000644000004100000410000003223712511426257022133 0ustar www-datawww-data#-- # # 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 mailfactory) # # mail = MailFactory.new # mail.to = 'someone@site.co' # mail.from = 'me@site.com' # mail.subject = 'hi!' # mail.text = 'hello world' # mail.html = '

hello world

' # # email = EM::P::SmtpClient.send( # :domain=>'site.com', # :from=>mail.from, # :to=>mail.to, # :content=>"#{mail.to_s}\r\n.\r\n" # ) # 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. # :header => Required 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, 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. I think the :body parameter should remain as it is, and we # should add a :content parameter that contains autoconversions and/or conversion parameters. # Then we can check if either :body or :content is present and do the right thing. # :content => Optional array or string # Alternative to providing header and body, an array or string of raw data which MUST be in # correct SMTP body format, including a trailing dot line # :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 receive_signon return invoke_error unless @range == 2 send_data "EHLO #{@args[:domain]}\r\n" @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_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 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 either in @args[:content] (an array or string of raw data # which MUST be in correct SMTP body format, including a trailing dot line), or a header and # body given in @args[:header] and @args[:body]. # if @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 "#{k}: #{v}\r\n" } else send_data @args[:header].to_s end send_data "\r\n" if @args[:body].is_a?(Array) @args[:body].each {|e| send_data e} else send_data @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 eventmachine-1.0.7/lib/em/protocols/saslauth.rb0000644000004100000410000001362412511426257021574 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/tcptest.rb0000644000004100000410000000255412511426257021436 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/httpclient.rb0000644000004100000410000002612412511426257022125 0ustar www-datawww-data#-- # # 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." 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 eventmachine-1.0.7/lib/em/protocols/httpclient2.rb0000644000004100000410000004314312511426257022207 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/memcache.rb0000644000004100000410000001717512511426257021517 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/protocols/object_protocol.rb0000644000004100000410000000224612511426257023135 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/protocols/header_and_content.rb0000644000004100000410000001003612511426257023546 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/protocols/socks4.rb0000644000004100000410000000271312511426257021153 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/streamer.rb0000644000004100000410000000713212511426257017543 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/pure_ruby.rb0000644000004100000410000006141612511426257017742 0ustar www-datawww-data#-- # # 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' # @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 # @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 # The extension version does NOT raise any kind of an error if an attempt is made # to close a non-existent connection. Not sure whether we should. For now, we'll # raise an error here in that case. # @private def close_connection target, after_writing selectable = Reactor.instance.get_selectable( target ) or raise "unknown close_connection target" selectable.schedule_close after_writing 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 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 # This method is not implemented for pure-Ruby implementation # @private def ssl? false 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_peername target" selectable.getsockopt level, optname end # @private def set_sock_opt signature, level, optname, optval selectable = Reactor.instance.get_selectable( signature ) or raise "unknown get_peername 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 end end module EventMachine # @private class Error < Exception; 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 end module EventMachine # @private class Reactor include Singleton HeartbeatInterval = 2 attr_reader :current_loop_time 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 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 #@loopbreak_writer.write '+' if @loopbreak_writer @loopbreak_writer.send('+',0,"127.0.0.1",@loopbreak_port) if @loopbreak_writer 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, :send_data def_delegator :@my_selectable, :schedule_close def_delegator :@my_selectable, :get_peername 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 end module EventMachine # @private class Selectable attr_reader :io, :uuid def initialize io @uuid = UuidGenerator.generate @io = io @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 set_inactivity_timeout tm @inactivity_timeout = tm end def heartbeat 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 # no-op rescue Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError @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 @outbound_q.unshift data rescue EOFError, Errno::ECONNRESET, Errno::ECONNREFUSED @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 # #schedule_close # The application wants to close the connection. def schedule_close after_writing if after_writing @close_requested = true else @close_scheduled = true 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_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::EINPROGRESS end EvmaTCPClient.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 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 = 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 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' eventmachine-1.0.7/lib/em/spawnable.rb0000644000004100000410000000417512511426257017701 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/timers.rb0000644000004100000410000000245312511426257017225 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/connection.rb0000644000004100000410000007535512511426257020074 0ustar www-datawww-datamodule 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 [String] :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. # # @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, cert_chain, verify_peer = args.values_at(:private_key_file, :cert_chain_file, :verify_peer) [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 EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer) 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 # 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 eventmachine-1.0.7/lib/em/deferrable/0000755000004100000410000000000012511426257017464 5ustar www-datawww-dataeventmachine-1.0.7/lib/em/deferrable/pool.rb0000644000004100000410000000013612511426257020762 0ustar www-datawww-datawarn "EM::Deferrable::Pool is deprecated, please use EM::Pool" EM::Deferrable::Pool = EM::Pooleventmachine-1.0.7/lib/em/buftok.rb0000644000004100000410000000420712511426257017213 0ustar www-datawww-data# 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 eventmachine-1.0.7/lib/em/protocols.rb0000644000004100000410000000300212511426257017735 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/future.rb0000644000004100000410000000352112511426257017231 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/resolver.rb0000644000004100000410000001045612511426257017565 0ustar www-datawww-datamodule EventMachine module DNS class Resolver def self.resolve(hostname) Request.new(socket, hostname) end @socket = @nameservers = nil def self.socket if !@socket || (@socket && @socket.error?) @socket = Socket.open @hosts = {} IO.readlines('/etc/hosts').each do |line| next if line =~ /^#/ addr, host = line.split(/\s+/) if @hosts[host] @hosts[host] << addr else @hosts[host] = [addr] end end end @socket end def self.nameservers=(ns) @nameservers = ns end def self.nameservers if !@nameservers @nameservers = [] IO.readlines('/etc/resolv.conf').each do |line| if line =~ /^nameserver (.+)$/ @nameservers << $1.split(/\s+/).first end end end @nameservers end def self.nameserver nameservers.shuffle.first end def self.hosts @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 eventmachine-1.0.7/lib/em/deferrable.rb0000644000004100000410000001747012511426257020022 0ustar www-datawww-data#-- # # 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 endeventmachine-1.0.7/lib/em/callback.rb0000644000004100000410000000422312511426257017453 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/version.rb0000644000004100000410000000005412511426257017402 0ustar www-datawww-datamodule EventMachine VERSION = "1.0.7" end eventmachine-1.0.7/lib/em/processes.rb0000644000004100000410000000724112511426257017730 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/process_watch.rb0000644000004100000410000000237112511426257020565 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/em/channel.rb0000644000004100000410000000266112511426257017333 0ustar www-datawww-datamodule 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 # 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 eventmachine-1.0.7/lib/em/completion.rb0000644000004100000410000002314012511426257020067 0ustar www-datawww-data# = EM::Completion # # A completion is a callback container for various states of completion. In # it's 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 eventmachine-1.0.7/lib/em/iterator.rb0000644000004100000410000001470412511426257017555 0ustar www-datawww-datamodule 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 # 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 # def initialize(list, concurrency = 1) raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a) raise ArgumentError, 'concurrency must be bigger than zero' unless (concurrency > 0) @list = list.to_a.dup @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 if @list.empty? @ended = true @workers -= 1 all_done.call else item = @list.shift @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 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 # TODO: support proc instead of enumerable? EM::Iterator.new(proc{ return queue.pop }) eventmachine-1.0.7/lib/em/messages.rb0000644000004100000410000000542512511426257017533 0ustar www-datawww-data#-- # # 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 eventmachine-1.0.7/lib/em/pool.rb0000644000004100000410000001020712511426257016667 0ustar www-datawww-datamodule EventMachine # = EventMachine::Pool # # A simple async resource pool based on a resource and work queue. Resources # are enqueued and work waits for resources to become available. # # Example: # # 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 # 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. # # For 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 Exception failure resource raise end end end eventmachine-1.0.7/lib/em/tick_loop.rb0000644000004100000410000000413212511426257017701 0ustar www-datawww-datamodule 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 endeventmachine-1.0.7/lib/em/threaded_resource.rb0000644000004100000410000000547412511426257021417 0ustar www-datawww-datamodule 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 Exception => 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 endeventmachine-1.0.7/lib/em/queue.rb0000644000004100000410000000354212511426257017046 0ustar www-datawww-datamodule 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 @items = [] @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 @items.empty? @popq << cb else cb.call @items.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 @items.push(*items) @popq.shift.call @items.shift until @items.empty? || @popq.empty? end end alias :<< :push # @return [Boolean] # @note This is a peek, it's not thread safe, and may only tend toward accuracy. def empty? @items.empty? end # @return [Integer] Queue size # @note This is a peek, it's not thread safe, and may only tend toward accuracy. def size @items.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 eventmachine-1.0.7/lib/em/file_watch.rb0000644000004100000410000000400312511426257020020 0ustar www-datawww-datamodule 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 eventmachine-1.0.7/lib/eventmachine.rb0000644000004100000410000016355012511426257017775 0ustar www-datawww-dataif 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 @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 @reactor_pid = Process.pid @reactor_running = true initialize_event_machine (b = blk || block) and add_timer(0, b) if @next_tick_queue && !@next_tick_queue.empty? add_timer(0) { signal_loopbreak } end @reactor_thread = Thread.current run_machine ensure until @tails.empty? @tails.pop.call end begin release_machine ensure if @threadpool @threadpool.each { |t| t.exit } @threadpool.each do |t| next unless t.alive? begin # Thread#kill! does not exist on 1.9 or rbx, and raises # NotImplemented on jruby t.kill! rescue NoMethodError, NotImplementedError t.kill # XXX t.join here? end end @threadqueue = nil @resultqueue = nil @threadpool = nil @all_threads_spawned = false end @next_tick_queue = [] end @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 self.reactor_running? self.stop_event_loop self.release_machine @reactor_running = false end self.run block end 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) fd = defined?(JRuby) ? JRuby.runtime.getDescriptorByFileno(io.fileno).getChannel : io.fileno 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. EM.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. # # ## 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. # # @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. # } # # EventMachine.defer(operation, callback) # # @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. # # @see EventMachine.threadpool_size def self.defer op = nil, callback = 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] 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 = *@threadqueue.pop rescue ThreadError $stderr.puts $!.message break # Ruby 2.0 may fail at Queue.pop end result = op.call @resultqueue << [result, cback] EventMachine.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 rescue @wrapped_exception = $! stop end elsif c = @acceptors.delete( conn_binding ) # no-op else if $! # Bubble user generated errors. @wrapped_exception = $! EM.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 == 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 eventmachine-1.0.7/metadata.yml0000644000004100000410000001775312511426257016542 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: eventmachine version: !ruby/object:Gem::Version version: 1.0.7 platform: ruby authors: - Francis Cianfrocca - Aman Gupta autorequire: bindir: bin cert_chain: [] date: 2015-02-10 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: test-unit requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.0' - !ruby/object:Gem::Dependency name: rake-compiler requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.8.3 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.8.3 - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.8.5.2 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 0.8.5.2 - !ruby/object:Gem::Dependency name: bluecloth requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: |- EventMachine implements a fast, single-threaded engine for arbitrary network communications. It's extremely easy to use in Ruby. EventMachine wraps all interactions with IP sockets, allowing programs to concentrate on the implementation of network protocols. It can be used to create both network servers and clients. To create a server or client, a Ruby program only needs to specify the IP address and port, and provide a Module that implements the communications protocol. Implementations of several standard network protocols are provided with the package, primarily to serve as examples. The real goal of EventMachine is to enable programs to easily interface with other programs using TCP/IP, especially if custom protocols are required. email: - garbagecat10@gmail.com - aman@tmm1.net executables: [] extensions: - ext/extconf.rb - ext/fastfilereader/extconf.rb extra_rdoc_files: - README.md - docs/DocumentationGuidesIndex.md - docs/GettingStarted.md - docs/old/ChangeLog - docs/old/DEFERRABLES - docs/old/EPOLL - docs/old/INSTALL - docs/old/KEYBOARD - docs/old/LEGAL - docs/old/LIGHTWEIGHT_CONCURRENCY - docs/old/PURE_RUBY - docs/old/RELEASE_NOTES - docs/old/SMTP - docs/old/SPAWNED_PROCESSES - docs/old/TODO files: - ".gitignore" - ".travis.yml" - ".yardopts" - CHANGELOG.md - GNU - Gemfile - LICENSE - README.md - Rakefile - docs/DocumentationGuidesIndex.md - docs/GettingStarted.md - docs/old/ChangeLog - docs/old/DEFERRABLES - docs/old/EPOLL - docs/old/INSTALL - docs/old/KEYBOARD - docs/old/LEGAL - docs/old/LIGHTWEIGHT_CONCURRENCY - docs/old/PURE_RUBY - docs/old/RELEASE_NOTES - docs/old/SMTP - docs/old/SPAWNED_PROCESSES - docs/old/TODO - eventmachine.gemspec - examples/guides/getting_started/01_eventmachine_echo_server.rb - examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb - examples/guides/getting_started/03_simple_chat_server.rb - examples/guides/getting_started/04_simple_chat_server_step_one.rb - examples/guides/getting_started/05_simple_chat_server_step_two.rb - examples/guides/getting_started/06_simple_chat_server_step_three.rb - examples/guides/getting_started/07_simple_chat_server_step_four.rb - examples/guides/getting_started/08_simple_chat_server_step_five.rb - examples/old/ex_channel.rb - examples/old/ex_queue.rb - examples/old/ex_tick_loop_array.rb - examples/old/ex_tick_loop_counter.rb - examples/old/helper.rb - ext/binder.cpp - ext/binder.h - ext/cmain.cpp - ext/ed.cpp - ext/ed.h - ext/em.cpp - ext/em.h - ext/eventmachine.h - ext/extconf.rb - ext/fastfilereader/extconf.rb - ext/fastfilereader/mapper.cpp - ext/fastfilereader/mapper.h - ext/fastfilereader/rubymain.cpp - ext/kb.cpp - ext/page.cpp - ext/page.h - ext/pipe.cpp - ext/project.h - ext/rubymain.cpp - ext/ssl.cpp - ext/ssl.h - java/.classpath - java/.project - java/src/com/rubyeventmachine/EmReactor.java - java/src/com/rubyeventmachine/EmReactorException.java - java/src/com/rubyeventmachine/EventableChannel.java - java/src/com/rubyeventmachine/EventableDatagramChannel.java - java/src/com/rubyeventmachine/EventableSocketChannel.java - lib/em/buftok.rb - lib/em/callback.rb - lib/em/channel.rb - lib/em/completion.rb - lib/em/connection.rb - lib/em/deferrable.rb - lib/em/deferrable/pool.rb - lib/em/file_watch.rb - lib/em/future.rb - lib/em/iterator.rb - lib/em/messages.rb - lib/em/pool.rb - lib/em/process_watch.rb - lib/em/processes.rb - lib/em/protocols.rb - lib/em/protocols/header_and_content.rb - lib/em/protocols/httpclient.rb - lib/em/protocols/httpclient2.rb - lib/em/protocols/line_and_text.rb - lib/em/protocols/line_protocol.rb - lib/em/protocols/linetext2.rb - lib/em/protocols/memcache.rb - lib/em/protocols/object_protocol.rb - lib/em/protocols/postgres3.rb - lib/em/protocols/saslauth.rb - lib/em/protocols/smtpclient.rb - lib/em/protocols/smtpserver.rb - lib/em/protocols/socks4.rb - lib/em/protocols/stomp.rb - lib/em/protocols/tcptest.rb - lib/em/pure_ruby.rb - lib/em/queue.rb - lib/em/resolver.rb - lib/em/spawnable.rb - lib/em/streamer.rb - lib/em/threaded_resource.rb - lib/em/tick_loop.rb - lib/em/timers.rb - lib/em/version.rb - lib/eventmachine.rb - lib/jeventmachine.rb - rakelib/cpp.rake_example - rakelib/package.rake - rakelib/test.rake - tests/client.crt - tests/client.key - tests/em_test_helper.rb - tests/test_attach.rb - tests/test_basic.rb - tests/test_channel.rb - tests/test_completion.rb - tests/test_connection_count.rb - tests/test_connection_write.rb - tests/test_defer.rb - tests/test_deferrable.rb - tests/test_epoll.rb - tests/test_error_handler.rb - tests/test_exc.rb - tests/test_file_watch.rb - tests/test_futures.rb - tests/test_get_sock_opt.rb - tests/test_handler_check.rb - tests/test_hc.rb - tests/test_httpclient.rb - tests/test_httpclient2.rb - tests/test_idle_connection.rb - tests/test_inactivity_timeout.rb - tests/test_iterator.rb - tests/test_kb.rb - tests/test_line_protocol.rb - tests/test_ltp.rb - tests/test_ltp2.rb - tests/test_many_fds.rb - tests/test_next_tick.rb - tests/test_object_protocol.rb - tests/test_pause.rb - tests/test_pending_connect_timeout.rb - tests/test_pool.rb - tests/test_process_watch.rb - tests/test_processes.rb - tests/test_proxy_connection.rb - tests/test_pure.rb - tests/test_queue.rb - tests/test_resolver.rb - tests/test_running.rb - tests/test_sasl.rb - tests/test_send_file.rb - tests/test_servers.rb - tests/test_set_sock_opt.rb - tests/test_shutdown_hooks.rb - tests/test_smtpclient.rb - tests/test_smtpserver.rb - tests/test_spawn.rb - tests/test_ssl_args.rb - tests/test_ssl_methods.rb - tests/test_ssl_verify.rb - tests/test_stomp.rb - tests/test_system.rb - tests/test_threaded_resource.rb - tests/test_tick_loop.rb - tests/test_timers.rb - tests/test_ud.rb - tests/test_unbind_reason.rb homepage: http://rubyeventmachine.com licenses: - Ruby - GPL metadata: {} post_install_message: rdoc_options: - "--title" - EventMachine - "--main" - README.md - "-x" - lib/em/version - "-x" - lib/jeventmachine require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: eventmachine rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: Ruby/EventMachine library test_files: [] has_rdoc: eventmachine-1.0.7/rakelib/0000755000004100000410000000000012511426257015633 5ustar www-datawww-dataeventmachine-1.0.7/rakelib/package.rake0000644000004100000410000000525212511426257020076 0ustar www-datawww-datarequire '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', 'x86-mswin32-60'] 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] eventmachine-1.0.7/rakelib/test.rake0000644000004100000410000000023212511426257017453 0ustar www-datawww-datarequire 'rake/testtask' Rake::TestTask.new(:test) do |t| t.libs << "tests" t.libs << "lib" t.pattern = 'tests/**/test_*.rb' t.warning = true end eventmachine-1.0.7/rakelib/cpp.rake_example0000644000004100000410000000435312511426257021001 0ustar www-datawww-data# EventMachine C++ Rakefile Stab Case # TODO : track header files as a build dependency... # TODO : cross platform support # TODO : configure style functionality namespace :cpp do require 'rake/clean' # *nix only atm... module Cpp class < [proc { |targ| targ.sub(%r{^#{EmConfig::Path}/(.*)\.o$}, "#{EmConfig::Path}/\\1.cpp") }] do |t| Cpp.compile t.source, t.name, EmConfig::Includes, EmConfig::Flags end file "#{EmConfig::Path}/libeventmachine.a" => EmConfig::Compiled do |t| Cpp.static t.name, EmConfig::Compiled end CLEAN.include("#{EmConfig::Path}/libeventmachine.a") module AppConfig Appname = 'echo_em' Sources = FileList['*.cpp'] Compiled = Sources.sub(%r{^(.*)\.cpp}, '\\1.o') Flags = ["", EmConfig::Flags].join(' ') Includes = ["-I. -I#{EmConfig::Path}", EmConfig::Includes].join(' ') Libs = ["-L#{EmConfig::Path} -leventmachine", EmConfig::Libs].join(' ') end CLEAN.include(AppConfig::Compiled) CLEAN.include(AppConfig::Appname) rule %r{^.*\.o$} => [proc { |targ| targ.sub(%r{^(.*)\.o$}, '\\1.cpp') }] do |t| Cpp.compile t.source, t.name, AppConfig::Includes, AppConfig::Flags end file AppConfig::Appname => ["#{EmConfig::Path}/libeventmachine.a", AppConfig::Compiled] do |t| Cpp.link AppConfig::Compiled, t.name, AppConfig::Libs, AppConfig::Flags end task :build => AppConfig::Appname task :run => AppConfig::Appname do sh "./#{AppConfig::Appname}" end endeventmachine-1.0.7/.gitignore0000644000004100000410000000021112511426257016204 0ustar www-datawww-datapkg rdoc Makefile *.bundle *.dll *.so *.jar *.class *.o *.log *.def *.pdb *.dSYM java/src/.project *.rbc Gemfile.lock .yardoc/* doc/* eventmachine-1.0.7/docs/0000755000004100000410000000000012511426257015152 5ustar www-datawww-dataeventmachine-1.0.7/docs/GettingStarted.md0000644000004100000410000005075612511426257020441 0ustar www-datawww-data# @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. eventmachine-1.0.7/docs/old/0000755000004100000410000000000012511426257015730 5ustar www-datawww-dataeventmachine-1.0.7/docs/old/SPAWNED_PROCESSES0000644000004100000410000001332512511426257020406 0ustar www-datawww-dataEventMachine (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"] eventmachine-1.0.7/docs/old/INSTALL0000644000004100000410000000101512511426257016756 0ustar www-datawww-dataIf 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. eventmachine-1.0.7/docs/old/DEFERRABLES0000644000004100000410000002511412511426257017374 0ustar www-datawww-dataEventMachine (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. eventmachine-1.0.7/docs/old/PURE_RUBY0000644000004100000410000000716712511426257017302 0ustar www-datawww-dataEventMachine 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. eventmachine-1.0.7/docs/old/EPOLL0000644000004100000410000001372212511426257016533 0ustar www-datawww-dataEventMachine 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. eventmachine-1.0.7/docs/old/SMTP0000644000004100000410000000025012511426257016433 0ustar www-datawww-dataThis 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. eventmachine-1.0.7/docs/old/KEYBOARD0000644000004100000410000000230312511426257017051 0ustar www-datawww-dataEventMachine (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. eventmachine-1.0.7/docs/old/RELEASE_NOTES0000644000004100000410000000651712511426257017714 0ustar www-datawww-dataRUBY/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 eventmachine-1.0.7/docs/old/LIGHTWEIGHT_CONCURRENCY0000644000004100000410000001312512511426257021306 0ustar www-datawww-dataEventMachine (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. eventmachine-1.0.7/docs/old/TODO0000644000004100000410000000045612511426257016425 0ustar www-datawww-dataTODO 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. eventmachine-1.0.7/docs/old/ChangeLog0000644000004100000410000002645712511426257017520 0ustar www-datawww-data01Oct06: 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) eventmachine-1.0.7/docs/old/LEGAL0000644000004100000410000000150412511426257016477 0ustar www-datawww-dataLEGAL 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). eventmachine-1.0.7/docs/DocumentationGuidesIndex.md0000644000004100000410000000275412511426257022446 0ustar www-datawww-data# 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. eventmachine-1.0.7/java/0000755000004100000410000000000012511426257015143 5ustar www-datawww-dataeventmachine-1.0.7/java/src/0000755000004100000410000000000012511426257015732 5ustar www-datawww-dataeventmachine-1.0.7/java/src/com/0000755000004100000410000000000012511426257016510 5ustar www-datawww-dataeventmachine-1.0.7/java/src/com/rubyeventmachine/0000755000004100000410000000000012511426257022060 5ustar www-datawww-dataeventmachine-1.0.7/java/src/com/rubyeventmachine/EmReactor.java0000644000004100000410000004006312511426257024607 0ustar www-datawww-data/** * $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; 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 (int 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) { return Connections.get(sig).getPeerName(); } public Object[] getSockName (long sig) { return Connections.get(sig).getSockName(); } 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(); } } eventmachine-1.0.7/java/src/com/rubyeventmachine/EventableSocketChannel.java0000644000004100000410000002441412511426257027277 0ustar www-datawww-data/** * $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; } } eventmachine-1.0.7/java/src/com/rubyeventmachine/EventableDatagramChannel.java0000644000004100000410000001345412511426257027571 0ustar www-datawww-data/** * $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; } } eventmachine-1.0.7/java/src/com/rubyeventmachine/EmReactorException.java0000644000004100000410000000205112511426257026461 0ustar www-datawww-data/** * $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); } } eventmachine-1.0.7/java/src/com/rubyeventmachine/EventableChannel.java0000644000004100000410000000351212511426257026122 0ustar www-datawww-data/** * $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(); } eventmachine-1.0.7/java/.project0000644000004100000410000000056112511426257016614 0ustar www-datawww-data em_reactor org.eclipse.jdt.core.javabuilder org.eclipse.jdt.core.javanature eventmachine-1.0.7/java/.classpath0000644000004100000410000000054612511426257017133 0ustar www-datawww-data eventmachine-1.0.7/.yardopts0000644000004100000410000000020112511426257016061 0ustar www-datawww-data--no-private --protected --markup="markdown" lib/**/*.rb --main README.md --exclude jeventmachine --exclude pure_ruby - docs/*.mdeventmachine-1.0.7/LICENSE0000644000004100000410000000515512511426257015235 0ustar www-datawww-dataEventMachine 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. eventmachine-1.0.7/eventmachine.gemspec0000644000004100000410000000335312511426257020241 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.unshift File.expand_path("../lib", __FILE__) require "em/version" Gem::Specification.new do |s| s.name = 'eventmachine' s.version = EventMachine::VERSION s.homepage = 'http://rubyeventmachine.com' s.rubyforge_project = 'eventmachine' s.licenses = ["Ruby", "GPL"] s.authors = ["Francis Cianfrocca", "Aman Gupta"] s.email = ["garbagecat10@gmail.com", "aman@tmm1.net"] s.files = `git ls-files`.split("\n") s.extensions = ["ext/extconf.rb", "ext/fastfilereader/extconf.rb"] s.add_development_dependency 'test-unit', '~> 2.0' s.add_development_dependency 'rake-compiler', '~> 0.8.3' s.add_development_dependency 'yard', ">= 0.8.5.2" s.add_development_dependency 'bluecloth' unless RUBY_PLATFORM =~ /java/ s.summary = 'Ruby/EventMachine library' s.description = "EventMachine implements a fast, single-threaded engine for arbitrary network communications. It's extremely easy to use in Ruby. EventMachine wraps all interactions with IP sockets, allowing programs to concentrate on the implementation of network protocols. It can be used to create both network servers and clients. To create a server or client, a Ruby program only needs to specify the IP address and port, and provide a Module that implements the communications protocol. Implementations of several standard network protocols are provided with the package, primarily to serve as examples. The real goal of EventMachine is to enable programs to easily interface with other programs using TCP/IP, especially if custom protocols are required." s.rdoc_options = ["--title", "EventMachine", "--main", "README.md", "-x", "lib/em/version", "-x", "lib/jeventmachine"] s.extra_rdoc_files = ["README.md"] + `git ls-files -- docs/*`.split("\n") end eventmachine-1.0.7/CHANGELOG.md0000644000004100000410000000634012511426257016036 0ustar www-datawww-data# Changelog ## 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] eventmachine-1.0.7/ext/0000755000004100000410000000000012511426257015022 5ustar www-datawww-dataeventmachine-1.0.7/ext/ssl.h0000644000004100000410000000371012511426257015775 0ustar www-datawww-data/***************************************************************************** $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 string &privkeyfile, const string &certchainfile); 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 string &privkeyfile, const string &certchainfile, bool verify_peer, const unsigned long 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(); void Shutdown(); protected: SslContext_t *Context; bool bIsServer; bool bHandshakeCompleted; bool bVerifyPeer; SSL *pSSL; BIO *pbioRead; BIO *pbioWrite; PageList OutboundQ; }; extern "C" int ssl_verify_wrapper(int, X509_STORE_CTX*); #endif // WITH_SSL #endif // __SslBox__H_ eventmachine-1.0.7/ext/page.h0000644000004100000410000000205412511426257016110 0ustar www-datawww-data/***************************************************************************** $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: deque Pages; }; #endif // __PageManager__H_ eventmachine-1.0.7/ext/pipe.cpp0000644000004100000410000002405012511426257016464 0ustar www-datawww-data/***************************************************************************** $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() { // 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); 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 | (SelectForWrite() ? EPOLLOUT : 0)); assert (MyEventMachine); MyEventMachine->Modify (this); #endif } else { #ifdef OS_UNIX if ((errno != EINPROGRESS) && (errno != EWOULDBLOCK) && (errno != EINTR)) #endif #ifdef OS_WIN32 if ((errno != WSAEINPROGRESS) && (errno != 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, int 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 eventmachine-1.0.7/ext/page.cpp0000644000004100000410000000334512511426257016447 0ustar www-datawww-data/***************************************************************************** $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 runtime_error ("no memory in pagelist"); memcpy (copy, buf, size); Pages.push_back (Page (copy, size)); } } eventmachine-1.0.7/ext/ed.cpp0000644000004100000410000015117612511426257016131 0ustar www-datawww-data/***************************************************************************** $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 } /**************************************** EventableDescriptor::EventableDescriptor ****************************************/ EventableDescriptor::EventableDescriptor (int 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), 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(); #ifdef HAVE_EPOLL EpollEvent.events = 0; EpollEvent.data.ptr = this; #endif LastActivity = MyEventMachine->GetCurrentLoopTime(); } /***************************************** EventableDescriptor::~EventableDescriptor *****************************************/ EventableDescriptor::~EventableDescriptor() { 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()) 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 unsigned long 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, int size) { assert(EventCallback); if (ProxyTarget) { if (BytesToProxy > 0) { unsigned long proxied = min(BytesToProxy, (unsigned long) 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 += (unsigned long) size; } } else { (*EventCallback)(GetBinding(), EM_CONNECTION_READ, buf, size); } } /********************************************* 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 (int 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; #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); if (write && SelectForWrite()) MyEventMachine->ArmKqueueWriter (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, int 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) { int 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, int 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) */ int 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 ******************************************/ void ConnectionDescriptor::_DispatchInboundData (const char *buffer, int size) { #ifdef WITH_SSL 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) { ScheduleClose(false); return; } _CheckHandshakeStatus(); _DispatchCiphertext(); } else { _GenericInboundDispatch(buffer, size); } #endif #ifdef WITHOUT_SSL _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->UsingKqueue()) { 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. */ int 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 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; 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 ******************************/ void ConnectionDescriptor::StartTls() { #ifdef WITH_SSL if (SslBox) throw std::runtime_error ("SSL/TLS already running on connection"); SslBox = new SslBox_t (bIsServer, PrivateKeyFilename, CertChainFilename, bSslVerifyPeer, GetBinding()); _DispatchCiphertext(); #endif #ifdef WITHOUT_SSL throw std::runtime_error ("Encryption not available on this event-machine"); #endif } /********************************* ConnectionDescriptor::SetTlsParms *********************************/ void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer) { #ifdef WITH_SSL 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; #endif #ifdef WITHOUT_SSL 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::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 (int 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 (int 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 unsigned long 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_in pin; socklen_t addrlen = sizeof (pin); int accept_count = EventMachine_t::GetSimultaneousAcceptCount(); for (int i=0; i < accept_count; i++) { int sd = accept (GetSocket(), (struct sockaddr*)&pin, &addrlen); 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. // 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 (!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 = (cd->SelectForRead() ? EPOLLIN : 0) | (cd->SelectForWrite() ? EPOLLOUT : 0); #endif assert (MyEventMachine); MyEventMachine->Add (cd); #ifdef HAVE_KQUEUE if (cd->SelectForWrite()) MyEventMachine->ArmKqueueWriter (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 } /******************************* AcceptorDescriptor::GetSockname *******************************/ bool AcceptorDescriptor::GetSockname (struct sockaddr *s, socklen_t *len) { bool ok = false; if (s) { int gp = getsockname (GetSocket(), s, len); if (gp == 0) ok = true; } return ok; } /************************************** DatagramDescriptor::DatagramDescriptor **************************************/ DatagramDescriptor::DatagramDescriptor (int 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() { int 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_in 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!!! */ int 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), sizeof(op->From)); #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 | (SelectForWrite() ? EPOLLOUT : 0)); assert (MyEventMachine); MyEventMachine->Modify (this); #endif #ifdef HAVE_KQUEUE if (SelectForWrite()) MyEventMachine->ArmKqueueWriter (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, int 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 MyEventMachine->ArmKqueueWriter (this); #endif return length; } /**************************************** DatagramDescriptor::SendOutboundDatagram ****************************************/ int DatagramDescriptor::SendOutboundDatagram (const char *data, int 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; sockaddr_in pin; unsigned long HostAddr; HostAddr = inet_addr (address); if (HostAddr == INADDR_NONE) { // The nasty cast to (char*) is because Windows is brain-dead. hostent *hp = gethostbyname ((char*)address); if (!hp) return 0; HostAddr = ((in_addr*)(hp->h_addr))->s_addr; } memset (&pin, 0, sizeof(pin)); pin.sin_family = AF_INET; pin.sin_addr.s_addr = HostAddr; pin.sin_port = htons (port); 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, pin)); OutboundDataSize += length; #ifdef HAVE_EPOLL EpollEvent.events = (EPOLLIN | EPOLLOUT); assert (MyEventMachine); MyEventMachine->Modify (this); #endif #ifdef HAVE_KQUEUE MyEventMachine->ArmKqueueWriter (this); #endif return length; } /********************************* ConnectionDescriptor::GetPeername *********************************/ bool ConnectionDescriptor::GetPeername (struct sockaddr *s, socklen_t *len) { bool ok = false; if (s) { int gp = getpeername (GetSocket(), s, len); if (gp == 0) ok = true; } return ok; } /********************************* ConnectionDescriptor::GetSockname *********************************/ bool ConnectionDescriptor::GetSockname (struct sockaddr *s, socklen_t *len) { bool ok = false; if (s) { int gp = getsockname (GetSocket(), s, len); if (gp == 0) ok = true; } return ok; } /********************************************** 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(struct sockaddr); memset (s, 0, sizeof(struct sockaddr)); memcpy (s, &ReturnAddress, sizeof(ReturnAddress)); ok = true; } return ok; } /******************************* DatagramDescriptor::GetSockname *******************************/ bool DatagramDescriptor::GetSockname (struct sockaddr *s, socklen_t *len) { bool ok = false; if (s) { int gp = getsockname (GetSocket(), s, len); if (gp == 0) 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"); } eventmachine-1.0.7/ext/rubymain.cpp0000644000004100000410000011003512511426257017354 0ustar www-datawww-data/***************************************************************************** $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 /******* 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 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_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 { unsigned long signature; int event; const char *data_str; unsigned long data_num; }; static inline VALUE ensure_conn(const unsigned long signature) { VALUE conn = rb_hash_aref (EmConnsHash, ULONG2NUM (signature)); if (conn == Qnil) rb_raise (EM_eConnectionNotBound, "unknown connection: %lu", signature); return conn; } /**************** t_event_callback ****************/ static inline void event_callback (struct em_event* e) { const unsigned long 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, ULONG2NUM (signature)); if (conn == Qnil) rb_raise (EM_eConnectionNotBound, "received %lu bytes of data for unknown signature: %lu", 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, ULONG2NUM(signature), INT2FIX(event), ULONG2NUM(data_num)); return; } case EM_CONNECTION_UNBOUND: { rb_funcall (EmModule, Intern_event_callback, 3, ULONG2NUM(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 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 unsigned long 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) { 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_without_threads *****************************/ static VALUE t_run_machine_without_threads (VALUE self) { evma_run_machine(); return Qnil; } /******************* t_add_oneshot_timer *******************/ static VALUE t_add_oneshot_timer (VALUE self, VALUE interval) { const unsigned long f = evma_install_oneshot_timer (FIX2INT (interval)); if (!f) rb_raise (rb_eRuntimeError, "%s", "ran out of timers; use #set_max_timers to increase limit"); return ULONG2NUM (f); } /************** t_start_server **************/ static VALUE t_start_server (VALUE self, VALUE server, VALUE port) { const unsigned long f = evma_create_tcp_server (StringValuePtr(server), FIX2INT(port)); if (!f) rb_raise (rb_eRuntimeError, "%s", "no acceptor (port is in use or requires root privileges)"); return ULONG2NUM (f); } /************* t_stop_server *************/ static VALUE t_stop_server (VALUE self, VALUE signature) { evma_stop_tcp_server (NUM2ULONG (signature)); return Qnil; } /******************* t_start_unix_server *******************/ static VALUE t_start_unix_server (VALUE self, VALUE filename) { const unsigned long f = evma_create_unix_domain_server (StringValuePtr(filename)); if (!f) rb_raise (rb_eRuntimeError, "%s", "no unix-domain acceptor"); return ULONG2NUM (f); } /******************** t_attach_sd ********************/ static VALUE t_attach_sd(VALUE self, VALUE sd) { const unsigned long f = evma_attach_sd(FIX2INT(sd)); if (!f) rb_raise (rb_eRuntimeError, "%s", "no socket descriptor acceptor"); return ULONG2NUM (f); } /*********** t_send_data ***********/ static VALUE t_send_data (VALUE self, VALUE signature, VALUE data, VALUE data_length) { int b = evma_send_data_to_connection (NUM2ULONG (signature), StringValuePtr (data), FIX2INT (data_length)); return INT2NUM (b); } /*********** t_start_tls ***********/ static VALUE t_start_tls (VALUE self, VALUE signature) { evma_start_tls (NUM2ULONG (signature)); return Qnil; } /*************** t_set_tls_parms ***************/ static VALUE t_set_tls_parms (VALUE self, VALUE signature, VALUE privkeyfile, VALUE certchainfile, VALUE verify_peer) { /* 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 (NUM2ULONG (signature), StringValuePtr (privkeyfile), StringValuePtr (certchainfile), (verify_peer == Qtrue ? 1 : 0)); return Qnil; } /*************** t_get_peer_cert ***************/ static VALUE t_get_peer_cert (VALUE self, VALUE signature) { VALUE ret = Qnil; #ifdef WITH_SSL X509 *cert = NULL; BUF_MEM *buf; BIO *out; cert = evma_get_peer_cert (NUM2ULONG (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); } #endif return ret; } /************** t_get_peername **************/ static VALUE t_get_peername (VALUE self, VALUE signature) { char buf[1024]; socklen_t len = sizeof buf; if (evma_get_peername (NUM2ULONG (signature), (struct sockaddr*)buf, &len)) { return rb_str_new (buf, len); } return Qnil; } /************** t_get_sockname **************/ static VALUE t_get_sockname (VALUE self, VALUE signature) { char buf[1024]; socklen_t len = sizeof buf; if (evma_get_sockname (NUM2ULONG (signature), (struct sockaddr*)buf, &len)) { return rb_str_new (buf, len); } return Qnil; } /******************** t_get_subprocess_pid ********************/ static VALUE t_get_subprocess_pid (VALUE self, VALUE signature) { pid_t pid; if (evma_get_subprocess_pid (NUM2ULONG (signature), &pid)) { return INT2NUM (pid); } return Qnil; } /*********************** t_get_subprocess_status ***********************/ static VALUE t_get_subprocess_status (VALUE self, VALUE signature) { VALUE proc_status = Qnil; int status; pid_t pid; if (evma_get_subprocess_status (NUM2ULONG (signature), &status)) { if (evma_get_subprocess_pid (NUM2ULONG (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) { return INT2NUM(evma_get_connection_count()); } /***************************** t_get_comm_inactivity_timeout *****************************/ static VALUE t_get_comm_inactivity_timeout (VALUE self, VALUE signature) { return rb_float_new(evma_get_comm_inactivity_timeout(NUM2ULONG (signature))); } /***************************** t_set_comm_inactivity_timeout *****************************/ static VALUE t_set_comm_inactivity_timeout (VALUE self, VALUE signature, VALUE timeout) { float ti = RFLOAT_VALUE(timeout); if (evma_set_comm_inactivity_timeout(NUM2ULONG(signature), ti)) { return Qtrue; } return Qfalse; } /***************************** t_get_pending_connect_timeout *****************************/ static VALUE t_get_pending_connect_timeout (VALUE self, VALUE signature) { return rb_float_new(evma_get_pending_connect_timeout(NUM2ULONG (signature))); } /***************************** t_set_pending_connect_timeout *****************************/ static VALUE t_set_pending_connect_timeout (VALUE self, VALUE signature, VALUE timeout) { float ti = RFLOAT_VALUE(timeout); if (evma_set_pending_connect_timeout(NUM2ULONG(signature), ti)) { return Qtrue; } return Qfalse; } /*************** t_send_datagram ***************/ static VALUE t_send_datagram (VALUE self, VALUE signature, VALUE data, VALUE data_length, VALUE address, VALUE port) { int b = evma_send_datagram (NUM2ULONG (signature), StringValuePtr (data), FIX2INT (data_length), StringValuePtr(address), FIX2INT(port)); return INT2NUM (b); } /****************** t_close_connection ******************/ static VALUE t_close_connection (VALUE self, VALUE signature, VALUE after_writing) { evma_close_connection (NUM2ULONG (signature), ((after_writing == Qtrue) ? 1 : 0)); return Qnil; } /******************************** t_report_connection_error_status ********************************/ static VALUE t_report_connection_error_status (VALUE self, VALUE signature) { int b = evma_report_connection_error_status (NUM2ULONG (signature)); return INT2NUM (b); } /**************** t_connect_server ****************/ static VALUE t_connect_server (VALUE self, 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 unsigned long f = evma_connect_to_server (NULL, 0, StringValuePtr(server), NUM2INT(port)); if (!f) rb_raise (EM_eConnectionError, "%s", "no connection"); return ULONG2NUM (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, 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 unsigned long f = evma_connect_to_server (StringValuePtr(bind_addr), NUM2INT(bind_port), StringValuePtr(server), NUM2INT(port)); if (!f) rb_raise (EM_eConnectionError, "%s", "no connection"); return ULONG2NUM (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, VALUE serversocket) { const unsigned long f = evma_connect_to_unix_server (StringValuePtr(serversocket)); if (!f) rb_raise (rb_eRuntimeError, "%s", "no connection"); return ULONG2NUM (f); } /*********** t_attach_fd ***********/ static VALUE t_attach_fd (VALUE self, VALUE file_descriptor, VALUE watch_mode) { const unsigned long f = evma_attach_fd (NUM2INT(file_descriptor), watch_mode == Qtrue); if (!f) rb_raise (rb_eRuntimeError, "%s", "no connection"); return ULONG2NUM (f); } /*********** t_detach_fd ***********/ static VALUE t_detach_fd (VALUE self, VALUE signature) { return INT2NUM(evma_detach_fd (NUM2ULONG (signature))); } /********************* t_get_file_descriptor *********************/ static VALUE t_get_file_descriptor (VALUE self, VALUE signature) { return INT2NUM(evma_get_file_descriptor (NUM2ULONG (signature))); } /************** t_get_sock_opt **************/ static VALUE t_get_sock_opt (VALUE self, VALUE signature, VALUE lev, VALUE optname) { int fd = evma_get_file_descriptor (NUM2ULONG (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, VALUE signature, VALUE lev, VALUE optname, VALUE optval) { int fd = evma_get_file_descriptor (NUM2ULONG (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, VALUE signature) { return evma_is_notify_readable(NUM2ULONG (signature)) ? Qtrue : Qfalse; } /********************* t_set_notify_readable *********************/ static VALUE t_set_notify_readable (VALUE self, VALUE signature, VALUE mode) { evma_set_notify_readable(NUM2ULONG (signature), mode == Qtrue); return Qnil; } /******************** t_is_notify_readable ********************/ static VALUE t_is_notify_writable (VALUE self, VALUE signature) { return evma_is_notify_writable(NUM2ULONG (signature)) ? Qtrue : Qfalse; } /********************* t_set_notify_writable *********************/ static VALUE t_set_notify_writable (VALUE self, VALUE signature, VALUE mode) { evma_set_notify_writable(NUM2ULONG (signature), mode == Qtrue); return Qnil; } /******* t_pause *******/ static VALUE t_pause (VALUE self, VALUE signature) { return evma_pause(NUM2ULONG (signature)) ? Qtrue : Qfalse; } /******** t_resume ********/ static VALUE t_resume (VALUE self, VALUE signature) { return evma_resume(NUM2ULONG (signature)) ? Qtrue : Qfalse; } /********** t_paused_p **********/ static VALUE t_paused_p (VALUE self, VALUE signature) { return evma_is_paused(NUM2ULONG (signature)) ? Qtrue : Qfalse; } /********************* t_num_close_scheduled *********************/ static VALUE t_num_close_scheduled (VALUE self) { return INT2FIX(evma_num_close_scheduled()); } /***************** t_open_udp_socket *****************/ static VALUE t_open_udp_socket (VALUE self, VALUE server, VALUE port) { const unsigned long f = evma_open_datagram_socket (StringValuePtr(server), FIX2INT(port)); if (!f) rb_raise (rb_eRuntimeError, "%s", "no datagram socket"); return ULONG2NUM (f); } /***************** t_release_machine *****************/ static VALUE t_release_machine (VALUE self) { evma_release_library(); return Qnil; } /****** t_stop ******/ static VALUE t_stop (VALUE self) { evma_stop_machine(); return Qnil; } /****************** t_signal_loopbreak ******************/ static VALUE t_signal_loopbreak (VALUE self) { evma_signal_loopbreak(); return Qnil; } /************** t_library_type **************/ static VALUE t_library_type (VALUE self) { return rb_eval_string (":extension"); } /******************* t_set_timer_quantum *******************/ static VALUE t_set_timer_quantum (VALUE self, VALUE interval) { evma_set_timer_quantum (FIX2INT (interval)); return Qnil; } /******************** t_get_max_timer_count ********************/ static VALUE t_get_max_timer_count (VALUE self) { return INT2FIX (evma_get_max_timer_count()); } /******************** t_set_max_timer_count ********************/ static VALUE t_set_max_timer_count (VALUE self, 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) { return INT2FIX (evma_get_simultaneous_accept_count()); } static VALUE t_set_simultaneous_accept_count (VALUE self, VALUE ct) { evma_set_simultaneous_accept_count (FIX2INT (ct)); return Qnil; } /*************** t_setuid_string ***************/ static VALUE t_setuid_string (VALUE self, VALUE username) { evma_setuid_string (StringValuePtr (username)); return Qnil; } /************** t_invoke_popen **************/ static VALUE t_invoke_popen (VALUE self, VALUE cmd) { // 1.8.7+ #ifdef RARRAY_LEN int len = RARRAY_LEN(cmd); #else int len = RARRAY (cmd)->len; #endif 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] = StringValuePtr (s); } strings[len] = NULL; unsigned long f = 0; try { f = evma_popen (strings); } catch (std::runtime_error e) { f = 0; // raise exception below } 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 ULONG2NUM (f); } /*************** t_read_keyboard ***************/ static VALUE t_read_keyboard (VALUE self) { const unsigned long f = evma_open_keyboard(); if (!f) rb_raise (rb_eRuntimeError, "%s", "no keyboard reader"); return ULONG2NUM (f); } /**************** t_watch_filename ****************/ static VALUE t_watch_filename (VALUE self, VALUE fname) { try { return ULONG2NUM(evma_watch_filename(StringValuePtr(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, VALUE sig) { evma_unwatch_filename(NUM2ULONG (sig)); return Qnil; } /*********** t_watch_pid ***********/ static VALUE t_watch_pid (VALUE self, VALUE pid) { try { return ULONG2NUM(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, VALUE sig) { evma_unwatch_pid(NUM2ULONG (sig)); return Qnil; } /********** t__epoll_p **********/ static VALUE t__epoll_p (VALUE self) { #ifdef HAVE_EPOLL return Qtrue; #else return Qfalse; #endif } /******** t__epoll ********/ static VALUE t__epoll (VALUE self) { evma_set_epoll (1); return Qtrue; } /*********** t__epoll_set ***********/ static VALUE t__epoll_set (VALUE self, VALUE val) { if (t__epoll_p(self) == Qfalse) 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) { #ifdef HAVE_KQUEUE return Qtrue; #else return Qfalse; #endif } /********* t__kqueue *********/ static VALUE t__kqueue (VALUE self) { evma_set_kqueue (1); return Qtrue; } /************* t__kqueue_set *************/ static VALUE t__kqueue_set (VALUE self, VALUE val) { if (t__kqueue_p(self) == Qfalse) 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) { #ifdef WITH_SSL return Qtrue; #else return Qfalse; #endif } /**************** t_send_file_data ****************/ static VALUE t_send_file_data (VALUE self, 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 (NUM2ULONG (signature), StringValuePtr(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", StringValuePtr(filename),(err?err:"???")); rb_raise (rb_eIOError, "%s", buf); } return INT2NUM (0); } /******************* t_set_rlimit_nofile *******************/ static VALUE t_set_rlimit_nofile (VALUE self, 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 (NUM2ULONG (sig))); } /****************************** conn_associate_callback_target ******************************/ static VALUE conn_associate_callback_target (VALUE self, VALUE sig) { // No-op for the time being. return Qnil; } /*************** t_get_loop_time ****************/ static VALUE t_get_loop_time (VALUE self) { #ifndef HAVE_RB_TIME_NEW static VALUE cTime = rb_path2class("Time"); static ID at = rb_intern("at"); #endif uint64_t current_time = evma_get_current_loop_time(); if (current_time != 0) { #ifndef HAVE_RB_TIME_NEW return rb_funcall(cTime, at, 2, INT2NUM(current_time / 1000000), INT2NUM(current_time % 1000000)); #else return rb_time_new(current_time / 1000000, current_time % 1000000); #endif } return Qnil; } /************* t_start_proxy **************/ static VALUE t_start_proxy (VALUE self, VALUE from, VALUE to, VALUE bufsize, VALUE length) { try { evma_start_proxy(NUM2ULONG (from), NUM2ULONG (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, VALUE from) { try{ evma_stop_proxy(NUM2ULONG (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, VALUE from) { try{ return ULONG2NUM(evma_proxied_bytes(NUM2ULONG (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, VALUE from) { try{ uint64_t current_time = evma_get_current_loop_time(); uint64_t time = evma_get_last_activity_time(NUM2ULONG (from)); if (current_time != 0 && time != 0) { if (time >= current_time) return ULONG2NUM(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) { return rb_float_new(evma_get_heartbeat_interval()); } /************************ t_set_heartbeat_interval *************************/ static VALUE t_set_heartbeat_interval (VALUE self, 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_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); rb_define_module_function (EmModule, "initialize_event_machine", (VALUE(*)(...))t_initialize_event_machine, 0); rb_define_module_function (EmModule, "run_machine", (VALUE(*)(...))t_run_machine_without_threads, 0); rb_define_module_function (EmModule, "run_machine_without_threads", (VALUE(*)(...))t_run_machine_without_threads, 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, 4); 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, "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_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); rb_define_const (EmModule, "TimerFired", INT2NUM(100)); rb_define_const (EmModule, "ConnectionData", INT2NUM(101)); rb_define_const (EmModule, "ConnectionUnbound", INT2NUM(102)); rb_define_const (EmModule, "ConnectionAccepted", INT2NUM(103)); rb_define_const (EmModule, "ConnectionCompleted", INT2NUM(104)); rb_define_const (EmModule, "LoopbreakSignalled", INT2NUM(105)); rb_define_const (EmModule, "ConnectionNotifyReadable", INT2NUM(106)); rb_define_const (EmModule, "ConnectionNotifyWritable", INT2NUM(107)); rb_define_const (EmModule, "SslHandshakeCompleted", INT2NUM(108)); } eventmachine-1.0.7/ext/extconf.rb0000644000004100000410000001424612511426257017024 0ustar www-datawww-datarequire 'fileutils' 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 ## # 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 def manual_ssl_config ssl_libs_heads_args = { :unix => [%w[ssl crypto], %w[openssl/ssl.h openssl/err.h]], :mswin => [%w[ssleay32 eay32], %w[openssl/ssl.h openssl/err.h]], } dc_flags = ['ssl'] dc_flags += ["#{ENV['OPENSSL']}/include", ENV['OPENSSL']] if /linux/ =~ RUBY_PLATFORM and ENV['OPENSSL'] libs, heads = case RUBY_PLATFORM when /mswin/ ; ssl_libs_heads_args[:mswin] else ssl_libs_heads_args[:unix] end dir_config(*dc_flags) check_libs(libs) and check_heads(heads) end # Eager check devs tools have_devel? if respond_to?(:have_devel?) if ENV['CROSS_COMPILING'] openssl_version = ENV.fetch("OPENSSL_VERSION", "1.0.1i") 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 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 end # Try to use pkg_config first, fixes #73 if (!ENV['CROSS_COMPILING'] and pkg_config('openssl')) || manual_ssl_config add_define "WITH_SSL" else add_define "WITHOUT_SSL" end add_define 'BUILD_FOR_RUBY' add_define 'HAVE_RBTRAP' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h']) add_define "HAVE_TBR" if have_func('rb_thread_blocking_region')# and have_macro('RUBY_UBF_IO', 'ruby.h') add_define "HAVE_RB_THREAD_CALL_WITHOUT_GVL" if have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') 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') add_define 'HAVE_WRITEV' if have_func('writev', 'sys/uio.h') add_define 'HAVE_RB_THREAD_FD_SELECT' if have_func('rb_thread_fd_select') add_define 'HAVE_RB_FDSET_T' if 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') # 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") and 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 when /solaris/ add_define 'OS_SOLARIS8' check_libs(%w[nsl socket], true) if CONFIG['CC'] == 'cc' and `cc -flags 2>&1` =~ /Sun/ # detect SUNWspro 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" 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) -shared -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 # solaris c++ compiler doesn't have make_pair() TRY_LINK.sub!('$(CC)', '$(CXX)') 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" eventmachine-1.0.7/ext/eventmachine.h0000644000004100000410000001200612511426257017640 0ustar www-datawww-data/***************************************************************************** $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 }; void evma_initialize_library (EMCallback); void evma_run_machine(); void evma_release_library(); const unsigned long evma_install_oneshot_timer (int seconds); const unsigned long evma_connect_to_server (const char *bind_addr, int bind_port, const char *server, int port); const unsigned long evma_connect_to_unix_server (const char *server); const unsigned long evma_attach_fd (int file_descriptor, int watch_mode); int evma_detach_fd (const unsigned long binding); int evma_get_file_descriptor (const unsigned long binding); int evma_is_notify_readable (const unsigned long binding); void evma_set_notify_readable (const unsigned long binding, int mode); int evma_is_notify_writable (const unsigned long binding); void evma_set_notify_writable (const unsigned long binding, int mode); int evma_pause(const unsigned long binding); int evma_is_paused(const unsigned long binding); int evma_resume(const unsigned long binding); int evma_num_close_scheduled(); void evma_stop_tcp_server (const unsigned long signature); const unsigned long evma_create_tcp_server (const char *address, int port); const unsigned long evma_create_unix_domain_server (const char *filename); const unsigned long evma_attach_sd (int sd); const unsigned long evma_open_datagram_socket (const char *server, int port); const unsigned long evma_open_keyboard(); void evma_set_tls_parms (const unsigned long binding, const char *privatekey_filename, const char *certchain_filenane, int verify_peer); void evma_start_tls (const unsigned long binding); #ifdef WITH_SSL X509 *evma_get_peer_cert (const unsigned long binding); void evma_accept_ssl_peer (const unsigned long binding); #endif int evma_get_peername (const unsigned long binding, struct sockaddr*, socklen_t*); int evma_get_sockname (const unsigned long binding, struct sockaddr*, socklen_t*); int evma_get_subprocess_pid (const unsigned long binding, pid_t*); int evma_get_subprocess_status (const unsigned long binding, int*); int evma_get_connection_count(); int evma_send_data_to_connection (const unsigned long binding, const char *data, int data_length); int evma_send_datagram (const unsigned long binding, const char *data, int data_length, const char *address, int port); float evma_get_comm_inactivity_timeout (const unsigned long binding); int evma_set_comm_inactivity_timeout (const unsigned long binding, float value); float evma_get_pending_connect_timeout (const unsigned long binding); int evma_set_pending_connect_timeout (const unsigned long binding, float value); int evma_get_outbound_data_size (const unsigned long binding); uint64_t evma_get_last_activity_time (const unsigned long); int evma_send_file_data_to_connection (const unsigned long binding, const char *filename); void evma_close_connection (const unsigned long binding, int after_writing); int evma_report_connection_error_status (const unsigned long 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(); float evma_get_heartbeat_interval(); int evma_set_heartbeat_interval(float); const unsigned long evma_popen (char * const*cmd_strings); const unsigned long evma_watch_filename (const char *fname); void evma_unwatch_filename (const unsigned long); const unsigned long evma_watch_pid (int); void evma_unwatch_pid (const unsigned long); void evma_start_proxy(const unsigned long, const unsigned long, const unsigned long, const unsigned long); void evma_stop_proxy(const unsigned long); unsigned long evma_proxied_bytes(const unsigned long); 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_ eventmachine-1.0.7/ext/ed.h0000644000004100000410000002451512511426257015572 0ustar www-datawww-data/***************************************************************************** $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); /************************* class EventableDescriptor *************************/ class EventableDescriptor: public Bindable_t { public: EventableDescriptor (int, EventMachine_t*); virtual ~EventableDescriptor(); int 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*) {return false;} virtual bool GetSockname (struct sockaddr*, socklen_t*) {return false;} virtual bool GetSubprocessPid (pid_t*) {return false;} virtual void StartTls() {} virtual void SetTlsParms (const char *, const char *, bool) {} #ifdef WITH_SSL virtual X509 *GetPeerCert() {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 virtual void StartProxy(const unsigned long, 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*,int){ 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: int MySocket; bool bAttached; bool bWatchOnly; EMCallback EventCallback; void _GenericInboundDispatch(const char*, int); 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 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 (int, EventMachine_t*); virtual ~LoopbreakDescriptor() {} virtual void Read(); virtual void Write(); virtual void Heartbeat() {} virtual bool SelectForRead() {return true;} virtual bool SelectForWrite() {return false;} }; /************************** class ConnectionDescriptor **************************/ class ConnectionDescriptor: public EventableDescriptor { public: ConnectionDescriptor (int, EventMachine_t*); virtual ~ConnectionDescriptor(); int SendOutboundData (const char*, int); 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 *privkey_filename, const char *certchain_filename, bool verify_peer); #ifdef WITH_SSL virtual X509 *GetPeerCert(); virtual bool VerifySslPeer(const char*); virtual void AcceptSslPeer(); #endif void SetServerMode() {bIsServer = true;} virtual bool GetPeername (struct sockaddr*, socklen_t*); virtual bool GetSockname (struct sockaddr*, socklen_t*); 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; deque OutboundPages; int OutboundDataSize; #ifdef WITH_SSL SslBox_t *SslBox; std::string CertChainFilename; std::string PrivateKeyFilename; bool bHandshakeSignaled; bool bSslVerifyPeer; 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, int size); void _DispatchCiphertext(); int _SendRawOutboundData (const char*, int); void _CheckHandshakeStatus(); }; /************************ class DatagramDescriptor ************************/ class DatagramDescriptor: public EventableDescriptor { public: DatagramDescriptor (int, 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*, int); int SendOutboundDatagram (const char*, int, 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*, socklen_t*); virtual bool GetSockname (struct sockaddr*, socklen_t*); virtual uint64_t GetCommInactivityTimeout(); virtual int SetCommInactivityTimeout (uint64_t value); protected: struct OutboundPage { OutboundPage (const char *b, int l, struct sockaddr_in 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_in From; }; deque OutboundPages; int OutboundDataSize; struct sockaddr_in ReturnAddress; }; /************************ class AcceptorDescriptor ************************/ class AcceptorDescriptor: public EventableDescriptor { public: AcceptorDescriptor (int, 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 GetSockname (struct sockaddr*, socklen_t*); static void StopAcceptor (const unsigned long binding); }; /******************** class PipeDescriptor ********************/ #ifdef OS_UNIX class PipeDescriptor: public EventableDescriptor { public: PipeDescriptor (int, pid_t, EventMachine_t*); virtual ~PipeDescriptor(); virtual void Read(); virtual void Write(); virtual void Heartbeat(); virtual bool SelectForRead(); virtual bool SelectForWrite(); int SendOutboundData (const char*, int); virtual int GetOutboundDataSize() {return OutboundDataSize;} 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; 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;} 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;} }; #endif // __EventableDescriptor__H_ eventmachine-1.0.7/ext/project.h0000644000004100000410000000733612511426257016652 0ustar www-datawww-data/***************************************************************************** $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_ #ifdef OS_WIN32 #pragma warning(disable:4786) #endif #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 // 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 using namespace std; #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 #include "binder.h" #include "em.h" #include "ed.h" #include "page.h" #include "ssl.h" #include "eventmachine.h" #endif // __Project__H_ eventmachine-1.0.7/ext/em.cpp0000644000004100000410000020067512511426257016141 0ustar www-datawww-data/***************************************************************************** $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 convert strings to internet addresses. IPv6-aware. * Not reentrant or threadsafe, optimized for speed. */ static struct sockaddr *name2address (const char *server, int port, int *family, int *bind_size); /*************************************** 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): NumCloseScheduled (0), HeartbeatInterval(2000000), EventCallback (event_callback), NextHeartbeatTime (0), LoopBreakerReader (-1), LoopBreakerWriter (-1), bTerminateSignalReceived (false), bEpoll (false), epfd (-1), bKqueue (false), kqfd (-1), inotify (NULL) { // Default time-slice is just smaller than one hundred mills. Quantum.tv_sec = 0; Quantum.tv_usec = 90000; /* Initialize monotonic timekeeping on OS X before the first call to GetRealTime */ #ifdef OS_DARWIN (void) mach_timebase_info(&mach_timebase); #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()) { map::iterator f = Files.begin(); UnwatchFile (f->first); } if (epfd != -1) close (epfd); if (kqfd != -1) close (kqfd); delete SelectData; } /************************* EventMachine_t::_UseEpoll *************************/ void EventMachine_t::_UseEpoll() { /* Temporary. * Use an internal flag to switch in epoll-based functionality until we determine * how it should be integrated properly and the extent of the required changes. * A permanent solution needs to allow the integration of additional technologies, * like kqueue and Solaris's events. */ #ifdef HAVE_EPOLL bEpoll = true; #endif } /************************** EventMachine_t::_UseKqueue **************************/ void EventMachine_t::_UseKqueue() { /* Temporary. * See comments under _UseEpoll. */ #ifdef HAVE_KQUEUE bKqueue = true; #endif } /**************************** 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(); } /******************************* 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 *************************************/ 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. */ #ifdef OS_UNIX if (!username || !*username) throw std::runtime_error ("setuid_string failed: no username specified"); struct passwd *p = getpwnam (username); if (!p) 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. #endif } /**************************************** (STATIC) EventMachine_t::SetRlimitNofile ****************************************/ int EventMachine_t::SetRlimitNofile (int nofiles) { #ifdef OS_UNIX 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; #endif #ifdef OS_WIN32 // No meaningful implementation on Windows. return 0; #endif } /********************************* EventMachine_t::SignalLoopBreaker *********************************/ void EventMachine_t::SignalLoopBreaker() { #ifdef OS_UNIX 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 (pipe (fd)) 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 int sd = socket (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 } /*************************** 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) { 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 (multimap::value_type (heartbeat, ed)); #else Heartbeats.insert (make_pair (heartbeat, ed)); #endif } } /****************************** EventMachine_t::ClearHeartbeat ******************************/ void EventMachine_t::ClearHeartbeat(uint64_t key, EventableDescriptor* ed) { multimap::iterator it; pair::iterator,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() { #ifdef HAVE_EPOLL if (bEpoll) { 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 (bKqueue) { 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 while (true) { _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(); _RunOnce(); if (bTerminateSignalReceived) break; } } /************************ EventMachine_t::_RunOnce ************************/ void EventMachine_t::_RunOnce() { if (bEpoll) _RunEpollOnce(); else if (bKqueue) _RunKqueueOnce(); else _RunSelectOnce(); _DispatchHeartbeats(); _CleanupSockets(); } /***************************** 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 ******************************/ void EventMachine_t::_RunKqueueOnce() { #ifdef HAVE_KQUEUE 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 cerr << "Discarding unknown kqueue event " << ke->filter << 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 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()) { multimap::iterator heartbeats = Heartbeats.begin(); next_event = heartbeats->first; } if (!Timers.empty()) { 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 (bEpoll) { 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 *********************************/ void EventMachine_t::_ModifyEpollEvent (EventableDescriptor *ed) { #ifdef HAVE_EPOLL if (bEpoll) { 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); } } #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_TBR) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) static VALUE _SelectDataSelect (void *v) { SelectData_t *sd = (SelectData_t*)v; sd->nSockets = select (sd->maxsocket+1, rb_fd_ptr(&(sd->fdreads)), rb_fd_ptr(&(sd->fdwrites)), rb_fd_ptr(&(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); int 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); int 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; int 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]; 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) { 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 unsigned long EventMachine_t::InstallOneshotTimer (int 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 multimap::iterator i = Timers.insert (multimap::value_type (fire_at, t)); #else multimap::iterator i = Timers.insert (make_pair (fire_at, t)); #endif return i->second.GetBinding(); } /******************************* EventMachine_t::ConnectToServer *******************************/ const unsigned long 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"); int family, bind_size; struct sockaddr bind_as, *bind_as_ptr = name2address (server, port, &family, &bind_size); if (!bind_as_ptr) throw std::runtime_error ("unable to resolve server address"); bind_as = *bind_as_ptr; // copy because name2address points to a static int sd = socket (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) { int bind_to_size, bind_to_family; struct sockaddr *bind_to = name2address (bind_addr, bind_port, &bind_to_family, &bind_to_size); if (!bind_to) { close (sd); throw std::runtime_error ("invalid bind address"); } if (bind (sd, bind_to, bind_to_size) < 0) { close (sd); throw std::runtime_error ("couldn't bind to address"); } } unsigned long out = 0; int e = 0; #ifdef OS_UNIX //if (connect (sd, (sockaddr*)&pin, sizeof pin) == 0) { if (connect (sd, &bind_as, bind_size) == 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 = error; } } else { // The error from connect was something other then EINPROGRESS (EHOSTDOWN, etc). // Fall through to the !out case below e = 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); cd->ScheduleClose (false); Add (cd); out = cd->GetBinding(); } #endif #ifdef OS_WIN32 //if (connect (sd, (sockaddr*)&pin, sizeof pin) == 0) { if (connect (sd, &bind_as, bind_size) == 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 ***********************************/ const unsigned long 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. */ #ifdef OS_WIN32 throw std::runtime_error ("unix-domain connection unavailable on this platform"); return 0; #endif // The whole rest of this function is only compiled on Unix systems. #ifdef OS_UNIX unsigned long 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); int fd = socket (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; #endif } /************************ EventMachine_t::AttachFD ************************/ const unsigned long EventMachine_t::AttachFD (int fd, bool watch_mode) { #ifdef OS_UNIX if (fcntl(fd, F_GETFL, 0) < 0) 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 unsigned long out = cd->GetBinding(); return out; } /************************ EventMachine_t::DetachFD ************************/ int EventMachine_t::DetachFD (EventableDescriptor *ed) { if (!ed) throw std::runtime_error ("detaching bad descriptor"); int fd = ed->GetSocket(); #ifdef HAVE_EPOLL if (bEpoll) { 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 (bKqueue) { // 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 ************/ struct sockaddr *name2address (const char *server, int port, int *family, int *bind_size) { // THIS IS NOT RE-ENTRANT OR THREADSAFE. Optimize for speed. // Check the more-common cases first. // Return NULL if no resolution. static struct sockaddr_in in4; #ifndef __CYGWIN__ static struct sockaddr_in6 in6; #endif struct hostent *hp; if (!server || !*server) server = "0.0.0.0"; memset (&in4, 0, sizeof(in4)); if ( (in4.sin_addr.s_addr = inet_addr (server)) != INADDR_NONE) { if (family) *family = AF_INET; if (bind_size) *bind_size = sizeof(in4); in4.sin_family = AF_INET; in4.sin_port = htons (port); return (struct sockaddr*)&in4; } #if defined(OS_UNIX) && !defined(__CYGWIN__) memset (&in6, 0, sizeof(in6)); if (inet_pton (AF_INET6, server, in6.sin6_addr.s6_addr) > 0) { if (family) *family = AF_INET6; if (bind_size) *bind_size = sizeof(in6); in6.sin6_family = AF_INET6; in6.sin6_port = htons (port); return (struct sockaddr*)&in6; } #endif #ifdef OS_WIN32 // TODO, must complete this branch. Windows doesn't have inet_pton. // A possible approach is to make a getaddrinfo call with the supplied // server address, constraining the hints to ipv6 and seeing if we // get any addresses. // For the time being, Ipv6 addresses aren't supported on Windows. #endif hp = gethostbyname ((char*)server); // Windows requires the cast. if (hp) { in4.sin_addr.s_addr = ((in_addr*)(hp->h_addr))->s_addr; if (family) *family = AF_INET; if (bind_size) *bind_size = sizeof(in4); in4.sin_family = AF_INET; in4.sin_port = htons (port); return (struct sockaddr*)&in4; } return NULL; } /******************************* EventMachine_t::CreateTcpServer *******************************/ const unsigned long 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. */ int family, bind_size; struct sockaddr *bind_here = name2address (server, port, &family, &bind_size); if (!bind_here) return 0; //struct sockaddr_in sin; int sd_accept = socket (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*)&sin, sizeof(sin))) { if (bind (sd_accept, bind_here, bind_size)) { //__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 unsigned long EventMachine_t::OpenDatagramSocket (const char *address, int port) { unsigned long output_binding = 0; int sd = socket (AF_INET, SOCK_DGRAM, 0); if (sd == INVALID_SOCKET) goto fail; // from here on, early returns must close the socket! struct sockaddr_in sin; memset (&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons (port); if (address && *address) { sin.sin_addr.s_addr = inet_addr (address); if (sin.sin_addr.s_addr == INADDR_NONE) { hostent *hp = gethostbyname ((char*)address); // Windows requires the cast. if (hp == NULL) goto fail; sin.sin_addr.s_addr = ((in_addr*)(hp->h_addr))->s_addr; } } else sin.sin_addr.s_addr = htonl (INADDR_ANY); // Set the new socket nonblocking. { if (!SetSocketNonblocking (sd)) //int val = fcntl (sd, F_GETFL, 0); //if (fcntl (sd, F_SETFL, val | O_NONBLOCK) == -1) goto fail; } if (bind (sd, (struct sockaddr*)&sin, sizeof(sin)) != 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 *******************************/ void EventMachine_t::ArmKqueueWriter (EventableDescriptor *ed) { #ifdef HAVE_KQUEUE if (bKqueue) { 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); } } #endif } /******************************* EventMachine_t::ArmKqueueReader *******************************/ void EventMachine_t::ArmKqueueReader (EventableDescriptor *ed) { #ifdef HAVE_KQUEUE if (bKqueue) { 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); } } #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 (bEpoll) { 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 (bKqueue) { // 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 (bEpoll) { set::iterator i = ModifiedDescriptors.begin(); while (i != ModifiedDescriptors.end()) { assert (*i); _ModifyEpollEvent (*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 (bEpoll) { 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 } /************************************** EventMachine_t::CreateUnixDomainServer **************************************/ const unsigned long 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. */ #ifdef OS_WIN32 throw std::runtime_error ("unix-domain server unavailable on this platform"); #endif // The whole rest of this function is only compiled on Unix systems. #ifdef OS_UNIX struct sockaddr_un s_sun; int sd_accept = socket (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; #endif // OS_UNIX } /************************************** EventMachine_t::AttachSD **************************************/ const unsigned long EventMachine_t::AttachSD (int sd_accept) { unsigned long 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 **************************/ const unsigned long EventMachine_t::Socketpair (char * const*cmd_strings) { #ifdef OS_WIN32 throw std::runtime_error ("socketpair is currently unavailable on this platform"); #endif // The whole rest of this function is only compiled on Unix systems. // Eventually we need this functionality (or a full-duplex equivalent) on Windows. #ifdef OS_UNIX // 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; unsigned long 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; #endif } /**************************** EventMachine_t::OpenKeyboard ****************************/ const unsigned long 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 () { return Descriptors.size() + NewDescriptors.size(); } /************************ EventMachine_t::WatchPid ************************/ const unsigned long EventMachine_t::WatchPid (int pid) { #ifdef HAVE_KQUEUE if (!bKqueue) 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); } #endif #ifdef HAVE_KQUEUE Bindable_t* b = new Bindable_t(); Pids.insert(make_pair (pid, b)); return b->GetBinding(); #endif throw std::runtime_error("no pid watching support on this system"); } /************************** 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 unsigned long sig) { for(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 unsigned long 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 (!bKqueue) 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(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 unsigned long sig) { for(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); 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 eventmachine-1.0.7/ext/ssl.cpp0000644000004100000410000003132312511426257016331 0ustar www-datawww-data/***************************************************************************** $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, int bufsize, int rwflag, void *userdata) { 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 string &privkeyfile, const string &certchainfile): 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(); } bIsServer = is_server; pCtx = SSL_CTX_new (is_server ? SSLv23_server_method() : SSLv23_client_method()); if (!pCtx) throw std::runtime_error ("no SSL context"); SSL_CTX_set_options (pCtx, SSL_OP_ALL); //SSL_CTX_set_options (pCtx, (SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3)); #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode (pCtx, SSL_MODE_RELEASE_BUFFERS); #endif if (is_server) { // 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); } SSL_CTX_set_cipher_list (pCtx, "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH"); if (is_server) { 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 string &privkeyfile, const string &certchainfile, bool verify_peer, const unsigned long binding): bIsServer (is_server), bHandshakeCompleted (false), bVerifyPeer (verify_peer), 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); 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); 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) SSL_set_verify(pSSL, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, ssl_verify_wrapper); if (!bIsServer) SSL_connect (pSSL); } /******************* 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 < 0) { int er = SSL_get_error (pSSL, e); if (er != SSL_ERROR_WANT_READ) { // 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; } /****************** ssl_verify_wrapper *******************/ extern "C" int ssl_verify_wrapper(int preverify_ok, X509_STORE_CTX *ctx) { unsigned long 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 = (unsigned long) 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 eventmachine-1.0.7/ext/binder.cpp0000644000004100000410000000500112511426257016765 0ustar www-datawww-data/***************************************************************************** $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" map Bindable_t::BindingBag; /******************************** STATIC Bindable_t::CreateBinding ********************************/ unsigned long Bindable_t::CreateBinding() { static unsigned long 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 unsigned long binding) { 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() { BindingBag.erase (Binding); } eventmachine-1.0.7/ext/cmain.cpp0000644000004100000410000005364712511426257016634 0ustar www-datawww-data/***************************************************************************** $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 int bUseEpoll = 0; static int bUseKqueue = 0; 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); if (bUseEpoll) EventMachine->_UseEpoll(); if (bUseKqueue) EventMachine->_UseKqueue(); } /******************** evma_release_library ********************/ extern "C" void evma_release_library() { ensure_eventmachine("evma_release_library"); delete EventMachine; EventMachine = NULL; } /**************** evma_run_machine ****************/ extern "C" void evma_run_machine() { ensure_eventmachine("evma_run_machine"); EventMachine->Run(); } /************************** evma_install_oneshot_timer **************************/ extern "C" const unsigned long evma_install_oneshot_timer (int seconds) { ensure_eventmachine("evma_install_oneshot_timer"); return EventMachine->InstallOneshotTimer (seconds); } /********************** evma_connect_to_server **********************/ extern "C" const unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long evma_attach_sd (int sd) { ensure_eventmachine("evma_attach_sd"); return EventMachine->AttachSD (sd); } /************************* evma_open_datagram_socket *************************/ extern "C" const unsigned long 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 unsigned long evma_open_keyboard() { ensure_eventmachine("evma_open_keyboard"); return EventMachine->OpenKeyboard(); } /******************* evma_watch_filename *******************/ extern "C" const unsigned long 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 unsigned long sig) { ensure_eventmachine("evma_unwatch_file"); EventMachine->UnwatchFile(sig); } /************** evma_watch_pid **************/ extern "C" const unsigned long evma_watch_pid (int pid) { ensure_eventmachine("evma_watch_pid"); return EventMachine->WatchPid(pid); } /**************** evma_unwatch_pid ****************/ extern "C" void evma_unwatch_pid (const unsigned long sig) { ensure_eventmachine("evma_unwatch_pid"); EventMachine->UnwatchPid(sig); } /**************************** evma_send_data_to_connection ****************************/ extern "C" int evma_send_data_to_connection (const unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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_start_tls **************/ extern "C" void evma_start_tls (const unsigned long 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 unsigned long binding, const char *privatekey_filename, const char *certchain_filename, int verify_peer) { 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)); } /****************** evma_get_peer_cert ******************/ #ifdef WITH_SSL extern "C" X509 *evma_get_peer_cert (const unsigned long binding) { ensure_eventmachine("evma_get_peer_cert"); EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); if (ed) return ed->GetPeerCert(); return NULL; } #endif /******************** evma_accept_ssl_peer ********************/ #ifdef WITH_SSL extern "C" void evma_accept_ssl_peer (const unsigned long 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 unsigned long 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 unsigned long 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 ***********************/ extern "C" int evma_get_subprocess_pid (const unsigned long binding, pid_t *pid) { ensure_eventmachine("evma_get_subprocess_pid"); #ifdef OS_UNIX 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 return 0; #endif } /************************** evma_get_subprocess_status **************************/ extern "C" int evma_get_subprocess_status (const unsigned long binding, 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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) { bUseEpoll = !!use; } /*************** evma_set_kqueue ***************/ extern "C" void evma_set_kqueue (int use) { bUseKqueue = !!use; } /********************** 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 unsigned long 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 unsigned long from, const unsigned long 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 unsigned long 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 unsigned long 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 unsigned long 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(); } eventmachine-1.0.7/ext/kb.cpp0000644000004100000410000000331312511426257016122 0ustar www-datawww-data/***************************************************************************** $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; read (GetSocket(), &c, 1); _GenericInboundDispatch(&c, 1); } eventmachine-1.0.7/ext/binder.h0000644000004100000410000000201412511426257016433 0ustar www-datawww-data/***************************************************************************** $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_ class Bindable_t { public: static unsigned long CreateBinding(); static Bindable_t *GetObject (const unsigned long); static map BindingBag; public: Bindable_t(); virtual ~Bindable_t(); const unsigned long GetBinding() {return Binding;} private: unsigned long Binding; }; #endif // __ObjectBindings__H_ eventmachine-1.0.7/ext/fastfilereader/0000755000004100000410000000000012511426257020002 5ustar www-datawww-dataeventmachine-1.0.7/ext/fastfilereader/mapper.cpp0000644000004100000410000001051112511426257021770 0ustar www-datawww-data/***************************************************************************** $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 "unistd.h" #include #include #include using namespace std; #include "mapper.h" /****************** Mapper_t::Mapper_t ******************/ Mapper_t::Mapper_t (const 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 runtime_error (strerror (errno)); struct stat st; if (fstat (Fd, &st)) throw 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 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 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 using namespace std; #include "mapper.h" /****************** Mapper_t::Mapper_t ******************/ Mapper_t::Mapper_t (const 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 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 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 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 eventmachine-1.0.7/ext/fastfilereader/rubymain.cpp0000644000004100000410000000556512511426257022347 0ustar www-datawww-data/***************************************************************************** $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 using namespace std; #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 (StringValuePtr (filename)); if (!m) rb_raise (rb_eException, "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_eException, "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_eException, "Mapper Range Error"); const char *chunk = m->GetChunk (_start); if (!chunk) rb_raise (rb_eException, "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_eException, "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_eException, "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); } eventmachine-1.0.7/ext/fastfilereader/extconf.rb0000644000004100000410000000553712511426257022007 0ustar www-datawww-datarequire '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' and `cc -flags 2>&1` =~ /Sun/ # detect SUNWspro 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" 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/ # on Unix we need a g++ link, not gcc. CONFIG['LDSHARED'] = "$(CXX) -shared -Wl,-G" 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" eventmachine-1.0.7/ext/fastfilereader/mapper.h0000644000004100000410000000222212511426257021435 0ustar www-datawww-data/***************************************************************************** $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 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_ eventmachine-1.0.7/ext/em.h0000644000004100000410000001620512511426257015600 0ustar www-datawww-data/***************************************************************************** $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_RBTRAP) #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_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 class EventableDescriptor; class InotifyDescriptor; struct SelectData_t; /******************** 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); virtual ~EventMachine_t(); void Run(); void ScheduleHalt(); void SignalLoopBreaker(); const unsigned long InstallOneshotTimer (int); const unsigned long ConnectToServer (const char *, int, const char *, int); const unsigned long ConnectToUnixServer (const char *); const unsigned long CreateTcpServer (const char *, int); const unsigned long OpenDatagramSocket (const char *, int); const unsigned long CreateUnixDomainServer (const char*); const unsigned long AttachSD (int); const unsigned long OpenKeyboard(); //const char *Popen (const char*, const char*); const unsigned long Socketpair (char* const*); void Add (EventableDescriptor*); void Modify (EventableDescriptor*); void Deregister (EventableDescriptor*); const unsigned long AttachFD (int, 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 unsigned long WatchFile (const char*); void UnwatchFile (int); void UnwatchFile (const unsigned long); #ifdef HAVE_KQUEUE void _HandleKqueueFileEvent (struct kevent*); void _RegisterKqueueFileEvent(int); #endif const unsigned long WatchPid (int); void UnwatchPid (int); void UnwatchPid (const unsigned long); #ifdef HAVE_KQUEUE void _HandleKqueuePidEvent (struct kevent*); #endif uint64_t GetCurrentLoopTime() { return MyCurrentLoopTime; } // Temporary: void _UseEpoll(); void _UseKqueue(); bool UsingKqueue() { return bKqueue; } bool UsingEpoll() { return bEpoll; } void QueueHeartbeat(EventableDescriptor*); void ClearHeartbeat(uint64_t, EventableDescriptor*); uint64_t GetRealTime(); private: void _RunOnce(); 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 { }; multimap Timers; multimap Heartbeats; map Files; map Pids; vector Descriptors; vector NewDescriptors; set ModifiedDescriptors; uint64_t NextHeartbeatTime; int LoopBreakerReader; int 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; bool bEpoll; int epfd; // Epoll file-descriptor #ifdef HAVE_EPOLL struct epoll_event epoll_events [MaxEvents]; #endif bool bKqueue; int kqfd; // Kqueue file-descriptor #ifdef HAVE_KQUEUE struct kevent Karray [MaxEvents]; #endif InotifyDescriptor *inotify; // pollable descriptor for our inotify instance }; /******************* struct SelectData_t *******************/ struct SelectData_t { SelectData_t(); ~SelectData_t(); int _Select(); void _Clear(); int maxsocket; rb_fdset_t fdreads; rb_fdset_t fdwrites; rb_fdset_t fderrors; timeval tv; int nSockets; }; #endif // __EventMachine__H_ eventmachine-1.0.7/README.md0000644000004100000410000000760312511426257015507 0ustar www-datawww-data# About EventMachine [![Code Climate](https://codeclimate.com/github/eventmachine/eventmachine.png)](https://codeclimate.com/github/eventmachine/eventmachine) ## 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, 1.9.2, 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/). ### Server example: Echo server ### Here's a fully-functional echo server written with EventMachine: 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/).