Fork me on GitHub
#clojure
<
2023-08-06
>
Omar02:08:30

What'd be the most ideal way to call javascript code from a clojure backend? I want to use https://github.com/web-push-libs/web-push, but not its CLI, preferably wrapping it in clojurescript and invoking.

Omar02:08:11

One approach I think could work is use nbb and run a lightweight node server with an endpoint to perform the web-push requests.

hifumi12305:08:36

I would use GraalJS. If you use GraalVM this lets you run JavaScript code with a JIT in your JVM

hifumi12305:08:52

I have used this before to call CLJS code in a Clojure backend

Omar07:08:01

@U0479UCF48H thanks for the answer! Checked it out a bit, do you know any examples offhand that would get me closer to consuming that web-push lib within clojure? Will continue scouring to see how to get it going.

pppaul15:08:57

why you want to avoid using the CLI? seems like that would be ideal

Omar02:08:25

might be more random JS libs I'd like to call from clojure in the future I'd like to use, so curious about options more than anything. I'll end up using the cli in this case, don't really have time to explore graal/graaljs at the moment.

pppaul15:08:21

one of the easiest ways of using a lib in another language is a cli tool

jasonjckn07:08:30

I'm doing some really rough benchmarks on a jetty v11 server, \w virtual threads, i'm noticing when the number of concurrent connections of the client is 1k, I see reasonable throughput, but when its 10k, the system chokes, and there's a lot of TCP socket read errors "read: connection reset by peer", and very little throughput. See thread for details of tests I ran.

Ben Sless07:08:15

If you're on jetty, just make sure the jetty logger level is not debug. It silently produces a ton of logs

jasonjckn07:08:12

is it set independently, or I just set the general log level of logback/log4j/etc

jasonjckn07:08:36

Context: running JDK 21 (ea) , tried both zulu and oracle on arm64 macos :jvm-opts ["-Xmx1g" ] deps.edn info.sunng/ring-jetty9-adapter {:mvn/version "0.22.1"} org.eclipse.jetty/jetty-server 11.0.15 The req handler here just really simple

(def ^:private get-uptest
  {:summary "check if this service is up/online"
   :handler (fn [_]
              (Thread/sleep 1000)
              {:status 200
               :body   {:status :online}}
              )})
Sleep for 1 second, (on a virtual thread). Jetty is configured roughly like
(defn mk-jetty-opts []
  {
   :configurator (fn [^org.eclipse.jetty.server.Server s]

                   (doseq [^org.eclipse.jetty.server.Connector c (.getConnectors s)]
                     (.setAcceptQueueSize c (* 65535))
                     (.setReuseAddress c true))

                   s)

   :h2?         true
   :thread-pool (doto
                    (org.eclipse.jetty.util.thread.QueuedThreadPool. ;; disruptor?
                      200
                      8
                      60000             ; thread idle timeout milliseconds
                      (java.util.concurrent.LinkedBlockingQueue.)
                      )

                    (.setVirtualThreadsExecutor
                      (VirtualThreads/getDefaultVirtualThreadsExecutor)
                      ))})
The tests are ran using 'hey' and 'wrk' \w 1k concurrent
⚡ wrk --latency --timeout 1m -d 2s -c 1000 -t 1000 ''
Running 2s test @ 
  1000 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.00s     3.29ms   1.02s    64.88%
    Req/Sec     0.04      0.20     1.00     95.70%
  Latency Distribution
     50%    1.00s
     75%    1.01s
     90%    1.01s
     99%    1.01s
  1999 requests in 2.10s, 322.10KB read
Requests/sec:    949.66
Transfer/sec:    153.02KB
\w 10k concurrent
⚡ wrk --latency --timeout 1m -d 2s -c 10000 -t 1000 ''
Running 2s test @ 
  1000 threads and 10000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.41s     1.80ms   1.41s    65.54%
    Req/Sec     7.59      6.04    30.00     88.24%
  Latency Distribution
     50%    1.41s
     75%    1.41s
     90%    1.41s
     99%    1.41s
  148 requests in 2.11s, 23.85KB read
  Socket errors: connect 0, read 4982, write 0, timeout 0
