Skip to content

Commit

Permalink
Backport filelocker changes
Browse files Browse the repository at this point in the history
  • Loading branch information
laeubi committed Dec 11, 2023
1 parent a9f59ad commit 1d0ca10
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 214 deletions.
26 changes: 21 additions & 5 deletions tycho-api/src/main/java/org/eclipse/tycho/FileLockService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011 SAP AG and others.
* Copyright (c) 2011, 2023 SAP AG and others.
* 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
Expand All @@ -13,6 +13,7 @@

package org.eclipse.tycho;

import java.io.Closeable;
import java.io.File;

/**
Expand All @@ -21,10 +22,25 @@
public interface FileLockService {

/**
* Get a locker object which can be used to protect read/write access from multiple processes on
* the given file. Locking is advisory only, i.e. all processes must use the same locking
* mechanism.
* Locks the given file to protect read/write access from multiple processes on it. Locking is
* advisory only, i.e. all processes must use the same locking mechanism.
* <p>
* This is equivalent to {@link #lock(File, long)} with a timeout argument of 10 seconds.
* </p>
*/
public FileLocker getFileLocker(File file);
default Closeable lock(File file) {
return lock(file, 10000L);
}

/**
* Locks the given file to protect read/write access from multiple processes on it. Locking is
* advisory only, i.e. all processes must use the same locking mechanism.
*/
Closeable lock(File file, long timeout);

/**
* Locks the given file for this JVM to protect read/write access from multiple threads in this
* JVM on it.
*/
Closeable lockVirtually(File file);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011, 2020 SAP AG and others.
* Copyright (c) 2011, 2023 SAP AG and others.
* 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
Expand All @@ -13,28 +13,62 @@

package org.eclipse.tycho.core.locking;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.codehaus.plexus.component.annotations.Component;
import org.eclipse.tycho.FileLockService;
import org.eclipse.tycho.LockTimeoutException;

