DATACOUCH-484 - Thread safety issue using findBy.

Support for positional parameters necessitated caching them, however
that was not done in a threadsafe fashion.  ThreadLocal storage is
sufficient to take care of the issue.  Added test for it while at it.
This commit is contained in:
David Kelly
2019-11-11 11:44:53 -07:00
parent e8468b7d2e
commit 7601b2beba
3 changed files with 52 additions and 6 deletions

View File

@@ -26,7 +26,6 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
;
import org.springframework.data.couchbase.ContainerResourceRunner;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
@@ -41,9 +40,15 @@ import org.springframework.data.repository.core.support.RepositoryFactorySupport
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
@@ -90,6 +95,40 @@ public class N1qlCouchbaseRepositoryIntegrationTests {
try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {}
}
@Test
public void shouldBeThreadsafe() {
// This doesn't guarantee it, but we should catch most thread issues without
// taking too long here...
int runs = 50;
for (int i=0; i<runs; i++) {
doShouldBeThreadsafe();
}
}
public void doShouldBeThreadsafe() {
int threads = 50;
ExecutorService service = Executors.newFixedThreadPool(threads);
List<Callable<Boolean>> callables = new ArrayList<>();
for (int thread = 0; thread < threads; ++thread) {
final int counter = thread;
Callable<Boolean> booleanSupplier = () -> {
String expectedName = "party like it's 199" + counter%12;
String foundName = partyRepository.findByName(expectedName).get(0).getName();
return expectedName.equals(foundName); //should never get false
};
callables.add(booleanSupplier);
}
try {
List<Future<Boolean>> futures = service.invokeAll(callables);
service.shutdown();
service.awaitTermination(5, TimeUnit.SECONDS);
for (Future<Boolean> future: futures) {
assertTrue(future.get());
}
} catch (InterruptedException | ExecutionException e) {
fail("Threads failed to run " + e.getMessage());
}
}
@Test
public void shouldFindAllWithSort() {
Iterable<Party> allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees"));

View File

@@ -42,6 +42,8 @@ public interface PartyRepository extends CouchbaseRepository<Party, String> {
List<Party> findByAttendeesGreaterThanEqual(int minAttendees);
List<Party> findByName(String name);
List<Party> findByEventDateIs(Date targetDate);
@View(designDocument = "party", viewName = "byDate")