From a3f9748be769214d033f0964f2d7fde8a9b7f513 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Wed, 30 Oct 2024 11:09:04 -0400 Subject: [PATCH] fix: Modify data kind constants to avoid Process.warmup rehash The data stores in the SDK have to deal with multiple types of data -- features (or flags) and segments. These types have long been simple hashes in our SDK, with one defining an optional lambda property. With Ruby 3.3 and the introduction of `Process.warmup`, we have seen an issue where, after warmup, the in memory store needs a `rehash` method call before these kind constants can be used reliably to access the store. To combat this, I am moving these kinds into a class. This class as explicit hash key behaviors, so theoretically shouldn't have this problem Local testing has shown this to be the case. This class is also given a dictionary interface to maintain compliance with the existing implementation. While people should not be relying on these constants explicitly, they do flow through the system in ways that might make their signatures somewhat public. --- lib/ldclient-rb/impl/data_store.rb | 50 ++++++++++++++++++++++++++++++ lib/ldclient-rb/in_memory_store.rb | 11 ++----- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/lib/ldclient-rb/impl/data_store.rb b/lib/ldclient-rb/impl/data_store.rb index e834e522..00043597 100644 --- a/lib/ldclient-rb/impl/data_store.rb +++ b/lib/ldclient-rb/impl/data_store.rb @@ -4,6 +4,56 @@ module LaunchDarkly module Impl module DataStore + + class DataKind + FEATURES = "features".freeze + SEGMENTS = "segments".freeze + + FEATURE_PREREQ_FN = lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }.freeze + + attr_reader :namespace + attr_reader :priority + + # + # @param namespace [String] + # @param priority [Integer] + # + def initialize(namespace:, priority:) + @namespace = namespace + @priority = priority + end + + # + # Maintain the same behavior when these data kinds were standard ruby hashes. + # + # @param key [Symbol] + # @return [Object] + # + def [](key) + return priority if key == :priority + return namespace if key == :namespace + return get_dependency_keys_fn() if key == :get_dependency_keys + nil + end + + # + # Retrieve the dependency keys for a particular data kind. Right now, this is only defined for flags. + # + def get_dependency_keys_fn() + return nil unless @namespace == FEATURES + + FEATURE_PREREQ_FN + end + + def eql?(other) + namespace == other.namespace && priority == other.priority + end + + def hash + [namespace, priority].hash + end + end + class StatusProvider include LaunchDarkly::Interfaces::DataStore::StatusProvider diff --git a/lib/ldclient-rb/in_memory_store.rb b/lib/ldclient-rb/in_memory_store.rb index ad4f6e85..3cb9dc48 100644 --- a/lib/ldclient-rb/in_memory_store.rb +++ b/lib/ldclient-rb/in_memory_store.rb @@ -11,17 +11,10 @@ module LaunchDarkly # to ensure data consistency during non-atomic updates. # @private - FEATURES = { - namespace: "features", - priority: 1, # that is, features should be stored after segments - get_dependency_keys: lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }, - }.freeze + FEATURES = Impl::DataStore::DataKind.new(namespace: "features", priority: 1).freeze # @private - SEGMENTS = { - namespace: "segments", - priority: 0, - }.freeze + SEGMENTS = Impl::DataStore::DataKind.new(namespace: "segments", priority: 0).freeze # @private ALL_KINDS = [FEATURES, SEGMENTS].freeze