Repository: gradle/native-platform
Issue: Stopping the watching causes a `timeout value is negative` exception

When we try to stop the watching, it is currently possible to end up with a negative timeout when trying to join the watcher thread. When this happens, we `join` method throws an `IllegalArgumentException`.

We should that the timeout to join the thread is always positive. This is where we stop watching: https://github.com/gradle/native-platform/blob/master/file-events/src/main/java/net/rubygrapefruit/platform/internal/jni/AbstractFileEventFunctions.java#L220-L232

And here is a stacktrace from a build failure on the Gradle side:

```
Caused by: java.lang.IllegalArgumentException: timeout value is negative	
at net.rubygrapefruit.platform.internal.jni.AbstractFileEventFunctions$NativeFileWatcher.awaitTermination(AbstractFileEventFunctions.java:227)	
at org.gradle.internal.watch.registry.impl.DefaultFileWatcherRegistry.close(DefaultFileWatcherRegistry.java:198)	
at org.gradle.internal.watch.vfs.impl.WatchingVirtualFileSystem.stopWatchingAndInvalidateHierarchy(WatchingVirtualFileSystem.java:357)	
at org.gradle.internal.watch.vfs.impl.WatchingVirtualFileSystem.withWatcherChangeErrorHandling(WatchingVirtualFileSystem.java:323)	
at org.gradle.internal.watch.vfs.impl.WatchingVirtualFileSystem.withWatcherChangeErrorHandling(WatchingVirtualFileSystem.java:312)	
at org.gradle.internal.watch.vfs.impl.WatchingVirtualFileSystem.updateRootNotifyingWatchers(WatchingVirtualFileSystem.java:90)	
at org.gradle.internal.watch.vfs.impl.WatchingVirtualFileSystem.lambda$update$0(WatchingVirtualFileSystem.java:81)	
at org.gradle.internal.vfs.impl.VfsRootReference.update(VfsRootReference.java:40)	
at org.gradle.internal.watch.vfs.impl.WatchingVirtualFileSystem.update(WatchingVirtualFileSystem.java:81)	
at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.record(DefaultFileSystemAccess.java:190)	
at org.gradle.caching.internal.controller.impl.DefaultBuildCacheCommandFactory$LoadCommand.lambda$snapshotUnpackedData$2(DefaultBuildCacheCommandFactory.java:144)	
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.visitOutputTrees(ExecuteActionsTaskExecuter.java:345)	
at org.gradle.caching.internal.controller.impl.DefaultBuildCacheCommandFactory$LoadCommand.snapshotUnpackedData(DefaultBuildCacheCommandFactory.java:122)	
at org.gradle.caching.internal.controller.impl.DefaultBuildCacheCommandFactory$LoadCommand.load(DefaultBuildCacheCommandFactory.java:95)	
at org.gradle.caching.internal.controller.DefaultBuildCacheController$Unpack$1.run(DefaultBuildCacheController.java:149)	
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)	
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)	
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)	
at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)	
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)	
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)	
at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)	
at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)	
at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)	
at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)	
at org.gradle.caching.internal.controller.DefaultBuildCacheController$Unpack.execute(DefaultBuildCacheController.java:145)	
at org.gradle.caching.internal.controller.DefaultBuildCacheController$Unpack.execute(DefaultBuildCacheController.java:134)	
at org.gradle.caching.local.internal.DirectoryBuildCacheService.loadInsideLock(DirectoryBuildCacheService.java:117)	
at org.gradle.caching.local.internal.DirectoryBuildCacheService.access$200(DirectoryBuildCacheService.java:42)	
at org.gradle.caching.local.internal.DirectoryBuildCacheService$1.run(DirectoryBuildCacheService.java:99)	
at org.gradle.internal.Factories$1.create(Factories.java:26)	
at org.gradle.cache.internal.LockOnDemandCrossProcessCacheAccess.withFileLock(LockOnDemandCrossProcessCacheAccess.java:90)	
at org.gradle.cache.internal.DefaultCacheAccess.withFileLock(DefaultCacheAccess.java:196)	
at org.gradle.cache.internal.DefaultPersistentDirectoryStore.withFileLock(DefaultPersistentDirectoryStore.java:182)	
at org.gradle.cache.internal.DefaultCacheFactory$ReferenceTrackingCache.withFileLock(DefaultCacheFactory.java:206)	
at org.gradle.caching.local.internal.DirectoryBuildCacheService.loadLocally(DirectoryBuildCacheService.java:94)	
at org.gradle.caching.internal.controller.service.DefaultLocalBuildCacheServiceHandle.load(DefaultLocalBuildCacheServiceHandle.java:49)	
at org.gradle.caching.internal.controller.DefaultBuildCacheController.load(DefaultBuildCacheController.java:100)	
at org.gradle.internal.execution.steps.CacheStep.lambda$executeWithCache$0(CacheStep.java:78)	
at org.gradle.internal.Try.ofFailable(Try.java:39)	
```

Changed files:
file-events/src/main/java/net/rubygrapefruit/platform/internal/jni/AbstractFileEventFunctions.java

diff --git a/file-events/src/main/java/net/rubygrapefruit/platform/internal/jni/AbstractFileEventFunctions.java b/file-events/src/main/java/net/rubygrapefruit/platform/internal/jni/AbstractFileEventFunctions.java
index b777c80..7d1a10b 100644
--- a/file-events/src/main/java/net/rubygrapefruit/platform/internal/jni/AbstractFileEventFunctions.java
+++ b/file-events/src/main/java/net/rubygrapefruit/platform/internal/jni/AbstractFileEventFunctions.java
@@ -224,7 +224,9 @@ public abstract class AbstractFileEventFunctions implements NativeIntegration {
             if (successful) {
                 long endTime = System.currentTimeMillis();
                 long remainingTimeout = timeoutInMillis - (endTime - startTime);
-                processorThread.join(remainingTimeout);
+                if (remainingTimeout > 0) {
+                    processorThread.join(remainingTimeout);
+                }
                 return !processorThread.isAlive();
             } else {
                 return false;