mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
switch InMemoryStateProvider to use synchronizedMap, to avoid ConcurrentModificationException in clean method
This commit is contained in:
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.state;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -58,7 +59,7 @@ public class InMemoryStateProvider implements StateProviderInterface
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private InMemoryStateProvider()
|
private InMemoryStateProvider()
|
||||||
{
|
{
|
||||||
this.map = new HashMap<>();
|
this.map = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
// Start a single thread executor to handle the cleaning //
|
// Start a single thread executor to handle the cleaning //
|
||||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.state;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
@ -103,17 +105,17 @@ public class InMemoryStateProviderTest extends BaseTest
|
|||||||
/////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////
|
||||||
// Add an entry that is 3 hours old, should not be cleaned //
|
// Add an entry that is 3 hours old, should not be cleaned //
|
||||||
/////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////
|
||||||
UUIDAndTypeStateKey newKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS, Instant.now().minus(3, ChronoUnit.HOURS));
|
UUIDAndTypeStateKey newKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS, Instant.now().minus(3, ChronoUnit.HOURS));
|
||||||
String newUUID = UUID.randomUUID().toString();
|
String newUUID = UUID.randomUUID().toString();
|
||||||
QRecord newQRecord = new QRecord().withValue("uuid", newUUID);
|
QRecord newQRecord = new QRecord().withValue("uuid", newUUID);
|
||||||
stateProvider.put(newKey, newQRecord);
|
stateProvider.put(newKey, newQRecord);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Add an entry that is 5 hours old, it should be cleaned //
|
// Add an entry that is 5 hours old, it should be cleaned //
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
UUIDAndTypeStateKey oldKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS, Instant.now().minus(5, ChronoUnit.HOURS));
|
UUIDAndTypeStateKey oldKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS, Instant.now().minus(5, ChronoUnit.HOURS));
|
||||||
String oldUUID = UUID.randomUUID().toString();
|
String oldUUID = UUID.randomUUID().toString();
|
||||||
QRecord oldQRecord = new QRecord().withValue("uuid", oldUUID);
|
QRecord oldQRecord = new QRecord().withValue("uuid", oldUUID);
|
||||||
stateProvider.put(oldKey, oldQRecord);
|
stateProvider.put(oldKey, oldQRecord);
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
@ -125,7 +127,33 @@ public class InMemoryStateProviderTest extends BaseTest
|
|||||||
Assertions.assertEquals(newUUID, qRecordFromState.getValueString("uuid"), "Should read value from state persistence");
|
Assertions.assertEquals(newUUID, qRecordFromState.getValueString("uuid"), "Should read value from state persistence");
|
||||||
|
|
||||||
Assertions.assertTrue(stateProvider.get(QRecord.class, oldKey).isEmpty(), "Key not found in state should return empty");
|
Assertions.assertTrue(stateProvider.get(QRecord.class, oldKey).isEmpty(), "Key not found in state should return empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** originally written with N=100000, but showed the error as small as 1000.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testDemonstrateConcurrentModificationIfNonSynchronizedMap()
|
||||||
|
{
|
||||||
|
int N = 1000;
|
||||||
|
InMemoryStateProvider stateProvider = InMemoryStateProvider.getInstance();
|
||||||
|
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(10);
|
||||||
|
executorService.submit(() ->
|
||||||
|
{
|
||||||
|
for(int i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
UUIDAndTypeStateKey oldKey = new UUIDAndTypeStateKey(UUID.randomUUID(), StateType.PROCESS_STATUS, Instant.now().minus(5, ChronoUnit.HOURS));
|
||||||
|
stateProvider.put(oldKey, UUID.randomUUID());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for(int i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
stateProvider.clean(Instant.now().minus(4, ChronoUnit.HOURS));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user