Skip to content

Commit 2ce0744

Browse files
test(spanner): unflake EndpointLifecycleManagerTest requestEndpointRecreationSkippedWhenAddressNotActive (#14474)
In EndpointLifecycleManagerTest, requestEndpointRecreationSkippedWhenAddressNotActive registers two addresses concurrently ("server1", "server2"). EndpointLifecycleManager dispatches background endpoint creation tasks across its 2-thread scheduler, which concurrently invoked get() on the test double FakeEndpointCache. Because FakeEndpointCache stored endpoints in an unsynchronized java.util.HashMap (unlike production GrpcChannelEndpointCache, which uses ConcurrentHashMap), concurrent computeIfAbsent() calls on the uninitialized map raced inside HashMap.resize(), silently dropping one of the endpoints and causing awaitCondition to time out after 5s. Fix: - Use ConcurrentHashMap in FakeEndpointCache to match production channel cache behavior. - Mark mutable state fields in FakeEndpoint and FakeManagedChannel volatile. - Add regression test concurrentBackgroundEndpointCreationDoesNotLoseEndpoints in EndpointLifecycleManagerTest.
1 parent 1db9757 commit 2ce0744

1 file changed

Lines changed: 5 additions & 5 deletions

File tree

java-spanner/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/KeyRangeCacheTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
import java.time.Instant;
4040
import java.time.ZoneId;
4141
import java.time.ZoneOffset;
42-
import java.util.HashMap;
4342
import java.util.Map;
4443
import java.util.Set;
44+
import java.util.concurrent.ConcurrentHashMap;
4545
import java.util.concurrent.TimeUnit;
4646
import java.util.concurrent.atomic.AtomicInteger;
4747
import javax.annotation.Nullable;
@@ -1925,9 +1925,9 @@ enum EndpointHealthState {
19251925
// --- Test doubles ---
19261926

19271927
static final class FakeEndpointCache implements ChannelEndpointCache {
1928-
private final Map<String, FakeEndpoint> endpoints = new HashMap<>();
1928+
private final Map<String, FakeEndpoint> endpoints = new ConcurrentHashMap<>();
19291929
private final FakeEndpoint defaultEndpoint = new FakeEndpoint("default");
1930-
private boolean createOnGet = true;
1930+
private volatile boolean createOnGet = true;
19311931

19321932
@Override
19331933
public ChannelEndpoint defaultChannel() {
@@ -1977,7 +1977,7 @@ static final class FakeEndpoint implements ChannelEndpoint {
19771977
private final String address;
19781978
private final FakeManagedChannel channel = new FakeManagedChannel();
19791979
private final AtomicInteger activeRequests = new AtomicInteger();
1980-
private EndpointHealthState state = EndpointHealthState.READY;
1980+
private volatile EndpointHealthState state = EndpointHealthState.READY;
19811981

19821982
FakeEndpoint(String address) {
19831983
this.address = address;
@@ -2042,7 +2042,7 @@ private static ConnectivityState toConnectivityState(EndpointHealthState healthS
20422042
}
20432043

20442044
private static final class FakeManagedChannel extends ManagedChannel {
2045-
private boolean shutdown = false;
2045+
private volatile boolean shutdown = false;
20462046
private volatile ConnectivityState connectivityState = ConnectivityState.READY;
20472047

20482048
void setConnectivityState(ConnectivityState state) {

0 commit comments

Comments
 (0)