From 794f6c93aaa2e05881c931ae732a3a8da5b8a040 Mon Sep 17 00:00:00 2001 From: zq Date: Thu, 16 Jul 2026 13:11:59 +0800 Subject: [PATCH] fix: feign RepeatedCollectManager depth leak causing record miss (#587) onEnter unconditionally called RepeatedCollectManager.enter() but onExit skipped exitAndValidate() when a mock result was returned, so CallDepth kept growing across replay-hit requests. Once depth passed 1, subsequent record attempts on the same thread saw exitAndValidate()==false and were silently dropped (issue #587 reports depth reaching 9 while normal is 4). - move outermost check (validate()) before enter() so nested http clients (e.g. Feign -> Apache) skip inner replay and avoid request body double consumption - call exitAndValidate() unconditionally in onExit to keep enter/exit balanced regardless of mock hit - update tests to cover the nested-call skip path --- .../httpclient/feign/FeignClientInstrumentation.java | 10 ++++++++-- .../feign/FeignClientInstrumentationTest.java | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java b/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java index a73aa2225..d58496136 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java +++ b/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java @@ -52,10 +52,13 @@ public static boolean onEnter(@Argument(0)Request request, if (IgnoreUtils.excludeOperation(uri.getPath())) { return false; } + // check outermost before enter() so nested http clients (e.g. Feign -> Apache) + // skip replay and avoid request body double consumption + boolean isOutermost = RepeatedCollectManager.validate(); RepeatedCollectManager.enter(); adapter = new FeignClientAdapter(request, uri); extractor = new HttpClientExtractor(adapter); - if (ContextManager.needReplay()) { + if (ContextManager.needReplay() && isOutermost) { mockResult = extractor.replay(); return mockResult != null && mockResult.notIgnoreMockResult(); } @@ -73,6 +76,9 @@ public static void onExit(@Local("adapter") FeignClientAdapter adapter, return; } + // pair enter() unconditionally to keep CallDepth balanced across replay/record mixed flows + boolean isOutermost = RepeatedCollectManager.exitAndValidate(); + if (mockResult != null && mockResult.notIgnoreMockResult()) { if (mockResult.getThrowable() != null) { throwable = mockResult.getThrowable(); @@ -82,7 +88,7 @@ public static void onExit(@Local("adapter") FeignClientAdapter adapter, return; } - if (ContextManager.needRecord() && RepeatedCollectManager.exitAndValidate()) { + if (ContextManager.needRecord() && isOutermost) { response = adapter.copyResponse(response); if (throwable != null) { extractor.record(throwable); diff --git a/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java b/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java index 75726d5c4..401dca276 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java +++ b/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java @@ -66,7 +66,12 @@ void onEnter() { // need replay and not exclude operation Mockito.when(ContextManager.needReplay()).thenReturn(true); + Mockito.when(RepeatedCollectManager.validate()).thenReturn(true); assertTrue(FeignClientInstrumentation.ExecuteAdvice.onEnter(request, null, null, null)); + + // nested call: outer already entered, inner replay should be skipped + Mockito.when(RepeatedCollectManager.validate()).thenReturn(false); + assertFalse(FeignClientInstrumentation.ExecuteAdvice.onEnter(request, null, null, null)); } @Test