This is a rather complicated bug, but I hope it's understandable.
Method XF\Service\User\Delete::delete
sets entity options to avoid running jobs XF:UserRenameCleanup
and XF:UserDeleteCleanup
when renaming a user before deletion:
[php]
if ($this->renameTo)
{
$user->reset();
$user->setOption('admin_edit', true);
$user->setOption('enqueue_rename_cleanup', false);
$user->setOption('enqueue_delete_cleanup', false);
$user->set('username', $this->renameTo);
[/php]
After deletion,. those jobs are enqueued in method runPostDeleteJobs
[php]
if ($this->renameTo)
{
$jobList = [
[
'XF:UserRenameCleanUp',
[
'originalUserId' => $user->user_id,
'originalUserName' => $this->originalUserName,
'newUserName' => $this->renameTo
]
],
[
'XF:UserDeleteCleanUp',
[
'userId' => $user->user_id,
'username' => $this->renameTo
]
]
];
$this->app->jobManager()->enqueueUnique('userRenameDelete' . $user->user_id, 'XF:Atomic', [
'execute' => $jobList
]);
[/php]
Job XF:UserRenameCleanUp
calls service XF:User\ContentChange
in method run
[php]
/** @var \XF\Service\User\ContentChange $contentChanger */
$contentChanger = $this->app->service(
'XF:User\ContentChange', $this->data['originalUserId'], $this->data['originalUserName']
);
$contentChanger->setupForNameChange($this->data['newUserName']);
$contentChanger->restoreState($this->data['currentStep'], $this->data['lastOffset']);
$result = $contentChanger->apply($maxRunTime);
[/php]
Job XF:UserDeleteCleanUp
calls service XF:User\DeleteCleanUp
in method run
[php]
$deleter = $this->app->service(
'XF:User\DeleteCleanUp', $this->data['userId'], $this->data['username']
);
$deleter->restoreState($this->data['currentStep'], $this->data['lastOffset']);
$result = $deleter->cleanUp($maxRunTime);
[/php]
Now finally service XF:User\DeleteCleanUp
calls service XF:User\ContentChange
in method stepChangeOwne
[php]
$contentChanger = $this->service('XF:User\ContentChange', $this->userId, $this->userName);
$contentChanger->setupForDelete();
if (is_array($lastOffset))
{
list($changeStep, $changeLastOffset) = $lastOffset;
$contentChanger->restoreState($changeStep, $changeLastOffset);
}
$result = $contentChanger->apply($maxRunTime);
[/php]
The overall effect of this call chain is that content is changed twice:
- First, only the username is changed to $newUsername via rename cleanup
- Afterwards the userid is changed to 0 via delete cleanup
This is inefficient and can cause quite some performance issues as noted in
[URL unfurl="true"]https://xenforo.com/community/threads/pruned-25k-members-and-now-the-server-is-grinding-to-a-halt.208030/post-1631745[/URL]
[URL unfurl="true"]https://xenforo.com/community/threads/too-many-mysql-processes-update-select-content_id-from-xf_reaction_content-where-content_type-and-reaction_.179637/post-1463518[/URL]
In case a user account is renamed before deletion there should be only one content change:
Update userid to 0 and set username to $newUsername