Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,7 @@ internal class SnapshotApplier(
return
}
if (version == currentVersion) {
logger.debug(
"Config snapshot ignored (app={}, env={}, version={}, currentVersion={}, documents={}, reason=version_not_newer)",
scope.app,
scope.env,
version,
currentVersion,
documents.size,
)
applyUninitializedBindings(scope, version, currentVersion, documents)
return
}
val documentsByKey =
Expand Down Expand Up @@ -65,6 +58,36 @@ internal class SnapshotApplier(
}
}

private fun applyUninitializedBindings(
scope: AppEnvScope,
version: Long,
currentVersion: Long,
documents: List<ConfigDocumentData>,
) {
val bindings = scope.bindingsSnapshot().filterValues { !it.initialized() }
if (bindings.isEmpty()) {
logger.debug(
"Config snapshot ignored (app={}, env={}, version={}, currentVersion={}, documents={}, reason=version_not_newer)",
scope.app,
scope.env,
version,
currentVersion,
documents.size,
)
return
}
val documentsByKey =
documents.associateBy { document -> ConfigKey(document.namespace, document.configKey) }
for ((configKey, binding) in bindings) {
val document = documentsByKey[configKey]
if (document != null) {
applyDocument(binding, document.contentJson)
} else {
handleMissingDocument(scope, binding)
}
}
}

fun applyCachedSnapshot(scope: AppEnvScope, snapshot: ConfigSnapshot) {
val documents =
snapshot.documents.map { (configKey, contentJson) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,61 @@ class ConfigScopeSynchronizerTest {
}
}

@Test
fun `late registration applies an unchanged full snapshot to its new binding`() {
val client =
RecordingConfigSyncClient(
getSnapshotHandler = {
SnapshotResult(
changed = true,
version = 1,
documents =
listOf(
ConfigDocumentData(
namespace = TestStringConfig.namespace,
configKey = TestStringConfig.key,
contentJson = "\"first\"",
),
ConfigDocumentData(
namespace = TestSecondConfig.namespace,
configKey = TestSecondConfig.key,
contentJson = "\"edge\"",
),
),
)
}
)
val executor = Executors.newSingleThreadScheduledExecutor()
val synchronizer =
ConfigScopeSynchronizer(
logger = LoggerFactory.getLogger("ConfigScopeSynchronizerLateRegistrationTest"),
configClientFactory = { client },
natsListenerFactory = { RecordingConfigChangeListener() },
refreshExecutorFactory = { executor },
)
val scope = AppEnvScope(app = "network", env = "stage")
val first = ConfigBinding(TestStringConfig)
scope.putBindingIfAbsent(ConfigKey(TestStringConfig.namespace, TestStringConfig.key), first)

try {
synchronizer.start("http://config", "nats://localhost:4222")
synchronizer.bootstrap(scope, first, ConfigStartupMode.FAIL_CLOSED)
val late = ConfigBinding(TestSecondConfig)
scope.putBindingIfAbsent(
ConfigKey(TestSecondConfig.namespace, TestSecondConfig.key),
late,
)

synchronizer.bootstrap(scope, late, ConfigStartupMode.FAIL_CLOSED)

assertTrue(late.initialized())
assertEquals("edge", late.get())
} finally {
synchronizer.close()
executor.shutdownNow()
}
}

@Test
fun `bootstrap retries transient grpc failures`() {
val retryDelays = CopyOnWriteArrayList<Long>()
Expand Down Expand Up @@ -834,6 +889,14 @@ class ConfigScopeSynchronizerTest {
defaultValue = "default",
)

private object TestSecondConfig :
ConfigDefinition<String>(
namespace = "resourcepacks",
key = "global",
type = String::class.java,
defaultValue = "stable",
)

private class RecordingConfigSyncClient(
private val operations: MutableList<String> = mutableListOf(),
private val syncDefaultsHandler: RecordingConfigSyncClient.() -> SyncDefaultsResult = {
Expand Down