You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On several customer branches a submitted item can silently end up orphaned: owning_collection = NULL and in_archive = false while its collection2item mapping and handle survive. The item then disappears from search and collection listings, has no breadcrumb, and "Edit Item" throws HTTP 500 (NPE in CanManageMappingsFeature, cf. #1348).
The behavioral fix already shipped to customer/zcu-data in #1352 (and previously to dtq-dev / customer/lindat as dc57ffded8 / #852). It is still missing from the remaining customer branches. This issue tracks propagating that fix to every customer instance that lacks it.
2. Root cause (hypothesis)
A Hibernate lost update via a leaked, thread-bound session on the request error path:
A slow submission file upload (WorkspaceItemRestRepository.upload → wis.find loads the Item, long stream, then context.commit() at the end) holds the Item in its Hibernate L1 cache with pre-deposit values (owning_collection = null, in_archive = false).
A concurrent deposit installs the item (sets owning_collection, in_archive = true) and deletes the workspace item the upload is working on.
The upload throws (500 / ClientAbortException: Broken pipe) before reaching context.commit().
DSpaceRequestContextFilter assigns its context local afterchain.doFilter(...), so on an exception that assignment is skipped, context stays null, and its finallyskips the abort — leaving the Hibernate session bound to the Tomcat worker thread (ThreadLocal), still holding the dirty, stale Item.
A later request that reuses that thread commits → Hibernate flushes the leftover dirty Item as a full-row UPDATE (Item has no @DynamicUpdate/@Version), reverting owning_collection → NULL and in_archive → false. The collection2item join row and handle survive → the observed orphan.
Real incident: item 62133a8a-fbd1-474f-9b55-fa92d318aa03 (handle 20.500.14592/107) on a ZCU-DATA instance.
3. Acceptance criteria
Every customer branch that lacks the guard carries the StatelessAuthenticationFilter change: chain.doFilter(...) wrapped in try { … } finally { … }, and in the finally the request Context is read from req.getAttribute(ContextUtil.DSPACE_CONTEXT) and abort()ed when still valid.
On the normal success path the change is a no-op (request already committed → context no longer valid).
The behavioral file StatelessAuthenticationFilter.java on each patched branch is equivalent to the already-shipped version on dtq-dev / customer/zcu-data / customer/vsb-tuo.
One PR per customer branch, base = that branch, CI green, human sign-off.
No orphaning under the concurrent upload-vs-deposit repro on a patched instance (matches LINDAT behaviour).
Port only the behavioral StatelessAuthenticationFilter hunk (the outer filter's try/finally abort of the leaked Context) to every customer branch missing it.
Out of scope
The diagnostic / CI files from the original dc57ffded8 (Context.java, Utils.java, HibernateDBConnection.java, log4j2.xml, build.yml) — they conflict on diverged branches and are not needed for the fix.
Defense-in-depth follow-ups (separate issues): fix the latent null-local bug in DSpaceRequestContextFilter (read the Context inside finally); add @DynamicUpdate/@Version to Item/DSpaceObject for a deterministic optimistic-lock guard; the read-path 500 guard (CanManageMappings, fix: NPE (HTTP 500) in CanManageMappingsFeature for item with null owning collection #1348).
6. Constraints
No DB migration — pure Java, no Flyway change.
API-compatible — no REST contract change; success path is a no-op.
Module boundary — change confined to dspace-server-webapp; relies only on Context, ContextUtil, and the existing log field, all already imported on the target branches.
Backport family by family; gate each PR on the green-light checklist (CI + human sign-off).
Registry gap:customer/uk (live, DSpace 7.6.1, last commit 2025-09) is a real customer branch but is absent from customers/registry.yml. It is included as a target here; the registry should be updated so future fan-outs don't silently skip it (lindat and palo-docker are likewise unlisted but already carry the fix).
BE module impact:dspace-server-webapp only. Target region (chain.doFilter(req, res); closing the doFilterInternal method) is byte-identical across all 5 missing branches, so the cherry-pick applies with minimal/no conflict.
8. Verification plan
Automated: per branch — mvn checkstyle:check -pl dspace-server-webapp + module compile; full test suite runs in PR CI.
Content check: post-port StatelessAuthenticationFilter.java is equivalent to the shipped dtq-dev / zcu-data version.
Manual (on a test instance): run the repro — start a large-file submission upload, trigger the deposit concurrently. Before: item ends owning_collection = NULL / in_archive = false. After: item stays correct (matches LINDAT).
1. Problem
On several customer branches a submitted item can silently end up orphaned:
owning_collection = NULLandin_archive = falsewhile itscollection2itemmapping and handle survive. The item then disappears from search and collection listings, has no breadcrumb, and "Edit Item" throws HTTP 500 (NPE inCanManageMappingsFeature, cf. #1348).The behavioral fix already shipped to
customer/zcu-datain #1352 (and previously todtq-dev/customer/lindatasdc57ffded8/ #852). It is still missing from the remaining customer branches. This issue tracks propagating that fix to every customer instance that lacks it.2. Root cause (hypothesis)
A Hibernate lost update via a leaked, thread-bound session on the request error path:
WorkspaceItemRestRepository.upload→wis.findloads the Item, long stream, thencontext.commit()at the end) holds the Item in its Hibernate L1 cache with pre-deposit values (owning_collection = null,in_archive = false).owning_collection,in_archive = true) and deletes the workspace item the upload is working on.ClientAbortException: Broken pipe) before reachingcontext.commit().DSpaceRequestContextFilterassigns itscontextlocal afterchain.doFilter(...), so on an exception that assignment is skipped,contextstaysnull, and itsfinallyskips the abort — leaving the Hibernate session bound to the Tomcat worker thread (ThreadLocal), still holding the dirty, stale Item.UPDATE(Item has no@DynamicUpdate/@Version), revertingowning_collection → NULLandin_archive → false. Thecollection2itemjoin row and handle survive → the observed orphan.Real incident: item
62133a8a-fbd1-474f-9b55-fa92d318aa03(handle20.500.14592/107) on a ZCU-DATA instance.3. Acceptance criteria
StatelessAuthenticationFilterchange:chain.doFilter(...)wrapped intry { … } finally { … }, and in thefinallythe request Context is read fromreq.getAttribute(ContextUtil.DSPACE_CONTEXT)andabort()ed when still valid.StatelessAuthenticationFilter.javaon each patched branch is equivalent to the already-shipped version ondtq-dev/customer/zcu-data/customer/vsb-tuo.4. Stack(s) & type
DSpace,dspace-server-webapp).5. Scope (in / out)
In scope
StatelessAuthenticationFilterhunk (the outer filter'stry/finallyabort of the leaked Context) to every customer branch missing it.Out of scope
dc57ffded8(Context.java,Utils.java,HibernateDBConnection.java,log4j2.xml,build.yml) — they conflict on diverged branches and are not needed for the fix.DSpaceRequestContextFilter(read the Context insidefinally); add@DynamicUpdate/@VersiontoItem/DSpaceObjectfor a deterministic optimistic-lock guard; the read-path 500 guard (CanManageMappings, fix: NPE (HTTP 500) in CanManageMappingsFeature for item with null owning collection #1348).6. Constraints
dspace-server-webapp; relies only onContext,ContextUtil, and the existinglogfield, all already imported on the target branches.7. Backport context
Source PR: ZCU-DATA/fix: prevent orphaned items — abort leaked request Context (backport dc57ffded8/#852 to zcu-data) #1352 (
ZCU-DATA/fix: prevent orphaned items — abort leaked request Context).Source commit to cherry-pick:
ea6116ebec1a82403b1bbe07be8cc5da38edaf8b(single-file port; cleaner to propagate than the multi-filedc57ffded8).Original fix:
dc57ffded8/ Transaction bug - close context in finally block (#845) #852 ("Transaction bug - close context in finally block").Targeting: all customers from
customers/registry.ymlthat lack the guard (do not hardcode). Current coverage audit:dc57ffded8)dc57ffded8)BE module impact:
dspace-server-webapponly. Target region (chain.doFilter(req, res);closing thedoFilterInternalmethod) is byte-identical across all 5 missing branches, so the cherry-pick applies with minimal/no conflict.8. Verification plan
mvn checkstyle:check -pl dspace-server-webapp+ module compile; full test suite runs in PR CI.StatelessAuthenticationFilter.javais equivalent to the shippeddtq-dev/zcu-dataversion.owning_collection = NULL/in_archive = false. After: item stays correct (matches LINDAT).9. Risk & rollback
zcu-data(ZCU-DATA/fix: prevent orphaned items — abort leaked request Context (backport dc57ffded8/#852 to zcu-data) #1352) and byte-identical tovsb-tuo/dtq-dev.10. Related links
dc57ffded8(PR Transaction bug - close context in finally block (#845) #852)