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

status: waiting-for-triageAn issue we've not yet triaged or decided ontheme: kotlinAn issue related to Kotlin support

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions