Skip to content

Commit a173664

Browse files
ohubautartembilan
authored andcommitted
GH-3445: Fix JdbcLock for IllegalMonitorStateEx
Fixes #3445 The `JdbcLockRegistry` improperly unlock the delegate (a `ReentrantLock`) in the case of `TransientDataAccessException` or a `TransactionTimedOutException` is thrown. As this occurs in a while loop, the next time the delegate is unlocked, it not longer as a owner thread and throws an `IllegalMonitorStateException`. * Move `delegate.unlock()` outside the while loop in the `JdbcLock.unlock()` method * Add `JdbcLockRegistryDelegateTests` for mocked `LockRepository` to add more coverage for `JdbcLockRegistry` functionality **Cherry-pick to 5.4.x, 5.3.x & 5.2.x** (cherry picked from commit 7e66509) # Conflicts: # spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/JdbcLockRegistry.java
1 parent 1c25683 commit a173664

File tree

2 files changed

+141
-13
lines changed

2 files changed

+141
-13
lines changed

spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/JdbcLockRegistry.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* @author Bartosz Rempuszewski
5050
* @author Gary Russell
5151
* @author Alexandre Strubel
52+
* @author Olivier Hubaut
5253
*
5354
* @since 4.3
5455
*/
@@ -238,21 +239,23 @@ public void unlock() {
238239
this.delegate.unlock();
239240
return;
240241
}
241-
while (true) {
242-
try {
243-
this.mutex.delete(this.path);
244-
return;
245-
}
246-
catch (TransientDataAccessException e) {
247-
// try again
248-
}
249-
catch (Exception e) {
250-
throw new DataAccessResourceFailureException("Failed to release mutex at " + this.path, e);
251-
}
252-
finally {
253-
this.delegate.unlock();
242+
try {
243+
while (true) {
244+
try {
245+
this.mutex.delete(this.path);
246+
return;
247+
}
248+
catch (TransientDataAccessException | TransactionTimedOutException e) {
249+
// try again
250+
}
251+
catch (Exception e) {
252+
throw new DataAccessResourceFailureException("Failed to release mutex at " + this.path, e);
253+
}
254254
}
255255
}
256+
finally {
257+
this.delegate.unlock();
258+
}
256259
}
257260

258261
@Override
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.jdbc.lock;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.mockito.ArgumentMatchers.anyString;
21+
import static org.mockito.Mockito.doAnswer;
22+
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.when;
24+
25+
import java.util.Random;
26+
import java.util.concurrent.atomic.AtomicBoolean;
27+
import java.util.concurrent.locks.Lock;
28+
import java.util.concurrent.locks.ReentrantLock;
29+
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
33+
import org.springframework.dao.TransientDataAccessException;
34+
import org.springframework.integration.test.util.TestUtils;
35+
import org.springframework.transaction.TransactionTimedOutException;
36+
37+
/**
38+
* @author Olivier Hubaut
39+
*
40+
* @since 5.2.11
41+
*/
42+
public class JdbcLockRegistryDelegateTests {
43+
44+
private JdbcLockRegistry registry;
45+
46+
private LockRepository repository;
47+
48+
@BeforeEach
49+
public void clear() {
50+
repository = mock(LockRepository.class);
51+
registry = new JdbcLockRegistry(repository);
52+
53+
when(repository.acquire(anyString())).thenReturn(true);
54+
}
55+
56+
@Test
57+
public void testLessAmountOfUnlockThanLock() {
58+
final Random random = new Random();
59+
final int lockCount = random.nextInt(5) + 1;
60+
final int unlockCount = random.nextInt(lockCount);
61+
62+
final Lock lock = registry.obtain("foo");
63+
for (int i = 0; i < lockCount; i++) {
64+
lock.tryLock();
65+
}
66+
for (int i = 0; i < unlockCount; i++) {
67+
lock.unlock();
68+
}
69+
70+
assertThat(TestUtils.getPropertyValue(lock, "delegate", ReentrantLock.class).isLocked()).isTrue();
71+
}
72+
73+
@Test
74+
public void testSameAmountOfUnlockThanLock() {
75+
final Random random = new Random();
76+
final int lockCount = random.nextInt(5) + 1;
77+
78+
final Lock lock = registry.obtain("foo");
79+
for (int i = 0; i < lockCount; i++) {
80+
lock.tryLock();
81+
}
82+
for (int i = 0; i < lockCount; i++) {
83+
lock.unlock();
84+
}
85+
86+
assertThat(TestUtils.getPropertyValue(lock, "delegate", ReentrantLock.class).isLocked()).isFalse();
87+
}
88+
89+
@Test
90+
public void testTransientDataAccessException() {
91+
final Lock lock = registry.obtain("foo");
92+
lock.tryLock();
93+
94+
final AtomicBoolean shouldThrow = new AtomicBoolean(true);
95+
doAnswer(invocation -> {
96+
if (shouldThrow.getAndSet(false)) {
97+
throw mock(TransientDataAccessException.class);
98+
}
99+
return null;
100+
}).when(repository).delete(anyString());
101+
102+
lock.unlock();
103+
104+
assertThat(TestUtils.getPropertyValue(lock, "delegate", ReentrantLock.class).isLocked()).isFalse();
105+
}
106+
107+
@Test
108+
public void testTransactionTimedOutException() {
109+
final Lock lock = registry.obtain("foo");
110+
lock.tryLock();
111+
112+
final AtomicBoolean shouldThrow = new AtomicBoolean(true);
113+
doAnswer(invocation -> {
114+
if (shouldThrow.getAndSet(false)) {
115+
throw mock(TransactionTimedOutException.class);
116+
}
117+
return null;
118+
}).when(repository).delete(anyString());
119+
120+
lock.unlock();
121+
122+
assertThat(TestUtils.getPropertyValue(lock, "delegate", ReentrantLock.class).isLocked()).isFalse();
123+
}
124+
125+
}

0 commit comments

Comments
 (0)