Ecotone version(s) affected: PDO Event Sourcing 1.326.1; reproduced on ecotone-dev main at 9a282a2b with Prooph PDO Event Store 1.16.5.
Description
After a database reconnect, a cached Prooph Projection Manager can keep using the previous PDO connection while the Event Store uses the new one. A synchronous event-driven projection then cannot see an event appended inside the current transaction, leaving the read model unchanged.
How to reproduce
- Initialize a synchronous event-driven projection and process a command so that the Prooph Projection Manager is cached.
- Close the underlying Doctrine DBAL connection, then begin a transaction on the same DBAL connection object.
- Send another aggregate command that appends an event and triggers the synchronous projection.
- Query the read model before committing.
Using the existing Ticket fixtures, the relevant part is:
$ecotone->initializeProjection(InProgressTicketList::IN_PROGRESS_TICKET_PROJECTION);
$ecotone->sendCommand(new RegisterTicket('123', 'Johnny', 'alert'));
$connection->close();
$connection->beginTransaction();
try {
$ecotone->sendCommand(new CloseTicket('123'));
self::assertTrue($connection->isTransactionActive());
self::assertEquals([], $ecotone->sendQueryWithRouting('getInProgressTickets'));
} finally {
$connection->rollBack();
}
Expected: the projection reads through the current connection and removes the closed ticket from the read model before commit.
Actual: the ticket remains in the read model because the cached manager reads through the old connection and cannot see the uncommitted event.
The complete regression test and bootstrap are included in #702 as test_synchronous_projection_uses_the_current_transaction_after_reconnecting. The test also checks rollback and a subsequent retry.
Possible Solution
LazyProophEventStore::getEventStore() already detects a changed PDO and rebuilds its inner Event Store. However, LazyProophProjectionManager::getProjectionManager() returns the manager cached for the context without checking whether its Event Store is still current.
Associate each cached manager with the inner Event Store used to create it, and rebuild the manager when that instance changes. Proposed fix: #702.
Context
Reproduced on PHP 8.5.10, Doctrine DBAL 4.4.4 and PostgreSQL 18, using a single primary database connection, not a read replica.
This affects connection reuse in long-lived processes after a reconnect. Gap retries do not resolve this case: an invisible trailing event can produce an empty stream read rather than a detectable position gap. Polling may mask the visibility symptom by reading after commit, but does not repair the stale manager reference.
Ecotone version(s) affected: PDO Event Sourcing 1.326.1; reproduced on ecotone-dev main at
9a282a2bwith Prooph PDO Event Store 1.16.5.Description
After a database reconnect, a cached Prooph Projection Manager can keep using the previous PDO connection while the Event Store uses the new one. A synchronous event-driven projection then cannot see an event appended inside the current transaction, leaving the read model unchanged.
How to reproduce
Using the existing Ticket fixtures, the relevant part is:
Expected: the projection reads through the current connection and removes the closed ticket from the read model before commit.
Actual: the ticket remains in the read model because the cached manager reads through the old connection and cannot see the uncommitted event.
The complete regression test and bootstrap are included in #702 as
test_synchronous_projection_uses_the_current_transaction_after_reconnecting. The test also checks rollback and a subsequent retry.Possible Solution
LazyProophEventStore::getEventStore()already detects a changed PDO and rebuilds its inner Event Store. However,LazyProophProjectionManager::getProjectionManager()returns the manager cached for the context without checking whether its Event Store is still current.Associate each cached manager with the inner Event Store used to create it, and rebuild the manager when that instance changes. Proposed fix: #702.
Context
Reproduced on PHP 8.5.10, Doctrine DBAL 4.4.4 and PostgreSQL 18, using a single primary database connection, not a read replica.
This affects connection reuse in long-lived processes after a reconnect. Gap retries do not resolve this case: an invisible trailing event can produce an empty stream read rather than a detectable position gap. Polling may mask the visibility symptom by reading after commit, but does not repair the stale manager reference.