launchdarkly-server-sdk 8.15.1 → 8.16.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: 06b22ffc1ba131d82969abf4c5e6b04ab80d18a5112393a41f59a3b7217e9633
4
- data.tar.gz: 52e8d3a1615a43d15d9fa548db9568456409c3cbfd716f558635c7377b35987f
3
+ metadata.gz: b7bea0f24a78b42aa3494b0c36a0523a91671e7db9c3bb264011bc6791c020f7
4
+ data.tar.gz: fefb72d9bbf9febfb214268935bb289fe3175447bb56529d67a5211ee0b689d5
5
5
  SHA512:
6
- metadata.gz: b632a4958bab5ad77398d87cf3b018e0fe35c37e53b5590dc330b6c395a6fe22f0fc50ef820182b197179ac9c933356bf1a718b8f5bdcd4d8b40d3daae8367b5
7
- data.tar.gz: 22284f8464984ed1de431109260354d8285ae2079a47429a62e2e8fb49abb954e4d75c220cf264a9409b20bcd0ddc2cb6ca167c8ddd481bf8d769e08b5642362
6
+ metadata.gz: 2c17d728ec723fb03031bea1b6a96f32ce030202ae29ab1dc094cdfad8602489bd69370d95fefd8b4a17fd29b07fac868e04146b3cabb0254b23640ea2256952
7
+ data.tar.gz: 9fbed6b65e5ec58597e2b09823c9f6ea25ce487a51061910480a123ed8e47eb188adc24e87cf675aed83ae99bfbf2f1d9dfc83ce4c4bb88e4e930bcf23a6a184
@@ -33,10 +33,6 @@ module LaunchDarkly
33
33
 
34
34
  def_delegators :@config, :logger
35
35
 
36
- # @!method flush
37
- # Delegates to {LaunchDarkly::EventProcessorMethods#flush}.
38
- def_delegator :@event_processor, :flush
39
-
40
36
  # @!method data_store_status_provider
41
37
  # Delegates to the data system {LaunchDarkly::Impl::DataSystem#data_store_status_provider}.
42
38
  # @return [LaunchDarkly::Interfaces::DataStore::StatusProvider]
@@ -79,6 +75,12 @@ module LaunchDarkly
79
75
  config.instance_id = SecureRandom.uuid
80
76
  @config = config
81
77
 
78
+ # The pid of the process that owns this client's background threads. A child process that
79
+ # inherits this client after a fork has a different pid. See #check_forked.
80
+ @owner_pid = Process.pid
81
+ # The pid of the process in which the fork warning was last logged, or nil.
82
+ @fork_warned_pid = Concurrent::AtomicReference.new(nil)
83
+
82
84
  start_up(wait_for_sec)
83
85
  end
84
86
 
@@ -97,6 +99,10 @@ module LaunchDarkly
97
99
  # reason, it is recommended that any listener or hook integrations be added postfork unless you are certain it can
98
100
  # survive the forking process.
99
101
  #
102
+ # If the SDK is used in a forked child process before this method is called, it logs a warning once per process.
103
+ # The warning says that flag updates and analytics events will not work until `postfork` is called. Calling this
104
+ # method clears that condition for the current process.
105
+ #
100
106
  # @param wait_for_sec [Float] maximum time (in seconds) to wait for initialization
101
107
  #
102
108
  def postfork(wait_for_sec = 5)
@@ -105,9 +111,51 @@ module LaunchDarkly
105
111
  @big_segment_store_manager = nil
106
112
  @flag_tracker = nil
107
113
 
114
+ @owner_pid = Process.pid
115
+ @fork_warned_pid.set(nil)
116
+
108
117
  start_up(wait_for_sec)
109
118
  end
110
119
 
120
+ FORK_WARNING_MESSAGE = "[LDClient] This process was forked after the LDClient was created. Background threads do not " +
121
+ "survive a fork, so flag updates and analytics events will not work in this process. Call LDClient#postfork after forking."
122
+ private_constant :FORK_WARNING_MESSAGE
123
+
124
+ #
125
+ # Log a warning once per process if this client is used in a process that was forked after the client was created.
126
+ #
127
+ # Ruby threads do not survive a fork. A child process that inherits a client has no stream thread, no event
128
+ # dispatcher thread, and no timer tasks, so flag updates and analytics events stop working. The SDK does not
129
+ # repair this by itself; the application must call {#postfork}.
130
+ #
131
+ # The check compares the current pid with the pid recorded in the constructor or in {#postfork}. It is a single
132
+ # `getpid` call, which is cheap compared to a flag evaluation. The "already warned" state is stored as the pid in
133
+ # which the warning was logged, not as a boolean. A boolean would be copied into every child on fork, so a
134
+ # grandchild process would inherit "already warned" from its parent and never get its own warning.
135
+ #
136
+ private def check_forked
137
+ pid = Process.pid
138
+ return if pid == @owner_pid
139
+ return unless fork_breaks_client?
140
+
141
+ if @fork_warned_pid.get_and_set(pid) != pid
142
+ @config.logger.warn { FORK_WARNING_MESSAGE }
143
+ end
144
+ end
145
+
146
+ #
147
+ # Return true if this configuration needs background threads. Offline mode has none. LDD mode with events
148
+ # disabled reads flags from the persistent store and sends nothing, so a fork does not break it.
149
+ #
150
+ # @return [Boolean]
151
+ #
152
+ private def fork_breaks_client?
153
+ return false if @config.offline?
154
+ return false if @config.use_ldd? && !@config.send_events
155
+
156
+ true
157
+ end
158
+
111
159
  private def start_up(wait_for_sec)
