From f92d26f8441378872afb92f28089d724e8f022eb Mon Sep 17 00:00:00 2001 From: Allan Fernandes Date: Mon, 1 Jun 2026 15:44:22 -0300 Subject: [PATCH] Preserve root exception when bulk scheduling fails in BulkManagement/MassSchedule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BulkManagement::scheduleBulk() caught all exceptions, logged only the message, and returned false — discarding the exception object entirely. MassSchedule::publishMass() then threw a new LocalizedException with no previous exception attached, making the root cause invisible to callers, error monitoring tools, and exception chain walkers. Added getLastException() to BulkManagement to expose the last caught exception. MassSchedule now retrieves it and passes it as the previous exception to LocalizedException, preserving the full chain for debugging. --- .../Model/BulkManagement.php | 16 ++++++++++ .../Model/MassSchedule.php | 12 ++++++-- .../Test/Unit/Model/BulkManagementTest.php | 29 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php index 3aac6a103d5..81cc7a6d779 100644 --- a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php +++ b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php @@ -69,6 +69,11 @@ class BulkManagement implements BulkManagementInterface */ private $logger; + /** + * @var Exception|null + */ + private $lastException = null; + /** * BulkManagement constructor. * @param EntityManager $entityManager @@ -134,12 +139,23 @@ public function scheduleBulk($bulkUuid, array $operations, $description, $userId } catch (Exception $exception) { $connection->rollBack(); $this->logger->critical($exception->getMessage()); + $this->lastException = $exception; return false; } return true; } + /** + * Return the last exception caught by scheduleBulk(), or null if none. + * + * @return Exception|null + */ + public function getLastException(): ?Exception + { + return $this->lastException; + } + /** * Retry bulk operations that failed due to given errors. * diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php index 1ea6b82f78f..851665741ee 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -131,8 +131,12 @@ public function publishMass($topicName, array $entitiesArray, $groupId = null, $ /** create new bulk without operations */ if (!$this->bulkManagement->scheduleBulk($groupId, [], $bulkDescription, $userId)) { + $previous = method_exists($this->bulkManagement, 'getLastException') + ? $this->bulkManagement->getLastException() + : null; throw new LocalizedException( - __('Something went wrong while processing the request.') + __('Something went wrong while processing the request.'), + $previous ); } } @@ -167,11 +171,15 @@ public function publishMass($topicName, array $entitiesArray, $groupId = null, $ } if (!$this->bulkManagement->scheduleBulk($groupId, $operations, $bulkDescription, $userId)) { + $previous = method_exists($this->bulkManagement, 'getLastException') + ? $this->bulkManagement->getLastException() + : null; try { $this->bulkManagement->deleteBulk($groupId); } finally { throw new LocalizedException( - __('Something went wrong while processing the request.') + __('Something went wrong while processing the request.'), + $previous ); } } diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php index a351166049c..8d3452be7dc 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkManagementTest.php @@ -196,6 +196,35 @@ public function testScheduleBulkWithException(): void $this->assertFalse($this->bulkManagement->scheduleBulk($bulkUuid, [$operation], $description, $userId)); } + public function testGetLastExceptionReturnsExceptionCaughtByScheduleBulk(): void + { + $bulkUuid = 'bulk-001'; + $description = 'Bulk summary description...'; + $userId = 1; + $connectionName = 'default'; + $rootCause = new \LogicException('Root cause: queue broker unavailable'); + $operation = $this->createMock(OperationInterface::class); + $metadata = $this->createMock(EntityMetadataInterface::class); + + $this->metadataPool->method('getMetadata')->willReturn($metadata); + $metadata->method('getEntityConnectionName')->willReturn($connectionName); + $connection = $this->createMock(AdapterInterface::class); + $this->resourceConnection->method('getConnectionByName')->willReturn($connection); + $connection->method('beginTransaction')->willReturnSelf(); + $connection->method('rollBack')->willReturnSelf(); + + $bulkSummary = $this->createMock(BulkSummaryInterface::class); + $this->bulkSummaryFactory->method('create')->willReturn($bulkSummary); + $this->entityManager->method('load')->willThrowException($rootCause); + $this->logger->method('critical'); + + $this->assertNull($this->bulkManagement->getLastException()); + + $this->bulkManagement->scheduleBulk($bulkUuid, [$operation], $description, $userId); + + $this->assertSame($rootCause, $this->bulkManagement->getLastException()); + } + /** * Test for scheduleBulk method with exception during publishing. *