Class: Ticketing::Conn

Inherits:
Object
  • Object
show all
Defined in:
lib/ticketing/connection.rb

Overview

One persistent TCP connection to a lock server, managed on a background thread. A dropped connection is retried automatically without disturbing others. Requests are pipelined: a reply is matched back to its waiter by (op, key).

Constant Summary collapse

RECONNECT_INTERVAL =
3.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(addr, logger) ⇒ Conn

Returns a new instance of Conn.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ticketing/connection.rb', line 54

def initialize(addr, logger)
  @addr = addr
  @logger = logger
  @host, @port = parse_addr(addr)
  @status = :down
  @writer = nil
  @socket = nil
  @running = true
  @thread = nil
  @writer_mutex = Mutex.new
  @pending_mutex = Mutex.new
  @acquire_waiters = {}
  @release_waiters = {}
end

Instance Attribute Details

#addrObject (readonly)

Returns the value of attribute addr.



52
53
54
# File 'lib/ticketing/connection.rb', line 52

def addr
  @addr
end

Instance Method Details

#request(op, key, frame) ⇒ Object

Enqueues a waiter under (op, key), writes the frame, then blocks until the reader thread resolves it. Raises IoError if the write fails or the connection is not up.



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
# File 'lib/ticketing/connection.rb', line 77

def request(op, key, frame)
  waiter = Waiter.new
  @writer_mutex.synchronize do
    w = @writer or raise Ticketing.closed_error

    @pending_mutex.synchronize do
      (waiters(op)[key] ||= []).push(waiter)
    end

    begin
      w.write(frame)
      w.flush
    rescue IOError, SystemCallError => e
      @pending_mutex.synchronize do
        queue = waiters(op)[key]
        if queue
          queue.pop
          waiters(op).delete(key) if queue.empty?
        end
      end
      raise IoError.new(e)
    end
  end
  waiter.await
end

#start_connectorObject

Starts the background connect/read/reconnect loop.



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
# File 'lib/ticketing/connection.rb', line 104

def start_connector
  @thread = Thread.new do
    ever_connected = false
    while @running
      sock = connect_socket
      next if sock.nil?

      @socket = sock
      @writer = sock
      @status = :up
      @logger.warn("reconnected: #{@addr}") if ever_connected
      ever_connected = true

      begin
        read_loop(sock)
      rescue StandardError
        nil
      end

      teardown(sock)
      @logger.warn("connection lost: #{@addr} — reconnecting every #{RECONNECT_INTERVAL}s")
      sleep RECONNECT_INTERVAL if @running
    end
  end
end

#stopObject

Stops the background thread, closes the socket, and fails all waiters.



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ticketing/connection.rb', line 131

def stop
  @running = false
  @status = :down
  thread = @thread
  begin
    @socket&.close
  rescue StandardError
    nil
  end
  thread&.kill
  clear_pending
end

#usable?Boolean

True while at least one reply can currently be sent and received.

Returns:

  • (Boolean)


70
71
72
# File 'lib/ticketing/connection.rb', line 70

def usable?
  @status == :up
end