Jenkins / Persistent state and controlled delivery / Lesson 16
Job concurrency, resource locks, and stale work
Coordinate the actual shared resource while independently checking candidate freshness.
On this page
Lesson goals
- Distinguish per-job serialization from cross-job resource coordination.
- Explain why a lock cannot by itself prevent an older candidate from adopting later.
Per-job concurrency has a limited scope
Declarative disableConcurrentBuilds can serialize runs of the same Pipeline job under its documented options. That does not universally coordinate every job targeting one shared environment. Parcel’s release job and a separate maintenance job may both affect the same target despite having different names. First identify the shared resource and all writers, then select an appropriate coordination mechanism. Aborting a previous run is another policy with external-state implications; it is not equivalent to patiently queuing every required transition or undoing work already accepted elsewhere.
A resource lock coordinates a named boundary
The Lockable Resources plugin provides lock steps under its installed configuration and permissions. A resource name should represent the actual shared target or capability, not a unique run identifier that never collides. Avoid a global lock that blocks unrelated environments unnecessarily. The fragment below is illustrative and only prints a label inside a hypothetical lock. No plugin is installed, resource configured, lock acquired, or target touched. Resource coordination also needs a documented owner and behavior for cancellation, abandoned work, and unavailable resources.
lock(resource: "parcel-staging-lab") {
echo "A real operation would still require candidate and target checks"
}
The resource and plugin are synthetic prerequisites. This fragment is not a deployment or a complete Pipeline and establishes no actual mutual exclusion. A lock protects only writers that correctly participate in the same coordination contract.
Mutual exclusion does not determine release order
Candidate r8 may finish testing before slower r7. If r8 adopts first and r7 later obtains the lock, serialization alone can regress the target. Recheck eligibility after waiting against authoritative state or a reviewed ordering policy. The Milestone plugin can help discard older runs at designated points under its specific same-job semantics, but it is not a cross-system transaction or a substitute for target-side generation checks. Required migration sequences may need every transition, while replaceable stateless candidates may permit supersession. State which model applies.
Make freshness and duplicate intent visible
The complete local model rejects a declared older generation and recognizes a repeated operation identity. It uses sequential in-memory state, not concurrent transactions or a Jenkins plugin. A real implementation must make relevant checks and updates atomic under its target’s guarantees and account for crashes or fencing. Do not infer chronology by sorting labels such as r9 and r10 as text. The numeric generation here is an explicitly declared ordering input, not a universal interpretation of a Jenkins build number across different jobs.
current = 8
seen = set()
def request(identity, generation):
global current
if identity in seen:
return "duplicate"
if generation < current:
return "stale"
current = generation
seen.add(identity)
return "accepted"
print(request("lab-one", 7))
print(request("lab-two", 9))
print(request("lab-two", 9))
stale
accepted
duplicate
The program neither acquires a Jenkins lock nor implements distributed idempotency. It illustrates declared invariants using synthetic generations and request names.
Coordinate cleanup and uncertain outcomes too
If a lock-protected job loses its response after submission, the external operation may continue after the Pipeline stops. Observe the original request before retrying or releasing application-level ownership assumptions. Cleanup must not remove a shared resource now used by another run. Keep submission, convergence, verification, and reporting separate so a report failure does not trigger another adoption. Retain the resolved resource identity and candidate with the decision record. A concurrency setting is useful only when every participating path respects its scope and recovery contract.
Common errors and fixes
disableConcurrentBuilds is assumed to lock an environment across unrelated jobs.
- Why it happens
- Job-local serialization was mistaken for resource-wide coordination.
- How to fix it
- Identify every writer and choose a shared reviewed resource contract.
- Prevent it next time
- Document coordination scope and ownership explicitly.
A newer candidate is overwritten by an older waiting run.
- Why it happens
- The lock prevents overlap but does not establish freshness.
- How to fix it
- Recheck eligibility after waiting using authoritative ordering state.
- Prevent it next time
- Review supersession and required-transition policies separately.
A unique build identifier is used as the resource-lock name.
- Why it happens
- Every writer requests a different resource and never coordinates.
- How to fix it
- Use the actual shared target identity as the boundary.
- Prevent it next time
- Test intended collisions and unrelated parallel cases in design review.
Check your understanding
1. Two jobs target the same environment but each disables its own concurrent builds. Is cross-job overlap prevented?
Show worked solution
Not by that setting alone. Each job serializes its own runs. A shared resource or target-side coordination mechanism must cover all relevant writers, with appropriate authority and recovery behavior.
2. Can a milestone guarantee safety for an external migration after submission?
Show worked solution
No. Its documented run-order behavior does not undo accepted external work or guarantee state compatibility. Preserve request identity, observe the target, and follow the migration’s ordered transition and recovery contract.
Key takeaways
- Coordinate all writers of the actual resource.
- Check overlap, freshness, duplicate intent, and recovery independently.