eclipse-vertx / vert.x

Vert.x is a tool-kit for building reactive applications on the JVM
http://vertx.io
Other
14.3k stars 2.08k forks source link

ConcurrentModificationException in JsonObject.encode #4402

Open glassfox opened 2 years ago

glassfox commented 2 years ago

ConcurrentModificationException has been occurred in JsonObject.encode. Following the inputs:

  1. Json Object is a part of Message from EventBus.publish event
  2. JsonCodec provided by JacksonFactory as singletone in spi manner
  3. Vertx version: 4.3.1
  4. Hard to reproduce
  5. Stack trace:
Unhandled exception
io.vertx.core.json.EncodeException: Failed to encode as JSON: (was java.util.ConcurrentModificationException) (through reference chain: java.util.LinkedHashMap["codes"]->java.util.LinkedHashMap["oRluxwlHiHIdcRUvmuJLeAD"]->java.util.LinkedHashMap["number"])
    at 
io.vertx.core.json.JsonObject.encode(JsonObject.java:728)
vietj commented 2 years ago

which precise version of Vert.x

vietj commented 2 years ago

can you show the exact code also that leads to that, without it we can only speculate.

glassfox commented 2 years ago

Version: vertx-4.3.1

Following code of JacksonFactory:

public class JacksonMysqlFactory extends JacksonFactory {
    public static final JacksonFactory INSTANCE = new JacksonFactory();

    public static final JacksonCodec CODEC;

    static {
        JacksonCodec codec;
        try {
            codec = new MysqlDatabindCodec();
        } catch (Throwable ignore) {
            ignore.printStackTrace();
            // No databind
            codec = new JacksonCodec();
        }
        CODEC = codec;
    }

    @Override
    public JsonCodec codec() {
        return CODEC;
    }
}

Currently I build JsonObject, suitable for reproducer.

pmlopes commented 2 years ago

I cannot exactly reproduce the concurrent modification but I do see some odd behavior here:

public class MainVerticle extends AbstractVerticle {

  public static void main(String[] args) {
    Vertx.clusteredVertx(new VertxOptions())
      .compose(vertx -> {
        return vertx.deployVerticle(new MainVerticle());
      })
      .onFailure(err -> {
      err.printStackTrace();
      System.exit(1);
    });
  }

  @Override
  public void start() {

    vertx.eventBus()
      .<JsonObject>consumer("webrtc.test")
      .handler(msg -> {
        vertx.setTimer(1L, t -> {
          System.out.println(msg.body());
        });
      });

    final JsonObject json = new JsonObject().put("key1", "value1");

    vertx.eventBus().send("webrtc.test", json);
    json.remove("key1");
  }
}

When using a cluster eventbus, because the delivery is lazy, the delivered message will be empty:

{}

While if we're doing single vert.x:

public static void main(String[] args) {
  Vertx.vertx().deployVerticle(new MainVerticle())
    .onFailure(err -> {
    err.printStackTrace();
    System.exit(1);
  });
}

The message is copied earlier:

{"key1", "value1"}
pmlopes commented 2 years ago

You will see that io.vertx.core.eventbus.impl.HandlerRegistration#deliverMessageLocally() copies the message at different times. This is called from sendOrPub()

vietj commented 2 years ago

I agree that the body should not be lazily copied but instead when the message is sent ? so the sender can modify it after sending it.