I am trying to solve a problem with conflicting concurrent database inserts via Hibernate in MySQL.
I have a piece of code that can easily be executed by multiple threads at the same time. It is checking the database for an existence of a record and if it does not exist a new record gets inserted. This same insert-if-nonexistent operation is performed on a related child record. I get a ConstraintViolationException if two threads try to persist the child record at the same time, because both threads see the record does not exist at the moment they are querying it, so both threads attempt to save the same record which violates a unique constraint, and one of them fails.
I am trying to synchronise the query-insert operations on the application level using a guarded block, so that a thread is waiting for another thread to finish inserting the records before querying the database. But even though I see the synchronisation works, querying for the record still returns no results, even if the record has been persisted in another thread. So the constraint violation still happens.
I am using Hibernate 5.1.0
I am managing database transactions manually
I have enabled query cache and second-level cache globally, but am using CacheMode.REFRESH for the SELECT queries
I am not using optimistic or pessimistic database locking or row versioning.
Here is a code example:
In each synchronized operation I try to persist a Product if it does not exist, and a related parent Supplier if it does not exist.
public class UpdateProcessor extends HttpServlet {
// Indicator used for synchronizing read-insert operations
public static Boolean newInsertInProgress = false;
#Override
public void doPost(HttpServletRequest request, HttpServletResponse response) {
Session hbSession = null;
Transaction tx = null;
try {
hbSession = HibernateUtils.getNewSession();
UpdateProcessor.waitForInsert(); // if there is an insert in progress, wait for it to finish
UpdateProcessor.notifyInsertStarted(); // obtain lock
tx = hbSession.beginTransaction();
Product existingProduct = findProductBySKU(sku);
if(existingProduct == null) {
Product newProduct = new Product();
newProduct.setSKU(sku);
Supplier existingSupplier = findSupplierByName(name);
if(existingSupplier == null) {
Supplier newSupplier = new Supplier();
newSupplier.setName(name);
db.save(newSupplier);
newProduct.setSupplier(newSupplier);
} else {
newProduct.setSupplier(existingSupplier);
}
db.save(newProduct);
}
tx.commit();
} catch (Exception t) {
// <rollback transaction>
response.sendError(500);
} finally {
// Safeguard to avoid thread deadlock - release lock always, if obtained
if(UpdateProcessor.newInsertInProgress) {
UpdateProcessor.notifyInsertFinished(); // release lock and notify next thread
}
// <close session>
}
}
private static synchronized void waitForInsert() {
if(!UpdateProcessor.newInsertInProgress) {
log("Skipping wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
return;
}
while(UpdateProcessor.newInsertInProgress) {
boolean loggedEntering = false;
if(!loggedEntering) {
log("Entering wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
loggedEntering = true;
}
try {
UpdateProcessor.class.wait();
} catch (InterruptedException e) {}
}
log("Exiting wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
}
private static synchronized void notifyInsertStarted() {
UpdateProcessor.newInsertInProgress = true;
UpdateProcessor.class.notify();
log("Notify start - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
}
private static synchronized void notifyInsertFinished() {
UpdateProcessor.newInsertInProgress = false;
UpdateProcessor.class.notify();
log("Notify finish - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
}
}
The output after concurrently making the request:
Skipping wait - thread 254 - 1478171162713
Notify start - thread 254 - 1478171162713
Entering wait - thread 255 - 1478171162713
Entering wait - thread 256 - 1478171162849
Notify finish - thread 254 - 1478171163050
Exiting wait - thread 255 - 1478171163051
Notify start - thread 255 - 1478171163051
Entering wait - thread 256 - 1478171163051
Error - thread 255:
org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '532-supplier-name-1' for key 'supplier_name_uniq'
Persisting the new supplier record still threw an exception in thread 255 because the unique constraint (id, name) is violated.
Why is the SELECT still not returning any records after a synchronized insert? Is guarded lock a correct way to avoid the multi-insert problem?
Based on Mechkov's answer above:
Short answer: I needed to include the Hibernate session creation in the synchronised piece of code.
Long answer:
The guarded block properly synchronised the query-insert block but the problem was that even though one thread finishes persisting the records, the second thread cannot see the change in the database until a fresh Hibernate session is created. So the effects of concurrent database modifications are not immediately visible to all threads. An up-to-date database state is obtained via creating a session AFTER an insert is made in some other thread. Including the session creation in the synchronised code ensures that is the case.
Related
I am playing around with Project Loom for the first time and I have some code
try (var executor = Executors.newVirtualThreadExecutor()) {
IntStream.range(0, 16).forEach(i -> {
System.out.println("i = " + i + ", Thread ID = " + Thread.currentThread());
executor.submit(() -> {
System.out.println("Thread ID = " + Thread.currentThread());
});
});
}
with output like
Thread ID = VirtualThread[#37]/runnable#ForkJoinPool-1-worker-4
Thread ID = VirtualThread[#33]/runnable#ForkJoinPool-1-worker-5
i = 9, Thread ID = Thread[#1,main,5,main]
Thread ID = VirtualThread[#43]/runnable#ForkJoinPool-1-worker-9
Thread ID = VirtualThread[#46]/runnable#ForkJoinPool-1-worker-11
i = 10, Thread ID = Thread[#1,main,5,main]
i = 11, Thread ID = Thread[#1,main,5,main]
Is there a way I can tell what Carrier Thread each Virtual Thread is running on?
Does ForkJoinPool-1-worker-11 represent a particular Carrier (Platform) Thread, or does it mean something else?
Yes, this suffix is the name of the current carrier thread.
When I use the following code
public static void main(String[] args) throws InterruptedException {
Set<String> threadStrings = ConcurrentHashMap.newKeySet();
try(var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.invokeAll(Collections.nCopies(16,
() -> threadStrings.add(Thread.currentThread().toString())));
}
System.out.println("\tSimple Run");
threadStrings.stream().sorted().forEachOrdered(System.out::println);
threadStrings.clear();
try(var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.invokeAll(Collections.nCopies(16, () -> {
threadStrings.add(Thread.currentThread().toString());
Thread.sleep(100);
return threadStrings.add(Thread.currentThread().toString());
}));
}
System.out.println("\tWith wait");
threadStrings.stream().sorted().forEachOrdered(System.out::println);
}
It prints
Simple Run
VirtualThread[#15]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#17]/runnable#ForkJoinPool-1-worker-2
VirtualThread[#18]/runnable#ForkJoinPool-1-worker-3
VirtualThread[#19]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#20]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#21]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#22]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#23]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#24]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#25]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#26]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#27]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#28]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#29]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#30]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#31]/runnable#ForkJoinPool-1-worker-4
With wait
VirtualThread[#36]/runnable#ForkJoinPool-1-worker-2
VirtualThread[#37]/runnable#ForkJoinPool-1-worker-3
VirtualThread[#37]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#38]/runnable#ForkJoinPool-1-worker-4
VirtualThread[#38]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#39]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#39]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#40]/runnable#ForkJoinPool-1-worker-5
VirtualThread[#40]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#41]/runnable#ForkJoinPool-1-worker-6
VirtualThread[#41]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#42]/runnable#ForkJoinPool-1-worker-7
VirtualThread[#42]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#43]/runnable#ForkJoinPool-1-worker-5
VirtualThread[#43]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#44]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#44]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#45]/runnable#ForkJoinPool-1-worker-5
VirtualThread[#45]/runnable#ForkJoinPool-1-worker-6
VirtualThread[#46]/runnable#ForkJoinPool-1-worker-5
VirtualThread[#46]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#47]/runnable#ForkJoinPool-1-worker-2
VirtualThread[#49]/runnable#ForkJoinPool-1-worker-1
VirtualThread[#49]/runnable#ForkJoinPool-1-worker-8
VirtualThread[#50]/runnable#ForkJoinPool-1-worker-2
VirtualThread[#50]/runnable#ForkJoinPool-1-worker-6
VirtualThread[#51]/runnable#ForkJoinPool-1-worker-3
VirtualThread[#51]/runnable#ForkJoinPool-1-worker-5
VirtualThread[#52]/runnable#ForkJoinPool-1-worker-2
VirtualThread[#52]/runnable#ForkJoinPool-1-worker-8
(results may vary)
demonstrating how the carrier thread might change when performing a sleep. But in the current snapshot (“build 18-loom+6-282”) it’s not possible to specify your own Executor anymore and there is no method for querying the virtual thread about the carrier thread it uses (other than the implicit hint via toString()). So, the management of the underlying host threads is mostly a black box in this version.
Keep in mind that this is an ongoing development. It’s not clear whether and how this will change.
I am trying to test how the isolation level of a transaction affects behavior across transactions. When using Isolation.READ_UNCOMMITTED, I was expecting that a different transaction would be able to see any uncommitted changes, however, I cannot see the same.
In the code below, I execute transactionA first. While it initially waits for 10 seconds, I invoke transactionB which inserts a new entity and waits for 15 seconds before committing. By the time transactionB commits, transactionA's wait time is finished. So my expectation is that when I try to fetch the entities from the DB in this method, I should be able to see the uncommitted entity persisted by transactionB. This is not happening (refer to output)
#Transactional(rollbackFor = ApiException.class, isolation = Isolation.READ_UNCOMMITTED)
public void transactionA() throws InterruptedException {
System.out.println("---Txn:A--- " + "START");
wait(10, "---Txn:A--- ");
System.out.println("---Txn:A--- " + "WAIT ENDED");
List<TestEntity> testEntities = testEntityDao.selectAll();
System.out.println("---Txn:A--- " + "FETCHED ALL ENTITIES");
for (TestEntity e : testEntities)
System.out.println("---Txn:A--- " + "ENTITY " + e.getId() + "," + e.getName());
}
#Transactional(rollbackFor = ApiException.class, isolation = Isolation.READ_UNCOMMITTED)
public void transactionB() throws InterruptedException {
System.out.println("---Txn:B--- " + "START");
TestEntity newEntity = new TestEntity();
newEntity.setName("NEW ENTITY TXN_B");
testEntityDao.insert(newEntity);
System.out.println("---Txn:B--- " + "PERSISTED NEW ENTITY");
wait(15, "---Txn:B--- ");
System.out.println("---Txn:B--- " + "WAIT ENDED");
}
Output:
1---Txn:A--- START
2---Txn:B--- START
3---Txn:B--- PERSISTED NEW ENTITY
4---Txn:A--- WAIT ENDED
5---Txn:A--- FETCHED ALL ENTITIES
6---Txn:B--- WAIT ENDED
If any entities were fetched, they should have been printed after line 5 above. However, nothing is being fetched. Why could this be occurring?
I am experiencing some issues with the delay of the executor. My thread looks like this:
public void run() {
logger.log("Starting " + tso.getName() + " for " + dataType);
List<String[]> allRows = new ArrayList<String[]>();
String[] lastRow;
LocalDateTime lastDate;;
LocalDateTime lastQuarterHour;
while (true) {
try {
// Make attempt to take CSV data for today. If there is no data wait several seconds and try again
allRows = getCSVRows();
if (allRows == null || allRows.isEmpty()) {
logger.log("Sleeping thread due to empty list for TSO " + tso.getName() + " and data type " + dataType);
Thread.sleep(MILISECONDS_TO_WAIT);
continue;
}
lastRow = allRows.get(allRows.size() - 1);
lastDate = convertStringToUTC(lastRow[0] + " " + lastRow[2]);
lastQuarterHour = takeLastQuorterHourTime();
// If CSV data is available take the last record if it is before last quarter hour wait SEVERAL seconds and try again
if (lastDate.isBefore(lastQuarterHour)) {
logger.log("Sleeping due to lack of information for the current quarter for TSO " + tso.getName() + " and data type " + dataType);
Thread.sleep(MILISECONDS_TO_WAIT);
} else {
break;
}
} catch (InterruptedException e) {
logger.log(e.getMessage());
}
}
}
}
The first time i run my thread the delay is OK, but when the thread sleeps 2 or 3 times, the delay when the next thread cycle starts, is not what is defined in:
executor.scheduleAtFixedRate(extractorThread, 0, WAIT_INTERVAL_MINUTES, TimeUnit.SECONDS);
So, when does the delay start, once the thread finishes its 2-3 sleeps and terminates or when the thread itself is started, no matter how long it works?
It might have something to do with the fact that you're sleeping inside the Thread
Thread.sleep(MILISECONDS_TO_WAIT);
Try removing those and see if the problem still occurs
[EDIT]
The answer to your question is that it will run every X amount of seconds and it counts from when the initial thread/next thread starts but if the thread is not available at the time of the scheduled execution (Because of the thread sleeping or for instance doing very heavy calculations) it will wait for it to become available.
I switched from making sequential HTTP calls to 4 REST services, to making 4 simultaneous calls using a commonj4 work manager task executor. I'm using WebLogic 12c. This new code works on my development environment, but in our test environment under load conditions, and occasionally while not under load, the results map is not populated with all of the results. The logging suggests that each work item did receive back the results though. Could this be a problem with the ConcurrentHashMap? In this example from IBM, they use their own version of Work and there's a getData() method, although it doesn't like that method really exists in their class definition. I had followed a different example that just used the Work class but didn't demonstrate how to get the data out of those threads into the main thread. Should I be using execute() instead of schedule()? The API doesn't appear to be well documented. The stuckthreadtimeout is sufficiently high. component.processInbound() actually contains the code for the HTTP call, but I the problem isn't there because I can switch back to the synchronous version of the class below and not have any issues.
http://publib.boulder.ibm.com/infocenter/wsdoc400/v6r0/index.jsp?topic=/com.ibm.websphere.iseries.doc/info/ae/asyncbns/concepts/casb_workmgr.html
My code:
public class WorkManagerAsyncLinkedComponentRouter implements
MessageDispatcher<Object, Object> {
private List<Component<Object, Object>> components;
protected ConcurrentHashMap<String, Object> workItemsResultsMap;
protected ConcurrentHashMap<String, Exception> componentExceptionsInThreads;
...
//components is populated at this point with one component for each REST call to be made.
public Object route(final Object message) throws RouterException {
...
try {
workItemsResultsMap = new ConcurrentHashMap<String, Object>();
componentExceptionsInThreads = new ConcurrentHashMap<String, Exception>();
final String parentThreadID = Thread.currentThread().getName();
List<WorkItem> producerWorkItems = new ArrayList<WorkItem>();
for (final Component<Object, Object> component : this.components) {
producerWorkItems.add(workManagerTaskExecutor.schedule(new Work() {
public void run() {
//ExecuteThread th = (ExecuteThread) Thread.currentThread();
//th.setName(component.getName());
LOG.info("Child thread " + Thread.currentThread().getName() +" Parent thread: " + parentThreadID + " Executing work item for: " + component.getName());
try {
Object returnObj = component.processInbound(message);
if (returnObj == null)
LOG.info("Object returned to work item is null, not adding to producer components results map, for this producer: "
+ component.getName());
else {
LOG.info("Added producer component thread result for: "
+ component.getName());
workItemsResultsMap.put(component.getName(), returnObj);
}
LOG.info("Finished executing work item for: " + component.getName());
} catch (Exception e) {
componentExceptionsInThreads.put(component.getName(), e);
}
}
...
}));
} // end loop over producer components
// Block until all items are done
workManagerTaskExecutor.waitForAll(producerWorkItems, stuckThreadTimeout);
LOG.info("Finished waiting for all producer component threads.");
if (componentExceptionsInThreads != null
&& componentExceptionsInThreads.size() > 0) {
...
}
List<Object> resultsList = new ArrayList<Object>(workItemsResultsMap.values());
if (resultsList.size() == 0)
throw new RouterException(
"The producer thread results are all empty. The threads were likely not created. In testing this was observed when either 1)the system was almost out of memory (Perhaps the there is not enough memory to create a new thread for each producer, for this REST request), or 2)Timeouts were reached for all producers.");
//** The problem is identified here. The results in the ConcurrentHashMap aren't the number expected .
if (workItemsResultsMap.size() != this.components.size()) {
StringBuilder sb = new StringBuilder();
for (String str : workItemsResultsMap.keySet()) {
sb.append(str + " ");
}
throw new RouterException(
"Did not receive results from all threads within the thread timeout period. Only retrieved:"
+ sb.toString());
}
LOG.info("Returning " + String.valueOf(resultsList.size()) + " results.");
LOG.debug("List of returned feeds: " + String.valueOf(resultsList));
return resultsList;
}
...
}
}
I ended up cloning the DOM document used as a parameter. There must be some downstream code that has side effects on the parameter.
I am currently reading a section on concurrency in The Well-Grounded Java Developer book and this particular code sample demonstrating block concurrency should deadlock, but as far as I can see it does not. Here's the code:
public class MicroBlogNode implements SimpleMicroBlogNode {
private final String ident;
public MicroBlogNode(String ident_){
ident = ident_;
}
public String getIdent(){
return ident;
}
public static Update getUpdate(String _name){
return new Update(_name);
}
public synchronized void propagateUpdate(Update upd_, MicroBlogNode backup_){
System.out.println(ident + ": received: " + upd_.getUpdateText() + " ; backup: " + backup_.getIdent());
backup_.confirmUpdate(this, upd_);
}
public synchronized void confirmUpdate(MicroBlogNode other_, Update update_){
System.out.println(ident + ": received confirm: " + update_.getUpdateText() + " from " + other_.getIdent() + "\n");
}
public static void main(String[] args) {
final MicroBlogNode local = new MicroBlogNode("localhost");
final MicroBlogNode other = new MicroBlogNode("remotehost");
final Update first = getUpdate("1");
final Update second = getUpdate("2");
new Thread(new Runnable() {
public void run() {
local.propagateUpdate(first, other);
}
}).start();
new Thread(new Runnable() {
public void run() {
other.propagateUpdate(second, local);
}
}).start();
}
}
When I run it I get the following output:
localhost: received: 1 ; backup: remotehost
remotehost: received confirm: 1 from localhost
remotehost: received: 2 ; backup: localhost
localhost: received confirm: 2 from remotehost
The book says that if you run the code, you’ll normally see an example of a deadlock—both threads will report receiving the update, but neither will confirm receiving the update for
which they’re the backup thread. The reason for this is that each thread requires the other to release the lock it holds before the confirmation method can progress.
As far as I can see this is not the case - each thread confirms receiving the update for which they are the backup thread.
Thanks in advance.
This looks like timing. Your output is showing that the localhost thread has completed before the remotehost (other) thread has started.
Try putting a Thread.sleep(1000) in the propogateUpdate method after the System.out
public synchronized void propagateUpdate(Update upd_, MicroBlogNode backup_){
System.out.println(ident + ": received: " + upd_.getUpdateText() + " ; backup: " + backup_.getIdent());
Thread.sleep(1000);
backup_.confirmUpdate(this, upd_);
}
This should force a deadlock.
The deadlock is happening when you have your local calling a threaded operation on confirmUpdate when other is attempting to make the same call. Hence, the deadlock happens following this order of operations
Local locks itself by calling propagateUpdate due to the declaration that it is synchronized (see Synchronized Member Function in Java)
'Other' locks itself by calling propagateUpdate
Local attempts to acquire the lock on Other to call confirmUpdate but can't since Other has already been locked in the other thread.
Other attempts to do the same thing and fails for the same reason.
If it's actually working, it's probably because it's happening so fast. Run it a few more times. Thread issue never work when you want them to work.