Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -14,6 +14,7 @@
package org.eclipse.ui.internal.ide.actions;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;

Expand Down Expand Up @@ -240,10 +241,16 @@ public void restart(String workspacePath) {
private boolean canRestartWithWorkspace(String workspacePath) throws IllegalStateException {
Path selectedWorkspace = Path.of(workspacePath);
try {
String workspaceLockDetails = WorkspaceLock.getWorkspaceLockDetails(selectedWorkspace.toUri().toURL());
if (workspaceLockDetails == null) {
URL url = selectedWorkspace.toUri().toURL();
if (!WorkspaceLock.isWorkspaceLocked(url)) {
return true;
}
String workspaceLockDetails = WorkspaceLock.getWorkspaceLockDetails(url);
if (workspaceLockDetails == null) {
// can only happen if the workspace is locked by an older Eclipse
// which doesn't write lock details
workspaceLockDetails = ""; //$NON-NLS-1$
}
WorkspaceLock.showWorkspaceLockedDialog(window.getShell(), workspacePath, workspaceLockDetails);
} catch (MalformedURLException e) {
MessageDialog.openError(window.getShell(), WorkbenchMessages.OpenWorkspaceAction_invalidWorkspacePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@
*******************************************************************************/
package org.eclipse.ui.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
Expand Down Expand Up @@ -101,7 +106,8 @@ public static String getWorkspaceLockDetails(URL workspaceUrl) {
}

/**
* Returns the lock file.
* Returns the lock <b>info</b> file (the file where information about last
* workspace lock owner is saved).
*
* @param workspaceUrl the <code>URL</code> of selected workspace
* @return the path to the <code>.lock_info</code> file within the specified
Expand All @@ -117,6 +123,25 @@ public static Path getLockInfoFile(URL workspaceUrl) {
}
}

/**
* Returns the workspace lock file for given workspace URL (the file which is
* actually <b>locked</b> if an Eclipse application is using this workspace).
*
* @param workspaceUrl the <code>URL</code> of selected workspace
* @return the path to the <code>.lock</code> file within the specified
* workspace, or <code> null</code> if the workspace URL cannot be
* converted to a valid URI
*/
public static Path getLockFile(URL workspaceUrl) {
// See org.eclipse.osgi.internal.location.BasicLocation.DEFAULT_LOCK_FILENAME
Path lockFile = Path.of(".metadata", ".lock"); //$NON-NLS-1$ //$NON-NLS-2$
try {
return Path.of(URIUtil.toURI(workspaceUrl)).resolve(lockFile);
} catch (URISyntaxException e) {
return null;
}
}

/**
* Opens an error dialog indicating that the selected workspace is locked by
* another Eclipse instance.
Expand All @@ -133,11 +158,58 @@ public static Path getLockInfoFile(URL workspaceUrl) {
*/
public static void showWorkspaceLockedDialog(Shell shell, String workspacePath, String workspaceLockOwner) {
String lockMessage = NLS.bind(WorkbenchMessages.IDEApplication_workspaceCannotLockMessage2, workspacePath);
String wsLockedError = lockMessage + System.lineSeparator() + System.lineSeparator()
+ NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockMessage, workspaceLockOwner);

String wsLockedError = lockMessage;
if (workspaceLockOwner != null && !workspaceLockOwner.isBlank()) {
String lockDetails = NLS.bind(WorkbenchMessages.IDEApplication_workspaceLockMessage, workspaceLockOwner);
wsLockedError += System.lineSeparator() + System.lineSeparator() + lockDetails;
}
MessageDialog.openError(shell,
WorkbenchMessages.IDEApplication_workspaceCannotLockTitle, wsLockedError);
}

/**
* Checks if the given workspace path is locked by another Eclipse instance.
*
* @param workspaceUrl the <code>URL</code> of workspace to check for lock
* @return <code>true</code> if the workspace is locked, <code>false</code>
* otherwise
*/
public static boolean isWorkspaceLocked(URL workspaceUrl) {
Path lockFile = getLockFile(workspaceUrl);
if (lockFile == null || !Files.exists(lockFile)) {
return false;
}
return isLocked(lockFile.toFile());
}

/**
* Follows the same strategy as
* <code>org.eclipse.osgi.internal.location.Locker_JavaNio#isLocked()</code>,
* trying to lock a file using Java NIO to check if the file is locked or not
* already.
*
* @return <code>true</code> if the file is definitely locked by any process,
* <code>false</code> otherwise
*/
private static boolean isLocked(File lockFile) {
try (RandomAccessFile temp = new RandomAccessFile(lockFile, "rw")) { //$NON-NLS-1$
try {
try (FileLock tempLock = temp.getChannel().tryLock(0, 1, false)) {
if (tempLock != null) {
// able to lock: it was not locked before
return false;
}
// is locked by some process
return true;
}
} catch (OverlappingFileLockException e) {
// is locked by some process
return true;
}
} catch (IOException e) {
// assume not locked if we have any troubles getting access to it
return false;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@
WorkbenchDatabindingTest.class,
ChooseWorkspaceDialogTests.class,
ViewerItemsLimitTest.class,
OpenCloseTest.class
OpenCloseTest.class,
WorkspaceLockTest.class
})
public class UiTestSuite {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*******************************************************************************
* Copyright (c) 2026 Andrey Loskutov <loskutov@gmx.de>.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Andrey Loskutov <loskutov@gmx.de> - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.tests;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

import org.eclipse.core.tests.harness.FileSystemHelper;
import org.eclipse.ui.internal.WorkspaceLock;
import org.junit.jupiter.api.Test;

/**
* Tests for {@link org.eclipse.ui.internal.WorkspaceLock}
*/
public class WorkspaceLockTest {

Path tempDir = FileSystemHelper.getRandomLocation().toPath().resolve("WorkspaceLockTest");

/**
* Test method for {@link org.eclipse.ui.internal.WorkspaceLock#getWorkspaceLockDetails(java.net.URL)}.
*/
@Test
public void testGetWorkspaceLockDetails() throws Exception {
URL workspaceUrl = tempDir.toUri().toURL();

// Test when no lock info file exists
String details = WorkspaceLock.getWorkspaceLockDetails(workspaceUrl);
assertNull(details, "Should return null when no lock info file exists");

// Create .metadata/.lock_info with properties
Path metadataDir = tempDir.resolve(".metadata");
Files.createDirectories(metadataDir);
Path lockInfoFile = metadataDir.resolve(".lock_info");

Properties props = new Properties();
props.setProperty(WorkspaceLock.USER, "testuser");
props.setProperty(WorkspaceLock.HOST, "testhost");
props.setProperty(WorkspaceLock.DISPLAY, ":0");
props.setProperty(WorkspaceLock.PROCESS_ID, "1234");
try (var os = Files.newOutputStream(lockInfoFile)) {
props.store(os, null);
}

details = WorkspaceLock.getWorkspaceLockDetails(workspaceUrl);
assertNotNull(details, "Should return details when lock info file exists");
assertTrue(details.contains("testuser"), "Should contain user info");
assertTrue(details.contains("testhost"), "Should contain host info");
assertTrue(details.contains(":0"), "Should contain display info");
assertTrue(details.contains("1234"), "Should contain PID info");
}

/**
* Test method for {@link org.eclipse.ui.internal.WorkspaceLock#getLockInfoFile(java.net.URL)}.
*/
@Test
public void testGetLockInfoFile() throws Exception {
URL workspaceUrl = tempDir.toUri().toURL();
Path lockInfoFile = WorkspaceLock.getLockInfoFile(workspaceUrl);
assertNotNull(lockInfoFile, "Should return a path");
assertEquals(tempDir.resolve(".metadata").resolve(".lock_info"), lockInfoFile,
"Should point to .metadata/.lock_info");
}

/**
* Test method for {@link org.eclipse.ui.internal.WorkspaceLock#getLockFile(java.net.URL)}.
*/
@Test
public void testGetLockFile() throws Exception {
URL workspaceUrl = tempDir.toUri().toURL();
Path lockFile = WorkspaceLock.getLockFile(workspaceUrl);
assertNotNull(lockFile, "Should return a path");
assertEquals(tempDir.resolve(".metadata").resolve(".lock"), lockFile, "Should point to .metadata/.lock");
}

/**
* Test method for {@link org.eclipse.ui.internal.WorkspaceLock#isWorkspaceLocked(java.net.URL)}.
*/
@Test
public void testIsWorkspaceLocked() throws Exception {
URL workspaceUrl = tempDir.toUri().toURL();

// Test when no lock file exists
assertFalse(WorkspaceLock.isWorkspaceLocked(workspaceUrl), "Should not be locked when no lock file exists");

// Create .metadata/.lock file & lock it
Path metadataDir = tempDir.resolve(".metadata");
Files.createDirectories(metadataDir);
Path lockFile = metadataDir.resolve(".lock");
try (RandomAccessFile lock = lock(lockFile.toFile())) {
assertTrue(WorkspaceLock.isWorkspaceLocked(workspaceUrl), "Should be locked");
}
}

/**
* Mimics
* {@linkplain org.eclipse.osgi.internal.location.Locker_JavaNio#lock(File)}
* locking a file using Java NIO and returning the RandomAccessFile if
* successful, null if the file is already locked by another process.
*/
static RandomAccessFile lock(File lockFile) {
try {
RandomAccessFile raFile = new RandomAccessFile(lockFile, "rw");
try {
FileLock lock = raFile.getChannel().tryLock(0, 1, false);
if (lock == null) {
raFile.close();
return null;
}
return raFile;
} catch (OverlappingFileLockException e) {
raFile.close();
return null;
}
} catch (IOException e) {
// already locked by some process, should not happen in test
return null;
}
}
}
Loading