Acquire file lock "gc.pid" before running gc
Git guards gc by locking a lock file "gc.pid" before starting execution. The lock file contains the pid and hostname of the process holding the lock. Git tries to kill the process holding that lock if the lock file wasn't modified in the last 12 hours and was started from the same host. Teach JGit to acquire this lock before running gc but skip execution if another process already holds the lock. Killing the other process could be undesired if it's a long running application. If the lock file wasn't modified in the last 12 hours try to lock it and run gc if locking succeeds. Register a shutdown hook for the lock file to ensure it is cleaned up if the process is gracefully killed. Change-Id: I00b838dcbf4fb0d03863bf7a2cd86b743c6c6971
This commit is contained in:
parent
380f091fa5
commit
8eee800fb1
|
@ -137,6 +137,7 @@ classCastNotA=Not a {0}
|
||||||
cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory
|
cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory
|
||||||
closed=closed
|
closed=closed
|
||||||
closeLockTokenFailed=Closing LockToken ''{0}'' failed
|
closeLockTokenFailed=Closing LockToken ''{0}'' failed
|
||||||
|
closePidLockFailed=Closing lock file ''{0}'' failed
|
||||||
collisionOn=Collision on {0}
|
collisionOn=Collision on {0}
|
||||||
commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
|
commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
|
||||||
commandRejectedByHook=Rejected by "{0}" hook.\n{1}
|
commandRejectedByHook=Rejected by "{0}" hook.\n{1}
|
||||||
|
@ -306,6 +307,7 @@ expectedReceivedContentType=expected Content-Type {0}; received Content-Type {1}
|
||||||
expectedReportForRefNotReceived={0}: expected report for ref {1} not received
|
expectedReportForRefNotReceived={0}: expected report for ref {1} not received
|
||||||
failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}
|
failedAtomicFileCreation=Atomic file creation failed, number of hard links to file {0} was not 2 but {1}
|
||||||
failedCreateLockFile=Creating lock file {} failed
|
failedCreateLockFile=Creating lock file {} failed
|
||||||
|
failedPidLock=Failed to lock ''{0}'' guarding git gc
|
||||||
failedReadHttpsProtocols=Failed to read system property https.protocols, assuming it is not set
|
failedReadHttpsProtocols=Failed to read system property https.protocols, assuming it is not set
|
||||||
failedToConvert=Failed to convert rest: %s
|
failedToConvert=Failed to convert rest: %s
|
||||||
failedToDetermineFilterDefinition=An exception occurred while determining filter definitions
|
failedToDetermineFilterDefinition=An exception occurred while determining filter definitions
|
||||||
|
@ -325,6 +327,7 @@ flagIsDisposed={0} is disposed.
|
||||||
flagNotFromThis={0} not from this.
|
flagNotFromThis={0} not from this.
|
||||||
flagsAlreadyCreated={0} flags already created.
|
flagsAlreadyCreated={0} flags already created.
|
||||||
funnyRefname=funny refname
|
funnyRefname=funny refname
|
||||||
|
gcAlreadyRunning=fatal: gc is already running on machine ''{0}'' pid {1}
|
||||||
gcFailed=Garbage collection failed.
|
gcFailed=Garbage collection failed.
|
||||||
gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire.
|
gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire.
|
||||||
headRequiredToStash=HEAD required to stash local changes
|
headRequiredToStash=HEAD required to stash local changes
|
||||||
|
@ -675,6 +678,7 @@ sslTrustAlways=Always skip SSL verification for this server from now on
|
||||||
sslTrustForRepo=Skip SSL verification for git operations for repository {0}
|
sslTrustForRepo=Skip SSL verification for git operations for repository {0}
|
||||||
sslTrustNow=Skip SSL verification for this single git operation
|
sslTrustNow=Skip SSL verification for this single git operation
|
||||||
sslVerifyCannotSave=Could not save setting for http.sslVerify
|
sslVerifyCannotSave=Could not save setting for http.sslVerify
|
||||||
|
stalePidLock=Lock file ''{0}'' is older than 12 hours and seems to be stale, lastModified: {1}, trying to lock it
|
||||||
staleRevFlagsOn=Stale RevFlags on {0}
|
staleRevFlagsOn=Stale RevFlags on {0}
|
||||||
startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported
|
startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported
|
||||||
stashApplyConflict=Applying stashed changes resulted in a conflict
|
stashApplyConflict=Applying stashed changes resulted in a conflict
|
||||||
|
|
|
@ -165,6 +165,7 @@ public static JGitText get() {
|
||||||
/***/ public String cloneNonEmptyDirectory;
|
/***/ public String cloneNonEmptyDirectory;
|
||||||
/***/ public String closeLockTokenFailed;
|
/***/ public String closeLockTokenFailed;
|
||||||
/***/ public String closed;
|
/***/ public String closed;
|
||||||
|
/***/ public String closePidLockFailed;
|
||||||
/***/ public String collisionOn;
|
/***/ public String collisionOn;
|
||||||
/***/ public String commandClosedStderrButDidntExit;
|
/***/ public String commandClosedStderrButDidntExit;
|
||||||
/***/ public String commandRejectedByHook;
|
/***/ public String commandRejectedByHook;
|
||||||
|
@ -334,6 +335,7 @@ public static JGitText get() {
|
||||||
/***/ public String expectedReportForRefNotReceived;
|
/***/ public String expectedReportForRefNotReceived;
|
||||||
/***/ public String failedAtomicFileCreation;
|
/***/ public String failedAtomicFileCreation;
|
||||||
/***/ public String failedCreateLockFile;
|
/***/ public String failedCreateLockFile;
|
||||||
|
/***/ public String failedPidLock;
|
||||||
/***/ public String failedReadHttpsProtocols;
|
/***/ public String failedReadHttpsProtocols;
|
||||||
/***/ public String failedToDetermineFilterDefinition;
|
/***/ public String failedToDetermineFilterDefinition;
|
||||||
/***/ public String failedToConvert;
|
/***/ public String failedToConvert;
|
||||||
|
@ -353,6 +355,7 @@ public static JGitText get() {
|
||||||
/***/ public String flagNotFromThis;
|
/***/ public String flagNotFromThis;
|
||||||
/***/ public String flagsAlreadyCreated;
|
/***/ public String flagsAlreadyCreated;
|
||||||
/***/ public String funnyRefname;
|
/***/ public String funnyRefname;
|
||||||
|
/***/ public String gcAlreadyRunning;
|
||||||
/***/ public String gcFailed;
|
/***/ public String gcFailed;
|
||||||
/***/ public String gcTooManyUnpruned;
|
/***/ public String gcTooManyUnpruned;
|
||||||
/***/ public String headRequiredToStash;
|
/***/ public String headRequiredToStash;
|
||||||
|
@ -703,6 +706,7 @@ public static JGitText get() {
|
||||||
/***/ public String sslTrustForRepo;
|
/***/ public String sslTrustForRepo;
|
||||||
/***/ public String sslTrustNow;
|
/***/ public String sslTrustNow;
|
||||||
/***/ public String sslVerifyCannotSave;
|
/***/ public String sslVerifyCannotSave;
|
||||||
|
/***/ public String stalePidLock;
|
||||||
/***/ public String staleRevFlagsOn;
|
/***/ public String staleRevFlagsOn;
|
||||||
/***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported;
|
/***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported;
|
||||||
/***/ public String stashApplyConflict;
|
/***/ public String stashApplyConflict;
|
||||||
|
|
|
@ -12,17 +12,24 @@
|
||||||
|
|
||||||
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
|
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
|
||||||
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
|
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
|
||||||
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
|
|
||||||
import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
|
import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
|
||||||
|
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.channels.OverlappingFileLockException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -44,6 +51,7 @@
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
@ -83,8 +91,11 @@
|
||||||
import org.eclipse.jgit.storage.pack.PackConfig;
|
import org.eclipse.jgit.storage.pack.PackConfig;
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.FS.LockToken;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
import org.eclipse.jgit.util.GitDateParser;
|
import org.eclipse.jgit.util.GitDateParser;
|
||||||
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
import org.eclipse.jgit.util.SystemReader;
|
import org.eclipse.jgit.util.SystemReader;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -264,13 +275,18 @@ private Collection<Pack> doGc() throws IOException, ParseException {
|
||||||
if (automatic && !needGc()) {
|
if (automatic && !needGc()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
pm.start(6 /* tasks */);
|
try (PidLock lock = new PidLock()) {
|
||||||
packRefs();
|
if (!lock.lock()) {
|
||||||
// TODO: implement reflog_expire(pm, repo);
|
return Collections.emptyList();
|
||||||
Collection<Pack> newPacks = repack();
|
}
|
||||||
prune(Collections.emptySet());
|
pm.start(6 /* tasks */);
|
||||||
// TODO: implement rerere_gc(pm);
|
packRefs();
|
||||||
return newPacks;
|
// TODO: implement reflog_expire(pm, repo);
|
||||||
|
Collection<Pack> newPacks = repack();
|
||||||
|
prune(Collections.emptySet());
|
||||||
|
// TODO: implement rerere_gc(pm);
|
||||||
|
return newPacks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1596,4 +1612,153 @@ private int getLooseObjectLimit() {
|
||||||
return repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION,
|
return repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION,
|
||||||
ConfigConstants.CONFIG_KEY_AUTO, DEFAULT_AUTOLIMIT);
|
ConfigConstants.CONFIG_KEY_AUTO, DEFAULT_AUTOLIMIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PidLock implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final String GC_PID = "gc.pid"; //$NON-NLS-1$
|
||||||
|
|
||||||
|
private final Path pidFile;
|
||||||
|
|
||||||
|
private LockToken token;
|
||||||
|
|
||||||
|
private FileLock lock;
|
||||||
|
|
||||||
|
private RandomAccessFile f;
|
||||||
|
|
||||||
|
private FileChannel channel;
|
||||||
|
|
||||||
|
PidLock() {
|
||||||
|
pidFile = repo.getDirectory().toPath().resolve(GC_PID);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean lock() {
|
||||||
|
if (Files.exists(pidFile)) {
|
||||||
|
Instant mtime = FS.DETECTED
|
||||||
|
.lastModifiedInstant(pidFile.toFile());
|
||||||
|
Instant twelveHoursAgo = Instant.now().minus(12,
|
||||||
|
ChronoUnit.HOURS);
|
||||||
|
if (mtime.compareTo(twelveHoursAgo) > 0) {
|
||||||
|
gcAlreadyRunning();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.warn(MessageFormat.format(JGitText.get().stalePidLock,
|
||||||
|
pidFile, mtime));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
token = FS.DETECTED.createNewFileAtomic(pidFile.toFile());
|
||||||
|
f = new RandomAccessFile(pidFile.toFile(), "rw"); //$NON-NLS-1$
|
||||||
|
channel = f.getChannel();
|
||||||
|
lock = channel.tryLock();
|
||||||
|
if (lock == null) {
|
||||||
|
failedToLock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
channel.write(ByteBuffer
|
||||||
|
.wrap(getProcDesc().getBytes(StandardCharsets.UTF_8)));
|
||||||
|
Thread cleanupHook = new Thread(() -> close());
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().addShutdownHook(cleanupHook);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// ignore - the VM is already shutting down
|
||||||
|
}
|
||||||
|
} catch (IOException | OverlappingFileLockException e) {
|
||||||
|
try {
|
||||||
|
failedToLock();
|
||||||
|
} catch (Exception e1) {
|
||||||
|
LOG.error(
|
||||||
|
MessageFormat.format(
|
||||||
|
JGitText.get().closePidLockFailed, pidFile),
|
||||||
|
e1);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failedToLock() {
|
||||||
|
close();
|
||||||
|
LOG.error(MessageFormat.format(JGitText.get().failedPidLock,
|
||||||
|
pidFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void gcAlreadyRunning() {
|
||||||
|
close();
|
||||||
|
try {
|
||||||
|
Optional<String> s = Files.lines(pidFile).findFirst();
|
||||||
|
String machine = null;
|
||||||
|
String pid = null;
|
||||||
|
if (s.isPresent()) {
|
||||||
|
String[] c = s.get().split("\\s+"); //$NON-NLS-1$
|
||||||
|
pid = c[0];
|
||||||
|
machine = c[1];
|
||||||
|
}
|
||||||
|
if (!StringUtils.isEmptyOrNull(machine)
|
||||||
|
&& !StringUtils.isEmptyOrNull(pid)) {
|
||||||
|
LOG.error(MessageFormat.format(
|
||||||
|
JGitText.get().gcAlreadyRunning, machine, pid));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
LOG.error(MessageFormat.format(JGitText.get().failedPidLock,
|
||||||
|
pidFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getProcDesc() {
|
||||||
|
StringBuffer s = new StringBuffer(Long.toString(getPID()));
|
||||||
|
s.append(' ');
|
||||||
|
s.append(getHostName());
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getPID() {
|
||||||
|
String processName = java.lang.management.ManagementFactory
|
||||||
|
.getRuntimeMXBean().getName();
|
||||||
|
if (processName != null && processName.length() > 0) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(processName.split("@")[0]); //$NON-NLS-1$
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHostName() {
|
||||||
|
try {
|
||||||
|
return InetAddress.getLocalHost().getHostName();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return ""; //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
boolean wasLocked = false;
|
||||||
|
try {
|
||||||
|
if (lock != null) {
|
||||||
|
lock.release();
|
||||||
|
wasLocked = true;
|
||||||
|
}
|
||||||
|
if (channel != null) {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
if (f != null) {
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
if (token != null) {
|
||||||
|
token.close();
|
||||||
|
}
|
||||||
|
if (wasLocked) {
|
||||||
|
FileUtils.delete(pidFile.toFile(), FileUtils.RETRY);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(MessageFormat
|
||||||
|
.format(JGitText.get().closePidLockFailed, pidFile), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue