Details
-
Bug
-
Resolution: Unresolved
-
Blocker
-
18.8.0-rc-1
-
Unknown
-
Description
Symptom
Opening an administration section contributed through XWiki.ConfigurableClass is supposed to lock the configurable page (and unlock the one of the previously visited section). On MySQL this lock is sometimes silently not taken: the page stays unlocked, so another user can start editing it concurrently without being warned, and nothing is reported to the user who opened the administration section.
Navigating from one administration section to another is what triggers it, because that is the moment where several lock requests overlap. Leaving an administration page sends an unlock (cancel) request for both the configurable page and XWiki.XWikiPreferences, while the page being loaded sends a lock request for its own configurable page and for XWiki.XWikiPreferences — four concurrent writes on the xwikilock table.
Cause
Two of those concurrent transactions deadlock in InnoDB, and the loser is rolled back, so the lock is never inserted:
lock/.../TestConfigurable2?ajax=1&action=admin WARN SqlExceptionHelper - SQL Error: 1213, SQLState: 40001 ERROR SqlExceptionHelper - Deadlock found when trying to get lock; try restarting transaction ERROR XWikiHibernateBaseStore - Exception while closing the transaction javax.persistence.OptimisticLockException: LockAcquisitionException: could not execute statement at HibernateStore.endTransaction(HibernateStore.java:843) at XWikiHibernateBaseStore.execute(XWikiHibernateBaseStore.java:838) at XWikiHibernateStore.saveLock(XWikiHibernateStore.java:2049) at XWikiDocument.setLock at LockAction.action(LockAction.java:61)
Since XWIKI-22421, XWikiHibernateStore#saveLock is a DELETE ... WHERE docId = ? followed by an INSERT. Under REPEATABLE READ — the InnoDB default, hence MySQL and MariaDB — a DELETE that matches no row still takes a gap lock on the gap where the row would go, and a concurrent INSERT into that gap has to wait for an insert-intention lock. Two such transactions deadlock each other. PostgreSQL, Oracle and HSQLDB take no gap locks, which is why only the MySQL environment is affected.
The failure is invisible to everyone involved:
- XWikiHibernateBaseStore#execute catches the exception thrown by the commit and only logs it, so saveLock returns normally and reports success on a rolled-back transaction.
- On the browser side, XWiki.DocumentLock#lock fires the request with new Ajax.Request(...) and no failure handler, and it has already marked the document as locked before sending it — so the client also believes it holds a lock it does not have.
Evidence
The functional test ConfigurableClassIT#testLockingAndUnlocking catches this. On ci.xwiki.org it fails only in the MySQL environment (MySQL latest, Tomcat 11-jdk25, Filesystem, Chrome) and always on the same assertion — the one checking that the second configurable page got locked — while the PostgreSQL environment, which uses the same Chrome and Tomcat, always passes. It started failing on that assertion on 2026-08-29, right after XWIKI-22421 was merged.
Example: https://ci.xwiki.org/job/XWiki%20Environment%20Tests/job/xwiki-platform/job/master/1363/
Note
XWIKI-24997 has since made the store default hibernate.connection.isolation to READ COMMITTED when the Hibernate configuration does not set one. Under READ COMMITTED InnoDB disables gap locking for searches and index scans, which should remove the trigger described above for a default installation. That change landed after every CI build analysed here, so it has not been exercised yet, and it does not help an installation whose hibernate.cfg.xml pins REPEATABLE READ.
Attachments
Issue Links
- is caused by
-
XWIKI-22421 XWikiHibernateBaseStore related error when editing in realtime
-
- Closed
-
- is related to
-
XWIKI-24997 XClass is cached as non-existing or outdated when it is saved while a document using it is being loaded (MySQL/MariaDB)
-
- Open
-