Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 235 lines (191 sloc) 6.624 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
#--
# Copyright (C)2007-10 Tony Arcieri, Roger Pack
# You can redistribute this under the terms of the Ruby license
# See file LICENSE for details
#++

require 'cool.io'

# EventMachine emulation for Cool.io:
#
# require 'coolio/eventmachine'
#
# Drawbacks: slightly slower than EM.
# Benefits: timers are more accurate using libev than using EM
# TODO: some things like connection timeouts aren't implemented yet
# DONE: timers and normal socket functions are implemented.
module EventMachine
  class << self
    # Start the Reactor loop
    def run
      yield if block_given?
      Coolio::Loop.default.run
    end

    # Stop the Reactor loop
    def stop_event_loop
      Coolio::Loop.default.stop
    end

    class OneShotEMTimer < Coolio::TimerWatcher
      def setup(proc)
        @proc = proc
      end

      def on_timer
       @proc.call
      end
    end

    # ltodo: use Coolio's PeriodicTimer to wrap EM's two similar to it
    # todo: close all connections on 'stop', I believe

    def add_timer(interval, proc = nil, &block)
      block ||= proc
      t = OneShotEMTimer.new(interval, false) # non repeating
      t.setup(block)

      # fire 'er off ltodo: do we keep track of these timers in memory?
      t.attach(Coolio::Loop.default)
      t
    end

    def cancel_timer(t)
      # guess there's a case where EM you can say 'cancel' but it's already fired?
      # kind of odd but it happens
     t.detach if t.attached?
    end

    def set_comm_inactivity_timeout(*args); end # TODO

    # Make an outgoing connection
    def connect(addr, port, handler = Connection, *args, &block)
      block = args.pop if Proc === args[-1]

      # make sure we're a 'real' class here
      klass = if (handler and handler.is_a?(Class))
        handler
      else
        Class.new( Connection ) {handler and include handler}
      end

      wrapped_child = CallsBackToEM.connect(addr, port, *args) # ltodo: args? what? they're used? also TODOC TODO FIX
      conn = klass.new(wrapped_child) # ltodo [?] addr, port, *args)
      wrapped_child.attach(Coolio::Loop.default) # necessary
      conn.heres_your_socket(wrapped_child)
      wrapped_child.call_back_to_this(conn) # calls post_init for us
      yield conn if block_given?
    end

    # Start a TCP server on the given address and port
    def start_server(addr, port, handler = Connection, *args, &block)
      # make sure we're a 'real' class here
      klass = if (handler and handler.is_a?(Class))
        handler
      else
        Class.new( Connection ) {handler and include handler}
      end

      server = Coolio::TCPServer.new(addr, port, CallsBackToEM, *args) do |wrapped_child|
        conn = klass.new(wrapped_child)
        conn.heres_your_socket(wrapped_child) # ideally NOT have this :)
        wrapped_child.call_back_to_this(conn)
        block.call(conn) if block
      end

      server.attach(Coolio::Loop.default)
    end

    def stop_server(server)
      server.close
    end

    # Set the maximum number of descriptors available to this process
    def set_descriptor_table_size(nfds)
      Coolio::Utils.maxfds = nfds
    end

    # Compatibility noop. Handled automatically by libev
    def epoll; end

    # Compatibility noop. Handled automatically by libev
    def kqueue; end
  end

  class CallsBackToEM < Coolio::TCPSocket
    class ConnectTimer < Coolio::TimerWatcher
      attr_accessor :parent
      def on_timer
       @parent.connection_has_timed_out
      end
    end

    def call_back_to_this parent
      @call_back_to_this = parent
      parent.post_init
    end

    def on_connect
      # @connection_timer.detach if @connection_timer
      # won't need that anymore :) -- with server connecteds we don't have it, anyway

      # TODO should server accepted's call this? They don't currently
      # [and can't, since on_connect gets called basically in the initializer--needs some code love for that to happen :)
      @call_back_to_this.connection_completed if @call_back_to_this
    end

    def connection_has_timed_out
      return if closed?

      # wonder if this works when you're within a half-connected phase.
      # I think it does. What about TCP state?
      close unless closed?
      @call_back_to_this.unbind
    end

    def on_write_complete
      close if @should_close_after_writing
    end

    def should_close_after_writing
      @should_close_after_writing = true;
    end

    def on_close
      @call_back_to_this.unbind # about the same ltodo check if they ARE the same here
    end

    def on_resolve_failed
      fail
    end

    def on_connect_failed
      fail
    end

    def on_read(data)
      @call_back_to_this.receive_data data
    end

    def fail
      #@connection_timer.detch if @connection_timer
      @call_back_to_this.unbind
    end

    def self.connect(*args)
      a = super *args
      # the connect timer currently kills TCPServer classes. I'm not sure why.
      #@connection_timer = ConnectTimer.new(14) # needs to be at least higher than 12 :)
      #@connection_timer.parent = a
      #@connection_timer.attach(Coolio::Loop.default)
      a
    end
  end

  class Connection
    def self.new(*args)
      allocate#.instance_eval do
      # initialize *args
      #end
    end

    # we will need to call 'their functions' appropriately -- the commented out ones, here
    #
    # Callback fired when connection is created
    def post_init
      # I thought we were 'overriding' EM's existing methods, here.
      # Huh? Why do we have to define these then?
    end

    # Callback fired when connection is closed
    def unbind; end

    # Callback fired when data is received
    # def receive_data(data); end
    def heres_your_socket(instantiated_coolio_socket)
      instantiated_coolio_socket.call_back_to_this self
      @wrapped_coolio = instantiated_coolio_socket
    end

    # Send data to the current connection -- called by them
    def send_data(data)
      @wrapped_coolio.write data
    end

    # Close the connection, optionally after writing
    def close_connection(after_writing = false)
      return close_connection_after_writing if after_writing
      @wrapped_coolio.close
    end

    # Close the connection after all data has been written
    def close_connection_after_writing
      @wrapped_coolio.output_buffer_size.zero? ? @wrapped_coolio.close : @wrapped_coolio.should_close_after_writing
    end

    def get_peername
      family, port, host_name, host_ip = @wrapped_coolio.peeraddr
      Socket.pack_sockaddr_in(port, host_ip) # pack it up :)
    end
  end
end

# Shortcut constant
EM = EventMachine
Something went wrong with that request. Please try again.