You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by "Andrus Adamchik (Jira)" <ji...@apache.org> on 2026/10/10 21:50:00 UTC
[jira] [Created] (CAY-3046) Refreshing an object from a snapshot discards to-one relationships, causing fault resolution on a shared DataContext
Andrus Adamchik created CAY-3046:
------------------------------------
Summary: Refreshing an object from a snapshot discards to-one relationships, causing fault resolution on a shared DataContext
Key: CAY-3046
URL: https://issues.apache.org/jira/browse/CAY-3046
Project: Cayenne
Issue Type: Bug
Reporter: Andrus Adamchik
Assignee: Andrus Adamchik
Fix For: 5.0-M4
DataRowUtils.refreshObjectWithSnapshot unconditionally replaces every to-one of the refreshed object with a new ToOneFault, even when the snapshot is unchanged:
{noformat}
public boolean visitToOne(ToOneProperty property) {
property.invalidate(object);
return true;
}
{noformat}
ObjectResolver.objectFromDataRow calls it for every row of a query whose object is already registered. So any select that returns an already-registered object throws away its resolved to-one targets. The select that does the damage does not need any prefetches of its own.
For readOnly entities and HOLLOW objects the same call also passes invalidateToManyRelationships=true, so prefetched to-many lists are discarded as well, and re-reading them costs a DB query. COMMITTED objects of writable entities are spared on the to-many side with the comment "they might have just been prefetched", which applies equally to to-ones.
On a shared DataContext this also becomes a lock contention problem that may cause an avalanche of faulting. Threads A and B run the same query with the same prefetches:
1. A resolves its result under the ObjectStore lock, the prefetch links the to-one targets into the parent objects, A releases the lock and starts reading them.
2. B resolves the same rows under the lock. HierarchicalObjectResolver first resolves all root rows, which faults every to-one on every shared object, and only afterwards walks the prefetch tree and links the targets back.
3. A reads a to-one without a lock during that gap, sees the ToOneFault, calls resolveFault, and blocks on the ObjectStore monitor until B is done. The target is already registered, so no DB query is issued, but A pays one monitor acquisition per faulted property per row.
If B's query does not prefetch the same path, or has no prefetches, the relationship stays a fault until some later query prefetches it again.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)