File: completion.rb [eventmachine-1.0.7 Documentation]

Last Modified
  2015-07-29 19:48:04 +0000
Requires

## Description

# 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.