Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-9291: Enhanced unlock() method of JdbcLock to verify successful unlocking #9292

Merged
merged 3 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
* @author Gary Russell
* @author Alexandre Strubel
* @author Ruslan Stelmachenko
* @author Eddie Cho
*
* @since 4.3
*/
Expand Down Expand Up @@ -389,9 +390,9 @@ public void close() {
}

@Override
public void delete(String lock) {
this.defaultTransactionTemplate.executeWithoutResult(
transactionStatus -> this.template.update(this.deleteQuery, this.region, lock, this.id));
public boolean delete(String lock) {
return this.defaultTransactionTemplate.execute(
transactionStatus -> this.template.update(this.deleteQuery, this.region, lock, this.id)) > 0;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2023 the original author or authors.
* Copyright 2016-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package org.springframework.integration.jdbc.lock;

import java.time.Duration;
import java.util.ConcurrentModificationException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -56,6 +57,7 @@
* @author Unseok Kim
* @author Christian Tzolov
* @author Myeonghyeon Lee
* @author Eddie Cho
*
* @since 4.3
*/
Expand Down Expand Up @@ -305,12 +307,20 @@ public void unlock() {
try {
while (true) {
try {
this.mutex.delete(this.path);
return;
if (this.mutex.delete(this.path)) {
return;
}
else {
throw new ConcurrentModificationException("Lock was released in the store due to expiration. " +
"The integrity of data protected by this lock may have been compromised.");
}
}
catch (TransientDataAccessException | TransactionTimedOutException | TransactionSystemException e) {
// try again
}
catch (ConcurrentModificationException e) {
throw e;
}
catch (Exception e) {
throw new DataAccessResourceFailureException("Failed to release mutex at " + this.path, e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 the original author or authors.
* Copyright 2016-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,6 +26,7 @@
* @author Dave Syer
* @author Alexandre Strubel
* @author Artem Bilan
* @author Eddie Cho
*
* @since 4.3
*/
Expand All @@ -41,8 +42,9 @@ public interface LockRepository extends Closeable {
/**
* Remove a lock from this repository.
* @param lock the lock to remove.
* @return deleted or not.
*/
void delete(String lock);
boolean delete(String lock);
artembilan marked this conversation as resolved.
Show resolved Hide resolved

/**
* Remove all the expired locks.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,7 +17,6 @@
package org.springframework.integration.jdbc.lock;

import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

Expand All @@ -31,17 +30,17 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* @author Olivier Hubaut
* @author Fran Aranda
* @author Eddie Cho
*
* @since 5.2.11
*/
public class JdbcLockRegistryDelegateTests {
class JdbcLockRegistryDelegateTests {

private JdbcLockRegistry registry;

Expand All @@ -56,7 +55,7 @@ public void clear() {
}

@Test
public void testLessAmountOfUnlockThanLock() {
void testLessAmountOfUnlockThanLock() {
final Random random = new Random();
final int lockCount = random.nextInt(5) + 1;
final int unlockCount = random.nextInt(lockCount);
Expand All @@ -73,11 +72,13 @@ public void testLessAmountOfUnlockThanLock() {
}

@Test
public void testSameAmountOfUnlockThanLock() {
void testSameAmountOfUnlockThanLock() {
final Random random = new Random();
final int lockCount = random.nextInt(5) + 1;

final Lock lock = registry.obtain("foo");
when(repository.delete(anyString())).thenReturn(true);

for (int i = 0; i < lockCount; i++) {
lock.tryLock();
}
Expand All @@ -89,53 +90,41 @@ public void testSameAmountOfUnlockThanLock() {
}

@Test
public void testTransientDataAccessException() {
void testTransientDataAccessException() {
final Lock lock = registry.obtain("foo");
lock.tryLock();

final AtomicBoolean shouldThrow = new AtomicBoolean(true);
doAnswer(invocation -> {
if (shouldThrow.getAndSet(false)) {
throw mock(TransientDataAccessException.class);
}
return null;
}).when(repository).delete(anyString());
when(repository.delete(anyString()))
.thenThrow(mock(TransientDataAccessException.class))
.thenReturn(true);

lock.unlock();

assertThat(TestUtils.getPropertyValue(lock, "delegate", ReentrantLock.class).isLocked()).isFalse();
}

@Test
public void testTransactionTimedOutException() {
void testTransactionTimedOutException() {
final Lock lock = registry.obtain("foo");
lock.tryLock();

final AtomicBoolean shouldThrow = new AtomicBoolean(true);
doAnswer(invocation -> {
if (shouldThrow.getAndSet(false)) {
throw mock(TransactionTimedOutException.class);
}
return null;
}).when(repository).delete(anyString());
when(repository.delete(anyString()))
.thenThrow(TransactionTimedOutException.class)
.thenReturn(true);

lock.unlock();

assertThat(TestUtils.getPropertyValue(lock, "delegate", ReentrantLock.class).isLocked()).isFalse();
}

@Test
public void testTransactionSystemException() {
void testTransactionSystemException() {
final Lock lock = registry.obtain("foo");
lock.tryLock();

final AtomicBoolean shouldThrow = new AtomicBoolean(true);
doAnswer(invocation -> {
if (shouldThrow.getAndSet(false)) {
throw mock(TransactionSystemException.class);
}
return null;
}).when(repository).delete(anyString());
when(repository.delete(anyString()))
.thenThrow(TransactionSystemException.class)
.thenReturn(true);

lock.unlock();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2022 the original author or authors.
* Copyright 2016-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package org.springframework.integration.jdbc.lock;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
Expand Down Expand Up @@ -45,18 +46,20 @@
import org.springframework.util.StopWatch;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
* @author Dave Syer
* @author Artem Bilan
* @author Glenn Renfro
* @author Alexandre Strubel
* @author Eddie Cho
*
* @since 4.3
*/
@SpringJUnitConfig(locations = "JdbcLockRegistryTests-context.xml")
@DirtiesContext
public class JdbcLockRegistryDifferentClientTests {
class JdbcLockRegistryDifferentClientTests {

private static final Log LOGGER = LogFactory.getLog(JdbcLockRegistryDifferentClientTests.class);

Expand Down Expand Up @@ -92,7 +95,7 @@ public void close() {
}

@Test
public void testSecondThreadLoses() throws Exception {
void testSecondThreadLoses() throws Exception {
for (int i = 0; i < 100; i++) {
final JdbcLockRegistry registry1 = this.registry;
final JdbcLockRegistry registry2 = this.child.getBean(JdbcLockRegistry.class);
Expand Down Expand Up @@ -129,7 +132,7 @@ public void testSecondThreadLoses() throws Exception {
}

@Test
public void testBothLock() throws Exception {
void testBothLock() throws Exception {
for (int i = 0; i < 100; i++) {
final JdbcLockRegistry registry1 = this.registry;
final JdbcLockRegistry registry2 = this.child.getBean(JdbcLockRegistry.class);
Expand Down Expand Up @@ -185,7 +188,7 @@ public void testBothLock() throws Exception {
}

@Test
public void testOnlyOneLock() throws Exception {
void testOnlyOneLock() throws Exception {
for (int i = 0; i < 100; i++) {
final BlockingQueue<String> locked = new LinkedBlockingQueue<>();
final CountDownLatch latch = new CountDownLatch(20);
Expand Down Expand Up @@ -231,7 +234,7 @@ public void testOnlyOneLock() throws Exception {
}

@Test
public void testExclusiveAccess() throws Exception {
void testExclusiveAccess() throws Exception {
DefaultLockRepository client1 = new DefaultLockRepository(dataSource);
client1.setApplicationContext(this.context);
client1.afterPropertiesSet();
Expand Down Expand Up @@ -281,7 +284,7 @@ public void testExclusiveAccess() throws Exception {
}

@Test
public void testOutOfDateLockTaken() throws Exception {
void testOutOfDateLockTaken() throws Exception {
DefaultLockRepository client1 = new DefaultLockRepository(dataSource);
client1.setTimeToLive(100);
client1.setApplicationContext(this.context);
Expand Down Expand Up @@ -314,7 +317,7 @@ public void testOutOfDateLockTaken() throws Exception {
});
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
data.add(2);
lock1.unlock();
assertThatThrownBy(lock1::unlock).isInstanceOf(ConcurrentModificationException.class);
for (int i = 0; i < 2; i++) {
Integer integer = data.poll(10, TimeUnit.SECONDS);
assertThat(integer).isNotNull();
Expand All @@ -323,7 +326,7 @@ public void testOutOfDateLockTaken() throws Exception {
}

@Test
public void testRenewLock() throws Exception {
void testRenewLock() throws Exception {
DefaultLockRepository client1 = new DefaultLockRepository(dataSource);
client1.setTimeToLive(500);
client1.setApplicationContext(this.context);
Expand Down
Loading