Fork me on GitHub
#clojure
<
2023-01-09
>
delaguardo10:01:16

Looks like with JDK 19 ^long no longer behave the same as ^Long type hint.

❯ java -version
openjdk version "19.0.1" 2022-10-18
OpenJDK Runtime Environment Temurin-19.0.1+10 (build 19.0.1+10)
OpenJDK 64-Bit Server VM Temurin-19.0.1+10 (build 19.0.1+10, mixed mode)
❯ clj
Clojure 1.11.1
user=> (def ^long foo 1)
#'user/foo
user=> #(Thread/sleep foo)
Syntax error (IllegalArgumentException) compiling . at (REPL:1:2).
Unable to resolve classname: clojure.core$long@26a94fa5
❯ clj          
Clojure 1.11.1
user=> (def ^Long foo 1)
#'user/foo
user=> #(Thread/sleep foo)
#object[user$eval1$fn__2 0x29182679 "user$eval1$fn__2@29182679"]

p-himik11:01:50

^long works the same. But Thread/sleep now has an extra overload with 1-arity, and that switches on parameter type matching.

delaguardo11:01:15

right, but then I would expect reflection warning, not Syntax error caused by "Unable to resolve classname: clojure.core$long@..." exception

delaguardo11:01:38

From syntax perspective those expressions suppose to be identical, isn't it?

p-himik11:01:11

Couple other things worth noting: • Vars can't store primitives • Tags on vars are resolved - they aren't preserved as symbols (that's why you see clojure.core$long@26a94fa5 instead of long) That means, (def ^long foo 1) is actually incorrect. Regardless of the Java version.

delaguardo12:01:02

aha, so that's why there is "Unable to resolve ..." exception: ^long tag got resolved into the reference to clojure.core/long but when reflection tries to find a proper method of Thread/sleep this reference does not point to any class. So this could happen in any JDK version and I hit it only because after JDK 19 Thread/sleep needs to use reflection. Do you know any place where I can read more about the topic? https://clojure.org/reference/java_interop#typehints mention only "can be placed on var names (when defined)" and in later section "^long" is an alias for "^java.lang.Long" together gave me an impression I can use it safely in expression like this (def foo ^long 42)

p-himik12:01:59

Exactly. The article you linked along with https://clojure.org/reference/vars#metadata I myself also like doing experiments in a REPL and reading Clojure implementation while occasionally stepping through it with a debugger.

delaguardo12:01:55

got it, thanks for the link, I see it now 🙂

andy.fingerhut13:01:07

Eastwood might be able to give warnings on such incorrect type hints. I haven't checked in a while.

andy.fingerhut13:01:47

A section of its docs gives examples of correct and incorrect type hints, but I do not know if it needs updating for more recent versions of Clojure.

vemv13:01:46

Eastwood indeed has some support in its wrong-tag linter Anyway I recommend studying https://clojure.org/reference/java_interop and specifically https://clojure.org/reference/java_interop#optimization the rules are not that hard and then boom, you're an expert clojure-spin

👍 2
vemv13:01:43

> "^long" is an alias for "^java.lang.Long" this seems wrong to me, primitives and boxed types are entirely different things. clj has support for both

chrisn14:01:35

The document does not state that long is an alias for java.lang.Long. Nowhere in the doc does the string java.lang.Long appear. IF you want to have a primitive on a def form you need to use the symbol itself - unliked function arguments or lots of other things:

user> (def ms-time 100)
#'user/ms-time
user> (Thread/sleep ms-time)
Reflection warning, *cider-repl cnuernber/dtype-next:localhost:37165(clj)*:54:7 - call to static method sleep on java.lang.Thread can't be resolved (argument types: unknown).
nil
user> (def ^{:tag 'long} ms-time 100)
#'user/ms-time
user> (Thread/sleep ms-time)
nil 

delaguardo14:01:43

right, my mistake was that I didn't know that meta tags got resolved for var names unlike for local bindings

p-himik14:01:33

> IF you want to have a primitive on a def form you need to use the symbol itself That still won't work - vars can't hold primitives. The tag will be useless. (Or maybe I'm wrong, hold on)

p-himik14:01:37

Yeah, I'm wrong - it'll be used, even though the var won't contain a primitive. The tag will be used only to select the right method and won't affect the actual value. Hence you can do stuff like (def ^{:tag 'long} x "x") and later get a ClassCastException when you pass it to some function that expects a long.

jdkealy16:01:46

My backend is crashing in doseq on 27k records. In the doseq, i'm just grabbing a record from the DB and making an HTTP request with a JSON serialized version of it. It gets to about 15k before dying. None of the JSON objects are very large. Is there a better way to do this than doseq

p-himik16:01:50

What do you mean by "crashing"? Is there an exception, do you have a stacktrace? Chances are, it's not doseq itself but rather some lazy collection that ends up doing more than you'd like.

jdkealy16:01:45

i'm using kubernetes, all i can see is that in the middle of the job, the REPL connection closes and i can see the kubernetes pod restarted

p-himik16:01:06

Then I'd say that the correct first step would be to set up proper monitoring - one where you'd get the error details instead of just the most superficial symptoms.

p-himik16:01:44

Or at the very least you could share the code that generates the seq and later consumes it, assuming you can do it given its licensing and there's not too much of it. Maybe you hit an OOM somewhere because you somehow accumulate the results. Maybe it's a stack overflow because you used concat excessively, maybe something else. Impossible to tell.

jdkealy16:01:34

(let [db-conn (db/_d)]
    (doseq [[u] (vec (user/all-user-ids db-conn))]
      (println u)
      (import-es-user (res/user-to-json (db/by-id u db-conn)))))

jdkealy16:01:15

So i'm using datomic... user-to-json just serializes the object to JSON

p-himik16:01:55

As you can probably guess, nothing's wrong with the above code, except for the fact that you use vec an thus query all the data eagerly. But maybe that's how one's supposed to work with Datomic, I have no clue. So it means that the error is somewhere else. As I said, impossible to tell without some details. Ideally, the stacktrace.

👍 2
jdkealy16:01:02

right, well how does one profile where a function might cause a spike in RAM usage

p-himik16:01:34

I'd start with VisualVM. But you're assuming that it's an OOM. It might easily be something else.

👍 2
jpmonettas17:01:38

I would just surround that with a (try (let ...) (catch Exception e (.printStackTrace e))) since you have a println already there I assume you have access to the standard output log of that process

p-himik17:01:00

Some things to add to the above: • You should catch Throwable instead • If it's OOM, it might not get printed • You should rethrow the error to make sure things work as intended • Full-on JVM crashes can't be caught (e.g. when there's a bug in the JVM or when you're using some native library that crashes)

💯 4
🧠 2
Martin Jul18:01:26

I don’t know if it is relevant in your case, but it is worth noting that Clojure lazy seqs easily blow the stack, e.g. (reduce concat (map vector (range 10000)))

Ben Sless19:01:02

Are you sending the http request blocking or non blocking?

ilmo21:01:34

Try to also find out why the pod is restarting by looking at the pod events. It may reveal stuff like OOMKilled or probe failures.

Ben Sless04:01:36

My guess is if sending the http requests is not blocking they queue up faster than they can be sent and that could lead to oom

emilaasa07:01:33

If k8s has 137 as the oomkilled signal maybe you can see that somewhere in your ops system?

👍 2
jumar08:01:09

A few tips • You can use profilers like VisualVM and YourKit - they are often more heavyweight but can reveal interesting findings like what paths allocate the most memory ◦ see e.g. https://www.yourkit.com/docs/java/help/allocations.jsp • You can Dump the memory on OOM (see https://www.baeldung.com/java-heap-dump-capture). you can then inspect it in some of the profilers (see above) or heap dump analyzers (like Eclipse MAT)

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-or-dir-path>
• You can profile the app with lightweight Async Profiler - see https://github.com/jvm-profiling-tools/async-profiler#allocation-profiling ◦ Note: may be more difficult in virtualized/containerized environments • You can try rudimentary memory allocation measuring by wrapping it with some custom code - see my snippet as an example: https://github.com/jumarko/clojure-experiments/blob/develop/src/clojure_experiments/performance/memory.clj#L186-L199

👍 2
Rupert (All Street)08:01:55

Just an aside on this: > I don’t know if it is relevant in your case, but it is worth noting that Clojure lazy seqs easily blow the stack, e.g. (reduce concat (map vector (range 10000))) This is just an issue with the way you are using reduce and concat together to combine seqs together. This works fine:

(apply concat (map vector (range 10000)))
If you use cons and lazy-seq (to add items to the front of the seq) or other lazy seq functions (map/filter/take etc) then you will not blow the stack even with infinite size sequences.

Maris19:01:02

I need to rename few files during leiningen release process. ( as part of :release-tasks ) Is there a plugin for that? Do I need to write my own ?

p-himik19:01:41

There's also #C0AB48493

Drew Verlee21:01:40

We updated some libs and now one of our http requests bodies is reaching ring-json/wrap-json-body as a Bytes.InputStream Which it parses to an empty string:

body
  ;; => #<org.httpkit.BytesInputStream@fa732ad BytesInputStream[len=100]>

 (java.io.InputStreamReader. body encoding)
  ;; => #<java.io.InputStreamReader@6596dcef>

  (slurp (java.io.InputStreamReader. body encoding))
  ;; => ""
it looks like we have to get the bytes out first
(String. (.bytes body) encoding)
  ;; => "{\"operationName\":\"queries_root__me\",\"variables\":{},\"query\":\"query queries_root__me { me { email } \"}"
(encoding is just "UTF-8") i suspect i'm not setting some server side body decoder on compojure-api or something, if anyone has run into this i would love to hear what you did. Atm were just creating our own version of wrap-json-body so we can get the ball rolling.

hiredman21:01:44

sounds like some other middleware is reading the body before json decoding gets to it

hiredman21:01:03

a way to check would be to use reflection to get access to the private pos field (https://github.com/http-kit/http-kit/blob/master/src/java/org/httpkit/BytesInputStream.java) and verify that it indicates the stream has been completely read

Drew Verlee22:01:58

Thanks @U0NCTKEV8 thats useful feedback. And i agree, i think some middlware before wrap-json-body has changed, or i didn't configure it properly when we updated compojure-api form 1->2 (which i know regret as it looks like 2 is all "alpha" and wont get support, i should have just worked on switching to retit!)

Drew Verlee22:01:39

How would reflection help check if other middleware had read it? i haven't done much with reflection directly.

hiredman22:01:06

it is a private field so you can't read it without reflection

hiredman22:01:28

https://github.com/arohner/clj-wallhack/blob/master/src/wall/hack.clj has some examples of how you would use reflection to read a private field, or you can just use the library

👀 2
👍 2
phronmophobic22:01:43

Another alternative is to add another middleware early in the stack that replaces the input stream with a mock input stream that throws an exception on read.

😃 4
👀 2
phronmophobic22:01:24

It won't fix your problem, but it might help identify which middleware is consuming the body

valerauko04:01:07

you can probably .reset the body InputStream to read it again

👀 2
valerauko04:01:04

parameter parsing middleware is usually sus

Drew Verlee16:01:59

@U7RJTCH6J yep! We added middle ware that captured input and output between the existing mw. The only frustrating part was that when i captured the input on the offending mw, it looked fine in a simple example and in the real example it looked like things had gone sour before it. Oh well. Thanks for the idea.

Drew Verlee16:01:58

@UAEH11THP I'll try reset. Thanks.

Drew Verlee20:01:08

I understood from what you said that the InputStream was being consumed, but i'm also able to reproduce it in a small example.

(def foo "foo")
  ;; => #'centriq-web.web-server/foo

  (def byte-input-stream (new BytesInputStream (.getBytes foo) (.length foo)))
  ;; => #'centriq-web.web-server/byte-input-stream

  (slurp byte-input-stream)
  ;; => "foo"

  (slurp byte-input-stream)
  ;; => ""

  (slurp (.bytes byte-input-stream))
  ;; => "foo"