magick-feature-flags 1.3.1 → 1.3.2

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: a34f03026e8631bc2d29c0935cb98e472e7ed387fc4628edc357776423bfc2c5
4
- data.tar.gz: d87af21843efb22d5dba60d342f3beb5fd807eb42f1b2948c82ae8dc3d6b67a2
3
+ metadata.gz: 58eabf5855bcd4578d09090c65cc5b8d1b33ba2caa38ae7dd85bf7ecebb76047
4
+ data.tar.gz: e57e675ff58e913e60ef883b4dd8078087c7575ef5ced4355c8811bb15bf2dd3
5
5
  SHA512:
6
- metadata.gz: '0296397245455dbecd448a2dab448b2209579f4c9c39a1e93b424e8f6982fe8a3f498140ac7af5f1127c70fc2cdaa3378194135afe2e55fe9254646a4c761e64'
7
- data.tar.gz: 43e1ce6dcf51815f83ccf4440845d415f8d351e6a6038c8f7bcd0a294dfc0def6531a04a118119d66e71a64d7cfcc1607273eda0c181dfdf951c9ee61d4cfae7
6
+ metadata.gz: 3fdaf5955fcf00d83047ff4ed785476b91b89e9d53ad80d890459e77134cf974cfe53a204060e471c491f133cd4d88f88e0f91813e042e5bf311821e09d804ea
7
+ data.tar.gz: d09483c77bb6af18a7f24565366d08e28a725c95d741c8cdcbf0ae3abcd17d8c3e0626942668a41f9041120a786f0463d35567c4ccfaaca3f56826f2888aa682
@@ -21,11 +21,34 @@ module Magick
21
21
  @last_reload_times = {} # Track last reload time per feature for debouncing
22
22
  @local_writes = {} # Track recent local writes to skip self-invalidation
23
23
  @reload_mutex = Mutex.new
24
+ @stopping = false
25
+ @shutdown_mutex = Mutex.new
24
26
  # Only start Pub/Sub subscriber if Redis is available
25
27
  # In memory-only mode, each process has isolated cache (no cross-process invalidation)
26
28
  start_cache_invalidation_subscriber if redis_adapter
27
29
  end
28
30
 
31
+ # Gracefully terminate the Pub/Sub subscriber thread and its Redis connection.
32
+ # Without this, Ruby/Puma shutdown waits on the blocking `subscribe` call.
33
+ def shutdown(timeout: 5)
34
+ @shutdown_mutex.synchronize do
35
+ return if @stopping
36
+
37
+ @stopping = true
38
+ end
39
+
40
+ close_subscriber_connection(@subscriber)
41
+ terminate_subscriber_thread(@subscriber_thread, timeout)
42
+
43
+ @subscriber = nil
44
+ @subscriber_thread = nil
45
+ true
46
+ end
47
+
48
+ def stopping?
49
+ @stopping == true
50
+ end
51
+
29
52
  def get(feature_name, key)
30
53
  # Try memory first (fastest) - no Redis calls needed thanks to Pub/Sub invalidation
31
54
  value = memory_adapter.get(feature_name, key) if memory_adapter
@@ -288,6 +311,32 @@ module Magick
288
311
 
289
312
  attr_reader :memory_adapter, :redis_adapter, :active_record_adapter, :circuit_breaker
290
313
 
314
+ # Signal the subscribe loop to return, then close the connection so any
315
+ # retry/reconnect attempt fails fast instead of sleeping for 5s.
316
+ def close_subscriber_connection(subscriber)
317
+ return unless subscriber
318
+
319
+ begin
320
+ subscriber.unsubscribe(CACHE_INVALIDATION_CHANNEL)
321
+ rescue StandardError
322
+ # connection may already be dead; fall through to close/kill
323
+ end
324
+
325
+ begin
326
+ subscriber.close
327
+ rescue StandardError
328
+ # ignore: best-effort close
329
+ end
330
+ end
331
+
332
+ def terminate_subscriber_thread(thread, timeout)
333
+ return unless thread
334
+ return if thread.join(timeout)
335
+
336
+ thread.kill
337
+ thread.join(1) # give it a moment to actually unwind
338
+ end
339
+
291
340
  # Record that this process just wrote a feature, so the subscriber
292
341
  # ignores its own Pub/Sub messages and doesn't revert the correct in-memory state.
293
342
  def record_local_write(feature_name)
@@ -421,9 +470,13 @@ module Magick
421
470
  (defined?(Rails) && Rails.env.test?)
422
471
  return if is_rspec_error
423
472
 
473
+ # Stop cleanly during app shutdown instead of sleeping + retrying,
474
+ # which would keep the process alive and delay termination.
475
+ return if @stopping
476
+
424
477
  warn "Cache invalidation subscriber error: #{e.message}" if defined?(Rails) && Rails.env.development?
425
478
  sleep 5
426
- retry
479
+ retry unless @stopping
427
480
  end
428
481
  @subscriber_thread.abort_on_exception = false
429
482
  end
@@ -125,6 +125,17 @@ if defined?(Rails)
125
125
  Magick.performance_metrics.enable_redis_tracking(enable: true)
126
126
  end
127
127
  end
128
+
129
+ # Terminate the Pub/Sub subscriber + async metrics thread on process exit.
130
+ # Without this, Ruby waits on the blocking `Redis#subscribe` call inside
131
+ # the subscriber thread and Puma/Rails shutdown stalls.
132
+ initializer 'magick.shutdown_hook' do
133
+ at_exit do
134
+ Magick.shutdown!
135
+ rescue StandardError
136
+ # Best-effort: never raise from an at_exit handler.
137
+ end
138
+ end
128
139
  end
129
140
  end
130
141
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Magick
4
- VERSION = '1.3.1'
4
+ VERSION = '1.3.2'
5
5
  end
data/lib/magick.rb CHANGED
@@ -265,6 +265,15 @@ module Magick
265
265
  @performance_metrics&.clear!
266
266
  end
267
267
 
268
+ # Gracefully terminate background threads (Redis Pub/Sub subscriber,
269
+ # async metrics processor) so the host process can exit promptly.
270
+ # Intended for use in Rails shutdown hooks, `at_exit`, or tests.
271
+ def shutdown!(timeout: 5)
272
+ safely_shutdown(@adapter_registry) { |r| r.shutdown(timeout: timeout) }
273
+ safely_shutdown(@performance_metrics, &:stop_async_processor)
274
+ true
275
+ end
276
+
268
277
  # Get default adapter registry (public method for use by other classes)
269
278
  def default_adapter_registry
270
279
  @default_adapter_registry ||= begin
@@ -279,5 +288,15 @@ module Magick
279
288
  end
280
289
 
281
290
  private
291
+
292
+ # Run a cleanup action on a collaborator, swallowing errors so that
293
+ # shutdown hooks (at_exit, Rails) never raise.
294
+ def safely_shutdown(collaborator)
295
+ return unless collaborator
296
+
297
+ yield(collaborator)
298
+ rescue StandardError
299
+ # Best-effort: termination paths must not raise.
300
+ end
282
301
  end
283
302
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magick-feature-flags
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Lobanov