lib/eventmachine.rb in eventmachine-win32-0.5.3 vs lib/eventmachine.rb in eventmachine-win32-0.7.0

- old
+ new

@@ -1,8 +1,8 @@ -# $Id: eventmachine.rb 52 2006-05-16 01:01:46Z blackhedd $ +# $Id: eventmachine.rb 283 2006-11-22 14:44:38Z blackhedd $ # -# Author:: blackhedd (gmail address: garbagecat20). +# Author:: blackhedd (gmail address: garbagecat10). # Date:: 8 Apr 2006 # # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. # # This program is made available under the terms of the GPL version 2. @@ -32,12 +32,36 @@ # #--------------------------------------------------------------------------- # # -require 'rubyeventmachine' +#-- Select in a library based on a global variable. +case $eventmachine_library +when :pure_ruby + require 'pr_eventmachine' +when :extension + require 'rubyeventmachine' +else + # This is the case that most user code will take. + # Prefer the extension if available. + begin + require 'rubyeventmachine' + rescue LoadError + require 'pr_eventmachine' + end +end + + +require "eventmachine_version" +require 'em/deferrable' +require 'em/eventable' +#-- Additional requires are at the BOTTOM of this file, because they +#-- depend on stuff defined in here. Refactor that someday. + + + # == Introduction # EventMachine provides a fast, lightweight framework for implementing # Ruby programs that can use the network to communicate with other # processes. Using EventMachine, Ruby programmers can easily connect # to remote servers and act as servers themselves. EventMachine does not @@ -130,11 +154,10 @@ # Interesting thought. # # module EventMachine - VERSION = "0.5.3" # EventMachine::run initializes and runs an event loop. # This method only returns if user-callback code calls stop_event_loop. # Use the supplied block to define your clients and servers. # The block is called by EventMachine::run immediately after initializing @@ -185,11 +208,11 @@ # to EventMachine#run, but it runs somewhat faster. # However, it must not be used in applications that spin # Ruby threads. def EventMachine::run_without_threads &block #EventMachine::run false, &block - EventMachine::run &block + EventMachine::run(&block) end # EventMachine#add_timer 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 <i>seconds</i> (not milliseconds). The second parameter, if @@ -224,16 +247,19 @@ # EventMachine::add_timer 5, proc { puts "Executing timer event: #{Time.now}" } # EventMachine::add_timer( 10 ) { puts "Executing timer event: #{Time.now}" } # } # # + #-- + # Changed 04Oct06: We now pass the interval as an integer number of milliseconds. + # def EventMachine::add_timer *args, &block interval = args.shift code = args.shift || block if code # check too many timers! - s = add_oneshot_timer interval + s = add_oneshot_timer((interval * 1000).to_i) @timers[s] = code end end # EventMachine#add_periodic_timer adds a periodic timer to the event loop. @@ -407,21 +433,33 @@ # puts "Now accepting connections on address #{host}, port #{port}..." # EventMachine::add_periodic_timer( 10 ) { $stderr.write "*" } # } # # - def EventMachine::start_server server, port, handler=nil + def EventMachine::start_server server, port, handler=nil, &block klass = if (handler and handler.is_a?(Class)) handler else Class.new( Connection ) {handler and include handler} end s = start_tcp_server server, port - @acceptors[s] = klass + @acceptors[s] = [klass,block] end + + def EventMachine::start_unix_domain_server filename, handler=nil, &block + klass = if (handler and handler.is_a?(Class)) + handler + else + Class.new( Connection ) {handler and include handler} + end + + s = start_unix_server filename + @acceptors[s] = [klass,block] + end + # EventMachine#connect initiates a TCP connection to a remote # server and sets up event-handling for the connection. # You can call EventMachine#connect in the block supplied # to EventMachine#run or in any callback method. # @@ -531,10 +569,33 @@ block_given? and yield c c end + #-- + # EXPERIMENTAL. DO NOT RELY ON THIS METHOD TO BE HERE IN THIS FORM, OR AT ALL. + # (03Nov06) + # 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. + # + def EventMachine::reconnect server, port, handler + 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 = connect_server server, port + handler.signature = s + @conns[s] = handler + block_given? and yield handler + handler + end + + # EventMachine#open_datagram_socket is for support of UDP-based # protocols. Its usage is similar to that of EventMachine#start_server. # It takes three parameters: an IP address (which must be valid # on the machine which executes the method), a port number, # and an optional Module name which will handle the data. @@ -584,22 +645,128 @@ # # 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. # + #-- + # Replaced the implementation on 01Oct06. Thanks to Tobias Gustafsson for pointing + # out that this originally did not take a class but only a module. + # def self::open_datagram_socket address, port, handler=nil + klass = if (handler and handler.is_a?(Class)) + handler + else + Class.new( Connection ) {handler and include handler} + end + s = open_udp_socket address, port + c = klass.new s + @conns[s] = c + block_given? and yield c + c + end +=begin + (Original, replaced 01Oct06) + def self::open_datagram_socket address, port, handler=nil + s = open_udp_socket address, port klass = Class.new( Connection ) { handler and include handler } c = klass.new s @conns[s] = c block_given? and yield c c end +=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 #add_timer and #add_periodic_timer. Most applications + # will not need to call this function. + # + # The argument is a number of milliseconds. Avoid setting the quantum to very low values because + # that may reduce performance under some extreme conditions. We recommend that you not set a quantum + # lower than 10. + # + # You MUST call this function while an EventMachine loop is running (that is, after a call to + # EventMachine#run and before a subsequent call to EventMachine#stop). + # + def self::set_quantum mills + set_timer_quantum mills.to_i + end + + + def self::run_deferred_callbacks # :nodoc: + until @resultqueue.empty? + result,cback = @resultqueue.pop + cback.call result if cback + end + end + + # #defer is for integrating blocking operations into EventMachine's control flow. + # Call #defer with one or two blocks, as shown below (the second block is <i>optional</i>): + # + # 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 ) + # + # 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, synchronous + # 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. + # + # <i>Caveats:</i> + # This is a <b>provisional</b> implementation and is subject to change. + # 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. [Need more explanation + # and examples.] + # 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. [We might put in a timer to detect this problem.] + # + def self::defer op, callback = nil + unless @threadqueue + + #start_server "127.0.0.1", 29999, DeferredTrigger + #@deferred_trigger = connect "127.0.0.1", 29999 + + require 'thread' + @threadqueue = Queue.new + @resultqueue = Queue.new + 20.times {|ix| + Thread.new { + my_ix = ix + loop { + op,cback = @threadqueue.pop + result = op.call + @resultqueue << [result, cback] + EventMachine.signal_loopbreak + #@deferred_trigger.send_data "." + } + } + } + end + + @threadqueue << [op,callback] + end + + + private def EventMachine::event_callback conn_binding, opcode, data case opcode when ConnectionData c = @conns[conn_binding] or raise ConnectionNotBound @@ -611,21 +778,49 @@ # no-op else raise ConnectionNotBound end when ConnectionAccepted - accep = @acceptors[conn_binding] or raise NoHandlerForAcceptedConnection + accep,blk = @acceptors[conn_binding] + raise NoHandlerForAcceptedConnection unless accep c = accep.new data @conns[data] = c + blk and blk.call(c) + c # (needed?) when TimerFired t = @timers.delete( data ) or raise UnknownTimerFired t.call + when ConnectionCompleted + c = @conns[conn_binding] or raise ConnectionNotBound + c.connection_completed + when LoopbreakSignalled + run_deferred_callbacks end end + # Documentation stub + #-- + # This is a provisional implementation of a stream-oriented file access object. + # We also experiment with wrapping up some better exception reporting. + class << self + def _open_file_for_writing filename, handler=nil + klass = if (handler and handler.is_a?(Class)) + handler + else + Class.new( Connection ) {handler and include handler} + end + s = _write_file filename + c = klass.new s + @conns[s] = c + block_given? and yield c + c + end + 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 <i>mixes in</i> @@ -648,10 +843,13 @@ # and unbind. All of the other instance methods defined here are called # only by user code. # class Connection + # EXPERIMENTAL. Added the reconnect methods, which may go away. + attr_accessor :signature + def initialize sig #:nodoc: @signature = sig post_init end @@ -768,11 +966,28 @@ # def send_data data EventMachine::send_data @signature, data, data.length end + # #connection_completed is 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. #connection_completed + # 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 + # send an initial set of data). + # #post_init will always be called. #connection_completed will only be called in case + # of a successful completion. A connection-attempt which fails will receive a call + # to #unbind after the failure. + 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. + # def start_tls EventMachine::start_tls @signature end @@ -798,10 +1013,60 @@ data = data.to_s EventMachine::send_datagram @signature, data, data.length, recipient_address, recipient_port end + # #get_peername 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. + def get_peername + EventMachine::get_peername @signature + end + + # comm_inactivity_timeout returns the current value (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 + + # Alias for #set_comm_inactivity_timeout. + def comm_inactivity_timeout= value + self.send :set_comm_inactivity_timeout, value + end + + # comm_inactivity_timeout= allows you to set the inactivity-timeout property for + # a network connection or datagram socket. Specify a non-negative numeric 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 set_comm_inactivity_timeout value + EventMachine::set_comm_inactivity_timeout @signature, value + end + + #-- + # EXPERIMENTAL. DO NOT RELY ON THIS METHOD TO REMAIN SUPPORTED. + # (03Nov06) + def reconnect server, port + EventMachine::reconnect server, port, self + end + end end # module EventMachine + +# At the bottom of this module, we load up protocol handlers that depend on some +# of the classes defined here. Eventually we should refactor this out so it's +# laid out in a more logical way. +# + +require 'protocols/tcptest' +require 'protocols/httpclient' +require 'protocols/line_and_text' +require 'protocols/header_and_content'