Fork me on GitHub
#clojure
<
2023-03-30
>
icemanmelting13:03:47

Hey guys, I am using [clojurewerkz/machine_head] latest version to connect to an MQTT broker, everything works as expected, but then I get a really weird issue. In a regular deployment, this code works fine (by regular deployment I mean running the application):

(let [conns (into {} (mapv #(vector % (mh/connect mqtt-sink-address mqtt-sink-conn-optns)) (range threads)))] ....)
Although when I am running tests it complains about the arity of mh/connect, saying that it can't use 2 arguments, and it expects only 1. The weird part is that if I run the function call inside the debugger, I get the same error, but if I run it in a regular REPL, it works 🤯 Any ideas?

p-himik13:03:06

Different classpaths?

icemanmelting13:03:42

Hmmmm how could that happen? I don't really see how that could occur

p-himik13:03:41

Using different aliases in different environments. Or relying on snapshot dependencies. Or something else.

icemanmelting13:03:00

Not the case here. I even checked if the dependencies weren't being overwritten by the test profile or dev. But I don't have anything in place for that

p-himik13:03:39

To be completely sure, you can compare (System/getProperty "java.class.path"). Also, something can potentially override the function at run time.

icemanmelting13:03:57

`/users/iceman/.m2/repository/clojurewerkz/machine_head/1.0.0/machine_head-1.0.0.jar` is in the classpath

icemanmelting13:03:02

that is what I am expecting

icemanmelting13:03:12

also (-> mh/connect class .getDeclaredMethods first .getParameterTypes alength) returns 1 during runtime (debug) and 2 in the repl 🥲

p-himik13:03:39

Is there a chance your debugging runtime has some classes on its classpath that has outdated compiled stuff? Or maybe it has something else on the classpath that overrides that particular function in that namespace.

icemanmelting13:03:40

forget it… I am just flabbergasted at my stupidity right now, sorry to have wasted your time (with-redefs [mh/connect (fn [uri] uri)

icemanmelting13:03:48

nuff said 😄

p-himik14:03:02

Well, there you go. As I mentioned, "something can override the function at run time". :)

icemanmelting14:03:21

Yes you were right… Well, I guess it is not a very good idea do work when tired 😄

👍 4
Eric Dvorsak15:03:07

I'm having this weird issue that only occurs in the uberjar and not during development in the stripe-java library:

[{:type java.lang.NoSuchMethodError
   :message "'com.google.gson.GsonBuilder com.google.gson.GsonBuilder.addReflectionAccessFilter(com.google.gson.ReflectionAccessFilter)'"
   :at [com.stripe.net.ApiResource createGson "ApiResource.java" 41]}]
 :trace
 [[com.stripe.net.ApiResource createGson "ApiResource.java" 41]
  [com.stripe.net.ApiResource <clinit> "ApiResource.java" 26]
  [com.stripe.net.Webhook constructEvent "Webhook.java" 49]
  [com.stripe.net.Webhook constructEvent "Webhook.java" 30]

Eric Dvorsak15:03:20

I build with the following build.clj

(ns build
  (:require [clojure.tools.build.api :as b]))

(def lib 'brian/api)
(def version (format "1.2.%s" (b/git-count-revs nil)))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file (format "target/uberjar/brian-api.jar"))

(defn clean [_]
  (b/delete {:path "target"}))

(defn uber [_]
  (clean nil)
  (b/copy-dir {:src-dirs ["src/main" "resources"]
               :target-dir class-dir})
  (b/compile-clj {:basis basis
                  :src-dirs ["src/main"]
                  :class-dir class-dir})
  (b/uber {:class-dir class-dir
           :uber-file uber-file
           :basis basis
           :main 'brian.server-components.http-server}))
and just "clojure -T:build uber"

Eric Dvorsak15:03:12

I tried pinning com.google.code.gson/gson 2.9.0 because it's the version linked in the readme of stripe-java

Eric Dvorsak15:03:53

it gets a different error:

[{:type java.lang.NoClassDefFoundError
   :message "com/google/gson/ReflectionAccessFilter"
   :at [com.stripe.net.Webhook constructEvent "Webhook.java" 49]}
  {:type java.lang.ClassNotFoundException
   :message "com.google.gson.ReflectionAccessFilter"
   :at [jdk.internal.loader.BuiltinClassLoader loadClass nil -1]}]

Eric Dvorsak15:03:14

this time the error happens both in dev and ubjerjar

Eric Dvorsak15:03:52

the original error happens as well when pinning the version of gson that stripe-java expects based on what I see in deps tree

Eric Dvorsak15:03:08

anyway I was just curious if anyone had an idea on how to resolve that kind of issue, for now I just ditched the whole stripe-java library since I was only using it to validate the signature. So if anyone finds this thread looking for an answer regarding this error I have not found one yet. But you can easily replace this lib if you use it to validate a signature with the following snippet: [buddy.core.codecs :as codecs] [buddy.core.mac :as mac]

(defn compute-hmac-sha256 [secret message]
  (-> message
      (mac/hash {:key secret :alg :hmac+sha256})
      codecs/bytes->hex))

(defn valid-signature? [stripe-webhook-secret payload stripe-signature]
  (let [{:strs [v1 t]} (apply hash-map
                              (mapcat #(str/split % #"=")
                                      (str/split stripe-signature #",")))
        signed-payload (str t "." payload)]
    (= v1 (compute-hmac-sha256 stripe-webhook-secret signed-payload))))

(defn process-webhook-event
  "May throw errors that should be caught by the http stack."

Eric Dvorsak15:03:33

(beware you should also compare t to the current timestamp if you want to prevent replay attacks)

hiredman16:03:58

you should check and make sure the same version of google's gson library is used in the repl and in your uberjar

hiredman16:03:53

you might also check that you rare using the same java version when you try and the uberjar and at the repl

jpmonettas20:03:09

@U03K8V573EC I would start the jvm with -verbose:class to see how it load classes, the order and where they are coming from (what jar and version) in both cases (specially the one that works and the one that fails). Also do a jar -tf target/uberjar/brian-api.jar to check the class is there on the uberjar and go from there

phill23:03:29

Very strange things can happen (along these very lines) if 2+ jars on the classpath contribute distinct versions of "the same" library or portions thereof.

Eric Dvorsak07:03:28

@U0HG4EHMH that might just be the explanation yeah, I'll run the experiment when I have more time

icemanmelting17:03:50

Any ideas what might be the cause of this?

:cause clojure.core$eval193$fn__194
 :via
 [{:type java.lang.ClassNotFoundException
   :message clojure.core$eval193$fn__194
   :at [jdk.internal.loader.BuiltinClassLoader loadClass BuiltinClassLoader.java 641]}]
 :trace
I am serializing a clojure map that contains serializable instances of java objects (regular classes) it serializes fine, but when I try to deserialize, I get that error. Any ideas?

hiredman17:03:50

the serialized data contains references to a class created at runtime by clojure (as a result of loading clojure code and compiling it to jvm bytecode)

hiredman17:03:44

that class is available through one of clojure's dynamicclassloaders, but is not available to the classloader that is being used while deserializing

icemanmelting17:03:26

is there a way to find out which class it is?

hiredman17:03:41

it is in the exception there

hiredman17:03:47

clojure.core$eval193$fn__194

icemanmelting17:03:24

I have looked through the structure that gets saved to the file, and there's nothing unknown in there, that's why I don't get how there's a reference to something generated in runtime

icemanmelting17:03:46

and this has worked before, it stopped working recently

hiredman17:03:14

are you doing this in the same jvm or across different jvms?

hiredman17:03:45

I mean is the same process serializing and deserializing, or different processes(either across time or space)?

icemanmelting17:03:14

it is the same jvm, let's say I serialize on termination

icemanmelting17:03:18

and deserialize on startup

icemanmelting17:03:22

this has worked before

hiredman17:03:24

so different processes

icemanmelting17:03:37

same executable code

hiredman17:03:42

are you using the same clojure version as the last time you serialized?

hiredman17:03:00

are you sure?

icemanmelting17:03:04

everything is the same, as I said, same jdk, same clojure, same executable

icemanmelting17:03:28

I run the executable, when I stop it, it saves, and when I run it again, it deserializes the data

hiredman17:03:38

the class name clojure.core$eval193$fn__194 corresponds to an anonymous function eval'ed while the value of *ns* (the current namespace) is clojure.core

hiredman17:03:07

that is kind of odd, since clojure is aot compiled, but you can generate something similar like:

% clj
Clojure 1.11.1
user=> (in-ns 'clojure.core)
#object[clojure.lang.Namespace 0x1e392345 "clojure.core"]
clojure.core=> (fn [])
#object[clojure.core$eval138$fn__139 0x7c9b78e3 "clojure.core$eval138$fn__139@7c9b78e3"]
clojure.core=> (class *1)
clojure.core$eval138$fn__139
clojure.core=>
%

hiredman17:03:38

also depending on how you are launching your code, it is possible *ns* just remains at its default value of clojure.core, in which cause any usuage of say clojure.core/eval would result in classes like that

icemanmelting17:03:09

how would one avoid that issue? or at least minimize it?

hiredman17:03:12

don't serialize references to classes generated by the clojure compiler

hiredman17:03:33

in certain circumstances it will generate strongly named classes (classes whose name will stay the same across different compilations) but that is the exception rather than the rule

icemanmelting17:03:32

I found the issue, and it is related with an anonymous function

seancorfield17:03:34

FWIW, no version of Clojure that I can find has core$eval193 in it so that seems like something created "on the fly" once Clojure is up and running?

icemanmelting17:03:12

is there a way to let's say, name the anonymous function on the fly, so that it doesn't end up with a name like clojure.core$eval193$fn__194?

seancorfield17:03:27

(fn somename [..] ..) you mean?

hiredman17:03:43

that still won't get you a stable name though

hiredman17:03:52

somename will be part of the generated class name

hiredman17:03:42

but some part of it will be a number that will be effected by how many times nextId is called in the compiler

hiredman17:03:11

basically the whole point of anonymous function is it has no name, but it is compiled to a jvm class, which needs a name, so the compiler generates one as to avoid colliding with other generated names

icemanmelting17:03:00

I am dynamically loading a configuration from an edn file, that can contain anonymous functions or point to specific functions that are already defined. But some of these might end up in the final serialized file. So is there a way to receive an anonymous function but name it when evaluated?

hiredman17:03:16

and generated class names are a bad mix with java serialization

icemanmelting17:03:23

if I used predefined functions, I can deserialize the file no issues

icemanmelting17:03:33

I was using nippy

icemanmelting17:03:51

but I still get the same issue, because it needs to use the Serializable interface

icemanmelting17:03:23

I found a possible solution:

(defn gen-fn
  [n as b]
  (let [n        (symbol n)
        as       (vec (map symbol as))
        fn-value (eval `(fn ~n ~as ~b))]
    (intern *ns* n fn-value)))

hiredman17:03:12

calling eval to read a config file is kind of gross

icemanmelting17:03:27

the file is not read with eval

hiredman17:03:30

but so is using java serialization

icemanmelting17:03:31

it is read with aero

icemanmelting17:03:43

but there are parts that need to be evaled

hiredman17:03:03

that is gross

icemanmelting17:03:25

thanks for the input

hiredman17:03:31

as long as you are using eval you are going to get generated class names, and those generated class names are non-deterministic, so mixing them with java serialization will always be fragile, and you'll hit things like moving code around (changing the order of eval) might break deserialization, changing clojure versions might break it) and changing jvm versions might break it

icemanmelting17:03:33

I am aware of that, but all of that would break even if I wasn't using Java Serialization. Since there are no guarantees of same serialization/deserialization between jdk versions

hiredman17:03:29

if you bound *ns* around the call to eval and maybe eval'ed deftypes instead of fns, you would have better control over the names which depending on how you create a name for each thing, might be less fragile

icemanmelting17:03:25

yeah, makes sense

icemanmelting17:03:29

thanks for all your help

respatialized17:03:59

Are there any map-like data structures that allow you to associate values with a given range of numeric keys? E.g. if :foo is associated with (2,35) and I call get with 16.7 on my "range map", I get back :foo but if I call get with 1.2 I get nil?

respatialized17:03:26

https://github.com/clojure/data.int-map I guess I could just enumerate the key space using data.int-map and round any decimal keys, but I'm also curious if there's a name for this kind of thing

Joshua Suskalo17:03:03

So is the intention that keys are specified with ranges when inserted and with exact values when looked up?

2
respatialized17:03:47

https://github.com/dco-dev/interval-tree And as soon as I knew what to google, it is there! Thanks @U05092LD5

💯 2
ghadi17:03:27

assuming the intervals don't overlap, B-tree is the classic d.s. here.

Matthew Odendahl18:04:39

https://docs.oracle.com/javase/8/docs/api/java/util/NavigableMap.html would probably work. (Two implementing classes.) It keeps keys in a sorted order. • If there are no gaps, you could just use the end of the interval with floorEntry(). • If there are known gaps, you could use a placeholder value. • If there are no overlaps, you could store both ends and make sure your key is between them by using both floorEntry() and higherEntry() . • If there are overlaps, this might be the wrong data structure, but I'm not sure how you'd want it to work.

nice 1
1
Mandimby Raveloarinjaka19:03:32

Hello, is there a way to use a http repo with tools.deps? I am asking because I have build environment where clojars is mirrored behind in a private repository which do not provide https.

Alex Miller (Clojure team)19:03:52

you can enable it with the env var CLOJURE_CLI_ALLOW_HTTP_REPO

👍 2
Mandimby Raveloarinjaka19:03:04

Excellent ! I was really dreading starting discussing with my IT department about configuring https

noonian20:03:03

Does anyone know why

clojure.core/*clojure-version*
is dynamic? Just out of curiosity

dpsutton20:03:41

Looking at the commit history, the name predates the warning issued when an earmuffed var is not marked dynamic. I think this might have been done to avoid a breaking change. But perhaps others here know more

👍 2
dpsutton20:03:13

(ie, wanting to preserve the name with earmuffs before that meant it was dynamic. and it was marked dynamic to not print a warning on startup each time)

noonian19:03:31

Thank you! Yes, I had a similar suspicion about the earmuffs and being marked dynamic

colliderwriter22:03:30

What web server is everyone using for maximum performance nowadays? I was about to open a spike repo using https://github.com/AppsFlyer/donkey but it's recently been marked as unsupported.

dpsutton22:03:52

we use jetty with ring on top. recently switched to

info.sunng/ring-jetty9-adapter            {:mvn/version "0.18.5"}
which actually wraps jetty 11 despite its name

dpsutton22:03:10

https://github.com/ring-clojure/ring/issues/439 is an issue to update the “regular” ring lib off of jetty 9

seancorfield22:03:36

We use the Sun Ng Jetty adapter in production and we're very happy with it. http-kit is another option but we found support for observability was very poor with New Relic which made us switch back to Jetty.

colliderwriter22:03:21

Food for thought: hazy memories led to this https://www.slideshare.net/metosin/naked-performance-with-clojure. It doesn't look like experimental repos they were playing with got any further, not that I'm complaining. Metosin has given me so much already.

Ben Sless06:03:04

What are your performance requirements? Are there any other constraints?

colliderwriter16:03:41

I'm moving to a spicedb-based permission system, which has all sorts of design ripple effects. I wanted to reexamine my priors just in case there had been advancements in the basics while my attention was elsewhere

Ben Sless18:03:26

Well, do you need to maximize throughput or response time? Do you have any soft upper bounds for response time percentiles at different request rates?

colliderwriter19:04:55

Throughput, but (and this is a larger topic than the choice of http component), by removing permissions from the db schema, I am starting to treat the db like an object store, so the datomic-style client-side cache starts to make sense. I'm going to play with that idea next, but on a Jetty server 🙂

Ben Sless05:04:36

I've seen all big Clojure server options handle large loads, so I doubt any choice will have adverse effects.

eval-on-point22:03:40

Why do let and family use a vector for bindings instead of a map?

dpsutton22:03:53

maps are unordered so you couldn’t (safely) reuse bindings

👍 4
dpsutton22:03:34

collect=> (let [x 1
                y (inc x)]
            y)
2
collect=> 
(let {x 1
      y (inc x)}
  y)

dpsutton22:03:58

so to handle that you’d have to either do some heavy analysis on dependency, or just use an ordered structure

dpsutton22:03:22

(also, lisps traditionally use lists and Clojure (tastefully) uses a similar but distinct [ for binding pairs)

eval-on-point22:03:41

Yup, and did away with the distinction between let and let* entirely. Thanks again

👍 2
pppaul00:03:48

if it were possible to use a map, what would destructuring look like?

pppaul00:03:56

I guess the key of the may would be a map or vector. map in that case seems to lose a bit of value

dpsutton00:03:13

oh. the fatal flaw.

(let [x 1
      x (+ x x)]
  x)
2
that binding shadows x. with a map, you’ve got duplicate keys. I suppose all of this can be solved if you make some new construct that is ordered, but that point you’re just talking about { having different semantics and not being a map. You’re just asking for a new token. And that gets super weird if you wanted to emit a let from a macro. You’d have to somehow say “this is the special {, not a map” when emitting a macro. Right now you just emit a vector.

💯 2
pppaul00:03:07

there is a map-let like thing though. prismatic plumbing graph

pppaul00:03:54

maybe OP could find some use with that

pppaul00:03:45

feels pretty close