This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-02-26
Channels
- # announcements (19)
- # babashka (27)
- # beginners (24)
- # calva (14)
- # clerk (5)
- # clj-commons (21)
- # clojure (51)
- # clojure-europe (14)
- # clojure-madison (1)
- # clojure-nl (1)
- # clojure-norway (9)
- # clojure-uk (4)
- # clojuredesign-podcast (32)
- # core-async (14)
- # datomic (7)
- # events (1)
- # honeysql (3)
- # hyperfiddle (14)
- # introduce-yourself (2)
- # kaocha (7)
- # malli (21)
- # off-topic (50)
- # portal (2)
- # reagent (41)
- # reitit (41)
- # releases (1)
- # scittle (6)
- # shadow-cljs (90)
- # tools-deps (10)
- # xtdb (1)
- # yamlscript (1)
Hello there, quick question. If you use jetty, ring websocket, overtone.at-at and an atom something like this: • Create a handler for jetty that is dedicated for websockets. • Inside the handler when a websocket is called. store the websocket into an atom (conj into vector) and start a 3 loop with overtone at at. • Every 3 seconds, read the websockets from the atom and publish a message into each socket. While the code technically works. When implemented like this the message is only published to the first socket, all other sockets are in closed or closing state according to websocket/closed? It seems that this happens either because of the conjoin or at read time but I can figure out why? The goal of the code is to publish the same information from to multiple sockets at once. Am I designing my code wrong or not taking into account something basic when working with sockets and atoms? Example code in a thread (excluding the jetty setup)
(ns app.handlers
(:require
[jsonista.core :as j]
[overtone.at-at :as at]
[ring.websocket :as ws])
(def websockets (atom []))
(def interval-running (atom false))
(defn publish-update
[]
(doseq [socket @websockets]
(when (ws/open? socket) ; only the first entry in the vector returns true?
(ws/send socket (j/write-value-as-string {:msg "stuff"})))))
(defn create-interval
[]
(when (= false @interval-running)
(at/every 3000 publish-update overtone-pool :initial-delay 3000)
(reset! interval-running true)))
(defn websocket [request]
(assert (ws/upgrade-request? request))
{::ws/listener
{:on-open
(fn [socket]
(prn "New socket:" (ws/open? socket))
(swap! websockets conj socket)
(prn "socket conjoined:" (ws/open? socket)) ;; returns true for every connection.
(create-interval)
(ws/send socket (j/write-value-as-string {:msg "Starting updates"})))
:on-message
(fn [socket message]
(if (= "exit" message)
(ws/close socket)))}})
It seems a bit awkward that only a single :main-opts
is taken when using multiple deps.edn aliases. How do people get around this when activating multiple aliases that require :main-opts
?
what would it mean to run multiple -main
functions?
Is that how you think of it? I thought of it more like passing multiple arguments to your one -main
fn.
you have to specify the namespace with -m
, so merging them would mean you're specifying multiple namespaces
if it was like :main-ns noah/some-ns :main-opts ["arg1" "arg2"]
then i could see it working to combine the :main-opts
. but as it is, you're doing both, you're specifying the target namesapce to run the -main
of, and you're supplying a base set of arguments
Ah, I see. I did not notice that every :main-opts
includes a --main
/`-m`. The https://clojure.org/reference/deps_edn#aliases_mainopts does not go to that depth intentionally, I guess.
oh interesting, it's not required? i'm not sure how you'd run something without specifying it, tho
But you can only combine some of those -- and anything that follows -m some.ns
is treated as a command-line arg for some.ns/-main
.
@UPWHQK562 What is your specific use case here? i.e., what :main-opts
would you want to combine?
I ran into problems because I have a global deps.edn alias for Portal (with nREPL middleware added in :main-opts) but a project I'm working on uses shadow-cljs and I think a conflict happened there. The explanation I found for it is here https://clojurians.slack.com/archives/C6N245JGG/p1671011984378119?thread_ts=1671006295.467309&cid=C6N245JGG but I'm not sure I fully understand how to use aliases now.
Aliases always "combine" but the rules depend on what specifically is in the alias. :main-opts
is the odd-one-out as it is "last one wins" but the rest either concatenate or merge.
So if Shadow is starting its own nREPL server, you somehow need to tell it what additional arguments need to be passed to nREPL (no idea how to do that -- I do not use Shadow).
The general guidance is to try to avoid combining :main-opts
and dependencies (`:extra-paths`, :extra-deps
) by using separate aliases, so you might have :test:runner
where :test
provides the paths/deps and :runner
provides the :main-opts
-- that lets you specify :test
in a situation where some tooling adds its own main opts (as Shadow does).
But that doesn't help you when you need to pass additional command-line arguments to some -main
function via an alias (such as middleware for nREPL). There's no good solution for that.
I have not encountered that piece of guidance. That's good to know and I will make a note of it. So aliases that tell the program what it needs to run should separate from the aliases that tell the program how it needs to run. Does that sound like a correct take on it? Then a concrete example might be that your running your tests is a completely different function from running your application for actual usage.
but looking at https://github.com/practicalli/clojure-cli-config/blob/66d7d37aa38080e8f02dc977aab55342f5aabdb5/deps.edn it looks like many or even most of the aliases that use :main-opts
also specify extra dependencies.
Well, the :test:runner
example is the one I like to use since you might want clojure -M:test:nrepl
to start an nREPL server with your tests and their dependencies on the classpath or clojure -M:test:runner
to actually run the tests.
If you never use the dependencies except when you need :main-opts
then there's no need to separate them. :nrepl
is probably a reasonable example: you're very unlikely to ever want the nREPL deps on your classpath unless you are starting an nREPL server, i.e., using :main-opts
for it. But :portal
can provide your deps without :main-opts
-- the problematic issue is when you want to specify (nREPL) middleware for Portal -- but that's a general issue with nREPL and middleware.
It's why my :dev/repl
alias looks to see what's on the classpath when its :main-opts
are invoked and it then adds whatever middleware it thinks is available: https://github.com/seancorfield/dot-clojure/blob/develop/deps.edn#L52-L62 and https://github.com/seancorfield/dot-clojure/blob/develop/src/org/corfield/dev/repl.clj#L122-L132
Okay, that took a bit of thinking. Your start-repl
fn looks quite sophisticated and I'll probably shy away from using something similar for now, but it's a good case to understand the problem.
If my understanding is right, the root of the issue (and it's a fairly common one) is that tooling which leverages nREPL often needs to inject tool-specific middleware, and that can only be done through :main-opts
. It's a problem without a clean solution, because you may want to use multiple tools that interface with nREPL, but you can't just use multiple aliases for that, since their need to include a custom middleware always clashes.
Yup, that's an accurate summary.
In any case, thanks very much for the help on this topic, @U04V70XH6 and @UEENNMX0T.
I think it mostly affects dev tooling, yes.
I wonder if there's an argument in favor of having some sort of :main-args
that would concatenate and be applied to the end of :main-opts
? I'll post on http://ask.clojure.org
Hmm, that wouldn't solve this problem since --middleware
expects a single vector of symbols so you couldn't combine aliases to produce it even if there was a separate :main-args
CLI option.
For nREPL middleware, I think this would require changes to nREPL itself...