clj-commons / byte-streams

A Rosetta stone for JVM byte representations
417 stars 33 forks source link

for graalvm native-image: `graph.Type` needs to implement `java.lang.Comparable` #48

Closed josh-7t closed 2 years ago

josh-7t commented 2 years ago

After compiling aleph with graal native-image calling manifold.stream.SplicedStream.put appears to pass a byte_stream.graph.Type to byte_streams.utils$fast_memoize

The following stck trace suggests that in the graal environment fast-memoize needs it's arguments to implement java.lang.Coparable interface:

[main] ERROR aleph.netty - cannot coerce clojure.lang.LazySeq into binary representation
java.lang.ClassCastException: byte_streams.graph.Type cannot be cast to java.lang.Comparable
    at clojure.lang.Util.compare(Util.java:153)
    at clojure.lang.APersistentVector.compareTo(APersistentVector.java:439)
    at java.util.concurrent.ConcurrentHashMap.compareComparables(ConcurrentHashMap.java:739)
    at java.util.concurrent.ConcurrentHashMap$TreeBin.<init>(ConcurrentHashMap.java:2819)
    at java.util.concurrent.ConcurrentHashMap.treeifyBin(ConcurrentHashMap.java:2676)
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1068)
    at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1541)
    at byte_streams.utils$fast_memoize$fn__3336.invoke(utils.clj:56)
    at byte_streams.graph$seq_conversion_fn$fn__3709.invoke(graph.clj:293)
    at clojure.core$map$fn__5884.invoke(core.clj:2759)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:51)
    at clojure.lang.RT.seq(RT.java:535)
    at clojure.core$seq__5419.invokeStatic(core.clj:139)
    at clojure.core$filter$fn__5911.invoke(core.clj:2813)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:51)
    at clojure.lang.Cons.next(Cons.java:39)
    at clojure.lang.RT.length(RT.java:1785)
    at clojure.lang.RT.seqToArray(RT.java:1726)
    at clojure.lang.LazySeq.toArray(LazySeq.java:132)
    at clojure.lang.RT.toArray(RT.java:1699)
    at clojure.core$to_array.invokeStatic(core.clj:346)
    at clojure.core$sort.invokeStatic(core.clj:3101)
    at clojure.core$sort_by.invokeStatic(core.clj:3107)
    at clojure.core$sort_by.invokeStatic(core.clj:3107)
    at byte_streams.graph$seq_conversion_fn.invokeStatic(graph.clj:291)
    at byte_streams$fn__4114.invokeStatic(byte_streams.clj:148)
    at byte_streams$fn__4114.invoke(byte_streams.clj:148)
    at byte_streams.utils$fast_memoize$fn__3336$fn__3339.invoke(utils.clj:52)
    at clojure.lang.Delay.deref(Delay.java:42)
    at clojure.core$deref.invokeStatic(core.clj:2324)
    at byte_streams.utils$fast_memoize$fn__3336.invoke(utils.clj:52)
    at byte_streams$convert.invokeStatic(byte_streams.clj:199)
    at byte_streams$convert.invoke(byte_streams.clj:162)
    at byte_streams$convert.invokeStatic(byte_streams.clj:177)
    at aleph.netty$fn__4597$to_byte_buf__4600.invoke(netty.clj:176)
    at aleph.netty.ChannelSink$fn__4632.invoke(netty.clj:382)
    at aleph.netty.ChannelSink.put(netty.clj:381)
    at manifold.stream.SplicedStream.put(stream.clj:403)
    at manifold.stream.graph$async_send.invokeStatic(graph.clj:81)
    at manifold.stream.graph$async_connect$this__2478.invoke(graph.clj:209)
    at manifold.stream.graph$async_connect$this__2478$fn__2479$fn__2480.invoke(graph.clj:191)
    at clojure.core$trampoline.invokeStatic(core.clj:6299)
    at manifold.stream.graph$async_connect$this__2478$fn__2479.invoke(graph.clj:191)
    at manifold.deferred.Listener.onSuccess(deferred.clj:219)
    at manifold.deferred.Deferred$fn__1948.invoke(deferred.clj:400)
    at manifold.deferred.Deferred.success(deferred.clj:400)
    at manifold.deferred$success_BANG_.invokeStatic(deferred.clj:245)
    at manifold.stream.default.Stream$fn__2578.invoke(default.clj:158)
    at manifold.stream.default.Stream.put(default.clj:144)
    at manifold.stream.default.Stream.put(default.clj:180)
    at manifold.stream$map$fn__3044.invoke(stream.clj:620)
    at manifold.stream.Callback.put(stream.clj:454)
    at manifold.stream.graph$async_send.invokeStatic(graph.clj:81)
    at manifold.stream.graph$async_connect$this__2478.invoke(graph.clj:209)
    at manifold.stream.graph$async_connect$this__2478$fn__2479$fn__2480.invoke(graph.clj:191)
    at clojure.core$trampoline.invokeStatic(core.clj:6299)
    at manifold.stream.graph$async_connect$this__2478$fn__2479.invoke(graph.clj:191)
    at manifold.deferred.Listener.onSuccess(deferred.clj:219)
    at manifold.deferred.Deferred$fn__1948.invoke(deferred.clj:400)
    at manifold.deferred.Deferred.success(deferred.clj:400)
    at manifold.deferred$success_BANG_.invokeStatic(deferred.clj:245)
    at manifold.stream.default.Stream$fn__2578.invoke(default.clj:158)
    at manifold.stream.default.Stream.put(default.clj:144)
    at manifold.stream.default.Stream.put(default.clj:180)
    at manifold.stream.SplicedStream.put(stream.clj:403)
    at servo.connection$send.invokeStatic(connection.clj:276)
    at servo.connection$send.invoke(connection.clj:273)
    at servo.connection$run.invokeStatic(connection.clj:148)
    at servo.connection$run.doInvoke(connection.clj:140)
    at clojure.lang.RestFn.invoke(RestFn.java:425)
    at servo.connection$ensure_db.invokeStatic(connection.clj:549)
    at servo.connection$ensure_db.invoke(connection.clj:547)
    at servo.connection$connect.invokeStatic(connection.clj:107)
    at scribe.main$_main$fn__7808.invoke(main.clj:16)
    at scribe.main$_main.invokeStatic(main.clj:16)
    at scribe.main$_main.doInvoke(main.clj:9)
    at clojure.lang.RestFn.invoke(RestFn.java:410)
    at clojure.lang.AFn.applyToHelper(AFn.java:154)
    at clojure.lang.RestFn.applyTo(RestFn.java:132)
    at scribe.main.main(Unknown Source)
