nats_messaging 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78d691503ace2b93a46a4e9f501ead1885404063abbe88d7883e59bcb7ed1fe0
4
- data.tar.gz: f9d2c4178caab4acbb383e39cb6e097044a0ab53f48bb7b1f0f3d05470188e63
3
+ metadata.gz: afa8e0bfe4f5c00bd91323e60c3d3e630bf1eea504495a079c667533fa98ca5a
4
+ data.tar.gz: 30a30e95c8c0feaabfaf01346faddedeeea6232e97f768ed58ddde810ea6285e
5
5
  SHA512:
6
- metadata.gz: d6053bbcfd6ca775166c61e55f7cf79395768ea840ab72f1763fb0c078e6c2118a99de35f68214c2bec0f74095dd87db7380f4ef0767c4b80fc4410a75697489
7
- data.tar.gz: 13a429b538791a448782c6264746b3a0ab63842914de2eb7b7387ef3bc550ccb36d391fcea81d520fd734f0beac647be75442d14da38ffe926af13105235182f
6
+ metadata.gz: 862f641b1555417327a7d16c7a566d43b3ed168f5cf4403ad5732e8ef7e6eba6530fbae98a530c0a73fc615b65bda4b31dc08efec34ab437bc48cc41774062d2
7
+ data.tar.gz: f700b68ecae1504dc079c6702bddb4d1720c779101f0de0c9595aafc6134610fc5754c977e4b95bc48fb7348415620c860ffa074dc5dafc00841d394d265a75e
@@ -1,21 +1,82 @@
1
1
  require 'nats/io/client'
2
2
  require 'msgpack'
3
+ require 'logger'
4
+ require 'active_support'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+ require 'timeout'
3
7
 
4
8
  module NatsMessaging
5
9
  class NatsService
6
- def initialize(nats_url, stream_name: nil, subjects: [])
7
- @nats = NATS.connect(nats_url)
10
+ # Reintentos infinitos: con un solo server en el pool, agotar los intentos
11
+ # deja el proceso sin NATS hasta un reinicio manual. ping_interval más corto
12
+ # detecta conexiones zombie en ~1 min en vez de los ~4 min por defecto.
13
+ DEFAULT_CONNECT_OPTIONS = {
14
+ reconnect: true,
15
+ max_reconnect_attempts: -1,
16
+ reconnect_time_wait: 30,
17
+ ping_interval: 30,
18
+ max_outstanding_pings: 2
19
+ }.freeze
20
+
21
+ # nats-pure usa el mismo max_reconnect_attempts tanto para el connect
22
+ # inicial (establish_connection!) como para la reconexión de una conexión
23
+ # ya establecida (attempt_reconnect): con max_reconnect_attempts: -1,
24
+ # can_reuse_server? siempre devuelve true y el connect inicial reintenta
25
+ # para siempre sin lanzar excepción, colgando el arranque del proceso host.
26
+ # El `connect_timeout` propio de nats-pure no sirve para acotar esto: solo
27
+ # limita un intento de conexión TCP/handshake individual, no la cantidad
28
+ # de reintentos. No hay forma nativa en nats-pure 2.5 de distinguir
29
+ # "primer connect" de "reconexión posterior", así que se acota el connect
30
+ # inicial desde afuera con Timeout.timeout. Es aceptable usar Timeout pese
31
+ # a su conocida rudeza (mata el hilo sin dar chance de cleanup) porque
32
+ # ocurre solo durante el boot, antes de que exista estado que corromper;
33
+ # si el timeout dispara, la instancia parcialmente construida simplemente
34
+ # se descarta al propagar la excepción.
35
+ DEFAULT_INITIAL_CONNECT_TIMEOUT = 15
36
+
37
+ attr_reader :pid
38
+
39
+ def initialize(nats_url, stream_name: nil, subjects: [], connect_options: {}, initial_connect_timeout: DEFAULT_INITIAL_CONNECT_TIMEOUT)
40
+ @pid = Process.pid
41
+ @subscriptions = {} # Store active subscriptions
42
+ begin
43
+ @nats = Timeout.timeout(initial_connect_timeout) do
44
+ NATS.connect(nats_url, DEFAULT_CONNECT_OPTIONS.merge(connect_options))
45
+ end
46
+ rescue Timeout::Error
47
+ raise NatsMessaging::InitialConnectTimeoutError,
48
+ "Could not connect to NATS at #{nats_url} within #{initial_connect_timeout}s"
49
+ end
50
+ register_lifecycle_callbacks
8
51
  if stream_name
9
52
  @js = @nats.jetstream
10
53
  create_stream(stream_name, subjects)
11
54
  end
12
- @subscriptions = {} # Store active subscriptions
55
+ end
56
+
57
+ def logger
58
+ @logger ||= (defined?(Rails) && Rails.logger) || Logger.new(STDOUT)
13
59
  end
14
60
 
15
61
  def log_duration(start_time, message_format, *args)
16
62
  duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
17
63
  full_message = "NATS [%.2fs]: #{message_format}" % [duration, *args]
