forest_admin_datasource_rpc 1.7.1 → 1.8.0

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: 149a87567ed3ae82d40b0273341c681e2714aa7a336106ad6a0ccebc4d8ea60d
4
- data.tar.gz: 878a081448c802e6406d905f60d7470b3ef379705ce307bebb64d8ce8dfffe34
3
+ metadata.gz: ede19279c81c39b065be45f1741b08acc5babc63efdec6314a9c691631eb41a7
4
+ data.tar.gz: 7e9701d233a9fa29ce2e5197ce8d5a41bba5daec470ed2e519cb1441ca7266b1
5
5
  SHA512:
6
- metadata.gz: 17578e18fd03f6749bf3392a304f3e1e1230d1d6d0b82a0947175d9101782e90288d5a03213953f7cb937499d9bf78349a96c034cb0a88c43199aafe7b144d91
7
- data.tar.gz: 9d82189dfd1d88f6973aeb9a30ccd6702006c406dd6aa490bff247e655a416df44b2cba982f6405e4835429cf5df54eeefc9cfa5e18fadffbeb79a29e0419498
6
+ metadata.gz: 7668783a404d091882070ae683cac5db1d04447c74310e7ce572c060ab2891589d8eb6ac919b93357edaca6a282ff297f3204d8858a47c8ab932c27a4cdc123a
7
+ data.tar.gz: e98bf680d0e374e2974470d209d52b61ffa28a977dcc332c9fb091c289a8458a1591cbc888e5a025ff0f0aac7e999c22e9bcfe9eae88e42d6dc5c441bc72878a
@@ -6,17 +6,58 @@ require 'ld-eventsource'
6
6
  module ForestAdminDatasourceRpc
7
7
  module Utils
8
8
  class SseClient
9
+ attr_reader :closed
10
+
11
+ MAX_BACKOFF_DELAY = 30 # seconds
12
+ INITIAL_BACKOFF_DELAY = 2 # seconds
13
+
9
14
  def initialize(uri, auth_secret, &on_rpc_stop)
10
15
  @uri = uri
11
16
  @auth_secret = auth_secret
12
17
  @on_rpc_stop = on_rpc_stop
13
18
  @client = nil
14
19
  @closed = false
20
+ @connection_attempts = 0
21
+ @reconnect_thread = nil
22
+ @connecting = false
15
23
  end
16
24
 
17
25
  def start
18
26
  return if @closed
19
27
 
28
+ attempt_connection
29
+ end
30
+
31
+ def close
32
+ return if @closed
33
+
34
+ @closed = true
35
+ ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE Client] Closing connection')
36
+
37
+ # Stop reconnection thread if running
38
+ if @reconnect_thread&.alive?
39
+ @reconnect_thread.kill
40
+ @reconnect_thread = nil
41
+ end
42
+
43
+ begin
44
+ @client&.close
45
+ rescue StandardError => e
46
+ ForestAdminRpcAgent::Facades::Container.logger&.log('Debug',
47
+ "[SSE Client] Error during close: #{e.message}")
48
+ end
49
+
50
+ ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE Client] Connection closed')
51
+ end
52
+
53
+ private
54
+
55
+ def attempt_connection
56
+ return if @closed
57
+ return if @connecting
58
+
59
+ @connecting = true
60
+ @connection_attempts += 1
20
61
  timestamp = Time.now.utc.iso8601(3)
21
62
  signature = generate_signature(timestamp)
22
63
 
@@ -26,29 +67,106 @@ module ForestAdminDatasourceRpc
26
67
  'X_SIGNATURE' => signature
27
68
  }
28
69
 
29
- ForestAdminRpcAgent::Facades::Container.logger.log('Debug', "Connecting to SSE at #{@uri}.")
70
+ ForestAdminRpcAgent::Facades::Container.logger&.log(
71
+ 'Debug',
72
+ "[SSE Client] Connecting to #{@uri} (attempt ##{@connection_attempts})"
73
+ )
30
74
 
31
- @client = SSE::Client.new(@uri, headers: headers) do |client|
32
- client.on_event do |event|
33
- handle_event(event)
75
+ begin
76
+ # Close existing client if any
77
+ begin
78
+ @client&.close
79
+ rescue StandardError
80
+ # Ignore close errors
34
81
  end
35
82
 
36
- client.on_error do |err|
37
- # TODO: optimisation on client close
38
- # ForestAdminRpcAgent::Facades::Container.logger.log('Warn', "[SSE] Error: #{err.class} - #{err.message}")
83
+ @client = SSE::Client.new(@uri, headers: headers) do |client|
84
+ client.on_event do |event|
85
+ handle_event(event)
86
+ end
87
+
88
+ client.on_error do |err|
89
+ handle_error_with_reconnect(err)
90
+ end
39
91
  end
