Uploaded image for project: 'XWiki Platform'
  1. XWiki Platform
  2. XWIKI-24907

Importing an extension in the repository may saves a stale document and overwrites the concurrent last-version update

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • Major
    • 18.4.6, 18.9.0-rc-1
    • 17.10.12
    • Repository
    • None
    • Unknown
    • N/A
    • N/A

    Description

      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 self-conflict was foreseen: the import is bracketed by ExtensionImportStartingEvent / ExtensionImportFinishedEvent (RepositoryManager.java:887 and :987) and the listener skips the update while observationContext.isIn(IMPORT_PROCESS), so within one import thread the guard does cover the version page saves made at RepositoryManager.java:972.

      It is not enough in practice. 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). Two explanations fit, and the server log or a reproduction is needed to tell them apart:

      • a version page is saved outside the import fold, in another execution context, so the guard does not apply; or
      • two imports of the same extension overlap, the listener firing in one while the other sits between its clone and its final save. The "Updated last version of the extension" revisions are attributed to XWiki.Admin because of the author problem described below, so they are not evidence of a separate session by that user.

      Either way, the final save at RepositoryManager.java:980 writes a document that was read before those updates.

      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 made outside the import's execution context, nor a second import running in parallel.

      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.

      Attachments

        Issue Links

          Activity

            People

              tmortagne Thomas Mortagne
              vmassol Vincent Massol
              Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: