Page MenuHomePhabricator

RuntimeException: Can't reblock a user with multiple blocks already present. Update calling code for multiblocks, providing a specific block to update
Closed, ResolvedPublicPRODUCTION ERROR

Description

Error
normalized_message
[{reqId}] {exception_url}   RuntimeException: Can\'t reblock a user with multiple blocks already present. Update calling code for multiblocks, providing a specific block to update.
FrameLocationCall
from/srv/mediawiki/php-1.44.0-wmf.19/includes/block/BlockUser.php(628)
#0/srv/mediawiki/php-1.44.0-wmf.19/includes/block/BlockUser.php(566)MediaWiki\Block\BlockUser->placeBlockInternal(bool)
#1/srv/mediawiki/php-1.44.0-wmf.19/includes/block/BlockUser.php(491)MediaWiki\Block\BlockUser->placeBlockUnsafe(bool)
#2/srv/mediawiki/php-1.44.0-wmf.19/includes/specials/SpecialBlock.php(1117)MediaWiki\Block\BlockUser->placeBlock(bool)
#3/srv/mediawiki/php-1.44.0-wmf.19/includes/specials/SpecialBlock.php(1201)MediaWiki\Specials\SpecialBlock::processFormInternal(array, MediaWiki\User\User, MediaWiki\Block\UserBlockCommandFactory, MediaWiki\Block\BlockTargetFactory)
#4/srv/mediawiki/php-1.44.0-wmf.19/includes/htmlform/HTMLForm.php(824)MediaWiki\Specials\SpecialBlock->onSubmit(array, MediaWiki\HTMLForm\OOUIHTMLForm)
#5/srv/mediawiki/php-1.44.0-wmf.19/includes/htmlform/HTMLForm.php(705)MediaWiki\HTMLForm\HTMLForm->trySubmit()
#6/srv/mediawiki/php-1.44.0-wmf.19/includes/htmlform/HTMLForm.php(721)MediaWiki\HTMLForm\HTMLForm->tryAuthorizedSubmit()
#7/srv/mediawiki/php-1.44.0-wmf.19/includes/specialpage/FormSpecialPage.php(240)MediaWiki\HTMLForm\HTMLForm->show()
#8/srv/mediawiki/php-1.44.0-wmf.19/includes/specials/SpecialBlock.php(150)MediaWiki\SpecialPage\FormSpecialPage->execute(string)
#9/srv/mediawiki/php-1.44.0-wmf.19/includes/specialpage/SpecialPage.php(729)MediaWiki\Specials\SpecialBlock->execute(string)
#10/srv/mediawiki/php-1.44.0-wmf.19/includes/specialpage/SpecialPageFactory.php(1737)MediaWiki\SpecialPage\SpecialPage->run(string)
#11/srv/mediawiki/php-1.44.0-wmf.19/includes/actions/ActionEntryPoint.php(503)MediaWiki\SpecialPage\SpecialPageFactory->executePath(string, MediaWiki\Context\RequestContext)
#12/srv/mediawiki/php-1.44.0-wmf.19/includes/actions/ActionEntryPoint.php(145)MediaWiki\Actions\ActionEntryPoint->performRequest()
#13/srv/mediawiki/php-1.44.0-wmf.19/includes/MediaWikiEntryPoint.php(202)MediaWiki\Actions\ActionEntryPoint->execute()
#14/srv/mediawiki/php-1.44.0-wmf.19/index.php(58)MediaWiki\MediaWikiEntryPoint->run()
#15/srv/mediawiki/w/index.php(3)require(string)
#16{main}
Impact
  • Some users have two block_target rows. These users cannot be unblocked.
Notes

See also: T387723: Special:InvestigateBlock: RuntimeException: Can't reblock a user with multiple blocks already present., T387730: Special:MassGlobalBlock: RuntimeException: Can't reblock a user with multiple blocks already present

Derived Requirement

Ensure that Special:Block properly handles users/IPs with multiple existing blocks by:

  • Preventing the RuntimeException: Can't reblock a user with multiple blocks already present.
  • Allowing unblock operations to function correctly even when multiple block_target rows exist.
  • Updating the calling code for multiblocks to provide a specific block to update, instead of failing with an exception.

Test Steps

