Lock expiration with per-script override - #308
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces expiring execution locks (with configurable TTL and per-script override) and adds a new LOCKED execution status, surfacing lock contention/stale-lock situations across the backend, UI, and documentation.
Changes:
- Add
LOCKEDexecution status and propagate it through queueing/execution logic and frontend rendering. - Implement lock TTL/expiration in the repository locker, plus a new “Lock Timeout” OSGi configuration and per-execution overrides.
- Document lock timeout semantics and add UI help text/toasts and history filtering for locked executions.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ui.frontend/src/types/execution.ts | Adds LOCKED to the frontend execution status enum. |
| ui.frontend/src/pages/HistoryPage.tsx | Adds “Locked” status filter item with icon in History. |
| ui.frontend/src/hooks/execution.ts | Adds a toast message for LOCKED terminal status. |
| ui.frontend/src/components/ScriptsAutomaticHelpButton.tsx | Documents lock behavior and expiration in the UI help dialog. |
| ui.frontend/src/components/ExecutionStatusBadge.tsx | Adds LOCKED icon + badge variant handling. |
| ui.frontend/src/components/ExecutionProgressBar.tsx | Treats LOCKED as a warning variant for completed display. |
| README.md | Adds end-user documentation for lock timeout configuration and overrides. |
| core/src/main/java/dev/vml/es/acm/core/util/DurationUtils.java | Introduces duration parsing utility for TTL inputs. |
| core/src/main/java/dev/vml/es/acm/core/repo/LockInfo.java | Adds a value object for lock timestamps. |
| core/src/main/java/dev/vml/es/acm/core/repo/Locker.java | Adds TTL support and lock expiration checks. |
| core/src/main/java/dev/vml/es/acm/core/code/Executor.java | Uses expiring locks, logs lock info, and returns LOCKED when blocked. |
| core/src/main/java/dev/vml/es/acm/core/code/ExecutionStatus.java | Adds backend LOCKED execution status. |
| core/src/main/java/dev/vml/es/acm/core/code/ExecutionQueue.java | Propagates LOCKED back to the job result message and cancellation behavior. |
| core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java | Adds lockTimeout to the execution context for script/extension overrides. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… into lock-expiration-auto-recovery
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
core/src/main/java/dev/vml/es/acm/core/code/Executor.java:114
- The new OSGi config description says only a negative value disables lock expiration, but the implementation treats any non-positive TTL as “no expiration” (
ttl.isNegative() || ttl.isZero()inLocker.lock(...)), and the README also documents0as disabling expiration. Update the description to avoid confusing operators who set the value to0.
@AttributeDefinition(
name = "Lock Timeout",
description =
"Expiration time (in milliseconds) of executable locks, protecting against stale locks left behind when an instance is killed abruptly (e.g. pod recycling). Must exceed the max expected execution time. Use a negative value to disable expiration.")
long lockTimeout() default 24L * 60L * 60L * 1000L;
ui.frontend/src/hooks/execution.ts:46
- The LOCKED toast says “already running or stale”, but the backend returns
LOCKEDonly when a non-expired lock is present (Locker.readLock(...)usesisLock(...) && !isExpired(...)). If the lock is stale and has an expiration, it should be removed and the run proceeds, so “stale” is misleading here.
ToastQueue.neutral('Code execution skipped — conditions not met.', { timeout: ToastTimeoutQuick });
} else if (queuedExecution.status === ExecutionStatus.LOCKED) {
ToastQueue.neutral('Code execution locked — already running or stale.', { timeout: ToastTimeoutQuick });
} else if (queuedExecution.status === ExecutionStatus.SUCCEEDED) {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
core/src/main/java/dev/vml/es/acm/core/repo/Locker.java:83
lock()checks only whether a resource exists at the lock path, butExecutordetermines locked state viareadLock()/isLockPresent(). If a non-lock resource exists at that path (e.g. missinglockedproperty),Executorwill proceed, whilelock()will return early and the execution will run without any lock protection. Consider handling only real locks viaisLockPresent(...)and failing fast (or explicitly cleaning up) when an unexpected resource exists.
Resource lockCurrent = getLock(name);
if (lockCurrent != null) {
if (readLock(lockCurrent).isExpired()) {
LOG.warn("Cannot create lock '{}' as it is stale - removing.", name);
resolver.delete(lockCurrent);
ui.frontend/src/hooks/execution.ts:45
- The
LOCKEDstatus indicates a lock is held, but it doesn’t necessarily mean the script is currently running (it could be a stale lock awaiting expiration). The toast message “already running” can be misleading; consider wording it in terms of the lock being held.
ToastQueue.neutral('Code execution locked — already running.', { timeout: ToastTimeoutQuick });
No description provided.