This issue has been created
 
 
XWiki Platform / cid:jira-generated-image-avatar-0ac0d880-6aa8-47b3-b536-800e2d0c275c XWIKI-25273 Open

A forced asynchronous placeholder for an already rendered result cannot be resolved

 
View issue   ยท   Add comment
 

Issue created

 
cid:jira-generated-image-avatar-451488a4-82a7-471f-a32b-21fcf0a6af1a Michael Hamann created this issue on 12/Oct/26 15:51
 
Summary: A forced asynchronous placeholder for an already rendered result cannot be resolved
Issue Type: cid:jira-generated-image-avatar-0ac0d880-6aa8-47b3-b536-800e2d0c275c Bug
Affects Versions: 18.8.0
Assignee: Unassigned
Components: Rendering - Async
Created: 12/Oct/26 15:51
Priority: cid:jira-generated-image-static-minor-6f5e9c81-62e4-45f7-9ad8-81bc12347c70 Minor
Reporter: Michael Hamann
Description:

Description

When an asynchronous renderer is executed with AsyncRendererConfiguration#setPlaceHolderForced(true) and a finished result is already available in the asynchronous rendering cache, DefaultAsyncRendererExecutor#render returns a placeholder with a new client id, but that client id is never registered in the short-lived cache used to resolve placeholders.

The cached-status branch of render() does:

} else if (asyncAllowed) {
    // Already running job, associate it with another client
    return new AsyncRendererExecutorResponse(status, newClientId());
}

The response constructor adds the client id to the status, which works for a job that is still running: the job stores its status for all its clients in AsyncRendererCache when it finishes. For a status that is already finished, nothing stores it again, so getAsyncStatus(id, clientId) finds neither a running job nor an entry for that client, and the async resource answers that there is no status for that id: the placeholder stays empty.

The same happens when the job finishes between the lookup of the running job and the registration of the new client.

Impact

No caller in XWiki Platform reaches this case: the only caller forcing the placeholder (the notifications script service, used by the Notifications macro) uses a renderer which doesn't allow caching. AsyncRendererConfiguration is an internal API. Low priority, but the behaviour is wrong.

Fix notes

  • Once the client is added, if the status is finished, store the status in the short-lived cache for that client.
  • Don't call AsyncRendererCache#put(status) from that branch: it runs while holding the cache's read lock and put() takes the write lock, which deadlocks with ReentrantReadWriteLock. put() would also register again all the clients that already received the result. A dedicated method storing only the new client in the short-lived cache, without taking the write lock, avoids both problems.
  • Add a unit test in DefaultAsyncRendererExecutorTest for a forced placeholder with a finished cached status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>