I've just checked a patch into the unstable branch which changes the
order of save operations a bit in a way which should help protect
against the bug, without adding any transactions or locking or whathaveyou.
The UPDATE to cur is moved up before the INSERT, *and* its WHERE
condition is modified to include the timestamp of the old edit (which
we've been using to detect edit conflicts, after all).
We then ask MySQL how many rows it modified in the UPDATE statement. If
it's zero, we know that there was an edit conflict; we abort the rest of
the save operation and (if I've got it right; I haven't tested it in
actual conflict) show an edit conflict screen.
Patch affects Article.php, EditPage.php, DatabaseFunctions.php.
New code should be running on
test.wikipedia.org now.
This isn't complete protection; there are probably scenarios under which
funny things can still happen. This also won't affect simultaneous page
_creation_, though that should be fixed by adding a UNIQUE KEY index on
cur_namespace and cur_title, so the second INSERT attempt would fail
instead of creating an inaccessible phantom entry.
Simultaneous edit scenarios, normal:
A B
SELECT cur
SELECT cur
UPDATE cur
UPDATE cur
[report conflict!]
INSERT old
A B
SELECT cur
UPDATE cur
SELECT cur
[report conflict!]
INSERT old
A B
SELECT cur
UPDATE cur
INSERT old
SELECT cur
[report conflict!]
Simultaneous edit scenarios, pathologically slow server:
A B
SELECT cur
UPDATE cur
[open edit _screen_]
SELECT cur
[start saving edit]
SELECT cur
INSERT old
UPDATE cur
INSERT old
This actually shouldn't be too wrong. Thread A would save the pre-A edit
into old, B would save A's edit into old, and the history should show
them all in the correct order, even if B'd insert to old actually
happened before A's, they're sorted by timestamp, not insert order and
will show correctly.
Updates of some of the other tables could conceivably end up out of
order or overwriting each other, though.
Simultaneous edit scenarios, pathologically fast editing:
A B
SELECT cur
UPDATE cur
INSERT old
SELECT cur
SELECT cur
UPDATE cur
[with same timestamp
as B's previous edit]
UPDATE cur
[miss conflict due to
same-second timestamp,
overwrite A's edit]
INSERT old
INSERT old
[duplicate of B's previous edit]
That could probably be protected against by checking the cur_user_text
field as well as cur_timestamp.
-- brion vibber (brion @
pobox.com)