Skip to content

Keep the ECOD read lock balanced when loading fails - #1150

Open
aalhossary wants to merge 1 commit into
biojava:masterfrom
aalhossary:aa/ecod-lock-on-failure
Open

Keep the ECOD read lock balanced when loading fails#1150
aalhossary wants to merge 1 commit into
biojava:masterfrom
aalhossary:aa/ecod-lock-on-failure

Conversation

@aalhossary

Copy link
Copy Markdown
Member

getDomainsForPdb and getAllDomains release the read lock inside their try block — so the loader they call can take the write lock — and re-acquire it immediately afterwards:

domainsFileLock.readLock().lock();
try {
    while (domainMap == null) {
        domainsFileLock.readLock().unlock();
        indexDomains();                       // if this throws...
        domainsFileLock.readLock().lock();    // ...this never runs
    }
    ...
} finally {
    domainsFileLock.readLock().unlock();      // ...and this unlocks a lock we do not hold
}

When the loader throws, the re-acquisition is skipped, and the outer finally unlocks a lock the thread no longer holds. IllegalMonitorStateException is then thrown from a finally block, so it supersedes the IOException that actually caused the failure.

Why it matters, concretely

Last night's nightly (33303708377) is a clean demonstration. One upstream change — ECOD now answers http://prodata.swmed.edu/ecod/distributions/… with a 308 that HttpURLConnection will not follow, see #1149 — produced eight failures wearing three different faces:

EcodInstallationTest.testDownloads:93   » HttpStatus HTTP 308 Permanent Redirect for http://…
EcodInstallationTest.testByPDB:148      » IllegalMonitorState attempt to unlock read lock, not locked by current thread
EcodInstallationTest.testAllDomains:104 NullPointer Cannot invoke "…getAllDomains()" because "ecod" is null

Only the first names the cause. The second is this bug. Diagnosing it meant reading past the noise to find the three tests that had told the truth.

The fix

Re-acquire in a finally of its own, so the lock is balanced on both paths and the original exception propagates intact:

domainsFileLock.readLock().unlock();
try {
    indexDomains();
} finally {
    domainsFileLock.readLock().lock();
}

Six lines in each of the two methods, no behaviour change on the success path. The other four read-lock sites in the class are already balanced — they lock, return, and unlock in a finally without releasing in between — so they are untouched.

Scope

This does not fix the ECOD download itself; that is #1149, and it needs FileDownloadUtils to follow the redirects the JDK declines. This only stops a failing load from disguising its own cause.

The third face above — EcodFactory handing back null after swallowing the error — is the same family of masking and is also not addressed here.

Testing

mvn test -pl biojava-structure -Dtest=EcodParserTest — green, and the class compiles unchanged otherwise. The failure path is not covered by an automated test: reproducing it needs a download that throws, which today means either the network being down or ECOD still being broken. If you would like it pinned, the natural way is a small unit test that injects a failing loader, which would mean making the loader overridable — happy to add that if you think it is worth the seam.

getDomainsForPdb and getAllDomains release the read lock inside their try
block, so that the loader they call can take the write lock, and re-acquire
it afterwards. When that loader throws, the re-acquisition never happens,
and the outer finally unlocks a lock the thread no longer holds.

The resulting IllegalMonitorStateException is thrown from a finally block,
so it supersedes the IOException that actually caused the failure. Last
night's nightly showed this: one upstream change - ECOD now answers with a
308 that HttpURLConnection will not follow - produced eight failures with
three different-looking causes, only three of which named the redirect.

Re-acquiring in a finally of its own keeps the lock balanced on both paths,
so the original exception propagates intact.
aalhossary added a commit to aalhossary/biojava that referenced this pull request Aug 30, 2026
Covers what is merged since 7.2.6 and what is open and expected to land:
the download and checksum work, the CATH and ECOD fixes, the contact and
ASA performance tweaks, electron density, and the JUnit 5 migration.

Five entries are for pull requests that are still open - biojava#1134, biojava#1147,
biojava#1148, biojava#1150 and biojava#1151 - and should be checked against what actually
merged before the release is tagged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant