diff --git a/Classes/NodeRendering/Infrastructure/RedisRenderingQueue.php b/Classes/NodeRendering/Infrastructure/RedisRenderingQueue.php index 2dede72..d56fa5b 100644 --- a/Classes/NodeRendering/Infrastructure/RedisRenderingQueue.php +++ b/Classes/NodeRendering/Infrastructure/RedisRenderingQueue.php @@ -123,6 +123,37 @@ public function registerRenderingAttempt( ); } + /** + * Tells the orchestrator that the given node has been rendered, so that it can add the document to the content + * release right away - before an editor's publish gets the chance to flush it from the content cache again. + * + * Call this before {@see removeRenderingJobFromReservedList()}: the orchestrator stops waiting as soon as no + * rendering is in progress anymore, and then drains this list a last time. + * + * @throws \JsonException + */ + public function reportRenderedJob( + ContentReleaseIdentifier $contentReleaseIdentifier, + EnumeratedNode $enumeratedNode, + ): void { + $this->redisClientManager->getPrimaryRedis()->rPush( + $this->redisKeyService->getRedisKeyForPostfix($contentReleaseIdentifier, 'renderedJobs'), + json_encode($enumeratedNode, JSON_THROW_ON_ERROR), + ); + } + + public function fetchNextRenderedJob(ContentReleaseIdentifier $contentReleaseIdentifier): ?EnumeratedNode + { + $nextEntry = $this->redisClientManager + ->getPrimaryRedis() + ->lPop($this->redisKeyService->getRedisKeyForPostfix($contentReleaseIdentifier, 'renderedJobs')); + if (!is_string($nextEntry)) { + return null; + } + + return EnumeratedNode::fromJsonString($nextEntry); + } + /** * @param ContentReleaseIdentifier $contentReleaseIdentifier * @param EnumeratedNode $enumeratedNode @@ -171,6 +202,7 @@ public function flush(ContentReleaseIdentifier $contentReleaseIdentifier) $this->redisKeyService->getRedisKeyForPostfix($contentReleaseIdentifier, 'renderingJobQueue'), $this->redisKeyService->getRedisKeyForPostfix($contentReleaseIdentifier, 'inProgressRenderings'), $this->redisKeyService->getRedisKeyForPostfix($contentReleaseIdentifier, 'renderAttempts'), + $this->redisKeyService->getRedisKeyForPostfix($contentReleaseIdentifier, 'renderedJobs'), ); } } diff --git a/Classes/NodeRendering/NodeRenderOrchestrator.php b/Classes/NodeRendering/NodeRenderOrchestrator.php index 50a6fa1..8d84aff 100644 --- a/Classes/NodeRendering/NodeRenderOrchestrator.php +++ b/Classes/NodeRendering/NodeRenderOrchestrator.php @@ -20,8 +20,11 @@ use Flowpack\DecoupledContentStore\NodeRendering\ProcessEvents\ExitEvent; use Flowpack\DecoupledContentStore\NodeRendering\ProcessEvents\RenderingIterationCompletedEvent; use Flowpack\DecoupledContentStore\NodeRendering\ProcessEvents\RenderingQueueFilledEvent; +use Flowpack\DecoupledContentStore\PrepareContentRelease\Dto\ContentReleaseMetadata; use Flowpack\DecoupledContentStore\PrepareContentRelease\Infrastructure\RedisContentReleaseService; use Neos\Flow\Annotations as Flow; +use Neos\Fusion\Core\Cache\ContentCache; +use Neos\Neos\Fusion\Helper\CachingHelper; /** * TODO: explain concept of Working Set @@ -98,6 +101,15 @@ class NodeRenderOrchestrator */ protected $redisContentReleaseSizeService; + #[Flow\Inject] + protected ContentCache $contentCache; + + #[Flow\Inject] + protected CachingHelper $cachingHelper; + + #[Flow\InjectConfiguration('nodeRendering.flushDocumentCacheOnRetry')] + protected bool $flushDocumentCacheOnRetry; + private const EXIT_ERRORSTATUSCODE_RELEASE_ALREADY_COMPLETED = 1; private const EXIT_ERRORSTATUSCODE_EMPTY_ENUMERATION = 2; private const EXIT_ERRORSTATUSCODE_RETRY_LIMIT_REACHED = 3; @@ -105,8 +117,8 @@ class NodeRenderOrchestrator /** * How often the very same set of nodes may be scheduled in a row before we give up on them. The rendering gets - * one real retry (which flushes the content cache for these nodes, {@see NodeRenderer::flushContentCacheForNode()}) - * before this kicks in. + * one real retry (which flushes the content cache for these nodes, {@see flushContentCacheForRetry()}) before + * this kicks in. */ private const MAX_ITERATIONS_WITHOUT_PROGRESS = 3; @@ -121,6 +133,12 @@ public function renderContentRelease( ContentReleaseLogger $contentReleaseLogger, ): \Generator { $releaseMetadata = $this->redisContentReleaseService->fetchMetadataForContentRelease($contentReleaseIdentifier); + if ($releaseMetadata === null) { + throw new \RuntimeException(sprintf( + 'No metadata found for content release %s.', + $contentReleaseIdentifier->getIdentifier(), + )); + } $renderStatus = $releaseMetadata->getStatus(); if ($renderStatus->hasCompleted()) { @@ -185,6 +203,9 @@ public function renderContentRelease( // goTroughEnumeratedNodesFillContentReleaseAndCheckWhatStillNeedsToBeDone $nodesScheduledForRendering = []; + // Retries are queued only after their cache entries are flushed, see flushContentCacheForRetry(); the first + // iteration flushes nothing, so its jobs are queued right away to keep the render workers busy. + $queueWhileChecking = $i === 1 || !$this->flushDocumentCacheOnRetry; foreach ($currentEnumeration as $enumeratedNode) { assert($enumeratedNode instanceof EnumeratedNode); @@ -213,36 +234,20 @@ public function renderContentRelease( ]); // the rendered document was not found, or has holes. so we need to re-render. $nodesScheduledForRendering[] = $enumeratedNode; - $this->redisRenderingQueue->appendRenderingJob($contentReleaseIdentifier, $enumeratedNode); + if ($queueWhileChecking) { + $this->redisRenderingQueue->appendRenderingJob($contentReleaseIdentifier, $enumeratedNode); + } } } if (empty($nodesScheduledForRendering)) { // we have NO nodes scheduled for rendering anymore, so that means we FINISHED successfully. - $contentReleaseLogger->info(sprintf( - 'Everything rendered completely in %d seconds. Finishing RenderOrchestrator', - time() - $startTime, - )); - - // The release is complete now, so this is the point where we can determine its size once. Calculating - // it is expensive, which is why the Backend UI relies on this stored value instead of re-calculating it. - $contentReleaseSize = $this->redisContentReleaseSizeService->calculateReleaseSize( - RedisInstanceIdentifier::primary(), + yield from $this->completeContentRelease( $contentReleaseIdentifier, + $contentReleaseLogger, + $releaseMetadata, + $startTime, ); - $contentReleaseLogger->info(sprintf('Content release size: %.2f MB', $contentReleaseSize)); - - // info to all renderers that we finished, and they should terminate themselves gracefully. - $this->redisContentReleaseService->setContentReleaseMetadata( - $contentReleaseIdentifier, - $releaseMetadata->withStatus(NodeRenderingCompletionStatus::success()) - ->withEndTime(new \DateTimeImmutable()) - ->withContentReleaseSize($contentReleaseSize), - RedisInstanceIdentifier::primary(), - ); - - // Exit successfully. - yield ExitEvent::createWithStatusCode(0); return; } @@ -282,6 +287,13 @@ public function renderContentRelease( return; } + if (!$queueWhileChecking) { + $this->flushContentCacheForRetry($nodesScheduledForRendering, $i, $contentReleaseLogger); + foreach ($nodesScheduledForRendering as $enumeratedNode) { + $this->redisRenderingQueue->appendRenderingJob($contentReleaseIdentifier, $enumeratedNode); + } + } + // we remember the $totalJobsCount for displaying the rendering progress $totalJobsCount = count($nodesScheduledForRendering); // $remainingJobsCount is needed to figure out @@ -295,25 +307,40 @@ public function renderContentRelease( // Now, we need to wait for the rendering to complete. yield RenderingQueueFilledEvent::create(); $contentReleaseLogger->info('Waiting for renderings to complete...'); - $waitTimer = 0; + $lastDataPointTime = microtime(true); + $nodesAddedToContentRelease = []; while ( $this->redisRenderingQueue->numberOfQueuedJobs($contentReleaseIdentifier) > 0 || $this->redisRenderingQueue->numberOfRenderingsInProgress($contentReleaseIdentifier) > 0 ) { + $passDeadline = microtime(true) + 1; + $nodesAddedToContentRelease += $this->addReportedRenderingsToContentRelease( + $contentReleaseIdentifier, + $contentReleaseLogger, + $passDeadline, + ); $this->redisRenderingStatisticsStore->replaceLastStatisticsIteration($contentReleaseIdentifier, RenderingStatistics::create( $remainingJobsCount, $totalJobsCount, $renderingsPerSecondDataPoints, )); - sleep(1); - $waitTimer++; - if (($waitTimer % 10) === 0) { + // A backlog of reported renderings uses up the whole pass and is worked off without pausing. + $secondsLeftInPass = $passDeadline - microtime(true); + if ($secondsLeftInPass > 0) { + usleep((int) ($secondsLeftInPass * 1_000_000)); + } + // Measured in wall-clock time: the deadline is only checked between two renderings, so a pass can take + // longer than a second. + $secondsSinceLastDataPoint = microtime(true) - $lastDataPointTime; + if ($secondsSinceLastDataPoint >= 10) { + $lastDataPointTime = microtime(true); $previousRemainingJobs = $remainingJobsCount; $remainingJobsCount = $this->redisRenderingQueue->numberOfQueuedJobs($contentReleaseIdentifier); - $jobsWorkedThroughOverLastTenSeconds = $previousRemainingJobs - $remainingJobsCount; - $renderingsPerSecondDataPoints[] = $jobsWorkedThroughOverLastTenSeconds / 10; + $jobsWorkedThroughSinceLastDataPoint = $previousRemainingJobs - $remainingJobsCount; + $renderingsPerSecondDataPoints[] = + $jobsWorkedThroughSinceLastDataPoint / $secondsSinceLastDataPoint; $contentReleaseLogger->debug('Waiting... ', [ 'numberOfQueuedJobs' => $remainingJobsCount, @@ -324,6 +351,11 @@ public function renderContentRelease( $this->concurrentBuildLockService->assertNoOtherContentReleaseWasStarted($contentReleaseIdentifier); } } + $nodesAddedToContentRelease += $this->addReportedRenderingsToContentRelease( + $contentReleaseIdentifier, + $contentReleaseLogger, + null, + ); // NOTE: we do not abort rendering inside NodeRenderer when we encounter the first error, but we try to render // all pages in the full iteration until we stop the content release here. @@ -356,11 +388,156 @@ public function renderContentRelease( yield RenderingIterationCompletedEvent::create(); - $contentReleaseLogger->info('Rendering iteration completed. Continuing with next iteration.'); - // here, the rendering has completed. in the next iteration, we try to copy the - // nodes which have been rendered in this iteration to the content store - so we iterate over the - // just-rendered nodes. - $currentEnumeration = $nodesScheduledForRendering; - } while (!empty($currentEnumeration)); + // The next iteration checks the content cache once more for every node which did not make it into the + // content release yet - those whose content cache entry already had holes when its rendering was + // reported, and any whose report went missing. + $currentEnumeration = array_values(array_filter( + $nodesScheduledForRendering, + fn(EnumeratedNode $enumeratedNode) => !array_key_exists( + json_encode($enumeratedNode, JSON_THROW_ON_ERROR), + $nodesAddedToContentRelease, + ), + )); + if (empty($currentEnumeration)) { + yield from $this->completeContentRelease( + $contentReleaseIdentifier, + $contentReleaseLogger, + $releaseMetadata, + $startTime, + ); + return; + } + $contentReleaseLogger->info(sprintf( + 'Rendering iteration completed; %d of %d rendered nodes are not in the content release yet. Continuing with next iteration.', + count($currentEnumeration), + $totalJobsCount, + )); + } while (true); + } + + /** + * Simply rendering a node again does not necessarily help: if its content cache entries are still valid, the + * rendering is served straight from the content cache. Fusion then never processes a document-level cache + * segment, so {@see \Flowpack\DecoupledContentStore\Aspects\CacheUrlMappingAspect} does not write the "doc--..." + * mapping entry the content release needs - and the node is scheduled again, and again, until the retry limit + * aborts the whole release. Flushing the node's cache entries turns the re-rendering into a real rendering. + * + * This happens here and before the rendering jobs are queued: the node tag covers all dimension variants of a + * node, so a flush by a render worker wipes the variants other workers have just rendered. + * + * @param EnumeratedNode[] $nodesScheduledForRendering + */ + private function flushContentCacheForRetry( + array $nodesScheduledForRendering, + int $iteration, + ContentReleaseLogger $contentReleaseLogger, + ): void { + $tags = []; + foreach ($nodesScheduledForRendering as $enumeratedNode) { + $tags[] = + 'Node_' + . $this->cachingHelper->renderWorkspaceTagForContextNode( + $enumeratedNode->getWorkspaceNameFromContextPath(), + ) + . '_' + . $enumeratedNode->getNodeIdentifier(); + } + + foreach (array_unique($tags) as $tag) { + $contentReleaseLogger->warn(sprintf( + 'Iteration %d: flushed %d content cache entries for tag %s before rendering its nodes again.', + $iteration, + $this->contentCache->flushByTag($tag), + $tag, + )); + } + } + + private function completeContentRelease( + ContentReleaseIdentifier $contentReleaseIdentifier, + ContentReleaseLogger $contentReleaseLogger, + ContentReleaseMetadata $releaseMetadata, + int $startTime, + ): \Generator { + $contentReleaseLogger->info(sprintf( + 'Everything rendered completely in %d seconds. Finishing RenderOrchestrator', + time() - $startTime, + )); + + // The release is complete now, so this is the point where we can determine its size once. Calculating + // it is expensive, which is why the Backend UI relies on this stored value instead of re-calculating it. + $contentReleaseSize = $this->redisContentReleaseSizeService->calculateReleaseSize( + RedisInstanceIdentifier::primary(), + $contentReleaseIdentifier, + ); + $contentReleaseLogger->info(sprintf('Content release size: %.2f MB', $contentReleaseSize)); + + // info to all renderers that we finished, and they should terminate themselves gracefully. + $this->redisContentReleaseService->setContentReleaseMetadata( + $contentReleaseIdentifier, + $releaseMetadata + ->withStatus(NodeRenderingCompletionStatus::success()) + ->withEndTime(new \DateTimeImmutable()) + ->withContentReleaseSize($contentReleaseSize), + RedisInstanceIdentifier::primary(), + ); + + // Exit successfully. + yield ExitEvent::createWithStatusCode(0); + } + + /** + * Copies every node the render workers reported as rendered ({@see RedisRenderingQueue::reportRenderedJob()}) + * from the content cache to the content release. + * + * This happens while the rendering iteration is still running: an editor publishing in the content module flushes + * content cache tags, and the longer a rendered page waits in the content cache, the likelier it is gone again - + * on a busy day often enough to run the release into the iteration limit. + * + * A node whose content cache entry already has holes is left alone; the next iteration schedules it again. + * + * Workers can report faster than a single orchestrator adds, so while rendering is running a deadline hands + * control back to the wait loop, which keeps the statistics and the concurrent release check going. + * + * @param float|null $deadline Unix timestamp (microtime) after which no further rendering is added; null for none + * @return array JSON-encoded nodes which were added to the content release + */ + private function addReportedRenderingsToContentRelease( + ContentReleaseIdentifier $contentReleaseIdentifier, + ContentReleaseLogger $contentReleaseLogger, + ?float $deadline, + ): array { + $nodesAddedToContentRelease = []; + while ($deadline === null || microtime(true) < $deadline) { + $enumeratedNode = $this->redisRenderingQueue->fetchNextRenderedJob($contentReleaseIdentifier); + if ($enumeratedNode === null) { + break; + } + $renderedDocumentFromContentCache = + $this->nodeRenderingExtensionManager->tryToExtractRenderingForEnumeratedNodeFromContentCache( + $enumeratedNode, + ); + if (!$renderedDocumentFromContentCache->isComplete()) { + $contentReleaseLogger->debug('Rendered node is incomplete in the content cache, it is scheduled again in the next iteration: ' + . $renderedDocumentFromContentCache->getIncompleteReason(), [ + 'url' => $renderedDocumentFromContentCache->getUrl(), + 'node' => $enumeratedNode, + ]); + continue; + } + + $contentReleaseLogger->debug('Node rendered, adding to content release', [ + 'url' => $renderedDocumentFromContentCache->getUrl(), + 'node' => $enumeratedNode, + ]); + $this->nodeRenderingExtensionManager->addRenderedDocumentToContentRelease( + $contentReleaseIdentifier, + $enumeratedNode, + $renderedDocumentFromContentCache, + $contentReleaseLogger, + ); + $nodesAddedToContentRelease[json_encode($enumeratedNode, JSON_THROW_ON_ERROR)] = true; + } + return $nodesAddedToContentRelease; } } diff --git a/Classes/NodeRendering/NodeRenderer.php b/Classes/NodeRendering/NodeRenderer.php index 9d851ca..ec6443a 100644 --- a/Classes/NodeRendering/NodeRenderer.php +++ b/Classes/NodeRendering/NodeRenderer.php @@ -25,9 +25,7 @@ use Neos\ContentRepository\Domain\Service\ContextFactoryInterface; use Neos\Flow\Annotations as Flow; use Neos\Flow\Persistence\PersistenceManagerInterface; -use Neos\Fusion\Core\Cache\ContentCache; use Neos\Neos\Domain\Repository\SiteRepository; -use Neos\Neos\Fusion\Helper\CachingHelper; /** * Not called directly, but through Scripts/renderWorker.sh. @@ -37,18 +35,12 @@ * * * The NodeRenderer does NOT directly add the rendered document to the Content Release, in order to reduce special - * cases and complexity. Instead, the NodeRenderer ONLY fills the Content Cache. + * cases and complexity. Instead, the NodeRenderer ONLY fills the Content Cache, and reports every rendered node back + * to the NodeRenderOrchestrator ({@see RedisRenderingQueue::reportRenderedJob()}), which copies it from the content + * cache to the content release within about a second. * - * This leads to unnecessary re-renderings in the following cases: - * - A page has been rendered by NodeRenderer. Thus, it was added to the content cache. - * - An editor does a change which flushes some cache tags. - * - depending on the cache tags, this can lead to our just-rendered page to be deleted from the content cache again. - * - After the rendering is complete, the NodeRenderOrchestrator again tries to copy the page to the content release from the cache, - * and this FAILS because it has been removed in the step before. - * - Thus, a re-rendering is triggered. - * - * If the above happens often, we can add additional code to take care of this. Right now I do not want to - * implement it to keep complexity low and keep the code paths in case of re-rendering or not re-rendering the same. + * If an editor publishes in that window and the publish flushes cache tags of the just-rendered page, the copy finds + * holes in the content cache and the page is rendered again in the next iteration. * * @Flow\Scope("singleton") */ @@ -75,24 +67,6 @@ class NodeRenderer */ protected $documentRenderer; - /** - * @Flow\Inject - * @var ContentCache - */ - protected $contentCache; - - /** - * @Flow\Inject - * @var CachingHelper - */ - protected $cachingHelper; - - /** - * @Flow\InjectConfiguration("nodeRendering.flushDocumentCacheOnRetry") - * @var bool - */ - protected $flushDocumentCacheOnRetry; - /** * @Flow\Inject * @var RedisRenderingQueue @@ -206,6 +180,7 @@ public function render( // images, which will re-appear once you open the page in the backend (because image URL generation // is fully deterministic). This happened 12/2022 to us. $this->persistenceManager->persistAll(); + $this->redisRenderingQueue->reportRenderedJob($contentReleaseIdentifier, $enumeratedNode); } finally { $removalSuccess = $this->redisRenderingQueue->removeRenderingJobFromReservedList( $contentReleaseIdentifier, @@ -278,16 +253,13 @@ protected function renderDocumentNodeVariant( } else { $nodeWasFound = true; - if ($renderingAttempt > 1) { - $this->flushContentCacheForNode($node, $enumeratedNode, $renderingAttempt, $contentReleaseLogger); - } - $contentReleaseLogger->debug('Rendering document node variant', [ 'node' => $node->getContextPath(), 'nodeIdentifier' => $node->getIdentifier(), 'workspaceName' => $enumeratedNode->getWorkspaceNameFromContextPath(), 'dimensions' => $enumeratedNode->getDimensionsFromContextPath(), 'arguments' => $enumeratedNode->getArguments(), + 'renderingAttempt' => $renderingAttempt, ]); $tracer = $this->renderTracerProvider->getTracer(); @@ -374,44 +346,6 @@ protected function renderDocumentNodeVariant( } } - /** - * A node is handed out for rendering more than once if the previous rendering did not produce a complete content - * cache entry for it - see {@see NodeRenderOrchestrator}. - * - * Simply rendering it again does not necessarily help: if the document's cache entries are still valid, the - * rendering is served straight from the content cache. In that case Fusion never processes a document-level cache - * segment, so {@see CacheUrlMappingAspect} does not write the "doc--..." mapping entry which the orchestrator is - * waiting for - and the node is scheduled again, and again, until the retry limit aborts the whole release. - * - * Flushing the node's cache entries before the retry turns the re-rendering into a real rendering again. - */ - private function flushContentCacheForNode( - NodeInterface $node, - EnumeratedNode $enumeratedNode, - int $renderingAttempt, - ContentReleaseLogger $contentReleaseLogger, - ): void { - if (!$this->flushDocumentCacheOnRetry) { - return; - } - - $flushedEntriesCount = 0; - foreach ($this->cachingHelper->nodeTag($node) as $tag) { - $flushedEntriesCount += $this->contentCache->flushByTag($tag); - } - - $contentReleaseLogger->warn( - sprintf( - 'Rendering attempt %d for this node; flushed %d content cache entries before re-rendering it.', - $renderingAttempt, - $flushedEntriesCount, - ), - [ - 'node' => $enumeratedNode->debugString(), - ], - ); - } - /** * @param EnumeratedNode $enumeratedNode * @return NodeInterface|null diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index a3dd0d5..6f5e062 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -198,6 +198,12 @@ Flowpack: transferMode: 'dump' isRequired: false copyOnQuickRelease: false + renderedJobs: + redisKeyPostfix: 'renderedJobs' + transfer: false + transferMode: 'dump' + isRequired: false + copyOnQuickRelease: false renderedMetadata: redisKeyPostfix: 'renderedMetadata' transfer: true diff --git a/README.md b/README.md index 378f231..26ee094 100644 --- a/README.md +++ b/README.md @@ -93,17 +93,24 @@ The following flow chart shows the rendering pipeline for creating a content rel - Then, the rendering takes place. In parallel, the **orchestrator** checks if pages are already fully rendered. If no, he creates rendering jobs. If yes, the rendered page is added to the in-progress content release. - The **renderers** simply render the pages as instructed by the orchestrator. + The **renderers** simply render the pages as instructed by the orchestrator, and report every rendered page back to + it. The orchestrator copies a reported page to the content release right away instead of waiting for the whole + rendering queue - a page which waits in the content cache for long is likely to be flushed again by an editor + publishing in the meantime. This makes the release eventually consistent: a page can go live in a state that is a + few minutes older or newer than a page it links to. The publish which made it outdated schedules the next + incremental release, which renders it again - unless automatic releases are paused. The **orchestrator** tries to render multiple times: It can happen that after a render, the rendering did not successfully work, because an editor has changed pages at the same time; leading to content cache flushes and "holes" in the output. - From the second attempt on, the renderer **flushes the document's content cache entries** (by node tag) before - re-rendering it. Without this, a retry can be answered completely from the content cache - then no document-level + From the second attempt on, the orchestrator **flushes the document's content cache entries** (by node tag) before + scheduling it again. Without this, a retry can be answered completely from the content cache - then no document-level cache segment is processed, `CacheUrlMappingAspect` writes no `doc--...` mapping entry, and the orchestrator schedules the very same node again in the next iteration. Can be turned off via the setting - `nodeRendering.flushDocumentCacheOnRetry`. If the identical set of nodes is scheduled three iterations in a row, the + `nodeRendering.flushDocumentCacheOnRetry`. The flush happens once per node and before any of its jobs is queued: + the node's cache tag covers all its dimension variants, so a render worker flushing it would wipe the variants other + workers have just rendered. If the identical set of nodes is scheduled three iterations in a row, the orchestrator gives up early and registers a rendering error per node instead of running into the 10-attempt limit. - During **validation**, checks can happen to see whether the content release is fully complete; to check whether it diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ba94632..59aa800 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -396,12 +396,6 @@ parameters: count: 3 path: Classes/Core/ConcurrentBuildLockService.php - - - message: '#^Parameter \#2 \$fields_and_vals of method Redis\:\:hSet\(\) expects string, string\|null given\.$#' - identifier: argument.type - count: 1 - path: Classes/Core/ConcurrentBuildLockService.php - - message: '#^Method Flowpack\\DecoupledContentStore\\Core\\Domain\\Dto\\ContentReleaseBatchResult\:\:__construct\(\) has parameter \$results with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -1176,18 +1170,6 @@ parameters: count: 1 path: Classes/NodeRendering/NodeRenderOrchestrator.php - - - message: '#^Cannot call method getStatus\(\) on Flowpack\\DecoupledContentStore\\PrepareContentRelease\\Dto\\ContentReleaseMetadata\|null\.$#' - identifier: method.nonObject - count: 1 - path: Classes/NodeRendering/NodeRenderOrchestrator.php - - - - message: '#^Cannot call method withStatus\(\) on Flowpack\\DecoupledContentStore\\PrepareContentRelease\\Dto\\ContentReleaseMetadata\|null\.$#' - identifier: method.nonObject - count: 5 - path: Classes/NodeRendering/NodeRenderOrchestrator.php - - message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#' identifier: empty.notAllowed @@ -1200,12 +1182,6 @@ parameters: count: 1 path: Classes/NodeRendering/NodeRenderOrchestrator.php - - - message: '#^Variable \$currentEnumeration in empty\(\) always exists and is not falsy\.$#' - identifier: empty.variable - count: 1 - path: Classes/NodeRendering/NodeRenderOrchestrator.php - - message: '#^Cannot call method getFirstActiveDomain\(\) on Neos\\Neos\\Domain\\Model\\Site\|null\.$#' identifier: method.nonObject