Conversation
This was referenced Aug 23, 2026
rantoniuk
added a commit
to rantoniuk/jira-plugin
that referenced
this pull request
Aug 31, 2026
Adds open-PR links to the rows they address, matched against each PR's actual diff/description rather than title alone: jenkinsci#1193 (draft, closes jenkinsci#1178), jenkinsci#1199/jenkinsci#1200 (HTTP stack and JiraSite fixes, jenkinsci#1200 alone covers five separate rows across epics 2-3), jenkinsci#1201, jenkinsci#1202, jenkinsci#1205, jenkinsci#1207 (closes jenkinsci#677), and jenkinsci#1190 (in-progress docs work). Adds one new epic 3 row for a correctness bug (jenkinsci#1208) that had no prior row to attach to. Excludes the Renovate dependency-bump PR (jenkinsci#1211) and the two already-textually-referenced older PRs (jenkinsci#562, jenkinsci#746) as out of scope. Also fixes the sidebar's "Roadmap" link, which pointed at a nonexistent roadmap.md instead of this file.
rantoniuk
added a commit
to rantoniuk/jira-plugin
that referenced
this pull request
Aug 31, 2026
Adds open-PR links to the rows they address, matched against each PR's actual diff/description rather than title alone: jenkinsci#1193 (draft, closes jenkinsci#1178), jenkinsci#1199/jenkinsci#1200 (HTTP stack and JiraSite fixes, jenkinsci#1200 alone covers five separate rows across epics 2-3), jenkinsci#1201, jenkinsci#1202, jenkinsci#1205, jenkinsci#1207 (closes jenkinsci#677), and jenkinsci#1190 (in-progress docs work). Adds one new epic 3 row for a correctness bug (jenkinsci#1208) that had no prior row to attach to. Excludes the Renovate dependency-bump PR (jenkinsci#1211) and the two already-textually-referenced older PRs (jenkinsci#562, jenkinsci#746) as out of scope. Also fixes the sidebar's "Roadmap" link, which pointed at a nonexistent roadmap.md instead of this file.
rantoniuk
added a commit
that referenced
this pull request
Aug 31, 2026
* docs(adr): add modernisation architecture decision records Record the load-bearing decisions behind the modernisation programme, in MADR format, plus an index and template pointer. 0002 Jira Cloud detection and search API routing (#747) — thread an explicit isCloudVersion through our own client construction rather than shadowing UriUtil; the fix works on the already-pinned JRJC 6.0.2 and the detection bug is still unfixed in 8.0.0. 0003 Future of the vendored Atlassian HTTP client — keep and fix in 3.x because the fork carries hudson.ProxyConfiguration support upstream lacks; target apache-httpcomponents-client-5-api in 4.0. 0004 Continuous delivery and version numbering — JEP-229 CD with a manually controlled 3.x prefix and an on-demand trigger, addressing both objections raised on the issue. 0005 Staged deprecation removal — fixes in 3.x, removals batched in 4.0 so users can take the Cloud fix without taking API breakage. 0006 HTTP client lifecycle — why a client is built per request. This is the shipped fix for JENKINS-60536 (9e3b451), not a regression; reverting it reintroduces a build hang, as has already happened. Refs #747, #468, #541 * docs: require doc updates per PR and reference ADRs Every PR that changes user-visible behaviour now needs a matching Markdown change under docs/ in the same PR, keeping the relevant Declarative Pipeline example correct and runnable. New and updated examples use Declarative form; the older scripted snippets get converted as they are touched. Also point CONTRIBUTING.md and AGENTS.md at .github/adr/, which was previously undiscoverable, with a note to check it before "fixing" code that only looks wrong. * docs(adr): relocate ADRs from .github/adr to docs/adr Move the ADR set into the docsify site so they're readable at jenkinsci.github.io/jira-plugin instead of only in the GitHub file browser, and fix all ADR-to-ADR cross-links and the index table for docsify's link resolution (relative to the docs/ root, not the current file's directory) — every sibling reference was broken before this, 404ing from both GitHub's blob view and the docsify site. * docs: overhaul docsify site and contributor docs * docs: add draft modernisation epics and issue backlog Derived from the modernisation plan and ADRs 0002-0006, for manual review before anything is created on GitHub. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: fold System Properties and Maintainers into parent pages Merges docs/system-properties.md into configuration.md (as a "System Properties" section) and docs/maintainers.md into CONTRIBUTING.md (as its "For Maintainers" section), removing both standalone pages and their sidebar entries so related content lives on one page instead of being split across a link. * docs: rewrite features page with a TOC and Pipeline examples Rewrites docs/features.md into a complete, source-verified feature inventory (including JiraCreateIssueNotifier and JiraIssueMigrator, previously undocumented) with a table of contents and a minimal declarative Pipeline example per feature, merging in and retiring docs/usage-examples.md. Points readers at the Jenkins.io Pipeline steps reference as the canonical, auto-generated parameter reference, since it's extracted from this plugin's own source on every release. Updates docs/README.md and AGENTS.md's doc-update instructions to match the new page structure. * docs: add epic 7, Pipeline step parity, targeted at 4.0 Moves the two Pipeline-parity rows buried in epic 3 into a new epic 7, retargeted from 3.x to 4.0: closing the freestyle-only gap (JiraCreateIssueNotifier, JiraIssueMigrator, JiraEnvironmentVariableBuilder) against the Jenkins.io Pipeline Steps Reference and docs/features.md's "What is not yet supported in Pipeline" section, following the same Builder+SimpleBuildStep+@symbol pattern epic 6 already uses to replace JiraVersionCreator/JiraReleaseVersionUpdater. * docs: link open upstream PRs to their epic rows Adds open-PR links to the rows they address, matched against each PR's actual diff/description rather than title alone: #1193 (draft, closes #1178), #1199/#1200 (HTTP stack and JiraSite fixes, #1200 alone covers five separate rows across epics 2-3), #1201, #1202, #1205, #1207 (closes #677), and #1190 (in-progress docs work). Adds one new epic 3 row for a correctness bug (#1208) that had no prior row to attach to. Excludes the Renovate dependency-bump PR (#1211) and the two already-textually-referenced older PRs (#562, #746) as out of scope. Also fixes the sidebar's "Roadmap" link, which pointed at a nonexistent roadmap.md instead of this file. * chore(docs): reorg * docs(roadmap): link every ADR reference to its file * chore: update AGENTS.md --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
JiraSite.getHttpClientOptions() never called setConnectionTimeout(), so the configured connect timeout was always overridden by the library's hardcoded 5s default. It also passed the read-timeout value into setRequestTimeout(), which the vendored Apache HTTP client never reads, making the "Read timeout" setting a no-op; the socket/read timeout it does honor was fed the connect-timeout value instead. Wire timeout -> connection timeout and readTimeout -> socket timeout so both settings take effect. Fixes #215 (cherry picked from commit ad05f98)
JiraSite.executorService was static but sized from the instance field threadExecutorNumber, so whichever site built it first fixed the pool size for the whole controller and every other site's "Thread Executor Size" setting became decorative. The null check sat outside the synchronized block with no re-check inside it, so two threads could both build a pool and orphan one with its threads still running. The damaging part was ownership: the single shared pool was handed to every site's HTTP client as the callback executor, and ApacheAsyncHttpClient.destroy() calls shutdown() on it. Closing one site's client therefore terminated the pool for every site on the controller, and since the field was never reset, every later callback was rejected until Jenkins restarted. Make it a per-site field with correct double-checked locking, rebuild it if it is found shut down, and release it from destroy() - which also stops "Validate Settings" leaking a thread pool per click, since that path builds a throwaway site and destroys it. Drops the LI_LAZY_INIT_STATIC suppression this bug was hiding behind. (cherry picked from commit b02d3b6)
…ials JiraSite caches one Jira session per site, but credentials are resolved per Item - CredentialsProvider.lookupCredentials walks the folder ancestry, so a folder-scoped credential can shadow the global one of the same id. The cached session did not track which credentials it was built from, and getSession() only looked at its Item argument on the very first call. Remember which credentials the cached session was built from and rebuild it whenever the requesting item resolves to different ones. Sessions are still reused for every item resolving to the same credentials, so the common case costs nothing.
getIssue() wrote Optional.empty() into the issue cache whenever there was no session yet, so "we could not reach Jira" was stored - and then served - as "this issue does not exist". Genuine 404s and timeouts, by contrast, throw out of the loader and are never cached, so the caching was exactly the wrong way round. The cache made it worse in two ways: it was unbounded, so every issue key ever matched by the changelog annotator stayed resident, and it expired after *access* rather than after write, so the annotator re-reading a key on every page render kept renewing the poisoned entry instead of letting the two minutes elapse. Check for the session before consulting the cache, bound the cache, and expire entries relative to when they were written. The maximum size can be overridden with -Dhudson.plugins.jira.JiraSite.issueCache.maxSize, matching the existing ioThreadCount tunable. (cherry picked from commit 195fc29)
getProjectKeys() fetched the list once and kept it for the lifetime of the JVM - its own FIXME said so - meaning a newly created Jira project stayed invisible until Jenkins restarted. All three ways the fetch could fail then collapsed into the same silent Collections.emptySet(): - tryLock(3s) returning false fell straight through, logging nothing - RestClientException was caught and discarded, with the exception unused - InterruptedException restored the flag and logged nothing An empty set is not neutral here. JiraChangeLogAnnotator uses it to decide whether an issue belongs to a known project, so every issue link on every changelog page silently disappeared, and nothing in the log said why. Give the list a one-hour TTL (overridable with -Dhudson.plugins.jira.JiraSite.projectKeys.ttlMillis), log each bail-out with a deferred supplier, and keep serving the previous list when a refresh fails instead of reporting "no projects". JiraSession memoises the same list one layer down, so it is now told to drop its copy before each refetch. (cherry picked from commit d6f2857)
Transitioning an issue took three identical GETs of that issue: getAvailableActions(key) fetched it to read its transitions link, progressWorkflowAction(key, id) fetched it again a moment later to hand to the transition call, and a third fetch afterwards reports the resulting status. On a JQL matching 200 issues that is 600 issue fetches where 400 were pure waste, and none of them go through JiraSite's issue cache. Add overloads that take an already-fetched Issue, and have progressMatchingIssues fetch once and reuse it for both calls. The key-taking overloads stay and delegate, since they are public API and JiraCreateIssueNotifier uses them. The issues from the JQL search cannot be reused for this: the search asks for seven named fields and does not include the transitions link, so getTransitions would have nothing to follow. That is called out on the new overload so nobody optimises the remaining fetch away. The duplicate, result-discarding getStatusById() call this issue also named was already removed in 25c62ce. (cherry picked from commit 9b733b0)
migrateCredentials() added the new credential to the in-memory store, caught the IOException from save(), logged a warning, and then returned the same value it returns on success. Callers cannot tell the two apart, so JiraSite stored the id of a credential that was never written to disk. That is worse than it sounds, because the legacy userName and password fields are transient: once the site is saved again they are gone. The site keeps working until the controller restarts and is then permanently broken, with a weeks-old warning as the only clue. Roll the in-memory addition back and propagate. FormException is already on the signature, on both callers and on readResolve, so nothing else changes shape. JiraSite.readResolve catches it and loads the site without credentials rather than letting deserialization fail - aborting there would take the whole global configuration down. (cherry picked from commit 161bef4)
…utor volatile alone is not treated as a thread-safe type by static analysis for a double-checked-locking cache; AtomicReference keeps the same happens-before guarantees while satisfying that check. Also merges a duplicated Javadoc block left over from the session-scoping change, and makes the workflow-transition log message lazily evaluated.
rantoniuk
force-pushed
the
fix/jira-site-session-hardening
branch
from
September 6, 2026 22:13
93c2cd9 to
bfe16ae
Compare
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Related issue
Split out of #1198 for a diff-scoped review. Groups the
JiraSite-centered fixesfrom that PR, since they land on the same file back-to-back and don't cleanly separate further.
Changes
Several correctness and reliability fixes to
JiraSite's connection, caching and session handling:getHttpClientOptions()never calledsetConnectionTimeout(), so the configuredconnect timeout was always overridden by the library's hardcoded 5s default. It also passed the
read-timeout value into
setRequestTimeout(), which the vendored Apache HTTP client never reads,making the "Read timeout" setting a no-op. Both settings now take effect.
staticbut sized from an instance field, andApacheAsyncHttpClient.destroy()callsshutdown()on it — closing one site's client disabled Jirafor every site on the controller. Now one pool per site, with correct double-checked locking.
that built it, so a site whose session was created under one set of resolved credentials no longer
keeps serving that same session once a caller resolves to different credentials.
Optional.empty()and served back as"this issue does not exist"; the cache was also unbounded and expired after access rather than
after write. Fixed, and bounded (
-Dhudson.plugins.jira.JiraSite.issueCache.maxSize, default 1000).every failure mode (lock timeout,
RestClientException, interrupt) collapsed into a silent emptyset. Now refreshed on a TTL (
-Dhudson.plugins.jira.JiraSite.projectKeys.ttlMillis, default 1h) andfailures are logged instead of swallowed, while still serving the last-known-good list.
GETs of it. Added overloadsthat take an already-fetched
Issueand reuse it across the transition and status-read calls.SystemCredentialsProvider.save()during credential migration waslogged and then reported as success, leaving the site referencing a credential that only existed in
memory. Now propagates the failure;
JiraSite.readResolve()catches it and loads the site withoutcredentials rather than failing deserialization outright.
Also switches the callback-executor cache from a raw
volatilefield toAtomicReference(samedouble-checked-locking pattern, but backed by a type static analysis recognizes as thread-safe), and
merges a duplicated Javadoc block left over from the session-scoping change.
Tests
mvn compile,mvn spotless:check, andmvn testall pass standalone on this branch.JiraSiteTest,JiraSessionTest,JiraRestServiceTest,CredentialsHelperTest, andChangingWorkflowTestall green (75 tests), including new coverage for each fix above.I have updated/added relevant documentation in the
docs/directoryI have verified that the Code Coverage is not lower than before / that all the changes are covered as needed
I have tested my changes with a Jira Cloud / Jira Server