diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/ProjectionErrorTest.php b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/ProjectionErrorTest.php
index 2f2551dcb5..48973e51fe 100644
--- a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/ProjectionErrorTest.php
+++ b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/ProjectionErrorTest.php
@@ -24,68 +24,6 @@
final class ProjectionErrorTest extends AbstractSubscriptionEngineTestCase
{
- /** @test */
- public function projectionWithErrorCanBeReactivated()
- {
- $this->eventStore->setup();
- $this->fakeProjection->expects(self::once())->method('setUp');
- $this->subscriptionEngine->setup();
- $this->fakeProjection->expects(self::any())->method('status')->willReturn(ProjectionStatus::ok());
- $result = $this->subscriptionEngine->boot();
- self::assertEquals(ProcessedResult::success(0), $result);
- $this->expectOkayStatus('contentGraph', SubscriptionStatus::ACTIVE, SequenceNumber::none());
- $this->expectOkayStatus('Vendor.Package:FakeProjection', SubscriptionStatus::ACTIVE, SequenceNumber::none());
-
- // commit an event
- $this->commitExampleContentStreamEvent();
-
- // catchup active tries to apply the commited event
- $exception = new \RuntimeException('This projection is kaputt.');
- $this->fakeProjection->expects($i = self::exactly(2))->method('apply')->willReturnCallback(function ($_, EventEnvelope $eventEnvelope) use ($i, $exception) {
- match($i->getInvocationCount()) {
- 1 => [
- self::assertEquals(1, $eventEnvelope->sequenceNumber->value),
- throw $exception
- ],
- 2 => [
- // on second call is repaired:
- self::assertEquals(1, $eventEnvelope->sequenceNumber->value),
- ]
- };
- });
- $expectedStatusForFailedProjection = ProjectionSubscriptionStatus::create(
- subscriptionId: SubscriptionId::fromString('Vendor.Package:FakeProjection'),
- subscriptionStatus: SubscriptionStatus::ERROR,
- subscriptionPosition: SequenceNumber::none(),
- subscriptionError: SubscriptionError::fromPreviousStatusAndException(SubscriptionStatus::ACTIVE, $exception),
- setupStatus: ProjectionStatus::ok(),
- );
-
- $result = $this->subscriptionEngine->catchUpActive();
- self::assertEquals(ProcessedResult::failed(1, Errors::fromArray([Error::create(
- SubscriptionId::fromString('Vendor.Package:FakeProjection'),
- $exception->getMessage(),
- $exception,
- SequenceNumber::fromInteger(1)
- )])), $result);
-
- self::assertEquals(
- $expectedStatusForFailedProjection,
- $this->subscriptionStatus('Vendor.Package:FakeProjection')
- );
- $this->expectOkayStatus('contentGraph', SubscriptionStatus::ACTIVE, SequenceNumber::fromInteger(1));
-
- //
- // fix projection and catchup
- //
-
- // reactivate and catchup
- $result = $this->subscriptionEngine->reactivate(SubscriptionEngineCriteria::create([SubscriptionId::fromString('Vendor.Package:FakeProjection')]));
- self::assertNull($result->errors);
-
- $this->expectOkayStatus('Vendor.Package:FakeProjection', SubscriptionStatus::ACTIVE, SequenceNumber::fromInteger(1));
- }
-
/** @test */
public function fixFailedProjectionViaReset()
{
@@ -189,23 +127,6 @@ public function irreparableProjection()
self::assertEquals(Result::success(), $result);
self::assertEquals($expectedFailure, $this->subscriptionStatus('Vendor.Package:SecondFakeProjection'));
- // reactivation will attempt to retry fix this, but can only work if the projection is repaired and will lead to an error otherwise:
- $result = $this->subscriptionEngine->reactivate();
- self::assertEquals(1, $result->numberOfProcessedEvents);
- self::assertEquals('Must not happen! Debug projection detected duplicate event 1 of type ContentStreamWasCreated', $result->errors->first()?->message);
-
- self::assertEquals(
- ProjectionSubscriptionStatus::create(
- subscriptionId: SubscriptionId::fromString('Vendor.Package:SecondFakeProjection'),
- subscriptionStatus: SubscriptionStatus::ERROR,
- subscriptionPosition: SequenceNumber::none(),
- // previous state is now an error too also error:
- subscriptionError: SubscriptionError::fromPreviousStatusAndException(SubscriptionStatus::ERROR, $result->errors->first()->throwable),
- setupStatus: ProjectionStatus::ok(),
- ),
- $this->subscriptionStatus('Vendor.Package:SecondFakeProjection')
- );
-
// expect the subscriptionError to be reset to null
$result = $this->subscriptionEngine->reset();
self::assertNull($result->errors);
diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/SubscriptionDetachedStatusTest.php b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/SubscriptionDetachedStatusTest.php
index 68dd3f8f7e..8762343bf7 100644
--- a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/SubscriptionDetachedStatusTest.php
+++ b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Subscription/SubscriptionDetachedStatusTest.php
@@ -111,7 +111,7 @@ public function projectionIsDetachedOnCatchupActive()
}
/** @test */
- public function projectionIsDetachedOnSetupAndReattachedIfPossible()
+ public function projectionIsDetachedOnSetup()
{
$this->fakeProjection->expects(self::once())->method('setUp');
$this->fakeProjection->expects(self::once())->method('apply');
@@ -159,12 +159,6 @@ public function projectionIsDetachedOnSetupAndReattachedIfPossible()
),
$this->subscriptionStatus('Vendor.Package:FakeProjection')
);
-
- // reactivate does re-attach as the projection if its found again
- $result = $this->subscriptionEngine->reactivate(SubscriptionEngineCriteria::create([SubscriptionId::fromString('Vendor.Package:FakeProjection')]));
- self::assertNull($result->errors);
-
- $this->expectOkayStatus('Vendor.Package:FakeProjection', SubscriptionStatus::ACTIVE, SequenceNumber::fromInteger(1));
}
/** @test */
diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryMaintainer.php b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryMaintainer.php
index 3c54d5ce7f..52bcf1edce 100644
--- a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryMaintainer.php
+++ b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryMaintainer.php
@@ -51,20 +51,19 @@
*
* Special cases:
*
- * *Replay*
+ * *Replay for initialisation*
*
* For initialising on a new database - which contains events already - a replay will make sure that the subscriptions
* are emptied and reapply the events. This can be triggered via {@see replaySubscription} or {@see replayAllSubscriptions}
*
* And after registering a new subscription a setup as well as a replay of this subscription is also required.
*
- * *Reactivate*
+ * *Replay to repair*
*
- * In case a subscription is detached but is reinstalled a reactivation is needed via {@see reactivateSubscription}
+ * In case a subscription is detached and then reinstalled a replay will make sure its caught up to all new events.
+ * And that the previous state will be reset as the projections logic might have changed.
*
- * Also in case a subscription runs into the error status, its code needs to be fixed, and it can also be attempted to be reactivated.
- *
- * Note that in both cases a subscription replay would also work, but with the difference that the subscription is reset as well.
+ * Also in case a subscription runs into the error status, its code needs to be fixed, and it can be attempted to be replayed.
*
* @api
*/
@@ -149,29 +148,6 @@ public function replayAllSubscriptions(\Closure|null $progressCallback = null):
return null;
}
- /**
- * Reactivate a subscription
- *
- * The explicit catchup is only needed for subscriptions in the error or detached status with an advanced position.
- * Running a full replay would work but might be overkill, instead this reactivation will just attempt
- * catchup the subscription back to active from its current position.
- */
- public function reactivateSubscription(SubscriptionId $subscriptionId, \Closure|null $progressCallback = null): Error|null
- {
- $subscriptionStatus = $this->subscriptionEngine->subscriptionStatus(SubscriptionEngineCriteria::create([$subscriptionId]))->first();
- if ($subscriptionStatus === null) {
- return new Error(sprintf('Subscription "%s" is not registered.', $subscriptionId->value));
- }
- if ($subscriptionStatus->subscriptionStatus === SubscriptionStatus::NEW) {
- return new Error(sprintf('Subscription "%s" is not setup and cannot be reactivated.', $subscriptionId->value));
- }
- $reactivateResult = $this->subscriptionEngine->reactivate(SubscriptionEngineCriteria::create([$subscriptionId]), progressCallback: $progressCallback, batchSize: self::REPLAY_BATCH_SIZE);
- if ($reactivateResult->errors !== null) {
- return self::createErrorForReason('Could not reactivate subscriber:', $reactivateResult->errors);
- }
- return null;
- }
-
/**
* WARNING: Removes all events from the content repository and resets the subscriptions
* This operation cannot be undone.
diff --git a/Neos.ContentRepository.Core/Classes/Subscription/Engine/SubscriptionEngine.php b/Neos.ContentRepository.Core/Classes/Subscription/Engine/SubscriptionEngine.php
index 22f60d23e7..fe9680fa27 100644
--- a/Neos.ContentRepository.Core/Classes/Subscription/Engine/SubscriptionEngine.php
+++ b/Neos.ContentRepository.Core/Classes/Subscription/Engine/SubscriptionEngine.php
@@ -92,14 +92,6 @@ public function catchUpActive(SubscriptionEngineCriteria|null $criteria = null,
);
}
- public function reactivate(SubscriptionEngineCriteria|null $criteria = null, \Closure $progressCallback = null, int $batchSize = null): ProcessedResult
- {
- $criteria ??= SubscriptionEngineCriteria::noConstraints();
- return $this->processExclusively(
- fn () => $this->catchUpSubscriptions($criteria, SubscriptionStatusFilter::fromArray([SubscriptionStatus::ERROR, SubscriptionStatus::DETACHED]), $progressCallback, $batchSize)
- );
- }
-
public function reset(SubscriptionEngineCriteria|null $criteria = null): Result
{
$criteria ??= SubscriptionEngineCriteria::noConstraints();
diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php
index c51957ed96..e312c2827a 100644
--- a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php
+++ b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php
@@ -84,9 +84,8 @@ public function statusCommand(string $contentRepository = 'default', bool $verbo
$contentRepositoryMaintainer = $this->contentRepositoryRegistry->buildService($contentRepositoryId, new ContentRepositoryMaintainerFactory());
$crStatus = $contentRepositoryMaintainer->status();
$hasErrors = false;
- $reactivationRequired = false;
+ $replayRequired = false;
$setupRequired = false;
- $bootingRequired = false;
$this->outputLine('Event Store:');
$this->output(' Setup: ');
$this->outputLine(match ($crStatus->eventStoreStatus->type) {
@@ -148,9 +147,9 @@ public function statusCommand(string $contentRepository = 'default', bool $verbo
$this->outputLine(' at position %d', [$status->subscriptionPosition->value]);
}
$hasErrors |= $status->subscriptionStatus === SubscriptionStatus::ERROR;
- $reactivationRequired |= $status->subscriptionStatus === SubscriptionStatus::ERROR;
- $bootingRequired |= $status->subscriptionStatus === SubscriptionStatus::BOOTING;
- $reactivationRequired |= $status->subscriptionStatus === SubscriptionStatus::DETACHED;
+ $replayRequired |= $status->subscriptionStatus === SubscriptionStatus::ERROR;
+ $replayRequired |= $status->subscriptionStatus === SubscriptionStatus::BOOTING;
+ $replayRequired |= $status->subscriptionStatus === SubscriptionStatus::DETACHED;
if ($verbose && $status->subscriptionError !== null) {
$lines = explode(chr(10), $status->subscriptionError->errorMessage ?: 'No details available.');
foreach ($lines as $line) {
@@ -164,11 +163,8 @@ public function statusCommand(string $contentRepository = 'default', bool $verbo
if ($setupRequired) {
$this->outputLine('Setup required, please run ./flow cr:setup');
}
- if ($bootingRequired) {
- $this->outputLine('Replay needed for BOOTING projections, please run ./flow subscription:replay [subscription-id]');
- }
- if ($reactivationRequired) {
- $this->outputLine('Reactivation of ERROR or DETACHED projection required, please run ./flow subscription:reactivate [subscription-id]');
+ if ($replayRequired) {
+ $this->outputLine('Replay needed for BOOTING, ERROR or DETACHED subscriptions, please run ./flow subscription:replay [subscription-id]');
}
}
if ($hasErrors) {
diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/SubscriptionCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/SubscriptionCommandController.php
index 4da391f52c..d8ceb350f8 100644
--- a/Neos.ContentRepositoryRegistry/Classes/Command/SubscriptionCommandController.php
+++ b/Neos.ContentRepositoryRegistry/Classes/Command/SubscriptionCommandController.php
@@ -17,18 +17,19 @@
*
* If any interaction is required "./flow cr:status" can be asked.
*
- * *Replay*
+ * *Replay for initialisation*
*
* For initialising on a new database - which contains events already - a replay will make sure that the subscriptions
* are emptied and reapply the events. This can be triggered via "./flow subscription:replay --subscription contentGraph" or "./flow subscription:replayall"
*
* And after registering a new subscription a setup as well as a replay of this subscription is also required.
*
- * *Reactivate*
+ * *Replay to repair*
*
- * In case a subscription is detached but is reinstalled a reactivation is needed via "./flow subscription:reactivate --subscription contentGraph"
+ * In case a subscription is detached and then reinstalled a replay will make sure its caught up to all new events.
+ * And that the previous state will be reset as the projections logic might have changed.
*
- * Also in case a subscription runs into the error status, its code needs to be fixed, and it can also be attempted to be reactivated.
+ * Also in case a subscription runs into the error status, its code needs to be fixed, and it can be attempted to be replayed.
*
* See also {@see ContentRepositoryMaintainer} for more information.
*/
@@ -137,44 +138,4 @@ public function replayAllCommand(string $contentRepository = 'default', bool $fo
$this->outputLine('Done.');
}
}
-
- /**
- * Reactivate a subscription
- *
- * The explicit catchup is only needed for projections in the error or detached status with an advanced position.
- * Running a full replay would work but might be overkill, instead this reactivation will just attempt
- * catchup the subscription back to active from its current position.
- *
- * @param string $subscription Identifier of the subscription to reactivate like it was configured (e.g. "contentGraph", "Vendor.Package:YourProjection")
- * @param string $contentRepository Identifier of the Content Repository instance to operate on
- * @param bool $quiet If set only fatal errors are rendered to the output (must be used with --force flag to avoid user input)
- */
- public function reactivateCommand(string $subscription, string $contentRepository = 'default', bool $quiet = false): void
- {
- $contentRepositoryId = ContentRepositoryId::fromString($contentRepository);
- $contentRepositoryMaintainer = $this->contentRepositoryRegistry->buildService($contentRepositoryId, new ContentRepositoryMaintainerFactory());
-
- $progressCallback = null;
- if (!$quiet) {
- $this->outputLine('Reactivate subscription "%s" of Content Repository "%s" ...', [$subscription, $contentRepositoryId->value]);
- // render memory consumption and time remaining
- $this->output->getProgressBar()->setFormat('debug');
- $this->output->progressStart();
- $progressCallback = fn () => $this->output->progressAdvance();
- }
-
- $result = $contentRepositoryMaintainer->reactivateSubscription(SubscriptionId::fromString($subscription), progressCallback: $progressCallback);
-
- if (!$quiet) {
- $this->output->progressFinish();
- $this->outputLine();
- }
-
- if ($result !== null) {
- $this->outputLine('%s', [$result->getMessage()]);
- $this->quit(1);
- } elseif (!$quiet) {
- $this->outputLine('Done.');
- }
- }
}
diff --git a/Neos.ContentRepositoryRegistry/Tests/Functional/ContentRepositoryMaintenanceCommandControllerTest.php b/Neos.ContentRepositoryRegistry/Tests/Functional/ContentRepositoryMaintenanceCommandControllerTest.php
index cf8de0c0b8..7dd43195d5 100644
--- a/Neos.ContentRepositoryRegistry/Tests/Functional/ContentRepositoryMaintenanceCommandControllerTest.php
+++ b/Neos.ContentRepositoryRegistry/Tests/Functional/ContentRepositoryMaintenanceCommandControllerTest.php
@@ -135,7 +135,7 @@ public function projectionInError(): void
// repair projection
$this->secondFakeProjection->killSaboteur();
- $this->subscriptionController->reactivateCommand(subscription: 'Vendor.Package:SecondFakeProjection', contentRepository: $this->contentRepository->id->value, quiet: true);
+ $this->subscriptionController->replayCommand(subscription: 'Vendor.Package:SecondFakeProjection', contentRepository: $this->contentRepository->id->value, force: true, quiet: true);
$this->expectOkayStatus('Vendor.Package:SecondFakeProjection', SubscriptionStatus::ACTIVE, SequenceNumber::fromInteger(2));
}