Class RunRequestService
RunRequest state transition (SPEC items 3, 5, 7;
D-20/D-21/D-22/D-23). No other class may change a request's status.
Concurrency (D-20): every load→validate→transition→persist sequence runs under one
ReentrantLock, which makes each transition an effective compare-and-set: concurrent
approve/approve or approve/cancel calls serialize, the loser sees the already-changed status
and is refused. The lock is never held across Queue operations (queue submission and
queue inspection happen outside it) to avoid lock-order inversion with the queue gate, which
calls back into consumeMarker(java.lang.String, java.lang.String) while Jenkins holds the queue lock.
Failure families: IllegalArgumentException for input validation,
IllegalStateException for wrong-state transitions, AccessDeniedException
(403 on the web layer) for authorization refusals.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringD-38a: the sentence the request detail page and the approver notification show when the requester does not holdItem/Buildon the request's job. -
Method Summary
Modifier and TypeMethodDescriptionEnds, as INVALIDATED, every request that missed a D-21 invalidation (invalidateForJob(java.lang.String, java.lang.String)) and can now be read and written, when the invalidation applies to the job it names; forgets the ones it does not apply to or that are no longer open.Approves a PENDING request and submits the build.booleancanCancel(RunRequest request) D-38b: whether the current user may cancelrequest: its requester holdingBatchControl/Requeston the job, or a Manage holder.Cancels a PENDING request; requester or a Manage holder only (SPEC 7).booleancanChangeApprovers(RunRequest request) D-38b: whether the current user may change the approvers ofrequest: its requester holdingBatchControl/Requeston the job.booleancanRequest(Job<?, ?> job) Whether the current user could submit a run request forjob(e2e-03 DEF-12, SPEC section 6 usability):BatchControl/RequestandItem/Readon the job (D-38a), the permission checks ofcreate(hudson.model.Job<?, ?>, java.util.List<hudson.model.ParameterValue>, java.lang.String, java.lang.String).changeApprover(String id, String newApprover) Single-approver form ofchangeApprovers(String, List).changeApprovers(String id, List<String> newApprovers) Replaces the designated approver set of a PENDING request; requester only (SPEC item 3, D-26, D-37).booleanconsumeMarker(String requestId, String jobFullName) Atomically claims the single consumption ticket of an approved request (D-23), including the D-20 check-at-submit expiry re-check.countPendingFor(org.springframework.security.core.Authentication auth) D-61: the PENDING run requests that concernauth, read from the open-request index only (no history scan): those awaiting their decision as a designated approver holding Jenkins-levelBatchControl/Approve, else their own.Single-approver form ofcreate(Job, List, String, List, String), without an incident.create(Job<?, ?> job, List<ParameterValue> values, String reason, List<String> approvers, String incidentId) Creates a PENDING run request holding the submitted typed values (D-72), linked to an incident whenincidentIdis given (SPEC item 11: the run listener links a successful rerun back to the incident).create(Job<?, ?> job, List<ParameterValue> values, Map<String, Long> uploaded, String reason, List<String> approvers, String incidentId) Ascreate(Job, List, String, List, String)for a web submission:uploadedmaps a parameter name to the size of the uploaded parts its value was created from (RequestBodyLimit.uploadedSize(org.kohsuke.stapler.StaplerRequest2, java.util.Collection<java.lang.String>)), which the kept-size check counts for a value whose own size cannot be read (D-74 (2)).Creates a PENDING run request from string values, designating a single approver: each value becomes a typed value through the job's parameter definition (SimpleParameterDefinition.createValue(String)), or aStringParameterValuewhere the job defines no such simple parameter, and the request is created as bycreate(Job, List, String, List, String).voidexpireOverdue(Set<String> queuedRequestIds, Instant queueSnapshotAt) Expires overdue requests: PENDING pastpendingTimeoutHoursand APPROVED, never-executed requests pastapprovedRunTimeoutMinuteswhose submission is not sitting in the queue right now.static RunRequestServiceget()booleanWhether requestidmissed a D-21 invalidation because its file could not be read or written (invalidateForJob(java.lang.String, java.lang.String)); its queued run, if any, is cancelled like that of an invalidated request.booleanWhether any request missed a D-21 invalidation (hasMissedInvalidation(java.lang.String)).booleanhasOwnRequests(org.springframework.security.core.Authentication auth) D-38b: whetherauthfiled at least one run request, in any status.invalidateForDeletedItem(String fullName, boolean withDescendants) #38: an item was deleted.invalidateForJob(String oldFullName, String reason) Invalidates every PENDING/APPROVED request that targets the given (old) job full name after a rename or move (D-21).list()All stored requests, in creation order.Loads a request by id, ornull.voidmarkExecuted(String requestId, String runId) Marks an APPROVED request as EXECUTED once its build has started (SPEC section 4).voidD-36: sendsNotificationEvent.EXPIRINGonce for every PENDING request whose pending timeout falls withinnotifyBeforeExpiryMinutesfrom now.queuedMarkerRequestIds(Jenkins jenkins) Request ids whose approval marker is currently sitting on a queue item, whoever asks.voidrecordQueueCancelled(String requestId, String jobFullName) D-72b (7), security-35 S-35-07: the queue item of the approved run ofrequestIdfor jobjobFullNamewas cancelled, so that run will not happen.voidRe-submits APPROVED, never-executed requests exactly once after a restart (SPEC item 4).Rejects a PENDING request; the comment is mandatory (SPEC 5).booleanrequesterLacksBuild(RunRequest request) D-38a (SPEC item 6): whether the requester ofrequest(not the current user) lacksItem/Buildon the request's job, so an approval also authorises a run the requester could not start.static booleanrequiresApprovalToRun(Job<?, ?> job) Whether a person's Build Now, Rebuild, Retry or Replay ofjobis refused by the queue gate and needs an approved run request instead (SPEC item 6): run control is on and the job requires approval.voidWrites the EXECUTED states that could not be written when their build started (markExecuted(java.lang.String, java.lang.String)), with the deletion of the typed values file that goes with them.voidT-GAP-384, D-72b (7): writes the queue cancellations that could not be written when they happened (recordQueueCancelled(java.lang.String, java.lang.String)), with the deletion of the typed values file that goes with them.
-
Field Details
-
REQUESTER_LACKS_BUILD_NOTICE
D-38a: the sentence the request detail page and the approver notification show when the requester does not holdItem/Buildon the request's job.- See Also:
-
-
Method Details
-
get
-
load
Loads a request by id, ornull. Its typed values stay in their own file (D-74); a screen, listing or listener never needs them. -
list
All stored requests, in creation order. -
countPendingFor
D-61: the PENDING run requests that concernauth, read from the open-request index only (no history scan): those awaiting their decision as a designated approver holding Jenkins-levelBatchControl/Approve, else their own. Every counted request is visible toauthunder P-09. The single source for the tab badge and the section. -
hasOwnRequests
public boolean hasOwnRequests(org.springframework.security.core.Authentication auth) D-38b: whetherauthfiled at least one run request, in any status. The requester always sees their own requests (P-09), so this tells the web layer that a user without Jenkins-levelBatchControl/Requeststill has something to see in the run requests section. Answered from the store's in-memory index; no item is visited. -
canCancel
D-38b: whether the current user may cancelrequest: its requester holdingBatchControl/Requeston the job, or a Manage holder. Permission only; the PENDING status is checked bycancel(String). -
canChangeApprovers
D-38b: whether the current user may change the approvers ofrequest: its requester holdingBatchControl/Requeston the job. Permission only. -
create
Single-approver form ofcreate(Job, List, String, List, String), without an incident. -
canRequest
Whether the current user could submit a run request forjob(e2e-03 DEF-12, SPEC section 6 usability):BatchControl/RequestandItem/Readon the job (D-38a), the permission checks ofcreate(hudson.model.Job<?, ?>, java.util.List<hudson.model.ParameterValue>, java.lang.String, java.lang.String).Item/Buildis not required (D-38a). Screens use it to show the Request Run entry and form only to such a user;create(hudson.model.Job<?, ?>, java.util.List<hudson.model.ParameterValue>, java.lang.String, java.lang.String)still checks for real. -
requesterLacksBuild
D-38a (SPEC item 6): whether the requester ofrequest(not the current user) lacksItem/Buildon the request's job, so an approval also authorises a run the requester could not start.truealso when that cannot be confirmed because the requester's account no longer resolves;falsewhen the requester holds it, or when the job does not exist or is not visible to the current user (who then cannot see the request's job either). Read only. Evaluated as the current user, with no switch toACL.SYSTEM2: the job is looked up with the caller's own Read, and the requester's permission is asked of the job's ACL with the requester's impersonated authentication, which needs no elevation. -
requiresApprovalToRun
Whether a person's Build Now, Rebuild, Retry or Replay ofjobis refused by the queue gate and needs an approved run request instead (SPEC item 6): run control is on and the job requires approval. Screens use it to replace or hide those entries (e2e-03 DEF-25). -
create
public RunRequest create(Job<?, ?> job, Map<String, String> parameters, String reason, String approver) Creates a PENDING run request from string values, designating a single approver: each value becomes a typed value through the job's parameter definition (SimpleParameterDefinition.createValue(String)), or aStringParameterValuewhere the job defines no such simple parameter, and the request is created as bycreate(Job, List, String, List, String). A value the definition refuses (a choice outside its choices) is anIllegalArgumentException. The requester is the current authentication. -
create
public RunRequest create(Job<?, ?> job, List<ParameterValue> values, String reason, List<String> approvers, String incidentId) Creates a PENDING run request holding the submitted typed values (D-72), linked to an incident whenincidentIdis given (SPEC item 11: the run listener links a successful rerun back to the incident). The requester is the current authentication; every designated approver must pass the SPEC item 3 checks (D-37); validation failures throwIllegalArgumentException. The masked display map is derived from the values here, once. The approved build is scheduled with the values unchanged.D-72b (1)(2), security-35 S-35-01/03: before anything is stored, each parameter name may occur once, every value has a name, every stored text (a secret's plaintext included) and every display text obeys the 10,000-character limit, and names, texts and the reason hold only characters XML 1.0 can store (
XmlChars); otherwiseIllegalArgumentExceptionwith a message naming the parameter (or the reason), so the form can show it next to the field.SPEC item 11, D-72a: a linked request names an existing incident of
job(elseIllegalArgumentException, nothing stored) and is listed among the incident's rerun requests once stored, whoever created it: the incident rerun, or the Request Run form afterIncidentService.linkableIncident(java.lang.String, hudson.model.Job<?, ?>)validated the reference it carried. A successful run of the request then recordsresolvedByRunIdon the incident.The request takes over the temporary files of the values' file parameters: when the request is refused (permission, reason, size, approvers) they are disposed of before the exception propagates (
ParameterFiles).D-82: right after the permission checks, a request for a job's sub-item (a matrix configuration, a Maven module) is refused with
IllegalArgumentExceptionnaming the parent job to request instead; its files are disposed of and nothing is stored.D-74 (2): right after the permission checks, a request whose values would keep more than the body cap (
RequestBodyLimit.keptSize(java.util.List<? extends hudson.model.ParameterValue>, java.util.Map<java.lang.String, java.lang.Long>)) is refused withRequestTooLargeException(anIllegalArgumentException), its files disposed of and nothing stored. This holds for every overload and for an incident rerun, which all end here; a caller without uploaded parts passes none. -
create
public RunRequest create(Job<?, ?> job, List<ParameterValue> values, Map<String, Long> uploaded, String reason, List<String> approvers, String incidentId) Ascreate(Job, List, String, List, String)for a web submission:uploadedmaps a parameter name to the size of the uploaded parts its value was created from (RequestBodyLimit.uploadedSize(org.kohsuke.stapler.StaplerRequest2, java.util.Collection<java.lang.String>)), which the kept-size check counts for a value whose own size cannot be read (D-74 (2)). -
approve
Approves a PENDING request and submits the build. The caller must be the designated approver holding the Approve permission (or the admin self-approval path). The status is committed to APPROVED before submission and is never rolled back by a submission failure (quiet-down tolerance); startup recovery and expiry handle stragglers.D-72b (1): the stored typed values are read and checked (
valuesProblem(io.jenkins.plugins.batchcontrol.model.RunRequest, java.util.List<hudson.model.ParameterValue>)) before the APPROVED commit; a request whose values do not match what the approver saw, or whose values file is missing although it has parameters, is refused withIllegalStateExceptionand stays PENDING (it can still be rejected), and nothing is scheduled. -
reject
Rejects a PENDING request; the comment is mandatory (SPEC 5). -
cancel
Cancels a PENDING request; requester or a Manage holder only (SPEC 7). -
changeApprover
Single-approver form ofchangeApprovers(String, List). -
changeApprovers
Replaces the designated approver set of a PENDING request; requester only (SPEC item 3, D-26, D-37). Every new member must pass the designation checks; the change is recorded as (previous set, new set, changed by, time). -
consumeMarker
Atomically claims the single consumption ticket of an approved request (D-23), including the D-20 check-at-submit expiry re-check. Called by the queue gate while Jenkins holds the queue lock; must therefore never be called while holding this service's lock on another thread path that also takes the queue lock.A refusal that is a genuine re-use of a marker the plugin once minted also lands in the audit history as
ChangeRecord(MARKER_REUSE_BLOCKED)(D-30), so the attempt is visible on the history screen and inchanges.csvand not only in the log. Two refusals qualify: the ticket is already spent (re-queue, rebuild, replay of the executed build), and the marker is presented on a job it was not issued for. The three remaining refusals are not re-use of a granted authorization and stay log-only: an unknown request id (nothing was ever approved under it), a request that was never approved or is no longer approved, and an approval that timed out before submission (D-20) — that one is the request's own EXPIRED transition, not an actor's attempt.- Returns:
trueif the submission is authorized (ticket claimed just now)
-
markExecuted
Marks an APPROVED request as EXECUTED once its build has started (SPEC section 4). D-72b (5): the typed values file is deleted now; the build has its own copy and owns the files, and the masked display map stays as the record.The build has started whatever happens here, so a request file that cannot be written does not undo the transition: it stays in effect in memory (
unsavedExecutions) and is written by the periodic work as soon as the store accepts it (retryUnsavedExecutions()). Never throws for that (the caller is a run listener on the build's own thread). -
retryUnsavedExecutions
public void retryUnsavedExecutions()Writes the EXECUTED states that could not be written when their build started (markExecuted(java.lang.String, java.lang.String)), with the deletion of the typed values file that goes with them. One that still fails stays for the next attempt; one whose request has no file any more, or whose file is no longer an APPROVED request waiting for its run, is dropped. Called by the periodic work; never throws. -
expireOverdue
Expires overdue requests: PENDING pastpendingTimeoutHoursand APPROVED, never-executed requests pastapprovedRunTimeoutMinuteswhose submission is not sitting in the queue right now.Boundary-window hardening (spec-review-S2 MINOR 1): the queue snapshot is taken outside this service's lock (lock-order discipline with the queue gate), so a marker consumed between the snapshot and the per-request lock acquisition is missing from the snapshot even though the request WAS submitted in time. The consumption ticket (
queuedAt), re-read here under the lock, closes that window: a ticket claimed at or after the snapshot instant proves the snapshot is stale for this request, so it is skipped this cycle (the next cycle sees it in the queue, executed, or genuinely gone).- Parameters:
queuedRequestIds- ids of requests that currently have a queue item carrying their approval marker (collected by the caller outside this service's lock)queueSnapshotAt- the instant just before the caller collected the snapshot
-
notifyExpiring
public void notifyExpiring()D-36: sendsNotificationEvent.EXPIRINGonce for every PENDING request whose pending timeout falls withinnotifyBeforeExpiryMinutesfrom now. The "notified" flag is persisted before dispatch, so a restart never resends. Expiry itself stays the clock comparison ofexpireOverdue(java.util.Set<java.lang.String>, java.time.Instant). Each request is handled on its own: one that cannot be read or written is logged and tried again on the next run, and never keeps the others from their notice. -
invalidateForJob
Invalidates every PENDING/APPROVED request that targets the given (old) job full name after a rename or move (D-21).Fail safe: an open request whose file cannot be read now (which job it names is unknown), or whose end cannot be written, is not ended here; the invalidation is remembered (
MissedInvalidations), so the request cannot be approved, and it is ended INVALIDATED once it can be read and written, if it names this job (applyMissedInvalidations()). One such request never keeps the others from being invalidated.- Returns:
- the ids of the requests that were invalidated
-
invalidateForDeletedItem
#38: an item was deleted. Every PENDING/APPROVED request on it ends INVALIDATED, and, withwithDescendants(a deleted folder or any other item group), so does every such request on an item below it, so that no request outlives its job and a job later created under the same name never runs a request that was about the deleted one. The same rule as for a rename or move (D-21) and as for the activation requests of a deleted job (SPEC 6a), with the same fail-safe handling of a request whose file cannot be read or written now (invalidateForJob(java.lang.String, java.lang.String)): it cannot be approved, and it ends INVALIDATED as soon as it can be read and written.- Returns:
- the ids of the requests that were invalidated
-
hasMissedInvalidation
Whether requestidmissed a D-21 invalidation because its file could not be read or written (invalidateForJob(java.lang.String, java.lang.String)); its queued run, if any, is cancelled like that of an invalidated request. -
hasMissedInvalidations
public boolean hasMissedInvalidations()Whether any request missed a D-21 invalidation (hasMissedInvalidation(java.lang.String)). -
applyMissedInvalidations
Ends, as INVALIDATED, every request that missed a D-21 invalidation (invalidateForJob(java.lang.String, java.lang.String)) and can now be read and written, when the invalidation applies to the job it names; forgets the ones it does not apply to or that are no longer open. Run by the periodic work every minute; a request that still cannot be read stays for the next run.- Returns:
- the ids of the requests ended now, whose queued runs the caller cancels
-
recoverApprovedRequests
public void recoverApprovedRequests()Re-submits APPROVED, never-executed requests exactly once after a restart (SPEC item 4). Idempotency (T-RT-17): a request whose marker is already sitting in the restored queue, or whose build already exists, is skipped; otherwise its consumption ticket is re-issued and the request is submitted again. D-72b (7), security-35 S-35-07: a request whose queue item was cancelled (RunRequest.getQueueCancelledAt()) is never submitted again; the approved-run timeout ends it and its files are disposed of then. The approved-run timeout is judged from the recovery moment (SPEC item 7 exception), implemented by moving the request's expiry base forward.Ordering with the core queue restore:
Queue.init(which restoresqueue.xml) runs in the sameJOB_CONFIG_ADAPTEDinitializer band asStartupRecovery, in undefined order, andQueue.load()clears the live queue before reading. The whole recovery therefore runs under the queue lock: ifqueue.xmlis still present the persisted queue is loaded here first (core's laterload()re-reads the state re-saved below), so the recovery dedup always sees the restored queue items. -
queuedMarkerRequestIds
Request ids whose approval marker is currently sitting on a queue item, whoever asks.ACL.SYSTEM2 switch, for reading the queue only (R2-04):
Queue.getItems()lists only the items the current user can read, and this answer decides whether an ending request's parameter files are still the queue's (a queued item disposes of its own files when it is cancelled) or are disposed of now; reading the queue as, for example, a user who just renamed a job they can no longer read would dispose of the files of a run that is still queued. No permission is exercised for the caller: the result is a set of request ids that is never shown to anyone, and every caller has completed its own permission checks before it ends a request. -
recordQueueCancelled
D-72b (7), security-35 S-35-07: the queue item of the approved run ofrequestIdfor jobjobFullNamewas cancelled, so that run will not happen. The cancellation is recorded on the APPROVED request: startup recovery never submits it again (its ticket stays claimed, so the queue gate refuses any other submission of it too), and the approved-run timeout ends it. The cancelled item's own parameter types have already deleted the temporary files it carried (core's and the file-parameters plugin's cancelled-item listeners, seeParameterFiles); the typed values file, which nothing can use any more, is deleted now (D-72b (5)). Called by the queue listener, as SYSTEM, while Jenkins holds the queue lock (lock order as forconsumeMarker(java.lang.String, java.lang.String)). Never throws.T-GAP-384: when the request file cannot be written, the mark stays in effect in memory (
unsavedQueueCancels) and is written by the periodic work as soon as the store accepts it (retryUnsavedQueueCancels()), so a restart after that does not submit the run again either (LIMITATIONS 32). -
retryUnsavedQueueCancels
public void retryUnsavedQueueCancels()T-GAP-384, D-72b (7): writes the queue cancellations that could not be written when they happened (recordQueueCancelled(java.lang.String, java.lang.String)), with the deletion of the typed values file that goes with them. One that still fails stays for the next attempt; one whose request has no file any more, or whose file already carries a cancellation, is dropped. Called by the periodic work; never throws.
-