Test Case 1: Verify that Special:Block does not throw a RuntimeException when reblocking a user with multiple existing blocks
  1. Navigate to Special:Block on a wiki where multiblocks are enabled.
  2. Enter a target user/IP and apply a first block.
  3. Apply a second block on the same user/IP.
  4. Attempt to reblock the same user/IP with a different expiration or block reason.
  5. Turn off multiblocks and Codex Special:Block
  6. Go to Special:Block again and try to reblock the same user
  7. ✅ ❓❌⬜AC1: Confirm that the system does not throw a RuntimeException and does not allow reblocking. You should see an error that you cannot reblock
  8. ✅❓❌⬜ AC2: Confirm that the existing blocks are correctly updated or preserved without corruption.

QA Results - Local

ACStatusDetails
1T388743#10654262
2T388743#10654262

Details

Request URL
https://es.wikipedia.org/wiki/Especial:Bloquear/Jaredlmns
Related Changes in Gerrit:

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript
Dreamy_Jazz renamed this task from RuntimeException: Can\'t reblock a user with multiple blocks already present. Update calling code for multiblocks, providing a specific block to update. to RuntimeException: Can't reblock a user with multiple blocks already present. Update calling code for multiblocks, providing a specific block to update..Mar 12 2025, 10:57 PM

This is weird - multiblocks isn't supposed to be enabled in production yet but because of some race condition a user got blocked twice anyway: https://es.wikipedia.org/w/index.php?title=Especial:Registro&page=User%3AJaredlmns&type=block

kostajh renamed this task from RuntimeException: Can't reblock a user with multiple blocks already present. Update calling code for multiblocks, providing a specific block to update. to RuntimeException: Can't reblock a user with multiple blocks already present. Update calling code for multiblocks, providing a specific block to update.Mar 13 2025, 8:35 AM

The user isn't just blocked twice, they also have two block_target rows. That's supposed to be prevented by a gap lock acquired by the UPDATE query in acquireTarget(). I must have missed something.

Just a bad assumption I guess. Easily reproduced. An update on a non-existent row acquires an IX lock, which can be shared by a different thread. Actually writing to the row acquires an X lock. So both threads involved in the race can do the update, then the inserts are serialized.

Here are the queries involved, slightly simplified:

  • UPDATE block_target SET bt_count=bt_count+1 WHERE bt_user=$user;
  • INSERT INTO block_target (bt_user) VALUES ($user);
  • COMMIT

With numbers indicating the thread executing the query, if the sequence is 1.update, 2.update, 1.insert, 2.insert, you get a deadlock error. If the sequence is 1.update, 1.insert, 2.update, 1.commit, 2.insert, you get two rows. If the 2.insert is issued after the 1.insert but before the 1.commit, it will wait for the other thread to commit and then succeed.

I think the schema is wrong, it needs a unique index. Then it's just an upsert. The only workaround I can think of is GET_LOCK() released in a transaction idle callback.

Subscribing @Ladsgroup and @aaron since they can at least understand what I'm talking about and commiserate.

Finding duplicate rows with

for w in $(expanddblist all); do sql $w -- -B --skip-column-names -e 'select group_concat(bt_id) from block_target where bt_user is not null group by bt_user having count(*) > 1; select group_concat(bt_id) from block_target where bt_address is not null group by bt_auto, bt_address having count(*) > 1;' | sed "s/^/$w /"; done

There are 75 such groups of rows, mostly close pairs of IDs, consistent with the race condition I described above. The task description relates to eswiki 568016,568023. However, in 15 cases, there is a duplicate between bt_id=1 and a much higher numbered bt_id, with the higher numbered one being an orphan row, with bt_count=1 but not connected to any block. So that's a separate bug.

1commonswiki 115213,115214
2commonswiki 122728,122730
3dewiki 193281,193282
4dewiki 193096,193097
5dewiki 198601,198603
6dewiki 198581,198582
7dewiki 198120,198121
8dewiki 198125,198126
9dewiki 198128,198129
10dewiki 202468,202469
11dewiki 202465,202466
12dewiki 202634,202635
13enwiki 2090149,2090152
14enwiki 2094546,2094547
15enwiki 1962595,1962597
16enwiki 2036238,2036243
17enwiki 2036234,2036236
18enwiki 2031776,2031781
19enwiki 2048728,2048729
20enwiki 2053674,2053675
21enwiki 2049730,2049731
22enwiki 2070289,2070290
23enwiki 2090148,2090153
24enwiki 2062038,2062040
25enwiki 2065412,2065414
26enwiki 2070872,2070873
27enwiki 2088814,2088816
28enwiki 2093175,2093178
29enwiki 2093176,2093179
30enwiki 2108445,2108446
31enwiki 2059859,2059860
32enwikibooks 1,7215
33enwikiquote 1,8222
34eswiki 568016,568023
35eswiki 568018,568025
36eswiki 568014,568021
37eswiki 547199,547204
38hewiki 24769,24771
39itwiki 156880,156881
40itwikibooks 1,529
41itwiktionary 1,351
42jawiki 171772,171773,171774
43jawiki 171769,171770
44jawiki 171754,171755,171756
45jawiki 171766,171767,171768
46jawiki 171759,171760
47jawiki 171763,171764,171765
48jawiki 189123,189125
49jawikibooks 1,4595
50jawikiquote 1,813
51jawikiversity 1,2685
52lnwiktionary 1,471
53miwiktionary 1,579
54nycwikimedia 198,199,200
55plwiki 23058,23060
56plwiki 23330,23332,23334
57plwiki 23445,23447
58plwiki 23971,23973
59plwiki 24007,24009
60plwiki 24050,24052
61plwiki 24678,24680
62plwiki 25319,25321,25323
63plwiki 26176,26178
64plwiki 28088,28090
65ptwiki 101929,101930
66ptwiki 1,89751
67ptwiki 92771,92773
68ptwiki 92775,92777
69ruwikiquote 1,3251144
70rwwiktionary 1,86
71svwikisource 1,513
72wikidatawiki 1,17910
73zhwiki 1,77215
74zhwiki 87167,87169
75zhwiki 95597,95599,95601

I checked two of the bt_id=1 conflicts, and they were both created on 2024-02-29, around the time migrateBlocks.php was running. So that's a migration bug.

Change #1128056 had a related patch set uploaded (by Tim Starling; author: Tim Starling):

[mediawiki/core@master] block: Suppress exception when reblocking a multi-blocked user

https://gerrit.wikimedia.org/r/1128056

Change #1128063 had a related patch set uploaded (by Tim Starling; author: Tim Starling):

[mediawiki/core@master] block: Add cleanupBlocks.php

https://gerrit.wikimedia.org/r/1128063

Change #1128056 merged by jenkins-bot:

[mediawiki/core@master] block: Suppress exception when reblocking a multi-blocked user

https://gerrit.wikimedia.org/r/1128056

I split off the cleanup script to T389301 so that this task can be closed.

I split off the cleanup script to T389301 so that this task can be closed.

Hi. I'm still unable to unblock user Jaredlmns at es.wikipedia. Can you tell me what ticket should I monitor?

@tstarling Is this LTS (1.43) backport worthy?

In T388743#10649916, @LuchoCR wrote:

Hi. I'm still unable to unblock user Jaredlmns at es.wikipedia. Can you tell me what ticket should I monitor?

Please try to unblock this user using the following two links:

@tstarling Is this LTS (1.43) backport worthy?

The exception is from December (cf1198155c27e1cb4b02ec9ab01d91c0820f0667) so should not affect 1.43. Prior to December, attempting to unblock or reblock a user with a duplicate block should select a random block for the given target and act on that one block. So users should be able to resolve it by going to Special:Unblock twice.

I'll think about a backportable solution for the race condition. You know my favourite idea at T389028 requires an SQLite version bump so is probably not backportable.

In T388743#10649916, @LuchoCR wrote:

Hi. I'm still unable to unblock user Jaredlmns at es.wikipedia. Can you tell me what ticket should I monitor?

Please try to unblock this user using the following two links:

Thank you! User is now unblock. Pura vida!

@tstarling Confirmed that the system does not throw a RuntimeException and does not allow reblocking as seen from the gif below. I will mark this as Resolved. Thanks for all your work!

Test Result - Local

Status: ✅ PASS
Environment: Local: MediaWiki- 1.44.0-alpha (af413e5) 13:51, 19 March 2025
OS: macOS Sequoia 15.4
Browser: Chrome 134
Device: MBA
Emulated Device: NA

Test Steps

Test Case 1: Verify that Special:Block does not throw a RuntimeException when reblocking a user with multiple existing blocks
  1. Navigate to Special:Block on a wiki where multiblocks are enabled.
  2. Enter a target user/IP and apply a first block.
  3. Apply a second block on the same user/IP.
  4. Attempt to reblock the same user/IP with a different expiration or block reason.
  5. Turn off multiblocks and Codex Special:Block
  6. Go to Special:Block again and try to reblock the same user
  7. AC1: Confirm that the system does not throw a RuntimeException and does not allow reblocking. You should see an error that you cannot reblock

2025-03-19_12-21-07.mp4.gif (1×1 px, 2 MB)

  1. AC2: Confirm that the existing blocks are correctly updated or preserved without corruption.

See AC1

GMikesell-WMF updated the task description. (Show Details)