Skip to content

Parent traceId is not reused when calling WebClient.awaitExchange function #36182

@grassehh

Description

@grassehh

Hi, we are migrating our project from Spring boot 3.5 to Spring Boot 4 and encountered an issue with observability.
Here is a simplified version of one of our tests:

@SpringBootTest
@AutoConfigureTracing
@ActiveProfiles("test")
@EnableWireMock
class TracingTest @Autowired constructor(
    private val observationRegistry: ObservationRegistry,
    private val webClient: WebClient,
    private val wireMock: WireMock,
    private val tracer: Tracer,
    @param:Value($$"${wiremock.server.port}") private val wireMockServerPort: Int
) {
    @Test
    fun test() {
        var traceId: String? = ""
        Observation.createNotStarted("test", observationRegistry).observe {
            traceId = tracer.currentTraceContext().context()?.traceId()
            runBlocking {
                webClient.get()
                    .uri("http://localhost:${wireMockServerPort}/test")
                    .awaitExchange { }
                webClient.get()
                    .uri("http://localhost:${wireMockServerPort}/test")
                    .awaitExchange { }
            }
        }

        await untilAsserted {
            wireMock.verifyThat(
                2,
                getRequestedFor(urlEqualTo("/test"))
                    .withHeader("X-B3-TraceId", equalTo(traceId))
            )
        }
    }
}

It creates an observation, executes a webclient call twice inside it, awaits the exchange inside, then checks that the traceId is the same for both calls (through the X-B3-TraceId) propagated header.

With Spring Boot 4, the test fails, but if we revert the code of the awaitExchange function as it was before this commit it works again. So basically, using this awaitExchange implementation:

suspend fun <T: Any> WebClient.RequestHeadersSpec<out WebClient.RequestHeadersSpec<*>>.awaitExchange(responseHandler: suspend (ClientResponse) -> T): T {
        val context = currentCoroutineContext().minusKey(Job.Key)
        return exchangeToMono { mono(context) { responseHandler.invoke(it) } }.awaitSingle()
    }

Is there anything we were doing wrong and it was not supposed to work or is there a regression ?
Thanks.

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)theme: kotlinAn issue related to Kotlin supporttype: regressionA bug that is also a regression

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions