Merge branch 'stable-6.1' into stable-6.2

* stable-6.1:
  Use Java 11 ProcessHandle to get pid of the current process
  Acquire file lock "gc.pid" before running gc
  Silence API errors introduced by 9424052f

Change-Id: I0562a4a224779ccf1e4cc1ff8f5a352e55ab220a
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 66adad5..84bffef 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -137,6 +137,7 @@
 cloneNonEmptyDirectory=Destination path "{0}" already exists and is not an empty directory
 closed=closed
 closeLockTokenFailed=Closing LockToken ''{0}'' failed
+closePidLockFailed=Closing lock file ''{0}'' failed
 collisionOn=Collision on {0}
 commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
 commandRejectedByHook=Rejected by "{0}" hook.\n{1}
@@ -309,6 +310,7 @@
 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}
 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
 failedToConvert=Failed to convert rest: %s
 failedToDetermineFilterDefinition=An exception occurred while determining filter definitions
@@ -328,6 +330,7 @@
 flagNotFromThis={0} not from this.
 flagsAlreadyCreated={0} flags already created.
 funnyRefname=funny refname
+gcAlreadyRunning=fatal: gc is already running on machine ''{0}'' pid {1}
 gcFailed=Garbage collection failed.
 gcTooManyUnpruned=Too many loose, unpruneable objects after garbage collection. Consider adjusting gc.auto or gc.pruneExpire.
 headRequiredToStash=HEAD required to stash local changes
@@ -690,6 +693,7 @@
 sslTrustForRepo=Skip SSL verification for git operations for repository {0}
 sslTrustNow=Skip SSL verification for this single git operation
 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}
 startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported
 stashApplyConflict=Applying stashed changes resulted in a conflict
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index efdb8e4..d4e3cca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -165,6 +165,7 @@ public static JGitText get() {
 	/***/ public String cloneNonEmptyDirectory;
 	/***/ public String closeLockTokenFailed;
 	/***/ public String closed;
+	/***/ public String closePidLockFailed;
 	/***/ public String collisionOn;
 	/***/ public String commandClosedStderrButDidntExit;
 	/***/ public String commandRejectedByHook;
@@ -337,6 +338,7 @@ public static JGitText get() {
 	/***/ public String expectedReportForRefNotReceived;
 	/***/ public String failedAtomicFileCreation;
 	/***/ public String failedCreateLockFile;
+	/***/ public String failedPidLock;
 	/***/ public String failedReadHttpsProtocols;
 	/***/ public String failedToDetermineFilterDefinition;
 	/***/ public String failedToConvert;
@@ -356,6 +358,7 @@ public static JGitText get() {
 	/***/ public String flagNotFromThis;
 	/***/ public String flagsAlreadyCreated;
 	/***/ public String funnyRefname;
+	/***/ public String gcAlreadyRunning;
 	/***/ public String gcFailed;
 	/***/ public String gcTooManyUnpruned;
 	/***/ public String headRequiredToStash;
@@ -718,6 +721,7 @@ public static JGitText get() {
 	/***/ public String sslTrustForRepo;
 	/***/ public String sslTrustNow;
 	/***/ public String sslVerifyCannotSave;
+	/***/ public String stalePidLock;
 	/***/ public String staleRevFlagsOn;
 	/***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported;
 	/***/ public String stashApplyConflict;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 8c0aca5..080af97 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -20,9 +20,16 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.io.RandomAccessFile;
 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.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.DirectoryStream;
 import java.nio.file.Files;
@@ -44,6 +51,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
@@ -84,8 +92,11 @@
 import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.treewalk.TreeWalk;
 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.GitDateParser;
+import org.eclipse.jgit.util.StringUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -262,13 +273,18 @@ private Collection<Pack> doGc() throws IOException, ParseException {
 		if (automatic && !needGc()) {
 			return Collections.emptyList();
 		}
-		pm.start(6 /* tasks */);
-		packRefs();
-		// TODO: implement reflog_expire(pm, repo);
-		Collection<Pack> newPacks = repack();
-		prune(Collections.emptySet());
-		// TODO: implement rerere_gc(pm);
-		return newPacks;
+		try (PidLock lock = new PidLock()) {
+			if (!lock.lock()) {
+				return Collections.emptyList();
+			}
+			pm.start(6 /* tasks */);
+			packRefs();
+			// TODO: implement reflog_expire(pm, repo);
+			Collection<Pack> newPacks = repack();
+			prune(Collections.emptySet());
+			// TODO: implement rerere_gc(pm);
+			return newPacks;
+		}
 	}
 
 	/**
@@ -1596,4 +1612,143 @@ private int getLooseObjectLimit() {
 		return repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION,
 				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() {
+			return ProcessHandle.current().pid();
+		}
+
+		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);
+			}
+		}
+
+	}
 }