Class: Ticketing::TicketBroker

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

Overview

Shared handle to the lock service. Declare once (e.g. a constant) and share it across threads — the client is fully thread-safe, and connection failures self-heal without ever breaking this object. All time values are seconds — the wire protocol is second-granular. 락 서비스 공유 핸들 — 상수로 한 번 선언해 스레드들이 공유한다. 클라이언트는 스레드 안전하며 연결 오류는 자가 치유되고 이 객체를 망가뜨리지 않는다. 시간 값은 전부 초 단위.

BROKER = Ticketing::TicketBroker.connect("127.0.0.1:5225")
ticket = BROKER.acquire("order-42", 5, 30)
# ... critical section — 임계 구역 ...
ticket.release

Constant Summary collapse

ACQUIRE_RETRY =
0.1
RELEASE_RETRY_WINDOW =
5.0
RELEASE_RETRY =
0.2

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(addrs, token: "", tls: nil, logger: nil) ⇒ TicketBroker

token/tls/logger are keyword options; tls accepts nil(off) | 'system-roots' | 'insecure-skip-verify' | CA PEM path. 토큰/TLS/로거는 키워드 옵션.



27
28
29
30
31
32
33
34
35
36
# File 'lib/ticketing/broker.rb', line 27

def initialize(addrs, token: "", tls: nil, logger: nil)
  ssl_context, verify_host = Tls.build(tls)
  list = addrs.map(&:to_s)
  list *= 2 if list.size == 1 # one address held with two connections. 단일 주소는 연결 2개.
  @topo = Topology.new(list)
  @rr = 0
  @conns = list.each_with_index.map do |addr, index|
    Conn.new(addr, index, token, ssl_context, verify_host, @topo, logger)
  end
end

Class Method Details

.connect(*addrs) ⇒ Object

Plaintext, empty-token broker. Never fails. 평문·빈 토큰 — 실패 불가.



22
# File 'lib/ticketing/broker.rb', line 22

def self.connect(*addrs) = new(addrs)

Instance Method Details

#acquire(key, wait, lease) ⇒ Object

Acquires the lock key, waiting up to wait seconds (0 = forever). lease bounds how long the server holds it if this client dies. Failovers (dead leader, election, redirect) are retried invisibly. 락 획득 — wait 초(0=무한) 대기, lease는 사망 대비 서버 보유 상한. 장애조치는 내부에서 투명하게 재시도된다.



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

def acquire(key, wait, lease)
  Protocol.validate_key!(key)
  frame = Protocol.acquire_frame(key, wait, lease)
  deadline = wait.positive? ? monotime + wait : nil

  loop do
    conn = pick
    raise TicketError.new(:not_connected, "no connected server") if conn.nil?

    reply = conn.request(Protocol::OP_ACQUIRE, key, frame).pop
    if reply.is_a?(TicketError)
      raise reply unless reply.aborted?
      raise TicketError.new(:timeout, "timed out waiting to acquire the lock") if deadline && monotime >= deadline

      sleep(ACQUIRE_RETRY)
      next
    end
    kind, token = reply
    case kind
    when :acquired then return Ticket.new(self, conn, key, token)
    when :timed_out then raise TicketError.new(:timeout, "timed out waiting to acquire the lock")
    else raise TicketError.new(:protocol, "unexpected reply for acquire")
    end
  end
end

#release_forget(granted, key) ⇒ Object

Fire-and-forget release (single attempt) — the lease cleans up if it is lost in a failover. 단발 백그라운드 해제 — 유실 시 lease가 정리.



109
110
111
112
113
# File 'lib/ticketing/broker.rb', line 109

def release_forget(granted, key)
  conn = granted.usable? ? granted : pick
  conn&.request(Protocol::OP_RELEASE, key, Protocol.release_frame(key))
  nil
end

#release_loop(granted, key) ⇒ Object

Prefer the granting connection, else any usable one; retry across failover so a held lock is not stranded. 부여 연결 우선 — 장애조치를 넘겨 재시도해 보유 락이 방치되지 않게 한다.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ticketing/broker.rb', line 84

def release_loop(granted, key)
  frame = Protocol.release_frame(key)
  deadline = monotime + RELEASE_RETRY_WINDOW
  loop do
    conn = granted.usable? ? granted : pick
    unless conn.nil?
      reply = conn.request(Protocol::OP_RELEASE, key, frame).pop
      if reply.is_a?(TicketError)
        raise reply unless reply.aborted?
      else
        case reply.first
        when :released then return true
        when :not_found then return false
        else raise TicketError.new(:protocol, "unexpected reply for release")
        end
      end
    end
    raise TicketError.new(:not_connected, "no connected server") if monotime >= deadline

    sleep(RELEASE_RETRY)
  end
end

#wait_ready(timeout) ⇒ Object

Waits up to timeout seconds for any connection to come up. 연결 1개가 올라올 때까지 대기(기동 편의).



71
72
73
74
75
76
77
78
79
# File 'lib/ticketing/broker.rb', line 71

def wait_ready(timeout)
  deadline = monotime + timeout
  loop do
    return true unless pick.nil?
    return false if monotime >= deadline

    sleep(0.02)
  end
end