refactor(framework): decouple Manager from TronJsonRpcImpl - #20
refactor(framework): decouple Manager from TronJsonRpcImpl#200xbigapple wants to merge 3 commits into
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
3 issues found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="framework/src/test/java/org/tron/core/jsonrpc/FilterPipelineDeliveryTest.java">
<violation number="1" location="framework/src/test/java/org/tron/core/jsonrpc/FilterPipelineDeliveryTest.java:63">
P3: `consumerThreadStartedOnce` asserts `Thread.getAllStackTraces()` has exactly one thread named "filter". That scan sees every live thread in the whole JVM, not just this test's context. The consumer is a daemon thread named "filter" (ExecutorServiceManager.newSingleThreadExecutor("filter", true)), so if another test class that also enables jsonRpcFilter holds its context open when this method runs (e.g. parallel Surefire, or a not-yet-torn-down daemon thread from a prior context), the count rises above 1 and the assertion fails spuriously. Scope the check to this instance, e.g. offer/await a capsule through the injected queue to verify the consumer loop is running, instead of counting global threads.</violation>
</file>
<file name="framework/src/main/java/org/tron/common/logsfilter/queue/FilterCapsuleQueue.java">
<violation number="1" location="framework/src/main/java/org/tron/common/logsfilter/queue/FilterCapsuleQueue.java:17">
P2: The queue is an unbounded LinkedBlockingQueue, so offer() always returns true for a non-null capsule. The producer call-sites in Manager.postBlockFilter/postLogsFilter treat a false return as a full queue and log "Too many filters, block filter lost", but that branch is unreachable, and there is no bound protecting memory if the consumer lags or stops. If loss/dropping on overflow is intended, give the queue a bounded capacity; otherwise the "lost filter" handling in Manager is dead code and the queue can grow without limit.</violation>
</file>
<file name="framework/src/main/java/org/tron/core/db/Manager.java">
<violation number="1" location="framework/src/main/java/org/tron/core/db/Manager.java:255">
P2: Manager.close() previously stopped the json-rpc filter consumer (stopFilterProcessThread). After this change the consumer is stopped only by ApplicationImpl.shutdown() -> TronJsonRpcImpl.close(); direct callers of the public Manager.close() with json-rpc filters enabled no longer terminate the consumer thread or its executor. Keep the leak-safe behavior by documenting/centralizing the shutdown contract, or have Manager.close() delegate the consumer shutdown so no code path leaves the daemon thread running.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| @Component | ||
| public class FilterCapsuleQueue { | ||
|
|
||
| private final BlockingQueue<FilterTriggerCapsule> queue = new LinkedBlockingQueue<>(); |
There was a problem hiding this comment.
P2: The queue is an unbounded LinkedBlockingQueue, so offer() always returns true for a non-null capsule. The producer call-sites in Manager.postBlockFilter/postLogsFilter treat a false return as a full queue and log "Too many filters, block filter lost", but that branch is unreachable, and there is no bound protecting memory if the consumer lags or stops. If loss/dropping on overflow is intended, give the queue a bounded capacity; otherwise the "lost filter" handling in Manager is dead code and the queue can grow without limit.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/java/org/tron/common/logsfilter/queue/FilterCapsuleQueue.java, line 17:
<comment>The queue is an unbounded LinkedBlockingQueue, so offer() always returns true for a non-null capsule. The producer call-sites in Manager.postBlockFilter/postLogsFilter treat a false return as a full queue and log "Too many filters, block filter lost", but that branch is unreachable, and there is no bound protecting memory if the consumer lags or stops. If loss/dropping on overflow is intended, give the queue a bounded capacity; otherwise the "lost filter" handling in Manager is dead code and the queue can grow without limit.</comment>
<file context>
@@ -0,0 +1,38 @@
+@Component
+public class FilterCapsuleQueue {
+
+ private final BlockingQueue<FilterTriggerCapsule> queue = new LinkedBlockingQueue<>();
+
+ public boolean offer(FilterTriggerCapsule capsule) {
</file context>
| private final BlockingQueue<FilterTriggerCapsule> queue = new LinkedBlockingQueue<>(); | |
| private final BlockingQueue<FilterTriggerCapsule> queue = new LinkedBlockingQueue<>(10000); |
| private boolean isRunFilterProcessThread = true; | ||
| private BlockingQueue<FilterTriggerCapsule> filterCapsuleQueue; | ||
| @Autowired | ||
| private FilterCapsuleQueue filterCapsuleQueue; |
There was a problem hiding this comment.
P2: Manager.close() previously stopped the json-rpc filter consumer (stopFilterProcessThread). After this change the consumer is stopped only by ApplicationImpl.shutdown() -> TronJsonRpcImpl.close(); direct callers of the public Manager.close() with json-rpc filters enabled no longer terminate the consumer thread or its executor. Keep the leak-safe behavior by documenting/centralizing the shutdown contract, or have Manager.close() delegate the consumer shutdown so no code path leaves the daemon thread running.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/java/org/tron/core/db/Manager.java, line 255:
<comment>Manager.close() previously stopped the json-rpc filter consumer (stopFilterProcessThread). After this change the consumer is stopped only by ApplicationImpl.shutdown() -> TronJsonRpcImpl.close(); direct callers of the public Manager.close() with json-rpc filters enabled no longer terminate the consumer thread or its executor. Keep the leak-safe behavior by documenting/centralizing the shutdown contract, or have Manager.close() delegate the consumer shutdown so no code path leaves the daemon thread running.</comment>
<file context>
@@ -253,8 +251,8 @@ public class Manager {
- private boolean isRunFilterProcessThread = true;
- private BlockingQueue<FilterTriggerCapsule> filterCapsuleQueue;
+ @Autowired
+ private FilterCapsuleQueue filterCapsuleQueue;
@Getter
</file context>
|
|
||
| @Test | ||
| public void consumerThreadStartedOnce() { | ||
| long count = Thread.getAllStackTraces().keySet().stream() |
There was a problem hiding this comment.
P3: consumerThreadStartedOnce asserts Thread.getAllStackTraces() has exactly one thread named "filter". That scan sees every live thread in the whole JVM, not just this test's context. The consumer is a daemon thread named "filter" (ExecutorServiceManager.newSingleThreadExecutor("filter", true)), so if another test class that also enables jsonRpcFilter holds its context open when this method runs (e.g. parallel Surefire, or a not-yet-torn-down daemon thread from a prior context), the count rises above 1 and the assertion fails spuriously. Scope the check to this instance, e.g. offer/await a capsule through the injected queue to verify the consumer loop is running, instead of counting global threads.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/core/jsonrpc/FilterPipelineDeliveryTest.java, line 63:
<comment>`consumerThreadStartedOnce` asserts `Thread.getAllStackTraces()` has exactly one thread named "filter". That scan sees every live thread in the whole JVM, not just this test's context. The consumer is a daemon thread named "filter" (ExecutorServiceManager.newSingleThreadExecutor("filter", true)), so if another test class that also enables jsonRpcFilter holds its context open when this method runs (e.g. parallel Surefire, or a not-yet-torn-down daemon thread from a prior context), the count rises above 1 and the assertion fails spuriously. Scope the check to this instance, e.g. offer/await a capsule through the injected queue to verify the consumer loop is running, instead of counting global threads.</comment>
<file context>
@@ -0,0 +1,100 @@
+
+ @Test
+ public void consumerThreadStartedOnce() {
+ long count = Thread.getAllStackTraces().keySet().stream()
+ .filter(t -> "filter".equals(t.getName())).count();
+ Assert.assertEquals(1, count);
</file context>
2380bd9 to
9f48952
Compare
Remove unused queue inspection and mutation methods, and mark the remaining stream accessor as test-only. Drop unreachable offer failure handling for the unbounded queue.
What does this PR do?
Removes the
Manager → TronJsonRpcImplreverse dependency left by tronprotocol#6732, where core-layerManagerholds the json-rpc filter consumer through a@Lazyinjection.FilterCapsuleQueuebean;Manager.postBlockFilter/postLogsFilterproduce into it, so Manager no longer references anything underorg.tron.core.services.jsonrpc(repo-wideframework/src/mainis now@Lazy-free).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 doubles as the loop's visibility-safe stop flag; the consumer executor is awaited beforelogsFilterPoolshuts down (the previous order could reject the in-flightsubmit(...).join()and silently drop events).ApplicationImpl.shutdown()now closes the consumer after producers stop and beforedbManager.close(), so the consumer structurally never outlives the database; the later Spring-destructionclose()is a no-op.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 tronprotocol#6732 review (see discussion).
@Lazytolerates the cycle rather than removing it:FullNodesetsallowCircularReferences(false), so no injection style can resolve the cycle — the reverse edge itself has to go, otherwise later json-rpc cleanups keep copying the pattern and core stays coupled to the API layer through a runtime proxy.This PR has been tested by:
eth_getFilterChangesdelivers; SIGTERM log order confirms consumer →logs-filter-pool→dbManager→ context close, single shutdown episode, no errorsfilter not foundafter restart; noRejectedExecutionException, no bean-cycle errors, shutdown order identical in every cycleFollow up
Extra details
Summary by cubic
Decouples core
Managerfrom JSON-RPCTronJsonRpcImplby inserting aFilterCapsuleQueuebetween producers and the consumer. This removes the Spring cycle, hardens shutdown (idempotent close and clean exit on interrupt), and keeps filter API behavior unchanged.FilterCapsuleQueuebean with a narrow API (offer, timedpoll;stream()is test-only).Manager.postBlockFilter/postLogsFilteroffer to it; removes direct references toorg.tron.core.services.jsonrpc. Drops unreachable offer-failure handling since the queue is unbounded.TronJsonRpcImpl; starts at@PostConstructonly whenisJsonRpcFilterEnabled(), runs in a single daemon thread, exits on interrupt, and warns on unknownFilterTriggerCapsulesubtypes.TronJsonRpcImpl.close()idempotent and stops the consumer beforelogsFilterPoolto avoid dropped events;ApplicationImpl.shutdown()closesTronJsonRpcImplafter producers stop and beforedbManager.close(), logging any close failures.Migration
new TronJsonRpcImpl(nodeInfoService, wallet, manager)and removesetManager(...)calls.FilterCapsuleQueueand useoffer(...); for tests, use itsstream()accessor.Written for commit d9db070. Summary will update on new commits.