Requests/sec:     70.00
Transfer/sec:     11.28KB
notice read socket errors if I use 'hey' , similar kind of stats, and it prints out a ton of
[1]   Get "": read tcp [::1]:62673->[::1]:3000: read: connection reset by peer
  [1]   Get "": read tcp [::1]:62675->[::1]:3000: read: connection reset by peer
  [1]   Get "": read tcp [::1]:62678->[::1]:3000: read: connection reset by peer
  [1]   Get "": read tcp [::1]:62680->[::1]:3000: read: connection reset by peer

oyakushev08:08:02

Have you tried profiling it?

jasonjckn08:08:26

@U06PNK4HG i have not, but as far as i can tell with the socket read errors happen long before my code is ever invoked

jasonjckn08:08:21

it’s possible something may show up further down the stack , maybe will try that tomorrow if i’m still stuck

👍 2
oyakushev08:08:58

Besides simple CPU profiling, I also suggest profiling for lock event, maybe something shows up there. https://github.com/clojure-goes-fast/clj-async-profiler can do that

👍 2
phill09:08:35

If Java logs its garbage collection, is it thrashing with Xmx1g? And what's at the other end of the connections?

jumar20:08:40

Could you upload the code to a public repo on GitHub or somewhere so other people can try?

jasonjckn21:08:25

@U06BE1L6T I've had to rip it out my project, here's a barebones reproducing project.

jasonjckn21:08:55

I've disable logging same issue, and fiddled with GC settings to no avail, i'm going to be profiling next.

jumar05:08:11

Thanks, I've tried it and getting about the same results as you got. My guess is that it may actually be a limitation of the load testing tool (the client) that cannot keep up with the server. It wasn't clear to me what threads and connections mean for wrk I found some information about wrk here: https://github.com/wg/wrk/issues/205 > 8000 connections is a lot, particularly for your CPU, and OSX performs poorly in general. However it may work if the bottleneck is server-side and the connections are mostly idle waiting for a response. For a fast HTTP server like nginx serving static content on Intel Xeon CPUs with many physical cores, running Linux, just 400 connections spread over 12 threads is enough to max out wrk's performance. In general, running the load testing tool on the same machine may not be the best idea

2
jumar08:08:27

Here's one profile from async-profiler when the app is exercided like this

hey -n 10000 -c 500 

jasonjckn23:08:16

@U06BE1L6T yah my profile attemp looks very much like that great points you raised, at some point i’ll have to figure out a way to use my LAN to run benchmarks so i can separate out the client from the server. I did try a dozen or so things but found no resolution yet , i opened a thread here https://github.com/sunng87/ring-jetty9-adapter/issues/104 that has some of my experiments I’ve also now tried upgrading to jetty v12 (releases a few days ago) via java interop calls, which has much better vthread architecture,.. but still same results so you may be right about your theory

jumar05:08:53

Thanks for the update. I'll watch the stuff in that github issue!

braai engineer08:08:10

My Electric Clojure app runs locally via REPL and builds via Docker, but when I try to build an uberjar on the host or in Docker, I get this exception about a missing protocol, presumably AOT-related:

Exception in thread "main" java.lang.NoClassDefFoundError: myproject/proto/AMyProto
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1022)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
...
Caused by: java.lang.ClassNotFoundException: myproject.proto.AMyProto
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:527)
        ... 114 more
Caused by this:
;; myproject/proto.clj
(defprotocol AMyProto
  (do-thing [this ...])
  ...)

;; myproject/mymock.clj
(ns mymock
  (:require [myproject.proto :as proto]))

(defrecord MyImpl [!history secret-token]
  proto/AMyProto
  (do-thing [this ...] ...))
The build entrypoint, prod.clj has (:gen-class) . I tried adding (:gen-class) to proto.clj, but no dice. another gen-class issue? AOT? Could it be JDK version issue?

didibus12:08:25

How are you creating the Uberjar ?

braai engineer09:08:37

I'm trying to understand the JVM better. What is happening under the hood with uberjar compilation and how is it different from local REPL development? I'm sure there are plenty of resources out there, but don't know where to start.

