I have been seeing problems while trying to use the MDC class provided as part of the SLF4J API. The logging provider is Log4J.
The MDC appears to work correctly at first, propagating keys and values from the event thread to worker threads when I call executeBlocking(). Eventually, however, when a worker gets reused, the original MDC appears to dominate over any new values that should be set.
Here is a test case to illustrate what I mean:
import io.vertx.core.Vertx;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
/**
* Make sure the SLF4J MDC feature behaves well with Vertx.
*/
@RunWith(VertxUnitRunner.class)
public class LogMdcTest {
private Logger log = LoggerFactory.getLogger(LogMdcTest.class);
int times = 0;
@Test
public void testMdcTransferToWorker(TestContext context) {
Async async = context.async();
Vertx vertx = Vertx.vertx();
vertx.createHttpServer()
.requestHandler(r -> {
// context.assertNull(MDC.getCopyOfContextMap());
MDC.put("userId", "joe");
MDC.put("requestId", "1");
log.debug("Request before blocking: " + MDC.getCopyOfContextMap());
vertx.executeBlocking(f -> {
log.debug("Request inside blocking: " + MDC.getCopyOfContextMap());
context.assertEquals("joe", MDC.get("userId"));
context.assertEquals("1", MDC.get("requestId"));
f.complete();
}, result -> {
log.debug("Request after blocking: " + MDC.getCopyOfContextMap());
context.assertEquals("joe", MDC.get("userId"));
context.assertEquals("1", MDC.get("requestId"));
r.response().end();
});
}).listen(8101, server -> {
vertx.createHttpClient().get(8101, "localhost", "/foo").handler(response ->
context.assertEquals(200, response.statusCode())
).end();
});
vertx.setPeriodic(100, id -> {
// Make sure we repeat enough times to cycle through all workers
if (times++ > 100) {
async.complete();
}
// context.assertNull(MDC.getCopyOfContextMap());
MDC.put("userId", "<server>");
log.debug("Timer before blocking: " + MDC.getCopyOfContextMap());
vertx.executeBlocking(f -> {
log.debug("Timer inside blocking: " + MDC.getCopyOfContextMap());
context.assertEquals("<server>", MDC.get("userId"));
context.assertNull(MDC.get("requestId"));
}, result -> {
log.debug("Timer after blocking: " + MDC.getCopyOfContextMap());
context.assertEquals("<server>", MDC.get("userId"));
context.assertNull(MDC.get("requestId"));
});
});
}
}
When I run this, it fails because eventually the periodic timer runs with the worker originally run as "joe", and in this case the worker continues to run as "joe", disregarding the "<server>" value from the calling event thread:
[vert.x-eventloop-thread-0] joe 1 Request before blocking: {userId=joe, requestId=1}
[vert.x-worker-thread-0] joe 1 Request inside blocking: {userId=joe, requestId=1}
[vert.x-eventloop-thread-0] joe 1 Request after blocking: {userId=joe, requestId=1}
[vert.x-eventloop-thread-1] <server> Timer before blocking: {userId=<server>}
[vert.x-worker-thread-1] <server> Timer inside blocking: {userId=<server>}
[vert.x-eventloop-thread-1] <server> Timer after blocking: {userId=<server>}
...snip Timer stuff for other worker threads...
[vert.x-eventloop-thread-1] <server> Timer before blocking: {userId=<server>}
[vert.x-worker-thread-0] joe 1 Timer inside blocking: {userId=joe, requestId=1}
[vert.x-eventloop-thread-1] <server> Timer after blocking: {userId=<server>}
java.lang.AssertionError: Not equals : <server> != joe
Any ideas how to avoid this behavior?
Thanks,
Garrick