- Lunar version: 2.0.0-alpha.4
- Laravel Version: 13.19.0
- PHP Version: 8.5.7
- Database Driver & Version: 11.8.6
I found a lifecycle mismatch in CacheInvalidator. The class describes itself as request-scoped, but LunarServiceProvider binds it as a singleton.
More importantly, recording inside a transaction adds the target to $pending and registers an afterCommit callback. A full rollback discards that callback, but the pending entry is not cleared. A later autocommit mutation or committed transaction can therefore flush an invalidation for a change that was rolled back. A later transaction for the same target can also skip callback registration because the stale key is still considered present.
The singleton lifetime increases the impact in long-lived workers and Octane, where abandoned state may survive beyond the request or job that created it.
How to reproduce
- Begin a transaction.
- Update a model that participates in cache invalidation.
- Roll back the transaction.
- Update another participating model outside a transaction.
- Observe that the second update calls
flush() and can emit the entry retained from step 2.
The existing rollback test checks only that no event is emitted immediately after rollback. It does not perform a later mutation that exposes the retained entry.
Suggested resolution
The binding should match the documented lifecycle, and pending state should be associated with its connection and transaction frame so a rollback discards only the affected entries.
This does not appear to have a safe one-block fix. Changing singleton to scoped limits cross-request or cross-job leakage but does not fix rollback followed by later work in the same scope. Clearing the entire pending array in a generic rollback callback can also remove valid outer-transaction entries during nested transaction handling.
Regression coverage
Please add tests for:
- root rollback followed by an autocommit mutation;
- root rollback followed by a later committed transaction;
- a later transaction for the same target;
- nested rollback followed by outer commit;
- the same target appearing in outer and nested transaction frames;
- lifecycle isolation in a long-lived process;
- unchanged one-event-per-target commit deduplication.
Pull request
I can provide a pull request fixing the recorder lifetime and rollback-state handling and adding the regression coverage above. I would first align the implementation with the maintainers' preferred nested-transaction semantics.
I wanted to run this by you all first before I submit a PR to make sure this is accurate.
Relevant source:
packages/core/src/Cache/CacheInvalidator.php,
packages/core/src/LunarServiceProvider.php, and
tests/core/Feature/CacheInvalidationTest.php.
I found a lifecycle mismatch in
CacheInvalidator. The class describes itself as request-scoped, butLunarServiceProviderbinds it as a singleton.More importantly, recording inside a transaction adds the target to
$pendingand registers anafterCommitcallback. A full rollback discards that callback, but the pending entry is not cleared. A later autocommit mutation or committed transaction can therefore flush an invalidation for a change that was rolled back. A later transaction for the same target can also skip callback registration because the stale key is still considered present.The singleton lifetime increases the impact in long-lived workers and Octane, where abandoned state may survive beyond the request or job that created it.
How to reproduce
flush()and can emit the entry retained from step 2.The existing rollback test checks only that no event is emitted immediately after rollback. It does not perform a later mutation that exposes the retained entry.
Suggested resolution
The binding should match the documented lifecycle, and pending state should be associated with its connection and transaction frame so a rollback discards only the affected entries.
This does not appear to have a safe one-block fix. Changing
singletontoscopedlimits cross-request or cross-job leakage but does not fix rollback followed by later work in the same scope. Clearing the entire pending array in a generic rollback callback can also remove valid outer-transaction entries during nested transaction handling.Regression coverage
Please add tests for:
Pull request
I can provide a pull request fixing the recorder lifetime and rollback-state handling and adding the regression coverage above. I would first align the implementation with the maintainers' preferred nested-transaction semantics.
I wanted to run this by you all first before I submit a PR to make sure this is accurate.
Relevant source:
packages/core/src/Cache/CacheInvalidator.php,packages/core/src/LunarServiceProvider.php, andtests/core/Feature/CacheInvalidationTest.php.