Ignore failures to update cache after a read

This commit is contained in:
ravi-signal
2025-12-10 16:21:18 -06:00
committed by GitHub
parent 1913dbf6f9
commit fecb032d8f
4 changed files with 71 additions and 21 deletions

View File

@@ -70,6 +70,7 @@ import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.stubbing.Answer;
@@ -508,13 +509,25 @@ class AccountsManagerTest {
verifyNoMoreInteractions(accounts);
}
@Test
void testGetAccountByUuidBrokenCache() {
enum FailureStep {
GET,
SET_ACI,
SET_PNI
}
@ParameterizedTest
@EnumSource(FailureStep.class)
void testGetAccountByUuidBrokenCache(final FailureStep step) {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
when(clusterCommands.get(eq("Account3::" + uuid))).thenThrow(new RedisException("Connection lost!"));
(switch (step) {
case GET -> when(clusterCommands.get(eq("Account3::" + uuid)));
case SET_ACI -> when(clusterCommands.setex(eq("Account3::" + uuid), anyLong(), anyString()));
case SET_PNI -> when(clusterCommands.setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())));
}).thenThrow(new RedisException("Connection lost!"));
when(accounts.getByAccountIdentifier(eq(uuid))).thenReturn(Optional.of(account));
Optional<Account> retrieved = accountsManager.getByAccountIdentifier(uuid);
@@ -524,27 +537,35 @@ class AccountsManagerTest {
verify(clusterCommands, times(1)).get(eq("Account3::" + uuid));
verify(clusterCommands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(clusterCommands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
// we only try setting the ACI if we successfully set the PNI
verify(clusterCommands, times(step == FailureStep.SET_PNI ? 0 : 1))
.setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(clusterCommands);
verify(accounts, times(1)).getByAccountIdentifier(eq(uuid));
verifyNoMoreInteractions(accounts);
}
@Test
void testGetAccountByUuidBrokenCacheAsync() {
@ParameterizedTest
@EnumSource(FailureStep.class)
void testGetAccountByUuidBrokenCacheAsync(final FailureStep step) {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
when(asyncClusterCommands.get(eq("Account3::" + uuid)))
.thenReturn(MockRedisFuture.failedFuture(new RedisException("Connection lost!")));
.thenReturn(MockRedisFuture.completedFuture(null));
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
when(accounts.getByAccountIdentifierAsync(eq(uuid)))
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
(switch (step) {
case GET -> when(asyncClusterCommands.get(eq("Account3::" + uuid)));
case SET_ACI -> when(asyncClusterCommands.setex(eq("Account3::" + uuid), anyLong(), anyString()));
case SET_PNI -> when(asyncClusterCommands.setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())));
}).thenReturn(MockRedisFuture.failedFuture(new RedisException("Connection lost!")));
Optional<Account> retrieved = accountsManager.getByAccountIdentifierAsync(uuid).join();
assertTrue(retrieved.isPresent());

View File

@@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
@@ -163,14 +164,19 @@ public class ProfilesManagerTest {
verifyNoMoreInteractions(profiles);
}
@Test
public void testGetProfileBrokenCache() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGetProfileBrokenCache(final boolean failUpdateCache) {
final UUID uuid = UUID.randomUUID();
final byte[] name = TestRandomUtil.nextBytes(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, null, "somecommitment".getBytes());
when(commands.hget(eq(ProfilesManager.getCacheKey(uuid)), eq("someversion"))).thenThrow(new RedisException("Connection lost"));
if (failUpdateCache) {
when(commands.hset(eq(ProfilesManager.getCacheKey(uuid)), eq("someversion"), anyString()))
.thenThrow(new RedisException("Connection lost"));
}
when(profiles.get(eq(uuid), eq("someversion"))).thenReturn(Optional.of(profile));
Optional<VersionedProfile> retrieved = profilesManager.get(uuid, "someversion");
@@ -186,15 +192,19 @@ public class ProfilesManagerTest {
verifyNoMoreInteractions(profiles);
}
@Test
public void testGetProfileAsyncBrokenCache() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGetProfileAsyncBrokenCache(final boolean failUpdateCache) {
final UUID uuid = UUID.randomUUID();
final byte[] name = TestRandomUtil.nextBytes(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, null, "somecommitment".getBytes());
when(asyncCommands.hget(eq(ProfilesManager.getCacheKey(uuid)), eq("someversion"))).thenReturn(MockRedisFuture.failedFuture(new RedisException("Connection lost")));
when(asyncCommands.hset(eq(ProfilesManager.getCacheKey(uuid)), eq("someversion"), anyString())).thenReturn(MockRedisFuture.completedFuture(null));
when(asyncCommands.hset(eq(ProfilesManager.getCacheKey(uuid)), eq("someversion"), anyString()))
.thenReturn(failUpdateCache
? MockRedisFuture.failedFuture(new RedisException("Connection lost"))
: MockRedisFuture.completedFuture(null));
when(profiles.getAsync(eq(uuid), eq("someversion"))).thenReturn(CompletableFuture.completedFuture(Optional.of(profile)));
Optional<VersionedProfile> retrieved = profilesManager.getAsync(uuid, "someversion").join();