mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-12 23:37:00 +08:00
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:
co-authored by
kseniaguzeeva
parent
039407b9db
commit
3c1766cd14
+9
-9
@@ -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),
|
||||
|
||||
+9
-9
@@ -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(),
|
||||
|
||||
-269
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-15
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user