18
- Rails.logger.info full_message
64
+ logger.info full_message
65
+ end
66
+
67
+ def register_lifecycle_callbacks
68
+ @nats.on_disconnect do |e|
69
+ logger.warn "NATS: disconnected#{e ? " (#{e.message})" : ''}"
70
+ end
71
+ @nats.on_reconnect do
72
+ logger.info "NATS: reconnected to #{@nats.connected_server}"
73
+ end
74
+ @nats.on_error do |e|
75
+ logger.error "NATS: error: #{e.message}"
76
+ end
77
+ @nats.on_close do
78
+ logger.error "NATS: connection CLOSED permanently"
79
+ end
19
80
  end
20
81
 
21
82
  # Create a stream
@@ -67,7 +128,7 @@ module NatsMessaging
67
128
 
68
129
  # Subscribe to a subject using MessagePack
69
130
  def subscribe_to_subject(subject, durable_name = "durable_name")
70
- Rails.logger.info "NATS: Subscribing to #{subject}"
131
+ logger.info "NATS: Subscribing to #{subject}"
71
132
  if @js
72
133
  @subscriptions[subject] = @js.subscribe(subject, durable: durable_name) do |msg|
73
134
  unpacked_data = process_received_message(msg, subject)
@@ -99,7 +160,7 @@ module NatsMessaging
99
160
 
100
161
  # Send a request to a subject and wait for response using MessagePack
101
162
  def send_request(subject, message, timeout = 5)
102
- Rails.logger.info "NATS: Sending request to #{subject} with message: #{message}"
163
+ logger.info "NATS: Sending request to #{subject} with message: #{message}"
103
164
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
104
165
  begin
105
166
  packed_message = message.to_msgpack # Serializar el mensaje
@@ -126,13 +187,13 @@ module NatsMessaging
126
187
  end
127
188
  begin
128
189
  # Create a new subscription
129
- Rails.logger.info "NATS: Listening on #{subject} with reply message: #{reply_message}"
190
+ logger.info "NATS: Listening on #{subject} with reply message: #{reply_message}"
130
191
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) # En caso de que falle antes del segundo begin
131
192
  @subscriptions[subject] = @nats.subscribe(subject) do |msg|
132
193
  begin
133
194
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
134
195
  unpacked_data = MessagePack.unpack(msg.data) # Deserializar mensaje recibido
135
- Rails.logger.info "NATS: Received request on #{subject}: #{unpacked_data}"
196
+ logger.info "NATS: Received request on #{subject}: #{unpacked_data}"
136
197
  indifferent_unpacked_data = make_data_indifferent(unpacked_data)
137
198
  yield_return = yield(subject, indifferent_unpacked_data) if block_given?
138
199
  reply = reply_message.nil? ? yield_return : reply_message
@@ -147,7 +208,7 @@ module NatsMessaging
147
208
  indifferent_unpacked_data
148
209
  rescue => e
149
210
  log_duration(start_time, "Error while processing message on %s: %s", subject, e.message)
150
- Rails.logger.error e.backtrace.join("\n")
211
+ logger.error e.backtrace.join("\n")
151
212
  end
152
213
  end
153
214
  rescue => e
@@ -1,8 +1,27 @@
1
1
  require 'nats_messaging/nats_service'
2
2
 
3
3
  module NatsMessaging
4
- # Main entry point to expose the functionalities
5
- def self.service(nats_url, stream_name: nil, subjects: [])
6
- @service ||= NatsService.new(nats_url, stream_name: stream_name, subjects: subjects)
4
+ # Se lanza cuando el connect inicial no logra establecerse dentro de
5
+ # initial_connect_timeout segundos. No debe confundirse con las
6
+ # desconexiones posteriores a una conexión ya establecida, que nats-pure
7
+ # reintenta indefinidamente en su propio hilo sin lanzar excepciones.
8
+ class InitialConnectTimeoutError < StandardError; end
9
+
10
+ # Main entry point to expose the functionalities.
11
+ #
12
+ # Memoiza la instancia por PID: si el proceso actual forkeó (Puma/solid_queue)
13
+ # después de que @service ya existía, la conexión heredada es inválida (el
14
+ # socket pertenece al padre) y se descarta sin intentar cerrarla, creando una
15
+ # instancia nueva con los parámetros de ESTA llamada. Dentro del mismo PID la
16
+ # memoización ignora los parámetros de llamadas posteriores, igual que antes.
17
+ def self.service(nats_url, stream_name: nil, subjects: [], connect_options: {}, initial_connect_timeout: NatsService::DEFAULT_INITIAL_CONNECT_TIMEOUT)
18
+ @service = nil if @service && @service.pid != Process.pid
19
+ @service ||= NatsService.new(
20
+ nats_url,
21
+ stream_name: stream_name,
22
+ subjects: subjects,
23
+ connect_options: connect_options,
24
+ initial_connect_timeout: initial_connect_timeout
25
+ )
7
26
  end
8
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nats_messaging
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bea Graboloza
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-08 00:00:00.000000000 Z
11
+ date: 2026-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nats-pure
@@ -24,6 +24,62 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: msgpack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: logger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '6.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '6.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
27
83
  description: A service to send and receive messages on a Rails application using NATS
28
84
  email: beatriz.graboloza@bpo-advisors.net
29
85
  executables: []
@@ -44,14 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
100
  requirements:
45
101
  - - ">="
46
102
  - !ruby/object:Gem::Version
47
- version: '0'
103
+ version: '3.0'
48
104
  required_rubygems_version: !ruby/object:Gem::Requirement
49
105
  requirements:
50
106
  - - ">="
51
107
  - !ruby/object:Gem::Version
52
108
  version: '0'
53
109
  requirements: []
54
- rubygems_version: 3.0.9
110
+ rubygems_version: 3.5.16
55
111
  signing_key:
56
112
  specification_version: 4
57
113
  summary: a NATS service for messaging