class ClientForPoslynx::Net::EM_Session

Provides a synchronous, structured API for making requests to POSLynx host using Event Machine and returning responses when received.

Eliminates the need for complicated and messy event callback chains in POSLynx client code.

Multiple sessions may be in effect at the same time, interacting with the same POSLyx host by using the same Event Machine connector. This is important for performing actions such as initiating a new payment interaction when a previous interaction was abandoned.

Attributes

connector[RW]
em_system[R]
fiber[R]
status[RW]

Public Class Methods

execute(connector) { |s| ... } click to toggle source

Executes the given block in the context of a session attached to the given Event Machine connector. The session is passed as the argument to the block.

# File lib/client_for_poslynx/net/em_session.rb, line 40
def self.execute(connector)
  new( connector ).execute { |s| yield s }
end
new(connector, opts={}) click to toggle source

Builds a new EM_Session instance attached to the given Event Machine connector.

Options

  • :em_system - The event machine system that will be called on for operations such as adding timers and deferring actions.

# File lib/client_for_poslynx/net/em_session.rb, line 57
def initialize(connector, opts={})
  self.connector = connector
  @em_system = opts.fetch( :em_system, ::EM )
  self.status = :initialized
end

Public Instance Methods

exec_dissociated(&block) click to toggle source

Given a block argument, returns control to EventMachine, executes the block in a separate thread, waits for the thread to complete, and then returns the value returned by the block.

This is implemented behind the scenes using EventMachine::defer and has the same considerations and caveats.

When a call to exec_dissociated is nested within a block passed to anothet call, only the outermost invo- cation is deferred, and the inner call executes in the same thread as the outer call.

Note that methods of the session should not be called by code within the block since those methods should only be called from code running in the main event loop thread.

# File lib/client_for_poslynx/net/em_session.rb, line 136
def exec_dissociated(&block)
  @currently_dissociated ||= false

  if @currently_dissociated
    block.call
  else
    begin
      @currently_dissociated = true
      was_successful, resp_data_or_ex = Fiber.yield( [:_exec_dissociated, block] )
      raise resp_data_or_ex unless was_successful
      resp_data_or_ex
    ensure
      @currently_dissociated = false
    end
  end
end
execute() { |self| ... } click to toggle source

Executes the given block in the context of the session The session is passed as the argument to the block.

# File lib/client_for_poslynx/net/em_session.rb, line 65
def execute
  @fiber = Fiber.new do
    yield self
    :done
  end
  self.status = :engaged
  dispatch fiber.resume
end
request(data) click to toggle source

Called from within an executed block of code for the session to send request data to the POSLynx and return the response to the request once it is received. If a connection could not be established or is lost before a response can be received, then a RequestError exception will be raised.

If another session attempts to supplant this request, but the response to this request is subsequently received, then the response is returned as normal, but the current session's status is also changed to detached, so any subsequent request attempts made in the session will result in RequestAfterDetachedError being raised.

If another session is already waiting for a response, then this will attempt to usurp or supplant the other request. If the new request is of the same type as the existing request, and the type is not PinPadReset, then this call will raise a ConflictingRequestError exception.

# File lib/client_for_poslynx/net/em_session.rb, line 95
def request(data)
  if status == :detached
    msg = "Session cannot make requests because it is detached"
    raise RequestAfterDetachedError, msg
  end
  if connector.request_pending?
    pending_request_data = connector.latest_request.request_data
    pending_callbacks = connector.latest_request.result_callbacks
    if Data::Requests::PinPadReset === data && Data::Requests::PinPadReset === pending_request_data
      pending_callbacks.call :on_failure
      was_successful, resp_data_or_ex = Fiber.yield( [:_get_response] )
    elsif data.class == pending_request_data.class
      msg = "Attempted a request while another request of the same type is in progress"
      raise ConflictingRequestError, msg
    else
      was_successful, resp_data_or_ex = Fiber.yield( [:_request, data, connector.latest_request] )
    end
  else
    was_successful, resp_data_or_ex = Fiber.yield( [:_request, data] )
  end
  raise resp_data_or_ex unless was_successful
  resp_data_or_ex
end
sleep(delay_time) click to toggle source

Returns control to EventMachine, and returns control to the session code after the given delay-time in seconds.

# File lib/client_for_poslynx/net/em_session.rb, line 155
def sleep(delay_time)
  Fiber.yield [:_sleep, delay_time]
end

Private Instance Methods

_exec_dissociated(op) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 249
def _exec_dissociated op
  wrapped_op = ->() {
    begin
      result = op.call
      [true, result]
    rescue => ex
      [false, ex]
    end
  }
  callback = ->(result) do
    dispatch fiber.resume result
  end
  em_system.defer wrapped_op, callback
end
_get_response() click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 183
def _get_response
  connector.get_response response_handlers
end
_request(data, overlaps_request=nil) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 169
def _request(data, overlaps_request=nil)
  connector.connect(
    on_success: ->() {
      send_request_callbacks = response_handlers( overlaps_request )
      connector.send_request data, send_request_callbacks
    },
    on_failure: ->() {
      dispatch fiber.resume( [false, RequestError.new] )
    }
  )
rescue => ex
  dispatch fiber.resume( [false, ex] )
end
_sleep(delay_time) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 264
def _sleep(delay_time)
  callback = ->() {
    dispatch fiber.resume
  }
  em_system.add_timer delay_time, callback
end
detach!() click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 244
def detach!
  self.connector = nil
  self.status = :detached
end
dispatch(fiber_callback) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 164
def dispatch(fiber_callback)
  return if fiber_callback == :done
  send *fiber_callback
end
on_failure_handler(overlaps_request) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 224
def on_failure_handler(overlaps_request)
  overlaps_request ?
    on_failure_handler_with_overlap( overlaps_request ) :
    simple_on_failure_handler
end
on_failure_handler_with_overlap(overlaps_request) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 230
def on_failure_handler_with_overlap(overlaps_request)
  overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
  ->() {
    overlapped_request_callbacks.call :on_failure if overlaps_request
    dispatch fiber.resume( [false, RequestError.new] )
  }
end
on_response_handler(overlaps_request) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 197
def on_response_handler(overlaps_request)
  overlaps_request ?
    on_response_handler_with_overlap( overlaps_request ) :
    simple_on_response_handler
end
on_response_handler_with_overlap(overlaps_request) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 203
def on_response_handler_with_overlap(overlaps_request)
  overlapped_request_data = overlaps_request && overlaps_request.request_data
  overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
  ->(response_data) {
    if overlapped_request_data.potential_response?( response_data )
      overlapped_request_callbacks.call :on_detached
      overlapped_request_callbacks.call :on_response, response_data
      _get_response
    else
      overlapped_request_callbacks.call :on_failure
      dispatch fiber.resume( [true, response_data] )
    end
  }
end
response_handlers(overlaps_request=nil) click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 187
def response_handlers(overlaps_request=nil)
  overlapped_request_data = overlaps_request && overlaps_request.request_data
  overlapped_request_callbacks = overlaps_request && overlaps_request.result_callbacks
  {
    on_response: on_response_handler( overlaps_request ),
    on_failure: on_failure_handler( overlaps_request ),
    on_detached: ->(){ detach! },
  }
end
simple_on_failure_handler() click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 238
def simple_on_failure_handler
  ->() {
    dispatch fiber.resume( [false, RequestError.new] )
  }
end
simple_on_response_handler() click to toggle source
# File lib/client_for_poslynx/net/em_session.rb, line 218
def simple_on_response_handler
  ->(response_data) {
    dispatch fiber.resume( [true, response_data] )
  }
end