refactor(framework): decouple Manager from TronJsonRpcImpl
What does this PR do?
Removes the Manager → TronJsonRpcImpl reverse dependency left by #6732, where core-layer Manager holds the json-rpc filter consumer through a @Lazy injection.
- Adds a standalone
FilterCapsuleQueuebean;Manager.postBlockFilter/postLogsFilterproduce into it, so Manager no longer references anything underorg.tron.core.services.jsonrpc(repo-wideframework/src/mainis now@Lazy-free). - Moves the consumer loop into
TronJsonRpcImpl: started by@PostConstructwhenisJsonRpcFilterEnabled(), single daemon thread, stopped byclose(). TronJsonRpcImplconstructor becomes(NodeInfoService, Wallet, Manager);setManager()is removed and the direct-construction test sites are migrated.close()is rewritten: anAtomicBooleanguard makes it idempotent and serves as a visibility-safe stop flag, the loop exits on interrupt, and the consumer is awaited beforelogsFilterPoolshuts down so an in-flight capsule completes on graceful shutdown.ApplicationImpl.shutdown()now closes the consumer after producers stop and beforedbManager.close(); the later Spring-destructionclose()is a no-op.- The
instanceofdispatch logs a warning for unknownFilterTriggerCapsulesubtypes instead of silently dropping them.
Runtime behavior of the filter API is unchanged: same unbounded queue, same discard-on-shutdown semantics, one consumer shared by the FullNode/solidity/PBFT json-rpc services.
Why are these changes required?
Follow-up agreed in the #6732 review (see discussion). FullNode sets allowCircularReferences(false); @Lazy, like ObjectProvider or a runtime getBean(), only makes Spring tolerate the cycle. The reverse core → API edge stays, obscuring the component graph, and later json-rpc cleanups keep copying the pattern. This PR removes the edge instead.
This PR has been tested by:
- Unit Tests
- Manual Testing
- private chain: block production with registered filters;
eth_getFilterChangesdelivers; SIGTERM log order confirms consumer →logs-filter-pool→dbManager→ context close, single shutdown episode, no errors - Nile and mainnet lite nodes: 5 restart cycles each with 100 filters created and polled per cycle (~2,500 queries/cycle, zero rpc errors); on mainnet the log path delivered 20k+ real contract log entries through the new queue; filters correctly report
filter not foundafter restart; noRejectedExecutionException, no bean-cycle errors, shutdown order identical in every cycle
- private chain: block production with registered filters;
Follow up
Extra details
Summary by cubic
Decouples the core Manager from JSON-RPC TronJsonRpcImpl by inserting a FilterCapsuleQueue between producers and the consumer. This removes the Spring circular reference, hardens shutdown (idempotent close, clean exit on interrupt, consumer stops before logsFilterPool), and keeps filter API behavior unchanged.
- Introduces
FilterCapsuleQueuebean with a narrow API (offer, timedpoll;stream()is test-only).Managerposts block/log filters to it, so it no longer referencesorg.tron.core.services.jsonrpc. Drops unreachable offer-failure handling since the queue is unbounded. - Moves the filter consumer loop into
TronJsonRpcImpl; starts at@PostConstructonly whenisJsonRpcFilterEnabled(), runs in a single daemon thread, exits on interrupt, warns on unknownFilterTriggerCapsulesubtypes. - Makes
TronJsonRpcImpl.close()idempotent and stops the consumer beforelogsFilterPool; an interrupted closer returns while preserving the interrupt status and keepslogsFilterPoolopen until the consumer exits so an in-flight over-threshold capsule is not rejected.ApplicationImpl.shutdown()closesTronJsonRpcImplafter producers stop and beforedbManager.close(), logging any close failures. - Adds end-to-end delivery (full/solidity and reorg paths), consumer lifecycle (disabled mode, interrupt, interrupted closer, context destruction, repeated close), and shutdown-race tests; updates existing tests to the new constructor.
Migration
- Replace direct constructions with
new TronJsonRpcImpl(nodeInfoService, wallet, manager)and removesetManager(...)calls. - If code inspected the filter queue directly, inject
FilterCapsuleQueueand useoffer(...); for tests, use itsstream()accessor.