After some digging, I find out Exception was thrown by Geode and then
wrapped and re-thrown by Spring data.
// Let's create 2 callables and try to update same copy of
customer
// Update address from A1 to A2
Callable<String> t1 = new Callable<String>() {
@Override
public String call() throws Exception {
try {
service.updateCustomer("1234",
"A2");
} catch (Exception e) {
System.out.println("update A2
failed with Exception " + e);
throw e;
}
System.out.println("t1 done");
return "T1 Done";
}
};
// Update address from A1 to A3
Callable<String> t2 = new Callable<String>() {
@Override
public String call() throws Exception {
try {
service.updateCustomer("1234",
"A3");
} catch (Exception e) {
System.out.println("update A3
failed with Exception " + e);
throw e;
}
System.out.println("t2 done");
return "T2 Done";
}
};
Here is the logging shown after run the example.
update A3 failed with Exception
org.springframework.data.gemfire.transaction.GemfireTransactionCommitException:
Unexpected failure occurred on commit of local cache transaction; nested
exception is org.apache.geode.cache.CommitConflictException: Concurrent
transaction commit detected The key 1234 in region /Customers was being
modified by another transaction locally.
Based on this, I think there is no issue in spring data or geode for your
test case.
-Eric
On Mon, Oct 22, 2018 at 4:52 PM Eric Shu <[email protected]> wrote:
> Hi Dharam,
>
> Only <pool-3-thread-1> tid=0x4b got the lock and apply its changes
> to Geode.
>
> [debug 2018/10/19 09:36:48.608 IST <pool-3-thread-1> tid=0x4b] txApplyPut
> cbEvent=EntryEventImpl[op=UPDATE;region=/Customers;key=1234;oldValue=null;newValue=com.example.geode.model.Customer@3491dea3;callbackArg=null;originRemote=false;originMember=192.168.31.62(SampleServer:9006)<ec><v0>:1024;id=EventIDid=30bytes;threadID=3;sequenceID=1]]
>
> <pool-3-thread-2> tid=0x4c did not get the lock and transaction failed with
> CommitConflictException:
>
> [debug 2018/10/19 09:36:48.609 IST <pool-3-thread-2> tid=0x4c]
> <DLockRequestProcessor 13 waiting for 1 replies from
> [192.168.31.62(SampleServer:9006)<ec><v0>:1024]> got
> process(DLockRequestProcessor.DLockResponseMessage responding
> TRY_LOCK_FAILED; serviceName=DTLS(version 0); objectName=[TXLockBatch:
> txLockId=TXLockId: 192.168.31.62(SampleServer:9006)<ec><v0>:1024-3;
> reqs=[regionPath=/Customers keys=[1234]]; participants=[]]; responseCode=4;
> keyIfFailed=The key 1234 in region /Customers was being modified by
> another transaction locally.; leaseExpireTime=0; processorId=13; lockId=13)
> from 192.168.31.62(SampleServer:9006)<ec><v0>:1024
>
> We do not see the txApplyPut in the logs for thread-2 which means the
> transaction does not write to cache.
>
> The following code snip shows that transaction is failed with
> CommitConflictException in TXLockServiceImpl.txLock()
>
> logger.debug("[TXLockServiceImpl.txLock] acquire try-locks for {}", batch);
>
>
> // TODO: get a readWriteLock to make the following block atomic...
>
> Object[] keyIfFail = new Object[1];
>
> gotLocks = this.dlock.acquireTryLocks(batch, TIMEOUT_MILLIS, LEASE_MILLIS,
> keyIfFail);
>
> if (gotLocks) { // ...otherwise race can occur between tryLocks and
> readLock
>
> acquireRecoveryReadLock();
>
> } else if (keyIfFail[0] != null) {
>
> --> throw new CommitConflictException(
>
> String.format("Concurrent transaction commit detected %s",
>
> keyIfFail[0]));
>
> } else {
>
> throw new CommitConflictException(
>
> String.format("Failed to request try locks from grantor: %s",
>
> this.dlock.getLockGrantorId()));
>
>
> I will try to find out why your application does not get the
> CommitConflictException thrown by Geode.
>
> -Eric
>
>
>
> On Sat, Oct 20, 2018 at 8:10 AM Dharam Thacker <[email protected]>
> wrote:
>
>> Hello Swapil & Team,
>>
>> Did you get chance to look into this? Ideally it's a plain case of
>> *optimistic
>> locking*. It works exactly fine with *JPA using @Version* property and
>> *optimistic
>> locking*. [Same example by changing Customer model to be compatible with
>> database table]
>>
>> As I mentioned that *both transactions are working exactly together*
>> reading first cut copy of Customer and still passing. I believe in geode,
>> we already have optimistic locking implemented in transactions. Could you
>> validate debug logs and/or POC project which I had send earlier?
>>
>> It's similar to below customer bank account analogy I have account with
>> bank balance as 0 initially.
>>
>> Tx1: When I started, customer balance was 0, so let me add $500 and end
>> result will be $500
>>
>> Tx2: When I started, customer balance was 0, so let me add $100 and end
>> result will be $100 .
>>
>> Both transactions start at the same time, they read customer account
>> object from cache, with balance as 0 initially which is nothing but a
>> snapshot copy when transaction started.
>> But at least 1 of the transaction should be proved wrong while
>> committing. That's not case here and it's becoming $100.
>>
>> *Note*: 1 second of delay which I introduced to simulate this scenario
>> in my poc project is just to simulate external service call which makes
>> sense for real life project.
>>
>> Thanks,
>> - Dharam Thacker
>>
>>
>> On Fri, Oct 19, 2018 at 9:45 AM Dharam Thacker <[email protected]>
>> wrote:
>>
>>> Thank you Swapnil! I understand your point. If I start threads as you
>>> mentioned it does work.
>>>
>>> But if you see my attachment, both transactions *actually start
>>> together with thread pool and reads same copy of customer from cache*
>>> as I have added 1 second of delay in update service as soon as they read
>>> the copy of customer from cache.
>>>
>>> I have attached *DEBUG level logs* *in file(Debug_Logs.txt)* as well
>>> more information. I am sure something is not right here.
>>>
>>> After that I was expecting 1 transaction to win as other thread has
>>> definitely older copy of customer data. Info logs shown below for quick
>>> information.
>>>
>>> *INFO Logs for quick glance:*
>>>
>>> pool-3-thread-2 : Entered into update method
>>> pool-3-thread-1 : Entered into update method
>>> pool-3-thread-2 : CurrentCopy >> {
>>> "cid" : "1234",
>>> *"name" : "A1",*
>>> "address" : "India"
>>> }
>>> pool-3-thread-1 : CurrentCopy >> {
>>> "cid" : "1234",
>>> *"name" : "A1",*
>>> "address" : "India"
>>> }
>>> pool-3-thread-2 : Current customer name >> A1
>>> pool-3-thread-1 : Current customer name >> A1
>>> pool-3-thread-2 : Final Data >> {
>>> "cid" : "1234",
>>> "name" : "A3",
>>> "address" : "India"
>>> }
>>> pool-3-thread-1 : Final Data >> {
>>> "cid" : "1234",
>>> "name" : "A2",
>>> "address" : "India"
>>> }
>>> pool-3-thread-1 : Exiting from update method
>>> pool-3-thread-2 : Exiting from update method
>>> Main thread is going to wait for 5 seconds...
>>> Main thread is going to shutdown pool...
>>>
>>>
>>> Thanks,
>>> Dharam
>>>
>>>
>>>
>>> On Fri, Oct 19, 2018, 03:25 Swapnil Bawaskar <[email protected]>
>>> wrote:
>>>
>>>> Hi Dharam,
>>>> If there are two requests that read and write the same entry, you can
>>>> trust Geode to throw a CommitConflictException if the changes are going to
>>>> overwrite one another. My above example will throw this exception.
>>>> If that exception is not thrown, then your transactions actually
>>>> happened one after the other, in which case the second transaction should
>>>> be able to read the change made by the first transaction.
>>>>
>>>>
>>>> On Thu, Oct 18, 2018 at 11:30 AM Dharam Thacker <
>>>> [email protected]> wrote:
>>>>
>>>>> Thank you Swapnil!
>>>>>
>>>>> But how can we guarantee this?
>>>>>
>>>>> I have a spring boot geode client as web app. It has several emdpoints
>>>>> to accept add/update/delete actions.
>>>>>
>>>>> If 2 users are trying to change same customer with undefined email as
>>>>> initial state in 2 different rest requests in almost same time, of course
>>>>> by reading original customer with no email and changing emails, how could
>>>>> this be ensured?
>>>>>
>>>>> It may happen that user1's transaction succeded due to many possible
>>>>> reasons, it's confirmed now that user2 has stale data and he will
>>>>> overwtite
>>>>> changes.
>>>>>
>>>>> Could you suggest how we can handle it?
>>>>>
>>>>> Thanks,
>>>>> Dharam
>>>>>
>>>>> On Thu, Oct 18, 2018, 23:46 Swapnil Bawaskar <[email protected]>
>>>>> wrote:
>>>>>
>>>>>> If you just throw two transactions in a thread pool, you may or may
>>>>>> not get an exception since one transaction may have completed before the
>>>>>> other transaction begins. To deterministically make one transaction fail,
>>>>>> you have to ensure that both threads have read the initial value and have
>>>>>> made change to the original value before either transaction commits.
>>>>>>
>>>>>> region.put("K", "V");
>>>>>>
>>>>>> CountDownLatch latch1 = new CountDownLatch(1);
>>>>>> CountDownLatch latch2 = new CountDownLatch(1);
>>>>>> Thread th1 = new Thread(() -> {
>>>>>> mgr.being();
>>>>>> region.put("K", "V1");
>>>>>> latch1.countDown();
>>>>>> latch2.await();
>>>>>> mgr.commit();
>>>>>>
>>>>>> });
>>>>>> Thread th2 = new Thread(() -> {
>>>>>> mgr.begin();
>>>>>> region.put("K", "V2");
>>>>>> latch1.await();
>>>>>> latch2.countDown();
>>>>>> mgr.commit();
>>>>>> });
>>>>>> th1.start();
>>>>>> th2.start();
>>>>>>
>>>>>>
>>>>>> On Thu, Oct 18, 2018 at 10:28 AM Dharam Thacker <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hello Team,
>>>>>>>
>>>>>>> I am trying to understand *transaction behavior* with apache geode.
>>>>>>> I have created a simple demo as attached here in with this email.
>>>>>>> It's a simple spring data geode application with apache geode 1.6.0
>>>>>>>
>>>>>>> Spring Startup Listener class >> SimpleApplicationListener .java
>>>>>>> Service class >> CustomerService.java
>>>>>>>
>>>>>>> 2 thread are simultaneously trying to update customer=1234 by
>>>>>>> changing name from [Thread-T1 - (From A1->To A2)] and [Thread-T2 - (From
>>>>>>> A1->To A3)]
>>>>>>> I assume that at least 1 thread has stale copy of data and should
>>>>>>> throw commit conflict exception but I think I am missing something and
>>>>>>> it's
>>>>>>> not behaving as expected.
>>>>>>>
>>>>>>> Could some one help me to understand what I am missing here?
>>>>>>> [Attached tar.gz file]
>>>>>>>
>>>>>>> Thanks & Regards,
>>>>>>> - Dharam Thacker
>>>>>>>
>>>>>>