There are 2 updates, 2 comments.
 
 
XWiki Platform / cid:jira-generated-image-avatar-f36a2e5f-3169-4e06-b8ae-dabfa47b3107 XWIKI-24778 Open

Loading a document with a context wiki different from the document's wiki silently drops all its xobjects

 
View issue   ยท   Add comment
 

2 updates

 
cid:jira-generated-image-avatar-7995fd03-3257-40f8-91fd-785f8de66b27 Changes by Vincent Massol on 02/Sep/26 10:02
 
Summary: Document cache entries collide across wikis when the Loading a document with a context wiki differs different from the document's wiki silently drops all its xobjects
Description: h2. Problem

{{ XWikiCacheStore XWikiHibernateStore#loadXWikiDoc }} derives its cache key resolves each xobject's document reference from the *wiki in the context* rather than instead of from the wiki of the document being loaded, saved or invalidated and then silently discards every xobject whose reference does not match the document :

{code:
language= java}
// XWikiCacheStore#getKey(XWikiDocument, XWikiContext)
DocumentReference reference
BaseObject object = doc it . getDocumentReferenceWithLocale next ();
...
//
The current wiki might be different from the reference wiki so fix it It seems to search before calculating is case insensitive. And this would break the key loading if we get an
// object which doesn't really belong to this document
if (! reference object . getWikiReference getDocumentReference ().equals( context doc . getWikiReference getDocumentReference ())) {
    continue;
}
{code}

The mismatch comes from {{BaseObject#setName(String)}}, the setter Hibernate calls when hydrating a row from {{xwikiobjects}} (the {{name}} column is mapped by property access in {{xwiki.hbm.xml}}). On a freshly hydrated object the document
    reference = is still null, so the stored local name is resolved with the _current mixed_ document reference .setWikiReference( resolver, that is against the wiki in the context .getWikiReference :

{code:java}
public void setName
( String name )
{
    DocumentReference reference = getDocumentReference(
);
}
    if (reference != null) {
return this         // . uidStringEntityReferenceSerializer . serialize(reference . keep the existing wiki , replace space and page ...
    } else {
reference = getCurrentMixedDocumentReferenceResolver( ) .resolve(name) ;
    }
    setDocumentReference(reference);
}
{code}

This was introduced deliberately by XWIKI\-13632, to match So any call that reaches the Hibernate store 's behaviour of loading with a document context wiki different from the document's wiki indicated gets back a document that exists, has its content, and has *no xobjects at all*, with nothing logged. {{XWikiCacheStore}} then stores that stripped document in the context regardless document cache, whose lifetime is unbounded, so it keeps being served until that document is next saved.

h2. This is not a cache key problem

An earlier version
of the wiki in the this issue blamed {{ XWikiCacheStore#getKey( XWikiDocument , XWikiContext) }} . The consequence , however, which normalises the key to the context wiki. That behaviour is that *two documents that share intended, it mirrors the same space Hibernate store, and page name in two different wikis share a single document cache slot* whenever an operation on one of them happens while changing it would break the API. Every {{XWiki}} API entry point into the store switches the context wiki points at to the other. document's own wiki before touching it, so the normalisation is a no-op and no cross-wiki key collision is reachable through the API:

So any * {{ loadXWikiDoc XWiki#getDocument(XWikiDocument, XWikiContext) }}
* {{XWiki#saveDocument(XWikiDocument
, String, boolean, boolean, XWikiContext)}}
*
{{ XWiki#deleteDocument(XWikiDocument, boolean, boolean, XWikiContext)}}
* {{XWiki#
exists (DocumentReference, XWikiContext) }} or

The
{{ saveXWikiDoc uid }} performed with a context wiki that serializer used for the key does not match include the document's wiki reads , populates or invalidates *another wiki's* cache entry so there is no blanket collision either . In particular an invalidation A search of the code base for callers that lands on reach an {{XWikiStoreInterface}} directly without aligning the wrong key leaves a stale entry behind, context found none: the direct {{getStore().loadXWikiDoc}} / {{saveXWikiDoc}} / {{deleteXWikiDoc}} uses outside {{XWiki}} are in the attachment stores and because in the document cache has legacy {{Package}}, all inside an unbounded lifetime that stale entry survives for already switched save path, and the rest only {{getNotCacheStore()}} user, {{LazyXWikiDocument}}, switches the wiki itself.

The point
of this issue is therefore not that such a caller is known to exist, but that when one does the instance's life store answers with silent data loss instead of an error .

h2. User
\- visible consequence

A page renders as if it had never been configured: its xobjects are invisible, so sheets and administration sections that read them show empty fields, or leak raw Velocity where a property of the missing XClass is dereferenced. Meanwhile REST, Solr backed live tables and direct queries keep returning the correct data, and nothing shows up in the logs.
On a multi \- wiki instance , pages that exist at the same path in several wikis \( this typically application/ hits configuration pages shipped by an extension that is installed on more than one wiki \) can be served from , since those exist at the cache same path in a state that does several wikis.

h2. Suggested fix

{{loadXWikiDoc}} should
not match silently return a document stripped of its xobjects. Either align the database: context to the content of another wiki document 's copy wiki for the duration of the load , or resolve each xobject's reference against the pre\-install "does not exist" state. Anything reading such a document through being loaded rather than against the cache then behaves as if context. In addition, the document had no XClass and no xobjects {{continue}} that drops an object should at least log a warning , while REST, Solr\-backed live tables and direct queries โ€” so that a caller which do does not go through this cache โ€” keep showing align the correct data. The result is context produces a wiki diagnosable message instead of an unexplained empty page that renders as if it were unconfigured, with no error anywhere in the logs .

h2. How it was found
This is
While investigating
the root cause of the flickering {{org.xwiki.image.style.test.ui.AllIT$NestedImageStyleIT#imageStyleAdministration}} test \ (see XWIKI \ -24720 for a different, already \- mitigated flicker of the same test \ ). Failure Its failure rate on the CI went from about 0.2% \ (June \- and July 2026 \ ) to about 2.8% \ (since mid \- August 2026 \ ) , consistent with a load\-sensitive race .

The
test uses {{@WikisSource}}, which runs it once on the main wiki and once on a subwiki, so {{Image.Style.Code.Configuration}} and {{Image.Style.Code.ConfigurationClass}} exist at the same path in both wikis โ€” exactly the collision condition.

Observations:

* The
failure is always {{NoSuchElementException: \ * \ [name='Image.Style.Code. ConfigurationClass\_0\_defaultStyle ConfigurationClass_0_defaultStyle ' \ ]}} in {{ImageStyleAdministrationPage#getDefaultStyle}}.
*
The archived failure screenshots show the Image Styles administration section rendered with two literal {{$escapetool.xml \ ($property.translatedPrettyName \ )}} labels and with without the "Default Style" select and "Force Default Style" checkbox missing , while the rest of the section \(including the "Update the default image style" button and the image styles live table\) renders normally.
*
Frames extracted from the failure video show the section is already broken on the *first* _first_ visit to the administration, more than 20 seconds before the assertion fails, and that it stays broken across page loads . It , so it is therefore not a UI / or wait race , and no page object change can work around it.
* Reproduced the exact
The
same rendering was reproduced on a local instance by making deleting the {{Image.Style.Code.ConfigurationClass}} XClass invisible to the request xobject of {{Image.Style.Code.Configuration}} over REST : {{$configurationDoc.display \ ('defaultStyle', 'edit' \ )}} then returns an empty string because {{XWikiDocument#display}} bails out on {{pclass == null}}, and {{$configurationObj.xWikiClass.get \ ('defaultStyle' \ )}} returns null, which is what leaks the two raw Velocity labels. A document loaded with a mismatched context wiki is in exactly that state.
*
The CI console log shows the extension provisioning into namespace test uses {{ \[wiki:wiki1\] @WikisSource }} completing 14 seconds before , which runs it once on the * main wiki * run of the test started and found once on a subwiki, so {{Image.Style.Code.Configuration}} exists at the *main wiki* broken same path in both wikis . The two parameterized runs fail independently of each other .

Note that the code path which reaches the store with a mismatched context in that scenario has *not* been identified
, as so this issue describes a per\-wiki cache collision predicts robustness defect in the store rather than a proven root cause for that test .

h2. Notes

* Same family as XWIKI \ -13632 and XWIKI \ -24673.
* {{XWikiDocument#getId
\ ( \ )}} deliberately excludes the wiki from the Hibernate id as well \("we don't use the wiki name in the document id's computation"\) , so the same page path in two wikis is also indistinguishable at the Hibernate identifier level too .
* Fixing this means deciding whether the store should key on the document's own wiki \(and align the context to it\) instead of the reverse, which changes behaviour for every caller that relies on the context\-wiki\-wins semantics documented in XWIKI\-13632. That decision is the point of this issue.

 
 

2 comments

 
cid:jira-generated-image-avatar-7995fd03-3257-40f8-91fd-785f8de66b27 Vincent Massol on 02/Sep/26 10:03
 

Rescoped after feedback on the developers chat: XWikiCacheStore's context wiki normalisation of the cache key is intended behaviour and changing it would be an API breakage, and the problem is not reachable through the XWiki API since every entry point aligns the context wiki with the document's wiki first.

Re-auditing the code with that in mind produced a better framing, which is what the description now says: when the store is reached with a mismatched context wiki, the damage is not a mis-keyed cache entry but a document returned without any of its xobjects, silently, which then gets cached.

Also removed the claim that this is the proven root cause of the imageStyleAdministration flicker. The observed symptom matches a document loaded without its xobjects, but the caller that would reach the store with a mismatched context has not been identified.

 
cid:jira-generated-image-avatar-7995fd03-3257-40f8-91fd-785f8de66b27 Vincent Massol on 02/Sep/26 10:04
 
Rescoped after feedback on the developers chat: {{XWikiCacheStore}}'s context wiki normalisation of the cache key is intended behaviour and changing it would be an API breakage, and the problem is not reachable through the XWiki API since every entry point aligns the context wiki with the document's wiki first.

Re
\ -auditing the code with that in mind produced a better framing, which is what the description now says: when the store _is_ reached with a mismatched context wiki, the damage is not a mis \ -keyed cache entry but a document returned without any of its xobjects, silently, which then gets cached.

Also removed the claim that this is the proven root cause of the {{imageStyleAdministration}} flicker. The observed symptom matches a document loaded without its xobjects, but the caller that would reach the store with a mismatched context has not been identified.