92
+
93
+ ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE Client] Connected successfully')
94
+ rescue StandardError => e
95
+ ForestAdminRpcAgent::Facades::Container.logger&.log(
96
+ 'Error',
97
+ "[SSE Client] Failed to connect: #{e.class} - #{e.message}"
98
+ )
99
+ @connecting = false
100
+ schedule_reconnect
40
101
  end
41
102
  end
42
103
 
43
- def close
104
+ def handle_error_with_reconnect(err)
105
+ # Ignore errors when client is intentionally closed
44
106
  return if @closed
45
107
 
46
- @closed = true
47
- @client&.close
48
- # ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] Client closed')
108
+ is_auth_error = false
109
+ log_level = 'Warn'
110
+
111
+ error_message = case err
112
+ when SSE::Errors::HTTPStatusError
113
+ # Extract more details from HTTP errors
114
+ status = err.respond_to?(:status) ? err.status : 'unknown'
115
+ body = err.respond_to?(:body) && !err.body.to_s.strip.empty? ? err.body : 'empty response'
116
+ is_auth_error = status.to_s =~ /^(401|403)$/
117
+
118
+ # Auth errors during reconnection are expected (server shutdown or credentials expiring)
119
+ log_level = 'Debug' if is_auth_error
120
+
121
+ "HTTP #{status} - #{body}"
122
+ when EOFError, IOError
123
+ # Connection lost is expected when server stops
124
+ log_level = 'Debug'
125
+ "Connection lost: #{err.class}"
126
+ when StandardError
127
+ "#{err.class} - #{err.message}"
128
+ else
129
+ err.to_s
130
+ end
131
+
132
+ ForestAdminRpcAgent::Facades::Container.logger&.log(log_level, "[SSE Client] Error: #{error_message}")
133
+
134
+ # Close client immediately to prevent ld-eventsource from reconnecting with stale credentials
135
+ begin
136
+ @client&.close
137
+ rescue StandardError
138
+ # Ignore close errors
139
+ end
140
+
141
+ # Reset connecting flag and schedule reconnection
142
+ @connecting = false
143
+
144
+ # For auth errors, increase attempt count to get longer backoff
145
+ @connection_attempts += 2 if is_auth_error
146
+
147
+ schedule_reconnect
49
148
  end
50
149
 
51
- private
150
+ def schedule_reconnect
151
+ return if @closed
152
+ return if @reconnect_thread&.alive?
153
+
154
+ @reconnect_thread = Thread.new do
155
+ delay = calculate_backoff_delay
156
+ ForestAdminRpcAgent::Facades::Container.logger&.log(
157
+ 'Debug',
158
+ "[SSE Client] Reconnecting in #{delay} seconds..."
159
+ )
160
+ sleep(delay)
161
+ attempt_connection unless @closed
162
+ end
163
+ end
164
+
165
+ def calculate_backoff_delay
166
+ # Exponential backoff: 1, 2, 4, 8, 16, 30, 30, ...
167
+ delay = INITIAL_BACKOFF_DELAY * (2**[@connection_attempts - 1, 0].max)
168
+ [delay, MAX_BACKOFF_DELAY].min
169
+ end
52
170
 
53
171
  def handle_event(event)
54
172
  type = event.type.to_s.strip
@@ -56,14 +174,34 @@ module ForestAdminDatasourceRpc
56
174
 
57
175
  case type
58
176
  when 'heartbeat'
59
- # ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] Heartbeat')
177
+ if @connecting
178
+ @connecting = false
179
+ @connection_attempts = 0
180
+ ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE Client] Connection stable')
181
+ end
60
182
  when 'RpcServerStop'
61
- # ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] RpcServerStop received')
62
- @on_rpc_stop&.call
183
+ ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE Client] RpcServerStop received')
184
+ handle_rpc_stop
63
185
  else
64
- ForestAdminRpcAgent::Facades::Container.logger.log('Debug',
65
- "[SSE] Unknown event: #{type} with payload: #{data}")
186
+ ForestAdminRpcAgent::Facades::Container.logger&.log(
187
+ 'Debug',
188
+ "[SSE Client] Unknown event: #{type} with payload: #{data}"
189
+ )
66
190
  end
191
+ rescue StandardError => e
192
+ ForestAdminRpcAgent::Facades::Container.logger&.log(
193
+ 'Error',
194
+ "[SSE Client] Error handling event: #{e.class} - #{e.message}"
195
+ )
196
+ end
197
+
198
+ def handle_rpc_stop
199
+ @on_rpc_stop&.call
200
+ rescue StandardError => e
201
+ ForestAdminRpcAgent::Facades::Container.logger&.log(
202
+ 'Error',
203
+ "[SSE Client] Error in RPC stop callback: #{e.class} - #{e.message}"
204
+ )
67
205
  end
68
206
 
69
207
  def generate_signature(timestamp)
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceRpc
2
- VERSION = "1.7.1"
2
+ VERSION = "1.8.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_rpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu