jankotek / mapdb

MapDB provides concurrent Maps, Sets and Queues backed by disk storage or off-heap-memory. It is a fast and easy to use embedded Java database engine.
https://mapdb.org
Apache License 2.0
4.87k stars 872 forks source link

map.compute not working with Serializer.JAVA #981

Open ttauchen opened 3 years ago

ttauchen commented 3 years ago

Using .compute on a ConcurrentMap created with mapdb will get it stuck in an infinite loop:

private static class Blob implements Serializable {
    private static final long serialVersionUID = 1L;
}

public static void main(String[] args) throws Exception {

ConcurrentMap<String, Blob> map1 = new ConcurrentHashMap<String, Blob>();

map1.put("key", new Blob());
map1.compute("key", (k, o) -> o);

System.out.println("works fine");

DB database = DBMaker.fileDB("data/test.db").transactionEnable().fileLockDisable().make();
ConcurrentMap<String, Blob> map2 = database.hashMap("files", Serializer.STRING, Serializer.JAVA).createOrOpen();

map2.put("key", new Blob());

map2.computeIfPresent("key", (i, o) -> {
    System.out.println("i am stuck in here");
    return o;
});

System.out.println("never reached");        
}
chenqi0805 commented 3 years ago

Found the similar issue as above. I think the problem is one cannot return the same object as in the argument of compute, e.g. the following code block will enter the dead loop:

        HTreeMap<String, List<String>> map = (HTreeMap<String, List<String>>) DBMaker.fileDB("map").make().hashMap("map").createOrOpen();
        for (int i = 0; i< 10; i++) {
            final int finalI = i;
            for (int j = 0; j< 10; j++) {
                final int finalJ = j;
                map.compute(String.valueOf(finalI), (key, value) -> {
                    if (value == null) {
                        value = new ArrayList<>();
                    }
                    value.add(String.format("%d_%d", finalI, finalJ));
                    return value;
                });
            }
        }
It falls into deadloop. Instead, I need to copy the value and then return the modified new copy to make it work.

HTreeMap<String, List<String>> map = (HTreeMap<String, List<String>>) DBMaker.fileDB("map").make().hashMap("map").createOrOpen();
        for (int i = 0; i< 10; i++) {
            final int finalI = i;
            for (int j = 0; j< 10; j++) {
                final int finalJ = j;
                map.compute(String.valueOf(finalI), (key, value) -> {
                    final List<String> newValue;
                    if (value == null) {
                        newValue = new ArrayList<>();
                    } else {
                        newValue = new ArrayList<>(value);
                    }
                    newValue.add(String.format("%d_%d", finalI, finalJ));
                    return newValue;
                });
            }
        }

while the following will work

HTreeMap<String, List<String>> map = (HTreeMap<String, List<String>>) DBMaker.fileDB("map").make().hashMap("map").createOrOpen();
        for (int i = 0; i< 10; i++) {
            final int finalI = i;
            for (int j = 0; j< 10; j++) {
                final int finalJ = j;
                map.compute(String.valueOf(finalI), (key, value) -> {
                    final List<String> newValue;
                    if (value == null) {
                        newValue = new ArrayList<>();
                    } else {
                        newValue = new ArrayList<>(value);
                    }
                    newValue.add(String.format("%d_%d", finalI, finalJ));
                    return newValue;
                });
            }
        }

since this does not happen in ConcurrentHashMap, I think this is a bug.