|  | #!/usr/bin/env ruby
 | 
  
    |  | #
 | 
  
    |  | 
 | 
  
    |  | ########
 | 
  
    |  | ######## c0000029 error: 
 | 
  
    |  | ######## ---------------
 | 
  
    |  | ########
 | 
  
    |  | ######## This file reproduces a c0000029 crash on
 | 
  
    |  | ######## ruby 1.9.2dev (2010-07-14) [i386-mswin32_100]
 | 
  
    |  | ######## (svn revision 28648)
 | 
  
    |  | ########
 | 
  
    |  | ######## $ svn co http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_9_2
 | 
  
    |  | ######## $ win32\configure.bat --prefix=m:/dev/ruby-build/v1_9_2-pure --program-suffix=19 --disable-install-doc
 | 
  
    |  | ########
 | 
  
    |  | ######## Note, the crash does NOT occur on trunk (1.9.3dev),
 | 
  
    |  | ######## nor does the crash occur if cont.c is back-ported
 | 
  
    |  | ######## from trunk to 1.9.2dev.
 | 
  
    |  | ########
 | 
  
    |  | ######## The one non-core dependency is the EventMachine
 | 
  
    |  | ######## library. (NOTE: see below regarding EventMachine build.)
 | 
  
    |  | ########
 | 
  
    |  | ####################
 | 
  
    |  | ########
 | 
  
    |  | ######## There are two comment blocks below marked c0000029 (A) and
 | 
  
    |  | ######## c0000029 (B).
 | 
  
    |  | ########
 | 
  
    |  | ######## With both (A) and (B) enabled, the crash occurs when the
 | 
  
    |  | ######## program exits.
 | 
  
    |  | ######## With (A) disabled, but (B) enabled, the crash does not occur.
 | 
  
    |  | ######## With (A) enabled, but (B) disabled, the crash happens sooner.
 | 
  
    |  | ######## (Both (A) and (B) disabled is not a valid combination.)
 | 
  
    |  | ########
 | 
  
    |  | ######## My OS version is Windows 7 - 64bit.
 | 
  
    |  | ######## 
 | 
  
    |  | ####################
 | 
  
    |  | ########
 | 
  
    |  | ######## Notes on building EventMachine:
 | 
  
    |  | ########
 | 
  
    |  | ######## The EventMachine version was pulled from git on 2010-07-15.
 | 
  
    |  | ########
 | 
  
    |  | ######## $ git clone git://github.com/eventmachine/eventmachine
 | 
  
    |  | ########
 | 
  
    |  | ######## NOTE: In order to build EventMachine on windows with
 | 
  
    |  | ######## the Visual Studio compiler, I needed to add #undef's
 | 
  
    |  | ######## for max and min in the file eventmachine/ext/ed.cpp:
 | 
  
    |  | ########   
 | 
  
    |  | ########   diff --git a/ext/ed.cpp b/ext/ed.cpp
 | 
  
    |  | ########   index 06c8f6c..cacb76b
 | 
  
    |  | ########   --- a/ext/ed.cpp
 | 
  
    |  | ########   +++ b/ext/ed.cpp
 | 
  
    |  | ########   @@ -19,6 +19,10 @@ See the file COPYING for complete licensing information.
 | 
  
    |  | ########   
 | 
  
    |  | ########    #include "project.h"
 | 
  
    |  | ########   
 | 
  
    |  | ########   +#ifdef OS_WIN32
 | 
  
    |  | ########   +#undef min
 | 
  
    |  | ########   +#undef max
 | 
  
    |  | ########   +#endif
 | 
  
    |  | ########   
 | 
  
    |  | ########   
 | 
  
    |  | ########    /********************
 | 
  
    |  | ########
 | 
  
    |  | 
 | 
  
    |  | require 'eventmachine'
 | 
  
    |  | require 'socket'  # for unpack_sockaddr_in
 | 
  
    |  | require 'fiber'
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | module ZZZZ end
 | 
  
    |  | 
 | 
  
    |  | class ZZZZ::Logger
 | 
  
    |  |   LOGLEVEL_ERROR = 0
 | 
  
    |  |   LOGLEVEL_WARN  = 1
 | 
  
    |  |   LOGLEVEL_INFO  = 2
 | 
  
    |  |   LOGLEVEL_DEBUG = 3
 | 
  
    |  |   
 | 
  
    |  |   attr_accessor :loglevel_debug, :loglevel_info, :logio, :prefix
 | 
  
    |  |   
 | 
  
    |  |   def initialize
 | 
  
    |  |     @loglevel_info = true
 | 
  
    |  |     @loglevel_debug = false
 | 
  
    |  |     @logio = nil
 | 
  
    |  |     @prefix = ""
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def loglevel=(lev)
 | 
  
    |  |     @loglevel_info  = (lev >= LOGLEVEL_INFO)
 | 
  
    |  |     @loglevel_debug = (lev >= LOGLEVEL_DEBUG)
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def info(msg, options={})
 | 
  
    |  |     log("[INFO]", msg, options) if @loglevel_info
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def warn(msg, options={})
 | 
  
    |  |     log("[WARN]", msg, options)
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def error(msg, options={})
 | 
  
    |  |     log("[ERR!]", msg, options)
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def dbg(msg, options={})
 | 
  
    |  |     log("[DBG_]", msg, options) if @loglevel_debug
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   protected
 | 
  
    |  | 
 | 
  
    |  |   def log(loglevel_tag, msg, options={})
 | 
  
    |  |     tstamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%L %a")
 | 
  
    |  |     pid_str = "[%05d:%08d]" % [Process.pid, Fiber.current.object_id]
 | 
  
    |  |     annotation = options[:annotation] || ""
 | 
  
    |  |     line = "[#{tstamp}] #{pid_str} #{loglevel_tag} #{@prefix}#{annotation}#{msg}"
 | 
  
    |  |     writelog(line)
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def writelog(msg)
 | 
  
    |  |     $stdout.puts msg
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | module ZZZZ
 | 
  
    |  | module PROTO
 | 
  
    |  | 
 | 
  
    |  | class RPCException < RuntimeError; end
 | 
  
    |  | class RPCNotConnectedError < RPCException; end
 | 
  
    |  | 
 | 
  
    |  | class RPC < EventMachine::Connection
 | 
  
    |  |   RPC_VERSION = 1
 | 
  
    |  | 
 | 
  
    |  |   # Optional fiber support:
 | 
  
    |  |   # Call once from EventMachine's fiber, for example,
 | 
  
    |  |   # at the beginning of EM.run { }
 | 
  
    |  |   def self.em_fiber_init
 | 
  
    |  |     @em_fiber = Fiber.current
 | 
  
    |  |     # @fiberpool = ZZZZ::FiberPool.new
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def self.em_fiber
 | 
  
    |  |     @em_fiber
 | 
  
    |  |   end
 | 
  
    |  |     
 | 
  
    |  |   def self.run_in_fiber(&block)
 | 
  
    |  |     # @fiberpool.run_in_fiber(&block)
 | 
  
    |  |     nextf = Fiber.current
 | 
  
    |  |     newf = Fiber.new do
 | 
  
    |  |       block.call
 | 
  
    |  |       nextf.transfer
 | 
  
    |  |     end
 | 
  
    |  |     newf.transfer
 | 
  
    |  |   end  
 | 
  
    |  | 
 | 
  
    |  |   def initialize(*args)
 | 
  
    |  | # warn "#{self.class.name} initialize"
 | 
  
    |  |     super(*args)
 | 
  
    |  |     @rpc_recv_state = :st_recv_handshake
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def post_init
 | 
  
    |  |     super
 | 
  
    |  |     @remote_port, @remote_ip = (Socket.unpack_sockaddr_in(self.get_peername) rescue [0,"0.0.0.0"])
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def rpc_owner_init(owner, logger)
 | 
  
    |  |     @rpc_owner = owner
 | 
  
    |  |     @logger = logger
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def rpc_initiate_handshake
 | 
  
    |  |     send_data "X"
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def unbind
 | 
  
    |  | @logger.dbg("#{self.class.name} unbind") if @logger
 | 
  
    |  |     @rpc_recv_state = :st_recv_null
 | 
  
    |  |     @rpc_owner.rpc_connection_unbind(self) if @rpc_owner
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def receive_data(dat)
 | 
  
    |  |     @rpc_recv_state = :st_recv_rpc_run
 | 
  
    |  |     @rpc_owner.rpc_connection_established(self)
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def rpc_connected?
 | 
  
    |  |     @rpc_recv_state == :st_recv_rpc_run
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def rpc_send_frame
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def rpc_have_frame_to_send?
 | 
  
    |  |     @rpc_pf_out_dirty
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def rpc_flush
 | 
  
    |  |     rpc_send_frame if rpc_have_frame_to_send?
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def rpc_append_msg(*msg)
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   private
 | 
  
    |  | 
 | 
  
    |  |   def st_recv_null
 | 
  
    |  |     # no-op: disconnected state
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def dispatch_rpc_messages(messages)
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | module RPCMessageDispatcher
 | 
  
    |  |   MSGPORT_ROOT = 0
 | 
  
    |  |   
 | 
  
    |  |   def init_message_dispatch(logger, &root_handler)
 | 
  
    |  | logger.dbg("#{self.class.name} - init_message_dispatch: root_handler=#{root_handler.inspect}")
 | 
  
    |  |     @@id2rpc ||= {}
 | 
  
    |  |     @msg_portmap = {}
 | 
  
    |  |     @dispatch_complete_callback = nil
 | 
  
    |  |     self.rpc_bind_reply_port(MSGPORT_ROOT, root_handler)
 | 
  
    |  |     self.rpc_owner_init(self, logger)
 | 
  
    |  |     self.rpc_initiate_handshake 
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def rpc_bind_reply_port(reply_port_num, reply_proc)
 | 
  
    |  |     @msg_portmap[reply_port_num] = reply_proc
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def rpc_unbind_reply_port(reply_port_num)
 | 
  
    |  |     @msg_portmap.delete reply_port_num
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   protected
 | 
  
    |  | 
 | 
  
    |  |   # called by RPCClientConn when RPC protocol handshake complete
 | 
  
    |  |   def rpc_connection_established(rpc)
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_connection_established")
 | 
  
    |  |     @@id2rpc[self.object_id] = self
 | 
  
    |  |     rpc_dispatch(MSGPORT_ROOT, MSGPORT_ROOT, :rpc_begin)
 | 
  
    |  |     rpc_flush
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   # called by RPCClientConn when RPC connection terminates
 | 
  
    |  |   def rpc_connection_unbind(rpc)
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_connection_unbind")
 | 
  
    |  |     rpc_dispatch(MSGPORT_ROOT, MSGPORT_ROOT, :rpc_unbind)
 | 
  
    |  |     @@id2rpc.delete self.object_id
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def rpc_dispatch(from_remote_port, to_local_port, msgname, *args)
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_dispatch: from=#{from_remote_port.inspect} to=#{to_local_port.inspect} #{msgname} #{args.inspect}")
 | 
  
    |  |       ########
 | 
  
    |  |       ######## c0000029: (A) 
 | 
  
    |  |       ######## -------------
 | 
  
    |  |       ######## Commenting out the run_in_fiber wrapper here makes the
 | 
  
    |  |       ######## crash NOT happen, as long as (B) is enabled below.
 | 
  
    |  |       ######## 
 | 
  
    |  |       RPC.run_in_fiber do
 | 
  
    |  |         handler = @msg_portmap[to_local_port]
 | 
  
    |  |         fake_replyport = true 
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_dispatch: calling handler: #{handler}")
 | 
  
    |  |         handler.call(fake_replyport, msgname, *args)
 | 
  
    |  |       end
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def rpc_dispatch_complete
 | 
  
    |  | # @logger.dbg("#{self.class.name} - rpc_dispatch_complete: have_frame=#{rpc_have_frame_to_send?}")
 | 
  
    |  |     @dispatch_complete_callback.call if @dispatch_complete_callback
 | 
  
    |  |     # Flush any rpc's we know of that have pending frames
 | 
  
    |  |     @@id2rpc.each_value {|r| r.rpc_flush}
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | class RPCServerConn < RPC
 | 
  
    |  |   include RPCMessageDispatcher
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | class RPCClientConn < RPC  
 | 
  
    |  |   include RPCMessageDispatcher
 | 
  
    |  | end
 | 
  
    |  |   
 | 
  
    |  | end # PROTO
 | 
  
    |  | end # ZZZZ
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | module ZZZZ
 | 
  
    |  | module PROTO
 | 
  
    |  | 
 | 
  
    |  | class RPCClient
 | 
  
    |  |   attr_reader :root
 | 
  
    |  |   attr_accessor :rpc_begin_hook, :rpc_unbind_hook, :root_msg_handler
 | 
  
    |  |   
 | 
  
    |  |   def initialize(logger)
 | 
  
    |  |     @logger = logger
 | 
  
    |  |     @rpc_conn = nil
 | 
  
    |  |     @root = nil
 | 
  
    |  |     @rpc_begin_hook = nil
 | 
  
    |  |     @rpc_unbind_hook = nil
 | 
  
    |  |     @root_msg_handler = nil
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def connect_async(rpc_host, rpc_port, &dispatch_complete_hook)
 | 
  
    |  | @logger.dbg "#{self.class.name} connect..."
 | 
  
    |  |     
 | 
  
    |  |     EventMachine::connect(rpc_host, rpc_port, ZZZZ::PROTO::RPCClientConn) do |rpc_conn|
 | 
  
    |  |       remote_port, remote_ip = (Socket.unpack_sockaddr_in(rpc_conn.get_peername) rescue [0,"0.0.0.0"])
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc conn established - #{remote_ip}:#{remote_port}")
 | 
  
    |  | 
 | 
  
    |  |       @rpc_conn = rpc_conn
 | 
  
    |  |       @rpc_conn.init_message_dispatch(@logger, &method(:handle_rpc_root_msg))
 | 
  
    |  |       
 | 
  
    |  |       if dispatch_complete_hook
 | 
  
    |  |         @rpc_conn.install_dispatch_complete_hook(&dispatch_complete_hook)
 | 
  
    |  |       end
 | 
  
    |  | 
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc conn block end")
 | 
  
    |  |     end
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def disconnect_async
 | 
  
    |  |     @root = nil
 | 
  
    |  |     if @rpc_conn
 | 
  
    |  |       @rpc_conn.rpc_flush
 | 
  
    |  |       @rpc_conn.close_connection_after_writing
 | 
  
    |  |     end
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def rpc_ready?
 | 
  
    |  |     !! @root
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   protected
 | 
  
    |  | 
 | 
  
    |  |   def handle_rpc_root_msg(replyport, msg, *args)
 | 
  
    |  | @logger.dbg("#{self.class.name} - handle_rpc_root_msg: #{msg} #{args.inspect}")
 | 
  
    |  |     case msg
 | 
  
    |  |     when :rpc_begin  then rpc_rpc_begin(replyport)
 | 
  
    |  |     when :rpc_unbind then rpc_rpc_unbind
 | 
  
    |  |     else
 | 
  
    |  |       if @root_msg_handler
 | 
  
    |  |         @root_msg_handler.call(replyport, msg, *args)
 | 
  
    |  |       else
 | 
  
    |  |         raise("unknown message: #{msg}")
 | 
  
    |  |       end
 | 
  
    |  |     end
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   # called by RPCMessageDispatcher when ready for first message
 | 
  
    |  |   def rpc_rpc_begin(remote_root)
 | 
  
    |  |     @root = remote_root
 | 
  
    |  |     @rpc_begin_hook.call(@root) if @rpc_begin_hook
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   # called by RPCMessageDispatcher when connection terminates
 | 
  
    |  |   def rpc_rpc_unbind
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_unbind")
 | 
  
    |  |     @root = nil
 | 
  
    |  |     @rpc_conn = nil
 | 
  
    |  |     @rpc_unbind_hook.call if @rpc_unbind_hook
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  |   
 | 
  
    |  | end # PROTO
 | 
  
    |  | end # ZZZZ
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | require 'rbconfig'
 | 
  
    |  | require 'test/unit'
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | module TestEMFiber2
 | 
  
    |  | 
 | 
  
    |  | class SyncWaiter
 | 
  
    |  |   def initialize
 | 
  
    |  |     @waiters = []
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   # Must not be called on the EventMachine fiber
 | 
  
    |  |   def waitfor(&testproc)
 | 
  
    |  |     return if testproc.call  # quick test up front
 | 
  
    |  |     cf = Fiber.current
 | 
  
    |  |     emf = ZZZZ::PROTO::RPC.em_fiber
 | 
  
    |  |     raise("SyncWaiter: waitfor called on EventMachine thread") if cf == emf
 | 
  
    |  |     begin
 | 
  
    |  |       @waiters << cf
 | 
  
    |  |       begin
 | 
  
    |  |         emf.transfer
 | 
  
    |  |       end until testproc.call
 | 
  
    |  |     ensure
 | 
  
    |  |       @waiters.delete cf
 | 
  
    |  |     end
 | 
  
    |  |   end
 | 
  
    |  |     
 | 
  
    |  |   # Typically called from EventMachine fiber
 | 
  
    |  |   def service_sync_waiters
 | 
  
    |  |     @waiters.each {|wf| wf.transfer}
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | # NOTE: Although we start a server, we only accept
 | 
  
    |  | # a single client connection.
 | 
  
    |  | #
 | 
  
    |  | class MockWindowServer
 | 
  
    |  |   def initialize(logger)
 | 
  
    |  |     @logger = logger
 | 
  
    |  |     @rpc_conn = nil
 | 
  
    |  |     @client = nil
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   # A port of zero means choose an ephemeral port.
 | 
  
    |  |   # The port number chosen is the return value.
 | 
  
    |  |   def start_server(extern_interface_ip, port=0)
 | 
  
    |  |     s = EventMachine::start_server(extern_interface_ip, port, ZZZZ::PROTO::RPCServerConn) do |client_rpc_conn|
 | 
  
    |  |       remote_port, remote_ip = Socket.unpack_sockaddr_in(client_rpc_conn.get_peername)
 | 
  
    |  | 
 | 
  
    |  |       if @rpc_conn
 | 
  
    |  |         @logger.error("#{self.class.name} - already have client connection, dropping connection from #{remote_ip}:#{remote_port}")
 | 
  
    |  |         client_rpc_conn.close_connection
 | 
  
    |  |       else
 | 
  
    |  | @logger.dbg("#{self.class.name} - client tcp conn established - #{remote_ip}:#{remote_port}")
 | 
  
    |  | 
 | 
  
    |  |         @rpc_conn = client_rpc_conn
 | 
  
    |  |         @rpc_conn.init_message_dispatch(@logger) do |replyport, msg, *args|
 | 
  
    |  |           handle_root_msg(replyport, msg, *args)
 | 
  
    |  |         end
 | 
  
    |  | 
 | 
  
    |  | @logger.dbg("#{self.class.name} - client tcp conn block end")
 | 
  
    |  |       end
 | 
  
    |  | 
 | 
  
    |  |     end
 | 
  
    |  |     port, iface = Socket.unpack_sockaddr_in(EventMachine.get_sockname(s))
 | 
  
    |  |     @logger.info("#{self.class.name} server listening on interface #{iface} tcp port #{port} (proto ver #{ZZZZ::PROTO::RPC::RPC_VERSION})")
 | 
  
    |  |     port
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   private
 | 
  
    |  | 
 | 
  
    |  |   def handle_root_msg(replyport, msg, *args)
 | 
  
    |  | @logger.dbg("#{self.class.name} - handle_root_msg: #{msg} #{args.inspect}")
 | 
  
    |  |     case msg
 | 
  
    |  |     when nil         then handle_root_error(args)
 | 
  
    |  |     when :rpc_begin  then rpc_begin(replyport)
 | 
  
    |  |     when :rpc_unbind then rpc_unbind
 | 
  
    |  |     else raise("unknown message: #{msg}")
 | 
  
    |  |     end
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def handle_root_error(errdata)
 | 
  
    |  |     @logger.error("#{self.class.name} - received error at root: #{errdata.inspect}")
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   # called by RPCMessageDispatcher when ready for first message
 | 
  
    |  |   def rpc_begin(remote_root)
 | 
  
    |  |     @client = remote_root
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_begin: nothing to do, awaiting msg from client...")
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   # called by RPCMessageDispatcher when connection terminates
 | 
  
    |  |   def rpc_unbind
 | 
  
    |  | @logger.dbg("#{self.class.name} - rpc_unbind")
 | 
  
    |  |     @client = nil
 | 
  
    |  |     @rpc_conn = nil
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | class MockApp
 | 
  
    |  |   def initialize(logger)
 | 
  
    |  |     @logger = logger
 | 
  
    |  |     @waiter = SyncWaiter.new
 | 
  
    |  |     @vis = nil
 | 
  
    |  |     @vis_client = ZZZZ::PROTO::RPCClient.new(@logger)
 | 
  
    |  |     @vis_client.rpc_begin_hook = lambda {|root| @vis = root}
 | 
  
    |  |     @vis_client.rpc_unbind_hook = lambda {@vis = nil; EventMachine.stop}
 | 
  
    |  |   end
 | 
  
    |  |   
 | 
  
    |  |   def waitfor(&testproc)
 | 
  
    |  |     @waiter.waitfor(&testproc)
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def startup(vis_host, vis_port)
 | 
  
    |  |     start_app_timer
 | 
  
    |  |     @vis_client.connect_async(vis_host, vis_port)
 | 
  
    |  |     waitfor {@vis}
 | 
  
    |  |     @logger.dbg("#{self.class.name} - startup: got vis=#{@vis.inspect}")
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   protected
 | 
  
    |  |   
 | 
  
    |  |   def start_app_timer
 | 
  
    |  |     EM.add_periodic_timer(0.1) {app_timer_callback}
 | 
  
    |  |   end
 | 
  
    |  | 
 | 
  
    |  |   def app_timer_callback
 | 
  
    |  | $stderr.puts "[fib=#{Fiber.current.object_id}] app_timer_callback {"
 | 
  
    |  |     @waiter.service_sync_waiters
 | 
  
    |  | $stderr.puts "[fib=#{Fiber.current.object_id}] app_timer_callback }"
 | 
  
    |  |   end
 | 
  
    |  | end
 | 
  
    |  | 
 | 
  
    |  | 
 | 
  
    |  | class TestEMFiber2 < Test::Unit::TestCase
 | 
  
    |  |   VIS_HOST = "127.0.0.1"
 | 
  
    |  | 
 | 
  
    |  |   def test_em_fiber
 | 
  
    |  |     @logger = ZZZZ::Logger.new
 | 
  
    |  |     @logger.prefix = "FibTest: "
 | 
  
    |  |     @logger.loglevel_debug = true
 | 
  
    |  |     @logger.loglevel_info = true
 | 
  
    |  | 
 | 
  
    |  |     vis_logger = @logger.dup
 | 
  
    |  |     vis_logger.prefix = "MockVis: "
 | 
  
    |  | 
 | 
  
    |  |     app_logger = @logger.dup
 | 
  
    |  |     app_logger.prefix = "MockApp: "
 | 
  
    |  | 
 | 
  
    |  |     EventMachine.run {
 | 
  
    |  |       ZZZZ::PROTO::RPC.em_fiber_init
 | 
  
    |  |       @vis_server = MockWindowServer.new(vis_logger)
 | 
  
    |  |       vis_port = @vis_server.start_server(VIS_HOST, 0)
 | 
  
    |  |       @app = MockApp.new(app_logger)
 | 
  
    |  |       ########
 | 
  
    |  |       ######## c0000029: (B) 
 | 
  
    |  |       ######## -------------
 | 
  
    |  |       ######## Commenting out the run_in_fiber wrapper here makes the
 | 
  
    |  |       ######## crash happen sooner, as long as (A) is enabled above.
 | 
  
    |  |       ######## 
 | 
  
    |  |       ZZZZ::PROTO::RPC.run_in_fiber do
 | 
  
    |  |         warn "huzzah"
 | 
  
    |  |         @app.startup(VIS_HOST, vis_port)
 | 
  
    |  |         EventMachine.stop
 | 
  
    |  |       end
 | 
  
    |  |     }
 | 
  
    |  |   end  
 | 
  
    |  | end # TestEMFiber2
 | 
  
    |  | 
 | 
  
    |  | end # module TestEMFiber2
 | 
  
    |  | 
 | 
  
    |  | 
 |