phill10:08:41

Have you done one or the other?

braai engineer10:08:07

See my post above. I'm asking because my app runs with local dev but uberjar build fails at runtime due to NoClassDefFoundError and I want to get to the bottom why this happens. Is that what you mean by one or the other?

phill10:08:42

Oops, sorry! You started a new thread and it showed up without prior context.

braai engineer10:08:10

(soz threads are related but separate questions)

Ben Sless10:08:24

I think the uberjar needs to be told which class and method are the entry point

Ben Sless10:08:53

You need a -main etc

braai engineer10:08:05

@UK0810AQ2 I am specifying that. See my thread above for specific case, but I am asking in the general case about what uberjar is doing.

phill10:08:21

A question of principle deserves a principled, top-down approach. You could begin with the question of the competent authority. AFAIK uberjar is neither a Clojure nor a Java term, but rather a feature of tools from the respective ecosystem. It would make sense first to address the question in the forum of the tool.

braai engineer10:08:49

ok where is that forum?

phill10:08:57

What is the tool?

didibus12:08:26

Uberjar is a jar file. Inside it, it's meant to contain the entire set of Java classes, Clojure source and/or AOT classes, and resources that your application needs to run. Finally it contains a manifest fine that tells it the entry point and a few other things. A jar file is a zip file with the jar extension name.

lread14:08:51

Clojure compiles code you load on the fly to JVM bytecode. Your uberjaring is likely compiling your Clojure code to .class files using ahead-of-time (AOT) compilation. These resources might help: • https://clojure.org/reference/compilationhttps://clojure.org/guides/dev_startup_time

didibus16:08:31

AOT should still work and not result in missing classes though

lread16:08:03

Ya true, we'd need some more details @U051SPP9Z if you'd like help diagnosing your NoClassDefFoundError. Is your project public?

Eugen07:08:06

NoClqssDefFoundError is thrown when a class is not present on the class path. I suspect that during uberjar build, some transitive deps are missing . print repl class path (use clojure -Stree ?!) . you should find the jar that holds the class I there. check the uberjar and you probably find the classes are missing the error is thrown when class is statically referenced. there is another error thrown when you try to load q class during runtime. should not be confused with that case

lread12:08:35

If I remember correctly, NoClassDefFoundError is different than ClassNotFoundException

Eugen14:08:42

yes. ClassNotFound is thrown when you try to load class dynamically, via code. https://www.baeldung.com/java-classnotfoundexception-and-noclassdeffounderror

Eugen14:08:30

the article is good for explaining the difference with examples

braai engineer11:08:21

I'm trying to install clj on EC2 Ubuntu box. Linuxbrew needs sudo root password. I don't want to set a root password. Is there a way around this?

lispyclouds11:08:03

the script based install here: https://clojure.org/guides/install_clojure#_linux_instructions is generally recommended for linux

lispyclouds11:08:28

using the --prefix there can avoid the root requirements

abishek00:08:31

Also, sudo is user password for root. Not root password. I don’t remember setting up a root password on ubuntu on any of my setups.

braai engineer07:08:19

@U7ERLH6JX the Clojure install guide recommends Linuxbrew

lispyclouds07:08:48

Don’t think there is a recommendation for Linux, but in my experience the scripted install is almost always better

lispyclouds07:08:12

Specially when keeping the installation process consistent across various Linux flavours. Issues manifest in interesting ways like package conflicts from things installed by the native package manager and brew. Since brew and the package manager can’t sync with each other, for me the openjdk downloaded as a dependency by the clojure brew install messed up the JDK for others too 😆 which depended on things to be in a certain path.

practicalli-johnny09:08:25

Using linuxbrew would make sense if also installing other tools via the same method and using an interactive install, but that does not seem to be described as the case. Install via the script is very easy to automate and using the prefix then no password will be prompted for. A repeatable install that uses the latest version of the Clojure CLI can be used by omitting the version number, e.g.

curl -O  && \
chmod +x linux-install.sh && \
./linux-install.sh --prefix $HOME/.local/
https://practical.li/clojure/install/clojure-cli/