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'