-
-
Notifications
You must be signed in to change notification settings - Fork 234
Description
Observation
The following exception occurs when trying to log in to the Neos backend:
Exception #1651153651 in line 101 of Packages/Libraries/neos/eventstore-doctrineadapter/src/DoctrineEventStore.php: Expected version: 0, actual version: 2
Analysis
The contentGraph
projection failed and stopped with status ERROR
(see cr_default_subscriptions
table) and the following error_message:
An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '<workspace-name>' for key 'cr_default_p_graph_workspace.PRIMARY'
And there are multiple WorkspaceWasCreated
events for the same workspace name, which should never happen 🤯
SQL query to debug that:
SELECT JSON_UNQUOTE(JSON_EXTRACT(payload, '$.workspaceName')) workspace, COUNT(*) count FROM cr_default_events WHERE type = 'WorkspaceWasCreated' GROUP BY JSON_EXTRACT(payload, '$.workspaceName') ORDER BY count DESC
(count
should be 1 for every workspace)
Bug
We're using the red model to enforce invariants, but there's a race condition between the check and the event publication:
neos-development-collection/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php
Lines 123 to 154 in 97a4585
$this->requireWorkspaceToNotExist($command->workspaceName, $commandHandlingDependencies); | |
$baseWorkspace = $commandHandlingDependencies->findWorkspaceByName($command->baseWorkspaceName); | |
if ($baseWorkspace === null) { | |
throw new BaseWorkspaceDoesNotExist(sprintf( | |
'The workspace %s (base workspace of %s) does not exist', | |
$command->baseWorkspaceName->value, | |
$command->workspaceName->value | |
), 1513890708); | |
} | |
$sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamVersion($baseWorkspace->currentContentStreamId); | |
$this->requireContentStreamToNotBeClosed($baseWorkspace->currentContentStreamId, $commandHandlingDependencies); | |
$this->requireContentStreamToNotExistYet($command->newContentStreamId, $commandHandlingDependencies); | |
// When the workspace is created, we first have to fork the content stream | |
yield $this->forkContentStream( | |
$command->newContentStreamId, | |
$baseWorkspace->currentContentStreamId, | |
$sourceContentStreamVersion, | |
sprintf('Create workspace %s with base %s', $command->workspaceName->value, $baseWorkspace->workspaceName->value) | |
); | |
yield new EventsToPublish( | |
WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(), | |
Events::with( | |
new WorkspaceWasCreated( | |
$command->workspaceName, | |
$command->baseWorkspaceName, | |
$command->newContentStreamId, | |
) | |
), | |
ExpectedVersion::ANY(), | |
); |
Fix
An easy fix would be to replace ExpectedVersion::ANY()
with ExpectedVersion::NO_STREAM()
.
This would mean that a workspace can't ever be re-created once it was deleted (via WorkspaceWasRemoved
).
But maybe that's the behavior we want to have since re-using a previously existing workspace sounds dangerous..
Alternatively we'll have to add a version
and removed
column to the workspace read model so that we can enforce constraints using optimistic locking (like we do with content streams)
Work around
- do a database backup!
- delete invalid
WorkspaceWasCreated
events from thecr_default_events
table - run the
./flow subscription:reactivate contentGraph
command to re-activate and catch up thecontentGraph
projection
Related: #5058