@Component(role = FileLockService.class)
public class FileLockServiceImpl implements FileLockService {
record FileLocks(FileLockerImpl fileLocker, Lock vmLock) {
}

private final Map<Path, FileLocks> lockers = new ConcurrentHashMap<>();

private final Map<String, FileLockerImpl> lockers = new ConcurrentHashMap<>();
@Override
public Closeable lock(File file, long timeout) {
FileLocks locks = getFileLocker(file.toPath());
FileLockerImpl locker = locks.fileLocker();
try {
if (!locks.vmLock().tryLock(timeout, TimeUnit.MILLISECONDS)) {
throw new LockTimeoutException("lock timeout: Could not acquire lock on file " + locker.lockMarkerFile
+ " for " + timeout + " msec");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockTimeoutException("Interrupted", e);
}
locker.lock(timeout);
return () -> {
locks.fileLocker().release();
locks.vmLock().unlock();
};
}

@Override
public FileLockerImpl getFileLocker(File file) {
String key;
public Closeable lockVirtually(File file) {
FileLocks locks = getFileLocker(file.toPath());
locks.vmLock().lock();
return locks.vmLock()::unlock;
}

FileLocks getFileLocker(Path file) {
Path key;
try {
key = file.getCanonicalPath();
key = file.toRealPath();
} catch (IOException e) {
key = file.getAbsolutePath();
key = file.toAbsolutePath().normalize();
}
return lockers.computeIfAbsent(key, k -> new FileLockerImpl(file));
return lockers.computeIfAbsent(key, f -> new FileLocks(new FileLockerImpl(f), new ReentrantLock()));
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011 SAP AG and others.
* Copyright (c) 2011, 2023 SAP AG and others.
* 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
Expand All @@ -15,60 +15,46 @@

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

import org.eclipse.tycho.FileLocker;
import org.eclipse.tycho.LockTimeoutException;

public class FileLockerImpl implements FileLocker {
public class FileLockerImpl {

private static final String LOCKFILE_SUFFIX = ".tycholock";

final File lockMarkerFile;
final Path lockMarkerFile;

private FileLock lock;

private File file;
private Path file;

public FileLockerImpl(File file) {
this.file = file;
FileLockerImpl(Path file) {
this.file = file.toAbsolutePath().normalize();
this.lockMarkerFile = Files.isDirectory(this.file) //
? this.file.resolve(LOCKFILE_SUFFIX)
: this.file.getParent().resolve(this.file.getFileName() + LOCKFILE_SUFFIX);
try {
if (file.isDirectory()) {
this.lockMarkerFile = new File(file, LOCKFILE_SUFFIX).getCanonicalFile();
} else {
this.lockMarkerFile = new File(file.getParentFile(), file.getName() + LOCKFILE_SUFFIX)
.getCanonicalFile();
if (Files.isDirectory(lockMarkerFile)) {
throw new IllegalStateException(
"Lock marker file " + lockMarkerFile + " already exists and is a directory");
}
if (lockMarkerFile.isDirectory()) {
throw new RuntimeException("Lock marker file " + lockMarkerFile + " already exists and is a directory");
}
File parentDir = lockMarkerFile.getParentFile();
if (!parentDir.mkdirs() && !parentDir.isDirectory()) {
throw new RuntimeException("Could not create parent directory " + parentDir + " of lock marker file");
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
Files.createDirectories(lockMarkerFile.getParent());
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalStateException(e);
}
}

@Override
public void lock() {
lock(10000L);
}

@Override
public void lock(long timeout) {
void lock(long timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("timeout must not be negative");
}
if (lock != null) {
throw new LockTimeoutException("already locked file " + file.getAbsolutePath());
throw new LockTimeoutException("already locked file " + file);
}
lock = aquireLock(timeout);

Expand All @@ -81,8 +67,7 @@ private FileLock aquireLock(long timeout) {
for (long i = 0; i < maxTries; i++) {
try {
if (channel == null) {
Path path = lockMarkerFile.toPath();
channel = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
channel = FileChannel.open(lockMarkerFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
}
FileLock fileLock = channel.tryLock();
if (fileLock != null) {
Expand All @@ -105,26 +90,22 @@ private FileLock aquireLock(long timeout) {
channel = null;
}
}
throw new LockTimeoutException("lock timeout: Could not acquire lock on file "
+ lockMarkerFile.getAbsolutePath() + " for " + timeout + " msec");
throw new LockTimeoutException(
"lock timeout: Could not acquire lock on file " + lockMarkerFile + " for " + timeout + " msec");
}

@Override
public synchronized void release() {
synchronized void release() {
if (lock != null) {
try {
lock.acquiredBy().close();
} catch (Exception e) {
}
lock = null;
if (!lockMarkerFile.delete()) {
lockMarkerFile.deleteOnExit();
File lockFile = lockMarkerFile.toFile();
if (!lockFile.delete()) {
lockFile.deleteOnExit();
}
}
}

public synchronized boolean isLocked() {
return lock != null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.eclipse.tycho.FileLockService;
import org.eclipse.tycho.FileLocker;
import org.eclipse.tycho.TychoConstants;

@Component(role = BundleReader.class)
Expand Down Expand Up @@ -189,9 +188,7 @@ public File getEntry(File bundleLocation, String path) {
throw new RuntimeException("can't get canonical path for " + cacheFile, e);
}
result = extractedFiles.computeIfAbsent(cacheKey, nil -> {
FileLocker locker = fileLockService.getFileLocker(outputDirectory);
locker.lock(LOCK_TIMEOUT);
try {
try (var locked = fileLockService.lock(outputDirectory, LOCK_TIMEOUT)) {
extractZipEntries(bundleLocation, path, outputDirectory);
if (cacheFile.exists()) {
return Optional.of(cacheFile);
Expand All @@ -200,8 +197,6 @@ public File getEntry(File bundleLocation, String path) {
} catch (IOException e) {
throw new RuntimeException(
"Can't extract '" + path + "' from " + bundleLocation + " to " + outputDirectory, e);
} finally {
locker.release();
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import java.util.Set;

import org.eclipse.tycho.FileLockService;
import org.eclipse.tycho.FileLocker;
import org.eclipse.tycho.core.shared.MavenContext;
import org.eclipse.tycho.core.shared.MavenLogger;

Expand All @@ -47,7 +46,7 @@ public class FileBasedTychoRepositoryIndex implements TychoRepositoryIndex {

private final File indexFile;
private final MavenLogger logger;
private FileLocker fileLocker;
private final FileLockService fileLockService;

private Set<GAV> addedGavs = new HashSet<>();
private Set<GAV> removedGavs = new HashSet<>();
Expand All @@ -58,28 +57,17 @@ private FileBasedTychoRepositoryIndex(File indexFile, FileLockService fileLockSe
super();
this.indexFile = indexFile;
this.mavenContext = mavenContext;
this.fileLocker = fileLockService.getFileLocker(indexFile);
this.fileLockService = fileLockService;
this.logger = mavenContext.getLogger();
if (indexFile.isFile()) {
lock();
try {
try (var locked = fileLockService.lock(indexFile)) {
gavs = read(new FileInputStream(indexFile));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
unlock();
}
}
}

private void lock() {
fileLocker.lock();
}

private void unlock() {
fileLocker.release();
}

@Override
public MavenContext getMavenContext() {
return mavenContext;
Expand Down Expand Up @@ -118,8 +106,7 @@ public synchronized void save() throws IOException {
if (!parentDir.isDirectory()) {
parentDir.mkdirs();
}
lock();
try {
try (var locked = fileLockService.lock(indexFile)) {
reconcile();
// minimize time window for corrupting the file by first writing to a temp file, then moving it
File tempFile = File.createTempFile("index", "tmp", indexFile.getParentFile());
Expand All @@ -128,8 +115,6 @@ public synchronized void save() throws IOException {
indexFile.delete();
}
tempFile.renameTo(indexFile);
} finally {
unlock();
}
}

Expand Down
Loading

0 comments on commit 1d0ca10

Please sign in to comment.