dbeaver/pro#4204 move file lock logic to dbeaver (#3287)

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
This commit is contained in:
Alexander Skoblikov
2025-03-11 12:46:07 +01:00
committed by GitHub
co-authored by kseniaguzeeva
parent 039407b9db
commit 3c1766cd14
7 changed files with 41 additions and 482 deletions
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.model.rm.lock.RMFileLockController;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -28,6 +27,7 @@ import org.jkiss.dbeaver.model.DBPDataSourceFolder;
import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMEvent;
@@ -57,11 +57,11 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull
protected final DBPWorkspace workspace;
@NotNull
protected final RMFileLockController lockController;
protected final FileLockController lockController;
protected BaseLocalResourceController(
@NotNull DBPWorkspace workspace,
@NotNull RMFileLockController lockController
@NotNull FileLockController lockController
) {
this.workspace = workspace;
this.lockController = lockController;
@@ -150,7 +150,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String configuration,
@Nullable List<String> dataSourceIds
) throws DBException {
try (var lock = lockController.lockProject(projectId, "updateProjectDataSources")) {
try (var lock = lockController.lock(projectId, "updateProjectDataSources")) {
DBPProject project = getWebProject(projectId, false);
return doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
@@ -180,7 +180,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String[] dataSourceIds
) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "deleteDatasources")) {
try (var projectLock = lockController.lock(projectId, "deleteDatasources")) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false), () -> {
DBPDataSourceRegistry registry = project.getDataSourceRegistry();
@@ -205,7 +205,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String projectId,
@NotNull String folderPath
) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "createDatasourceFolder")) {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Creating data source folder '" + folderPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -230,7 +230,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String[] folderPaths,
boolean dropContents
) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "createDatasourceFolder")) {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
doFileWriteOperation(projectId, project.getMetadataFolder(false),
() -> {
@@ -257,7 +257,7 @@ public abstract class BaseLocalResourceController implements RMController {
@NotNull String oldPath,
@NotNull String newPath
) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "createDatasourceFolder")) {
try (var projectLock = lockController.lock(projectId, "createDatasourceFolder")) {
DBPProject project = getWebProject(projectId, false);
log.debug("Moving data source folder from '" + oldPath + "' to '" + newPath + "' in project '" + projectId + "'");
doFileWriteOperation(projectId, project.getMetadataFolder(false),
@@ -19,7 +19,6 @@ package io.cloudbeaver.model.rm.local;
import io.cloudbeaver.BaseWebProjectImpl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.model.app.ServletApplication;
import io.cloudbeaver.model.rm.lock.RMFileLockController;
import io.cloudbeaver.service.security.SMUtils;
import io.cloudbeaver.service.sql.WebSQLConstants;
import io.cloudbeaver.utils.ServletAppUtils;
@@ -32,6 +31,7 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentials;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl;
import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl;
import org.jkiss.dbeaver.model.rm.*;
@@ -83,7 +83,7 @@ public class LocalResourceController extends BaseLocalResourceController {
Path sharedProjectsPath,
Supplier<SMController> smControllerSupplier
) throws DBException {
super(workspace, new RMFileLockController(ServletAppUtils.getServletApplication()));
super(workspace, new FileLockController(ServletAppUtils.getServletApplication().getApplicationInstanceId()));
this.credentialsProvider = credentialsProvider;
this.rootPath = rootPath;
this.userProjectsPath = userProjectsPath;
@@ -297,7 +297,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteProject(@NotNull String projectId) throws DBException {
try (var projectLock = lockController.lockProject(projectId, "deleteProject")) {
try (var projectLock = lockController.lock(projectId, "deleteProject")) {
RMProject project = makeProjectFromId(projectId, false);
Path targetPath = getProjectPath(projectId);
if (!Files.exists(targetPath)) {
@@ -395,7 +395,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String resourcePath,
boolean isFolder
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "createResource")) {
try (var ignoredLock = lockController.lock(projectId, "createResource")) {
validateResourcePath(resourcePath);
Path targetPath = getTargetPath(projectId, resourcePath);
if (Files.exists(targetPath)) {
@@ -427,7 +427,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String oldResourcePath,
@NotNull String newResourcePath
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "moveResource")) {
try (var ignoredLock = lockController.lock(projectId, "moveResource")) {
var normalizedOldResourcePath = CommonUtils.normalizeResourcePath(oldResourcePath);
var normalizedNewResourcePath = CommonUtils.normalizeResourcePath(newResourcePath);
if (log.isDebugEnabled()) {
@@ -499,7 +499,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@Override
public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "deleteResource")) {
try (var ignoredLock = lockController.lock(projectId, "deleteResource")) {
if (log.isDebugEnabled()) {
log.debug("Removing resource from '" + resourcePath + "' in project '" + projectId + "'" + (recursive ? " recursive" : ""));
}
@@ -587,7 +587,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull byte[] data,
boolean forceOverwrite
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "setResourceContents")) {
try (var ignoredLock = lockController.lock(projectId, "setResourceContents")) {
validateResourcePath(resourcePath);
Number fileSizeLimit = ServletAppUtils.getServletApplication()
.getAppConfiguration()
@@ -632,7 +632,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String propertyName,
@Nullable Object propertyValue
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "resourcePropertyUpdate")) {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -653,7 +653,7 @@ public class LocalResourceController extends BaseLocalResourceController {
@NotNull String resourcePath,
@NotNull Map<String, Object> properties
) throws DBException {
try (var ignoredLock = lockController.lockProject(projectId, "resourcePropertyUpdate")) {
try (var ignoredLock = lockController.lock(projectId, "resourcePropertyUpdate")) {
validateResourcePath(resourcePath);
BaseWebProjectImpl webProject = getWebProject(projectId, false);
doFileWriteOperation(projectId, webProject.getMetadataFilePath(),
@@ -1,269 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.rm.lock;
import com.google.gson.Gson;
import io.cloudbeaver.model.app.ServletApplication;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.utils.IOUtils;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
/**
* File based resource locks
*/
public class RMFileLockController {
private static final Log log = Log.getLog(RMFileLockController.class);
private static final int DEFAULT_MAX_LOCK_TIME = 1 * 60 * 1000; // 1 min
private static final int CHECK_PERIOD = 10;
private static final String LOCK_META_FOLDER = ".locks";
private static final String LOCK_FILE_EXTENSION = ".lock";
private final Gson gson = new Gson();
private final Path lockFolderPath;
private final String applicationId;
private final int maxLockTime;
public RMFileLockController(ServletApplication application) throws DBException {
this(application, DEFAULT_MAX_LOCK_TIME);
}
// for tests
public RMFileLockController(ServletApplication application, int maxLockTime) throws DBException {
this.lockFolderPath = application.getWorkspaceDirectory()
.resolve(DBPWorkspace.METADATA_FOLDER)
.resolve(LOCK_META_FOLDER);
this.applicationId = application.getApplicationInstanceId();
this.maxLockTime = maxLockTime;
}
/**
* Lock the project for the duration of any operation.
* Other threads/processes will also see this lock, and will wait for it to end
* or force intercept lock, if the operation will take too long and
* exceeds the maximum available locking time {@link #maxLockTime} or the lock is invalid {@link #awaitUnlock)}.
*
* @param projectId - project to be locked
* @param operationName - executed operation name
* @return - lock
*/
@NotNull
public RMLock lockProject(@NotNull String projectId, @NotNull String operationName) throws DBException {
synchronized (RMFileLockController.class) {
try {
RMLockInfo lockInfo = new RMLockInfo.Builder(projectId, UUID.randomUUID().toString())
.setApplicationId(applicationId)
.setOperationName(operationName)
.setOperationStartTime(System.currentTimeMillis())
.build();
Path projectLockFile = getProjectLockFilePath(projectId);
if (!IOUtils.isFileFromDefaultFS(lockFolderPath)) {
// fake lock for external file system?
return new RMLock(projectLockFile);
}
createLockFolderIfNeeded();
createProjectFolder(projectId);
createLockFile(projectLockFile, lockInfo);
return new RMLock(projectLockFile);
} catch (Exception e) {
throw new DBException("Failed to lock project: " + projectId, e);
}
}
}
/**
* if the project is already locked, the operation will be executed as a child of the first lock,
* otherwise it creates its own lock.
*
* @param projectId - project to be locked
* @param operationName - executed operation name
* @return - lock
*/
@Nullable
public RMLock lockIfNotLocked(@NotNull String projectId, @NotNull String operationName) throws DBException {
synchronized (RMFileLockController.class) {
if (isProjectLocked(projectId)) {
return null;
}
return lockProject(projectId, operationName);
}
}
/**
* Check that project locked
*/
public boolean isProjectLocked(String projectId) {
Path projectLockFilePath = getProjectLockFilePath(projectId);
return isLocked(projectLockFilePath);
}
protected boolean isLocked(Path lockFilePath) {
return Files.exists(lockFilePath);
}
private void createLockFile(Path projectLockFile, RMLockInfo lockInfo) throws DBException, InterruptedException {
boolean lockFileCreated = false;
while (!lockFileCreated) {
if (Files.exists(projectLockFile)) {
awaitUnlock(lockInfo.getProjectId(), projectLockFile);
}
try {
Files.createFile(projectLockFile);
lockFileCreated = true;
} catch (IOException e) {
if (Files.exists(projectLockFile)) {
log.info("Looks like file was locked by another rm instance at the same time");
continue;
} else {
throw new DBException("Failed to create lock file: " + projectLockFile, e);
}
}
try {
Files.write(projectLockFile, gson.toJson(lockInfo).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
log.error("Failed to write lock info, unlock project: " + lockInfo.getProjectId());
try {
Files.deleteIfExists(lockFolderPath);
} catch (IOException ex) {
throw new DBException("Failed to remove invalid lock file: " + projectLockFile, ex);
}
throw new DBException("Failed to lock project: " + lockInfo.getProjectId(), e);
}
}
}
private void createProjectFolder(String projectId) throws DBException {
Path projectLocksFolder = lockFolderPath.resolve(projectId);
if (Files.exists(projectLocksFolder)) {
return;
}
try {
Files.createDirectories(projectLocksFolder);
} catch (Exception e) {
if (Files.exists(projectLocksFolder)) {
// ignore, because file can be created by another server
} else {
throw new DBException("Failed to create project lock folder: " + projectId, e);
}
}
}
protected void awaitUnlock(String projectId, Path projectLockFile) throws InterruptedException, DBException {
if (!isLocked(projectLockFile)) {
return;
}
awaitingUnlock(projectId, projectLockFile);
}
protected void awaitingUnlock(String projectId, Path projectLockFile) throws DBException, InterruptedException {
log.info("Waiting for a file to be unlocked: " + projectLockFile);
RMLockInfo originalLockInfo = readLockInfo(projectId, projectLockFile);
boolean fileUnlocked = originalLockInfo == null; //lock can be removed at the moment when we try to read lock file info
int maxIterations = maxLockTime / CHECK_PERIOD;
int currentCheckCount = 0;
while (!fileUnlocked) {
fileUnlocked = !isLocked(projectLockFile);
if (currentCheckCount >= maxIterations || fileUnlocked) {
break;
}
if (originalLockInfo != null & originalLockInfo.isBlank()) {
// possible in situation where the project has just been locked
// and the lock information has not yet been written
originalLockInfo = readLockInfo(projectId, projectLockFile);
}
currentCheckCount++;
Thread.sleep(CHECK_PERIOD);
}
if (fileUnlocked) {
return;
}
RMLockInfo currentLockInfo = readLockInfo(projectId, projectLockFile);
if (currentLockInfo == null) {
// file unlocked now
return;
}
//checking that this is not a new lock from another operation
if (originalLockInfo.getOperationId().equals(currentLockInfo.getOperationId())) {
forceUnlock(projectLockFile);
} else {
awaitUnlock(projectId, lockFolderPath);
}
}
protected void forceUnlock(Path projectLockFile) {
// something went wrong and lock is invalid
log.warn("File has not been unlocked within the expected period, force unlock");
try {
Files.deleteIfExists(projectLockFile);
} catch (IOException e) {
log.error(e);
}
}
@Nullable
/**
@return
- null if lock not exist;
- empty lock info if the lock has just been created and the information has not yet been written;
- lock info
*/
private RMLockInfo readLockInfo(String projectId, Path projectLockFile) throws DBException {
if (Files.notExists(projectLockFile)) {
return null;
}
try (Reader reader = Files.newBufferedReader(projectLockFile, StandardCharsets.UTF_8)) {
return gson.fromJson(reader, RMLockInfo.class);
} catch (IOException e) {
if (!isLocked(projectLockFile)) {
return null;
}
log.warn("Failed to read lock file info, but lock file still exist: " + projectLockFile);
return RMLockInfo.emptyLock(projectId);
}
}
private Path getProjectLockFilePath(String projectId) {
return lockFolderPath.resolve(projectId).resolve(projectId + LOCK_FILE_EXTENSION);
}
private void createLockFolderIfNeeded() throws IOException {
synchronized (RMFileLockController.class) {
if (Files.notExists(lockFolderPath)) {
Files.createDirectories(lockFolderPath);
}
}
}
}
@@ -1,63 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.rm.lock;
import org.jkiss.dbeaver.Log;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Resource Manager resource lock
*/
public class RMLock implements AutoCloseable {
private static final Log log = Log.getLog(RMLock.class);
private final Path lockFilePath;
public RMLock(Path lockFilePath) {
this.lockFilePath = lockFilePath;
}
/**
* Unlock resource and remove .lock file
*/
public void unlock() {
try {
Files.deleteIfExists(lockFilePath);
} catch (IOException e) {
log.error("Failed to unlock file: " + lockFilePath, e);
if (Files.exists(lockFilePath)) {
// file still locket, try to unlock again
unlock();
}
}
}
/**
* @return path to the lock file
*/
protected Path getLockFilePath() {
return lockFilePath;
}
@Override
public void close() {
unlock();
}
}
@@ -1,110 +0,0 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.model.rm.lock;
public class RMLockInfo {
private final String applicationId;
private final String projectId;
private final String operationId;
private final String operationName;
private final long operationStartTime;
private RMLockInfo(
String applicationId,
String projectId,
String operationId,
String operationName,
long operationStartTime
) {
this.applicationId = applicationId;
this.projectId = projectId;
this.operationId = operationId;
this.operationName = operationName;
this.operationStartTime = operationStartTime;
}
static RMLockInfo emptyLock(
String projectId
) {
return new RMLockInfo(
"",
projectId,
"",
"",
System.currentTimeMillis()
);
}
public boolean isBlank() {
return operationId.isEmpty();
}
public String getApplicationId() {
return applicationId;
}
public String getProjectId() {
return projectId;
}
public String getOperationId() {
return operationId;
}
public String getOperationName() {
return operationName;
}
public long getOperationStartTime() {
return operationStartTime;
}
public static final class Builder {
private String applicationId;
private final String projectId;
private final String operationId;
private String operationName;
private long operationStartTime;
public Builder(String projectId, String operationId) {
this.projectId = projectId;
this.operationId = operationId;
}
public Builder setApplicationId(String applicationId) {
this.applicationId = applicationId;
return this;
}
public Builder setOperationName(String operationName) {
this.operationName = operationName;
return this;
}
public Builder setOperationStartTime(long operationStartTime) {
this.operationStartTime = operationStartTime;
return this;
}
public RMLockInfo build() {
return new RMLockInfo(applicationId, projectId, operationId, operationName, operationStartTime);
}
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package io.cloudbeaver.model.rm.lock;
import io.cloudbeaver.app.CEAppStarter;
import io.cloudbeaver.test.platform.CEServerTestSuite;
import org.jkiss.dbeaver.Log;
import org.junit.Assert;
import org.junit.Test;
@@ -50,7 +49,7 @@ public class RMLockTest {
AtomicReference<Throwable> exceptionReference = new AtomicReference<>();
Runnable runnable1 = () -> {
try (var lock = lockController1.lockProject(project1, "testThatProjectLocked1")) {
try (var lock = lockController1.lock(project1, "testThatProjectLocked1")) {
isLockedByThread1.set(true);
thread2CDL.countDown();
thread1CDL.await(1, TimeUnit.MINUTES);
@@ -77,15 +76,15 @@ public class RMLockTest {
}
return invocationOnMock.callRealMethod();
}
}).when(lockController2).awaitingUnlock(Mockito.any(), Mockito.any());
}).when(lockController2).awaitingUnlock(Mockito.any());
Runnable runnable2 = () -> {
try {
thread2CDL.await(1, TimeUnit.MINUTES);
Assert.assertTrue("Project not locket by thread 1", isLockedByThread1.get());
Assert.assertTrue("Project not locked", lockController2.isProjectLocked(project1));
try (var lock = lockController2.lockProject(project1, "testThatProjectLocked2")) {
Assert.assertTrue("Project not locked", lockController2.isFileLocked(project1));
try (var lock = lockController2.lock(project1, "testThatProjectLocked2")) {
//that we were really waiting for the file and the lock was not removed earlier
Mockito.verify(lockController2, Mockito.atLeast(atLeastWaitCalls)).awaitingUnlock(Mockito.any(), Mockito.any());
Mockito.verify(lockController2, Mockito.atLeast(atLeastWaitCalls)).awaitingUnlock(Mockito.any());
}
} catch (Throwable e) {
log.error(e);
@@ -101,7 +100,7 @@ public class RMLockTest {
if (exceptionReference.get() != null) {
throw exceptionReference.get();
}
Assert.assertFalse(lockController2.isProjectLocked(project1));
Assert.assertFalse(lockController2.isFileLocked(project1));
}
@Test
@@ -117,7 +116,7 @@ public class RMLockTest {
AtomicBoolean isLockedByThread2 = new AtomicBoolean(false);
AtomicReference<Throwable> exceptionReference = new AtomicReference<>();
Runnable runnable1 = () -> {
try (var lock = lockController1.lockProject(project1, "testAccessToDifferentProjects1")) {
try (var lock = lockController1.lock(project1, "testAccessToDifferentProjects1")) {
isLockedByThread1.set(true);
thread2InitCDL.countDown();
thread1CDL.await(1, TimeUnit.MINUTES);
@@ -135,7 +134,7 @@ public class RMLockTest {
var lockController2 = new TestLockController(CEAppStarter.getTestApp(), 1);
Runnable runnable2 = () -> {
try {
try (var lock = lockController2.lockProject(project2, "testAccessToDifferentProjects2")) {
try (var lock = lockController2.lock(project2, "testAccessToDifferentProjects2")) {
thread2InitCDL.await();
Assert.assertTrue("Project1 not locket by thread1", isLockedByThread1.get());
isLockedByThread2.set(true);
@@ -158,8 +157,8 @@ public class RMLockTest {
throw exceptionReference.get();
}
Assert.assertFalse(lockController2.isProjectLocked(project1));
Assert.assertFalse(lockController2.isProjectLocked(project2));
Assert.assertFalse(lockController2.isFileLocked(project1));
Assert.assertFalse(lockController2.isFileLocked(project2));
}
@Test
@@ -172,7 +171,7 @@ public class RMLockTest {
AtomicBoolean isLockedByThread1 = new AtomicBoolean(false);
AtomicReference<Throwable> exceptionReference = new AtomicReference<>();
Runnable runnable1 = () -> {
try (var lock = lockController1.lockProject(project1, "testForceUnlock1")) {
try (var lock = lockController1.lock(project1, "testForceUnlock1")) {
isLockedByThread1.set(true);
thread1CDL.await(1, TimeUnit.MINUTES);
} catch (Throwable e) {
@@ -187,7 +186,7 @@ public class RMLockTest {
var lockController2 = Mockito.spy(new TestLockController(CEAppStarter.getTestApp(), 100));
Runnable runnable2 = () -> {
try {
try (var lock = lockController2.lockProject(project1, "testForceUnlock2")) {
try (var lock = lockController2.lock(project1, "testForceUnlock2")) {
Assert.assertTrue("Project1 not locket by thread1", isLockedByThread1.get());
Mockito.verify(lockController2, Mockito.atLeast(5)).isLocked(Mockito.any());
thread1CDL.countDown();
@@ -206,6 +205,6 @@ public class RMLockTest {
if (exceptionReference.get() != null) {
throw exceptionReference.get();
}
Assert.assertFalse(lockController2.isProjectLocked(project1));
Assert.assertFalse(lockController2.isFileLocked(project1));
}
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,28 +17,30 @@
package io.cloudbeaver.model.rm.lock;
import io.cloudbeaver.model.app.ServletApplication;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.fs.lock.FileLockController;
import java.nio.file.Path;
public class TestLockController extends RMFileLockController {
public class TestLockController extends FileLockController {
public TestLockController(ServletApplication application) throws DBException {
super(application);
super(application.getApplicationInstanceId());
}
public TestLockController(ServletApplication application, int maxTimeout) throws DBException {
super(application, maxTimeout);
super(application.getApplicationInstanceId(), maxTimeout);
}
//avoid mockito access method error
@Override
public void awaitingUnlock(String projectId, Path projectLockFile) throws InterruptedException, DBException {
super.awaitingUnlock(projectId, projectLockFile);
public void awaitingUnlock(@NotNull Path projectLockFile) throws InterruptedException, DBException {
super.awaitingUnlock(projectLockFile);
}
//avoid mockito access method error
@Override
public boolean isLocked(Path lockFilePath) {
public boolean isLocked(@NotNull Path lockFilePath) {
return super.isLocked(lockFilePath);
}
}