Skip to content
Draft
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 @@ -25,6 +25,9 @@ public class RemoteConfigServiceImpl implements ConfigurationChangesTypedListene
private final List<Consumer<byte[]>> subscribers = new CopyOnWriteArrayList<>();
private volatile byte[] current;

/** Key of the current document, guarded by the subscribers lock. */
@Nullable private String currentConfigKey;

/**
* Whether the current document was received before any subscriber, so the poller acknowledged it
* without validation. Guarded by the subscribers lock.
Expand Down Expand Up @@ -114,8 +117,11 @@ public synchronized void close() {
this.configurationPoller.removeCapabilities(CAPABILITY_FFE_FLAG_CONFIGURATION_RULES);
this.configurationPoller.removeListeners(FFE_FLAGS);
this.configurationPoller.stop();
this.subscribers.clear();
this.current = null;
synchronized (this.subscribers) {
this.subscribers.clear();
this.current = null;
this.currentConfigKey = null;
}
}

/**
Expand All @@ -134,6 +140,11 @@ public void accept(
final PollingRateHinter pollingRateHinter) {
final Iterator<Consumer<byte[]>> snapshot;
synchronized (this.subscribers) {
// RC applies a replacement before removing the old target.
if (content == null && !configKey.equals(this.currentConfigKey)) {
return;
}
this.currentConfigKey = content == null ? null : configKey;
this.current = content;
this.unvalidated = content != null && this.subscribers.isEmpty();
snapshot = this.subscribers.iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.slf4j.LoggerFactory;
import org.tabletest.junit.TableTest;

class RemoteConfigServiceImplTest {
private static final byte[] DOCUMENT = "{\"flags\":{}}".getBytes(UTF_8);
Expand Down Expand Up @@ -80,6 +81,39 @@ void forwardsDocumentsAndRemovalsToAllSubscribers() {
assertEquals(2, second.size());
}

@TableTest({
"Scenario | previousKey | replacementKey",
"enable dependency | base | dependent ",
"disable dependency | dependent | base ",
"semver upgrade | base | semver "
})
void keepsReplacementWhenPreviousConfigurationIsRemoved(
final String previousKey, final String replacementKey) {
final RemoteConfigServiceImpl service =
new RemoteConfigServiceImpl(mock(ConfigurationPoller.class));
final List<byte[]> received = new ArrayList<>();
service.subscribe(received::add);
final byte[] replacement = "{\"flags\":{\"replacement\":{}}}".getBytes(UTF_8);

// The RC poller applies the new target before removing the obsolete target.
service.accept(previousKey, DOCUMENT, null);
service.accept(replacementKey, replacement, null);
service.accept(previousKey, null, null);

assertEquals(2, received.size(), "Removing the old target must not clear its replacement");
assertArrayEquals(replacement, received.get(1));
final List<byte[]> replayed = new ArrayList<>();
service.subscribe(replayed::add);
assertEquals(1, replayed.size());
assertArrayEquals(replacement, replayed.get(0));

service.accept(replacementKey, null, null);
assertEquals(3, received.size());
assertNull(received.get(2));
assertEquals(2, replayed.size());
assertNull(replayed.get(1));
}

@Test
void reportsRejectedDocumentsOnceEverySubscriberIsNotified() {
final RemoteConfigServiceImpl service =
Expand Down Expand Up @@ -339,6 +373,7 @@ void reportsReceivedConfiguration() {

@Test
void reportsRemovedConfiguration() {
service.accept("config", DOCUMENT, mock(PollingRateHinter.class));
service.accept("config", null, mock(PollingRateHinter.class));

assertLogged("configuration removed: key=config");
Expand Down
Loading