KingMob commented 2 years ago

@josh-7t Thanks for submitting this PR to improve byte-streams. I appreciate the effort and the interest.

However, there's two issues, one big and one small.

The small issue is the fn as written violates the Comparable contract. If the objects compared aren't equal, it will always return -1, which would mean the objects are always before each other, which is impossible. This is at least fixable by picking an artificial ordering based on type and wrapper. There's no inherent ordering of Types, but as long as it's consistent for TreeBin, it might be ok.

The bigger issue is the ConcurrentHashMap behind fast-memoize is using reflection, which is not exactly friendly to GraalVM. In addition to Java code, Zach's code wasn't shy about doing stuff like this either, so even if we change this here, you're likely to run into it again elsewhere. I suspect supporting Graal is a much bigger endeavor than a few PRs will solve.

That being said, I'm not convinced fast-memoize is worth it over plain memoize, if it's causing issues. If you want to replace it, I'd support that. We'd need a performance comparison between the two. As long as it's not egregious, they should be swappable.

josh-7t commented 2 years ago

Thanks for responding! I think I would be interested in replacing fast-memoize since Implementing Comparable on this type seems very arbitrary. Would you be amenable to me using https://github.com/clojure/core.memoize instead of the default memoize? It seems like it's implemented to work better for multithreaded apps and it has different cache strategies available so that the cache doesn't just grow forever.

josh-7t commented 2 years ago

superseded by https://github.com/clj-commons/byte-streams/pull/50