112
160
  environment_metadata = get_environment_metadata
113
161
  plugin_hooks = get_plugin_hooks(environment_metadata)
@@ -281,6 +329,7 @@ module LaunchDarkly
281
329
  # @return the variation for the provided context, or the default value if there's an error
282
330
  #
283
331
  def variation(key, context, default)
332
+ check_forked
284
333
  context = Impl::Context::make_context(context)
285
334
  result = evaluate_with_hooks(key, context, default, :variation) do
286
335
  detail, _, _ = variation_with_flag(key, context, default)
@@ -314,6 +363,7 @@ module LaunchDarkly
314
363
  # @return [EvaluationDetail] an object describing the result
315
364
  #
316
365
  def variation_detail(key, context, default)
366
+ check_forked
317
367
  context = Impl::Context::make_context(context)
318
368
  result = evaluate_with_hooks(key, context, default, :variation_detail) do
319
369
  detail, _, _ = evaluate_internal(key, context, default, true)
@@ -439,6 +489,7 @@ module LaunchDarkly
439
489
  # @return [Array<Symbol, Interfaces::Migrations::OpTracker>]
440
490
  #
441
491
  def migration_variation(key, context, default_stage)
492
+ check_forked
442
493
  unless Migrations::VALID_STAGES.include? default_stage
443
494
  @config.logger.error { "[LDClient] default_stage #{default_stage} is not a valid stage; continuing with 'off' as default" }
444
495
  default_stage = Migrations::STAGE_OFF
@@ -480,6 +531,7 @@ module LaunchDarkly
480
531
  # @return [void]
481
532
  #
482
533
  def identify(context)
534
+ check_forked
483
535
  context = LaunchDarkly::Impl::Context.make_context(context)
484
536
  unless context.valid?
485
537
  @config.logger.warn("Identify called with invalid context: #{context.error}")
@@ -516,6 +568,7 @@ module LaunchDarkly
516
568
  # @return [void]
517
569
  #
518
570
  def track(event_name, context, data = nil, metric_value = nil)
571
+ check_forked
519
572
  context = LaunchDarkly::Impl::Context.make_context(context)
520
573
  unless context.valid?
521
574
  @config.logger.warn("Track called with invalid context: #{context.error}")
@@ -536,6 +589,7 @@ module LaunchDarkly
536
589
  # @param tracker [LaunchDarkly::Interfaces::Migrations::OpTracker]
537
590
  #
538
591
  def track_migration_op(tracker)
592
+ check_forked
539
593
  unless tracker.is_a? LaunchDarkly::Interfaces::Migrations::OpTracker
540
594
  @config.logger.error { "invalid op tracker received in track_migration_op" }
541
595
  return
@@ -571,6 +625,8 @@ module LaunchDarkly
571
625
  def all_flags_state(context, options={})
572
626
  return FeatureFlagsState.new(false) if @config.offline?
573
627
 
628
+ check_forked
629
+
574
630
  unless initialized?
575
631
  if @data_system.store.initialized?
576
632
  @config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store" }
@@ -628,11 +684,24 @@ module LaunchDarkly
628
684
  state
629
685
  end
630
686
 
687
+ #
688
+ # Tells the client that all pending analytics events should be delivered as soon as possible.
689
+ #
690
+ # Delegates to {LaunchDarkly::EventProcessorMethods#flush}.
691
+ #
692
+ # @return [void]
693
+ #
694
+ def flush
695
+ check_forked
696
+ @event_processor.flush
697
+ end
698
+
631
699
  #
632
700
  # Releases all network connections and other resources held by the client, making it no longer usable.
633
701
  #
634
702
  # @return [void]
635
703
  def close
704
+ check_forked
636
705
  @config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
637
706
  @data_system.stop
638
707
  @event_processor.stop
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "8.15.1" # x-release-please-version
2
+ VERSION = "8.16.0" # x-release-please-version
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.15.1
4
+ version: 8.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-28 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb