Class: NatsMessaging::NatsService
- Inherits:
-
Object
- Object
- NatsMessaging::NatsService
- Defined in:
- lib/nats_messaging/nats_service.rb
Constant Summary collapse
- DEFAULT_CONNECT_OPTIONS =
Reintentos infinitos: con un solo server en el pool, agotar los intentos deja el proceso sin NATS hasta un reinicio manual. ping_interval más corto detecta conexiones zombie en ~1 min en vez de los ~4 min por defecto.
{ reconnect: true, max_reconnect_attempts: -1, reconnect_time_wait: 30, ping_interval: 30, max_outstanding_pings: 2 }.freeze
- DEFAULT_INITIAL_CONNECT_TIMEOUT =
nats-pure usa el mismo max_reconnect_attempts tanto para el connect inicial (establish_connection!) como para la reconexión de una conexión ya establecida (attempt_reconnect): con max_reconnect_attempts: -1, can_reuse_server? siempre devuelve true y el connect inicial reintenta para siempre sin lanzar excepción, colgando el arranque del proceso host. El
connect_timeoutpropio de nats-pure no sirve para acotar esto: solo limita un intento de conexión TCP/handshake individual, no la cantidad de reintentos. No hay forma nativa en nats-pure 2.5 de distinguir "primer connect" de "reconexión posterior", así que se acota el connect inicial desde afuera con Timeout.timeout. Es aceptable usar Timeout pese a su conocida rudeza (mata el hilo sin dar chance de cleanup) porque ocurre solo durante el boot, antes de que exista estado que corromper; si el timeout dispara, la instancia parcialmente construida simplemente se descarta al propagar la excepción. 15
Instance Attribute Summary collapse
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
Instance Method Summary collapse
-
#create_stream(stream_name, subjects) ⇒ Object
Create a stream.
-
#initialize(nats_url, stream_name: nil, subjects: [], connect_options: {}, initial_connect_timeout: DEFAULT_INITIAL_CONNECT_TIMEOUT) ⇒ NatsService
constructor
A new instance of NatsService.
-
#listen_and_reply(subject, reply_message = nil) ⇒ Object
Listen to a subject and reply with a message using MessagePack.
- #log_duration(start_time, message_format, *args) ⇒ Object
- #logger ⇒ Object
- #make_data_indifferent(data) ⇒ Object
-
#process_received_message(msg, subject) ⇒ Object
Process received message.
-
#publish_message(subject, message) ⇒ Object
Publish a message to a subject using MessagePack.
- #register_lifecycle_callbacks ⇒ Object
-
#send_request(subject, message, timeout = 5) ⇒ Object
Send a request to a subject and wait for response using MessagePack.
-
#subscribe_to_subject(subject, durable_name = "durable_name") ⇒ Object
Subscribe to a subject using MessagePack.
Constructor Details
#initialize(nats_url, stream_name: nil, subjects: [], connect_options: {}, initial_connect_timeout: DEFAULT_INITIAL_CONNECT_TIMEOUT) ⇒ NatsService
Returns a new instance of NatsService.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/nats_messaging/nats_service.rb', line 39 def initialize(nats_url, stream_name: nil, subjects: [], connect_options: {}, initial_connect_timeout: DEFAULT_INITIAL_CONNECT_TIMEOUT) @pid = Process.pid @subscriptions = {} # Store active subscriptions begin @nats = Timeout.timeout(initial_connect_timeout) do NATS.connect(nats_url, DEFAULT_CONNECT_OPTIONS.merge()) end rescue Timeout::Error raise NatsMessaging::InitialConnectTimeoutError, "Could not connect to NATS at #{nats_url} within #{initial_connect_timeout}s" end register_lifecycle_callbacks if stream_name @js = @nats.jetstream create_stream(stream_name, subjects) end end |
Instance Attribute Details
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
37 38 39 |
# File 'lib/nats_messaging/nats_service.rb', line 37 def pid @pid end |
Instance Method Details
#create_stream(stream_name, subjects) ⇒ Object
Create a stream
83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/nats_messaging/nats_service.rb', line 83 def create_stream(stream_name, subjects) begin start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) stream = @js.stream_info(stream_name) log_duration(start_time, "Stream %s already exists", stream_name) rescue NATS::JetStream::Error::NotFound @js.add_stream(name: stream_name, subjects: subjects) log_duration(start_time, "Stream %s created", stream_name) rescue => e log_duration(start_time, "Failed to create stream: %s", e.) end end |
#listen_and_reply(subject, reply_message = nil) ⇒ Object
Listen to a subject and reply with a message using MessagePack
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 |
# File 'lib/nats_messaging/nats_service.rb', line 183 def listen_and_reply(subject, =nil) # Cancel active subscription if it already exists for this subject if @subscriptions[subject] @subscriptions[subject].unsubscribe end begin # Create a new subscription logger.info "NATS: Listening on #{subject} with reply message: #{}" start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) # En caso de que falle antes del segundo begin @subscriptions[subject] = @nats.subscribe(subject) do |msg| begin start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) unpacked_data = MessagePack.unpack(msg.data) # Deserializar mensaje recibido logger.info "NATS: Received request on #{subject}: #{unpacked_data}" indifferent_unpacked_data = make_data_indifferent(unpacked_data) yield_return = yield(subject, indifferent_unpacked_data) if block_given? reply = .nil? ? yield_return : packed_reply = reply.to_msgpack # Serializar respuesta # Asegurar que msg.reply existe antes de responder if msg.reply @nats.publish(msg.reply, packed_reply) log_duration(start_time, "Replied to %s with message: %s", subject, reply.inspect) else log_duration(start_time, "No reply subject for %s, cannot respond", subject) end indifferent_unpacked_data rescue => e log_duration(start_time, "Error while processing message on %s: %s", subject, e.) logger.error e.backtrace.join("\n") end end rescue => e log_duration(start_time, "Error while replying: %s", e.) end end |
#log_duration(start_time, message_format, *args) ⇒ Object
61 62 63 64 65 |
# File 'lib/nats_messaging/nats_service.rb', line 61 def log_duration(start_time, , *args) duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time = "NATS [%.2fs]: #{}" % [duration, *args] logger.info end |
#logger ⇒ Object
57 58 59 |
# File 'lib/nats_messaging/nats_service.rb', line 57 def logger @logger ||= (defined?(Rails) && Rails.logger) || Logger.new(STDOUT) end |
#make_data_indifferent(data) ⇒ Object
121 122 123 124 125 126 127 |
# File 'lib/nats_messaging/nats_service.rb', line 121 def make_data_indifferent(data) if data.is_a?(Hash) data.with_indifferent_access else data end end |
#process_received_message(msg, subject) ⇒ Object
Process received message
150 151 152 153 154 155 156 157 158 159 |
# File 'lib/nats_messaging/nats_service.rb', line 150 def (msg, subject) begin start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) unpacked_data = MessagePack.unpack(msg.data) log_duration(start_time, "Message received on %s: %s", subject, unpacked_data.inspect) unpacked_data rescue => e log_duration(start_time, "Error while processing message on %s: %s", subject, e.) end end |
#publish_message(subject, message) ⇒ Object
Publish a message to a subject using MessagePack
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/nats_messaging/nats_service.rb', line 97 def (subject, ) = .to_msgpack # Serializar a MessagePack if @js begin start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) ack = @js.publish(subject, ) log_duration(start_time, "Message sent to %s: %s, ACK: %s", subject, , ack.inspect) rescue NATS::JetStream::Error::NotFound @nats.publish(subject, ) log_duration(start_time, "Stream not found, falling back to regular NATS publish for subject %s", subject) rescue => e log_duration(start_time, "Unexpected error while publishing to %s: %s", subject, e.) end else begin start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) @nats.publish(subject, ) log_duration(start_time, "Message sent to %s: %s", subject, ) rescue => e log_duration(start_time, "Unexpected error: %s", e.) end end end |
#register_lifecycle_callbacks ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/nats_messaging/nats_service.rb', line 67 def register_lifecycle_callbacks @nats.on_disconnect do |e| logger.warn "NATS: disconnected#{e ? " (#{e.})" : ''}" end @nats.on_reconnect do logger.info "NATS: reconnected to #{@nats.connected_server}" end @nats.on_error do |e| logger.error "NATS: error: #{e.}" end @nats.on_close do logger.error "NATS: connection CLOSED permanently" end end |
#send_request(subject, message, timeout = 5) ⇒ Object
Send a request to a subject and wait for response using MessagePack
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/nats_messaging/nats_service.rb', line 162 def send_request(subject, , timeout = 5) logger.info "NATS: Sending request to #{subject} with message: #{}" start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) begin = .to_msgpack # Serializar el mensaje response = @nats.request(subject, , timeout: timeout) # Timeout of 5 seconds or custom unpacked_response = MessagePack.unpack(response.data) # Deserializar respuesta log_duration(start_time, "Received reply: %s", unpacked_response.inspect) indifferent_unpacked_data = make_data_indifferent(unpacked_response) yield(subject, indifferent_unpacked_data) if block_given? indifferent_unpacked_data rescue NATS::IO::Timeout log_duration(start_time, "Request timed out for subject: %s", subject) nil rescue => e log_duration(start_time, "Unexpected error for subject %s: %s", subject, e.) nil end end |
#subscribe_to_subject(subject, durable_name = "durable_name") ⇒ Object
Subscribe to a subject using MessagePack
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/nats_messaging/nats_service.rb', line 130 def subscribe_to_subject(subject, durable_name = "durable_name") logger.info "NATS: Subscribing to #{subject}" if @js @subscriptions[subject] = @js.subscribe(subject, durable: durable_name) do |msg| unpacked_data = (msg, subject) indifferent_unpacked_data = make_data_indifferent(unpacked_data) yield(msg.subject, indifferent_unpacked_data) if block_given? indifferent_unpacked_data end else @subscriptions[subject] = @nats.subscribe(subject) do |msg| unpacked_data = (msg, subject) indifferent_unpacked_data = make_data_indifferent(unpacked_data) yield(msg.subject, indifferent_unpacked_data) if block_given? indifferent_unpacked_data end end end |