Problem When an extension is imported (refreshed) in the Repository application, the extension page has two concurrent writers and the import wins with content it read minutes earlier:
- RepositoryManager.tryUpdateExtension clones the extension document up front (RepositoryManager.java:703), then does the long work - resolving every version from the remote repository, creating/updating/deleting the version pages - and only at the very end saves that now-stale clone (RepositoryManager.java:980).
- Since
XWIKI-24669, ExtensionUpdaterListener also reacts to version page create/update/delete and re-loads and saves the extension page as a minor edit (ExtensionUpdaterListener.java:97-108, calling RepositoryManager.updateLastExtensionVersion at RepositoryManager.java:489).
The listener's negated observationContext.isIn(IMPORT_PROCESS) guard is meant to suppress exactly this during an import, and it does not hold: a version page saved in another execution context during the import still triggers the listener. In the history of extensions:Extension.Release Notes Application.WebHome on extensions.xwiki.org, revision 88.2 "Updated last version of the extension" and revision 89.1 "Imported extension" are 5 seconds apart (30 Aug 2026) - the same operation. Two consequences:
- The listener's update is silently overwritten, since the import saves a document it read before that update.
- On MariaDB with innodb_snapshot_isolation ON (the default since 11.6.2), the database refuses the stale write instead, and the import fails with Record has changed since last read in table 'xwikidoc', wrapped as an opaque Error number 3201. That is what happens on extensions.xwiki.org, where an import fails on the first attempt and succeeds when retried immediately - see XINFRA-496.
The final save of a stale clone is older than XWIKI-24669, but it was harmless while the import was the only writer of that page. Proposal Re-read the extension document just before the final save and re-apply the changes, instead of saving the clone taken at the start of the import. Widening the guard is not sufficient on its own, since it cannot cover a save that happens in another execution context. Side note updateLastExtensionVersion (RepositoryManager.java:489-501) saves through xcontext.getWiki().saveDocument(...) without setting the author, unlike the private RepositoryManager.saveDocument helper, so those edits keep the author of the last import (they show up as XWiki.Admin on extensions.xwiki.org). Cosmetic, but it makes the history misleading. |