This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-08-06
Channels
- # announcements (3)
- # architecture (16)
- # beginners (5)
- # cherry (1)
- # cider (3)
- # cljsrn (2)
- # clojure (54)
- # clojure-dev (11)
- # clojure-europe (14)
- # datalevin (26)
- # emacs (8)
- # helix (5)
- # honeysql (5)
- # hyperfiddle (40)
- # lsp (12)
- # malli (23)
- # missionary (7)
- # nrepl (2)
- # off-topic (18)
- # releases (2)
- # yamlscript (1)
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.
One approach I think could work is use nbb and run a lightweight node server with an endpoint to perform the web-push requests.
I would use GraalJS. If you use GraalVM this lets you run JavaScript code with a JIT in your JVM
@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.
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.
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.
If you're on jetty, just make sure the jetty logger level is not debug. It silently produces a ton of logs
https://clojurians.slack.com/archives/C03L9H1FBM4/p1681223084938609?thread_ts=1681223084.938609&cid=C03L9H1FBM4 I don't recall but see the conclusion of this thread
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
@U06PNK4HG i have not, but as far as i can tell with the socket read errors happen long before my code is ever invoked
it’s possible something may show up further down the stack , maybe will try that tomorrow if i’m still stuck
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
If Java logs its garbage collection, is it thrashing with Xmx1g? And what's at the other end of the connections?
Could you upload the code to a public repo on GitHub or somewhere so other people can try?
@U06BE1L6T I've had to rip it out my project, here's a barebones reproducing project.
I've disable logging same issue, and fiddled with GC settings to no avail, i'm going to be profiling next.
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
Here's one profile from async-profiler when the app is exercided like this
hey -n 10000 -c 500
@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
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?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.
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?
(soz threads are related but separate questions)
@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.
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.
ok where is that forum?
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.
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/compilation
• https://clojure.org/guides/dev_startup_time
Ya true, we'd need some more details @U051SPP9Z if you'd like help diagnosing your NoClassDefFoundError
.
Is your project public?
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
yes. ClassNotFound is thrown when you try to load class dynamically, via code. https://www.baeldung.com/java-classnotfoundexception-and-noclassdeffounderror
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?
the script based install here: https://clojure.org/guides/install_clojure#_linux_instructions is generally recommended for linux
using the --prefix there can avoid the root requirements
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.
@U7ERLH6JX the Clojure install guide recommends Linuxbrew
Don’t think there is a recommendation for Linux, but in my experience the scripted install is almost always better
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.
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/