'Gradle Daemon stopped working in parallel due to file lock timeouts
Yesterday all of the sudden my projects on a Windows 10 machine stopped running in parallel due to file lock timeouts.
All my projects are using gradle-wrapper and provide a run task
When I start the 1st run-task, it work normally, but any following run-tasks break with the error like this:
> .\gradlew run
Starting a Gradle Daemon, 1 busy and 4 stopped Daemons could not be reused, use --status for details
FAILURE: Build failed with an exception.
* What went wrong:
Gradle could not start your build.
> Could not create service of type FileAccessTimeJournal using GradleUserHomeScopeServices.createFileAccessTimeJournal().
> Timeout waiting to lock journal cache (C:\Users\injec\.gradle\caches\journal-1). It is currently in use by another Gradle instance.
Owner PID: 16440
Our PID: 12216
Owner Operation:
Our operation:
Lock file: C:\Users\injec\.gradle\caches\journal-1\journal-1.lock
the --status option shows:
> .\gradlew --status
PID STATUS INFO
12216 IDLE 6.9.1
16440 BUSY 6.9.1
14992 STOPPED (stop command received)
7856 STOPPED (other compatible daemons were started and after being idle for 0 minutes and not recently used)
26680 STOPPED (by user or operating system)
18556 STOPPED (by user or operating system)
I tried different tricks, like switching the Gradle verison 5.6.1 - 6.8.3 - 6.9.1 and using the --stop option, but the error remains.
Adding the --stacktrace to the run command reveals that not only journal-1 cache is involved, but also some others dirs like modules-2.
I didn't do any changes to my system, apart from regular Win10 updates.
How can the problem be fixed?
TIA
Solution 1:[1]
It's likely that gradle process was exited abnormally and left the lock file behind. Check in the task manager if process with id 16440 exists, and if not just remove the orphan lock file C:\Users\injec\.gradle\caches\journal-1\journal-1.lock
Solution 2:[2]
This may be the file-system permissions of C:\Users\injec\.gradle... while you may have overseen one detail: you're calling .\gradlew instead of ./gradlew or gradlew.bat ...which means that you are not running on CMD, but on PS or WSL. gradlew.bat run would run directly on CMD.
Solution 3:[3]
As I said in my comments, the code you have in your question does not provide a cancel() function that the caller can use. It has a cancel() function internally, but that's not something the caller can use. As written that function just returns a promise so the caller has nothing they can call to cancel the retries.
So, I decided to write my own version of fetchWithRetry() that would work for your use case. This has a number of capabilities that the one in your question does not:
- It returns both the promise and a cancel function so the caller can cancel the retries.
- It allows you to pass the
initoptions forfetch()so you can pass any of the various arguments thatfetch()supports and are often needed such aswithCredentials. - It has an option to check the
response.okboolean so it will detect and retry more things that you would if you required the promise to be rejected before a retry (note:fetch()doesn't reject on a 404, for example). - If There was a
fetch()rejection and it was either cancelled or it ran out of retries, then it will use the newestErrorclass feature where it will set thecauseto the actualfetch()error so the caller can see what the original error was.
Note that this version of fetchWithRetry() returns an object containing both a promise and a cancel function. The caller uses the promise the same way they would any promise from fetch() and they can use the cancel() function to cancel any further retries.
Here's the code:
const Deferred = function() {
if (!(this instanceof Deferred)) {
return new Deferred();
}
const p = this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
this.then = p.then.bind(p);
this.catch = p.catch.bind(p);
if (p.finally) {
this.finally = p.finally.bind(p);
}
}
function fetchWithRetry(url, userOptions = {}, init = {}) {
const options = {
// default options values, can be overridden by userOptions
retries: 3,
retryDelay: 1000,
checkResponseOk: true,
...userOptions
};
let cancelled = false;
let timerDeferred;
let timer;
function run() {
return fetch(url, init).then(response => {
// force retry on non 2xx responses too
if (options.checkResponseOk && !response.ok) {
throw new Error(`fetch failed with status ${response.status}`);
}
return response;
}).catch(err => {
// got error, set up retry
console.log(err);
if (cancelled) {
throw new Error("fetch cancelled", { cause: err });
}
--options.retries;
if (options.retries < 0) {
throw new Error("fetch max retries exceeded", { cause: err });
}
// create new Deferred object for use with our timer
// so it can be resolved by the timer or rejected
// by the cancel callback
timerDeferred = new Deferred();
timer = setTimeout(() => {
timerDeferred.resolve();
timer = null;
}, options.retryDelay);
return timerDeferred.then(() => {
if (cancelled) {
throw new Error("fetch cancelled", { cause: err });
}
return run();
});
});
}
return {
promise: run(),
cancel: () => {
cancelled = true;
// if currently in a timer waiting, reject immediately
if (timer) {
clearTimeout(timer);
timer = null;
}
if (timerDeferred) {
timerDeferred.reject(new Error("fetch cancelled"));
}
}
}
};
Sample usage:
const result = fetchWithRetry(someUrl);
result.promise.then(resp => {
return resp.text().then(data => {
// got final result here
console.log(data.slice(0, 100));
});
}).catch(err => {
console.log(err);
});
// simulate user cancel after 1.5 seconds
setTimeout(() => {
result.cancel();
}, 1500);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | kofemann |
| Solution 2 | |
| Solution 3 | jfriend00 |
