clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
lilactown 2020-12-01T01:20:49.350900Z

When would I use unsynchronized-mutable over volatile-mutable in a deftype?

2020-12-01T01:28:00.351700Z

the only reason I can imagine is if you know the code is single threaded in all cases and need the performance boost of not propagating the value

2020-12-01T01:28:26.352200Z

I can't imagine that coming up much in normal clojure code

2020-12-01T01:29:19.352900Z

see "Java Concurrency In Practice" - best source I know of for what these terms imply design wise with the jvm

2020-12-01T01:31:21.353800Z

the doc string says basically "if you don't know what these mean don't use them", for good reason

... Fields can be qualified
  with the metadata :volatile-mutable true or :unsynchronized-mutable
  true, at which point (set! afield aval) will be supported in method
  bodies. Note well that mutable fields are extremely difficult to use
  correctly, and are present only to facilitate the building of higher
  level constructs, such as Clojure's reference types, in Clojure
  itself. They are for experts only - if the semantics and
  implications of :volatile-mutable or :unsynchronized-mutable are not
  immediately apparent to you, you should not be using them.

lilactown 2020-12-01T01:42:30.355600Z

Yeah I find that doc insulting tbh. I’m plenty experienced enough to burn myself and recover if I have a good explanation to fall back on

2020-12-01T01:45:47.358100Z

"I have a marvelous explanation of these features but this docstring is too narrow to contain them"

😆 3
lilactown 2020-12-01T01:48:59.360800Z

I’ve been writing Clojure professionally for years but only touched Java concurrency primitives a handful of times. Understanding how they work in the context of Clojure’s higher level abstractions would be lovely

lilactown 2020-12-01T01:52:10.364300Z

Anyway I’ll just litter my code with volatile-mutable to replace my CLJS mutable notations and hopefully someone will open a PR and explain why I shouldn’t of it’s wrong

2020-12-01T01:56:09.366800Z

Explaining volatile, even metaphorically, is tricky, it pulls in the java memory model, and now you need a metaphor for how you determine the order of reads and writes to memory in a concurrent program

2020-12-01T02:01:32.369600Z

Maybe, like, imagine you have a set of all the reads and writes a concurrent program does to some field. For the reads and writes that happen on the same thread you can establish an order easily, just the order the reads and writes in the order they occur in your program

2020-12-01T02:02:34.371200Z

For reads and writes across threads, you can't establish an order, any interleaving, any concurrent execution could happen

2020-12-01T02:05:51.374800Z

Volatile fields (and I encourage corrections) can be thought of as saying there is a total order of those reads and writes. It doesn't say what the order is, but there is some order

💯 1
2020-12-01T02:09:20.379Z

So one place they are used is inside transducers that hold mutable state, so if you use that transducer in a go channel, where the state is passed the between threads, it ensures that when one thread manipulates the state it sees all the changes that the previous thread made

1
kenj 2020-12-01T02:13:21.383100Z

I’m probably wrong, but I was under the impression you could explain volatile in terms of informing the low level bits that two threads might be reading/writing to a variable in memory, and as such, avoiding generating code to keep said variable in a CPU cache, where another thread running on another core, would not be able to read the latest value.

kenj 2020-12-01T02:14:59.384400Z

In other words, always read/write from RAM.

2020-12-01T02:18:33.388200Z

That is an explanation of the observed behavior, but I don't believe that is the actual mechanics

2020-12-01T02:19:44.389900Z

The mechanics of the generated code depends on the memory model of the cpu architecture (I believe x86's is pretty loose say compared to arm's)

2020-12-01T02:29:52.390700Z

Maybe I have that reversed

2020-12-01T02:30:18.391500Z

http://gee.cs.oswego.edu/dl/jmm/cookbook.html is a decent place to start digging deeper

alexmiller 2020-12-01T02:40:20.392400Z

with volatiles all threads will see writes (non volatile don't without some other synchronization that creates a happens-before constraint)

alexmiller 2020-12-01T02:42:44.393700Z

with volatiles, reads and writes can't be atomic though - you can't read and then write assuming that the value hasn't changed

2020-12-01T02:44:22.396100Z

Which is problematic when people start sprinkling vswap! around thinking it's like swap! But better

alexmiller 2020-12-01T02:44:56.396700Z

I would not recommend using :unsynchronized-mutable unless you have an external synchronization mechanism (like you are using locking around all calls to the field)

1
2020-12-01T02:48:22.400200Z

(vswap! Is not technically something you can use with a volatile mutable field, but clojure now exposes volatile two ways, and that is the other)

alexmiller 2020-12-01T02:49:04.400800Z

there really is too much to say in that docstring other than "here be dragons, you're outside normal Clojure semantics". the real answer is to read JCIP and/or the JMM

jumar 2020-12-01T03:42:47.403700Z

It doesn’t have to read from RAM. It just have to reflect the value wrby the other thread. The cache coherence protocol might ensure that without forcing cpu caches to be written to the main memory

lilactown 2020-12-01T03:54:58.404100Z

I appreciate the explanation from both of you 🙂

cfleming 2020-12-01T04:45:58.404900Z

I think a slightly edited version of what you wrote above: > with volatiles all threads will see writes (non volatile don’t without some other synchronization that creates a happens-before constraint) > with volatiles, reads and writes can’t be atomic though - you can’t read and then write assuming that the value hasn’t changed Would be a pretty good summary.

cfleming 2020-12-01T04:49:44.405100Z

I read both JCIP and the JMM a long time ago, and just remembering basically that has served me for pretty much all uses of volatile since then - I’ve forgotten all the details about the mechanics.

2020-12-01T13:48:54.411400Z

We’re having an issue with our production web app that has me stumped. It’s a clojure app built with http-kit as the web server and connections to external datomic and mongo db’s. Occasionally the whole server will just hang for a minute or two, and the logs are showing the last operation before the hang was to dispatch a potentially long-running Mongo query. I can understand how that might lock up the thread that was dispatching the query, but how can it be locking up the whole server so that it doesn’t even respond to the heartbeat query from our monitoring system? It’s not even making more than a ripple on the CPU usage, according to monitoring on the host.

2020-12-01T13:49:55.412400Z

I’m talking with the IT folks about installing some additional JMX based monitoring (New Relic) on the server, but I’m not sure what I should be looking for in the output.

alexmiller 2020-12-01T13:54:02.417300Z

From the description, could be either GC pause or that the thread blocked on the query is holding a lock that others are waiting on. For the former, I’d turn on the jvm gc logging - its designed to be run under load and you can shunt them off to their own log. If the latter or something else, getting a thread dump (kill -3 on the pid or use jstack) will tell you what threads are doing when it locks up and what monitors are held

2020-12-01T13:58:35.417500Z

Ok, thanks much. I hadn’t thought much about the possibility of a shared lock, so this plus the GC logging gives us something to pursue. This issue has happened 3 times that I know of since July, so data gathering is slow, but now we know where to spread the nets.

dominicm 2020-12-01T14:14:28.417700Z

http-kit is built on core.async, so if you're locking up the core.async thread pool (e.g. doing I/O inside of a go), that could cause problems too.

dominicm 2020-12-01T14:14:39.417900Z

I'm not sure how http-kit feels about I/O inside of handler functions.

alexmiller 2020-12-01T14:22:15.420200Z

core.async now has a system property you can set to catch use of blocking async ops in go blocks. Might be worth turning that on in dev (not in prod!) to see if that’s happening

alexmiller 2020-12-01T14:23:19.420600Z

See doc at top of http://clojure.github.io/core.async/

2020-12-01T14:23:31.420800Z

We’re not using core.async in the app itself, not sure if that matters.

2020-12-01T14:23:41.421Z

Thanks for the link, I’ll definitely check that out

alexmiller 2020-12-01T14:24:39.421600Z

Easy thing to check out at least

2020-12-01T14:24:46.421800Z

Oo, yes, this is going to come in very handy

borkdude 2020-12-01T14:35:20.422Z

@dominicm What makes you think http-kit is based on core.async?

dominicm 2020-12-01T14:35:52.422200Z

The haziness of memory 😁

dominicm 2020-12-01T14:36:25.422800Z

That's got to be a lie, right 😁

borkdude 2020-12-01T14:36:43.423100Z

No, http-kit is a light weight web server in Clojure + some Java without any deps

alexmiller 2020-12-01T14:36:59.423300Z

admittedly that sounded weird to me too :)

borkdude 2020-12-01T14:38:19.423600Z

@manutter51 are you using the async part of httpkit?

dominicm 2020-12-01T14:38:33.423800Z

http://http-kit.github.io/http-kit/org.httpkit.server.html#var-as-channel Hmm, I'd always thought the channels in http-kit were core.async channels. I guess I'm just crazy :)

borkdude 2020-12-01T14:39:43.424Z

@manutter51 Maybe this issue will have some relevant info. Not sure: https://github.com/http-kit/http-kit/issues/345

borkdude 2020-12-01T14:40:32.424400Z

Is the request or response body very large as in, would it be problematic to hold it in memory at once? This can also be a problem with http-kit

2020-12-01T14:42:38.424700Z

We’re not doing anything fancy with http-kit, just

(http/start
     (-> env
         (assoc :handler #'handler/app)
         (assoc :max-body max-body)
         (assoc :threads server-threads)
         (update :io-threads #(or % (* 2 (.availableProcessors (Runtime/getRuntime)))))
         (update :port #(or port %))))

2020-12-01T14:43:21.424900Z

Hmm, that’s actually calling start from luminus.http-server

2020-12-01T14:43:58.425100Z

which is basically calling http-kit/run-server, wrapped inside a try/catch

2020-12-01T14:50:31.425300Z

Yeah, looks like the response could be pretty large sometimes. :thinking_face:

borkdude 2020-12-01T14:58:28.425500Z

http-kit doesn't have an optimal scenario for this. I have made notes about that here: https://github.com/borkdude/babashka/wiki/HTTP-client-and-server-considerations#http-kit-holds-entire-requestresponses-in-memory I might switch to jetty in the future for babashka because of this. Although for babashka scripts it may not be a dealbreaker, I don't feel comfortable about it.

borkdude 2020-12-01T15:00:10.425700Z

Also see: https://github.com/http-kit/http-kit/issues/90 Again, not sure if this is your issue, but it could be.

2020-12-01T15:01:07.426100Z

This is really helpful, thanks much

2020-12-01T15:03:32.426300Z

Wait, I take back what I said about a large response — the code I’m looking at pulls potentially a lot of data out of Mongo, but then it batches it out to offline processing (via future) for a later download. The http response itself is basically empty.

borkdude 2020-12-01T15:04:12.426500Z

ok well, that's not it then.

borkdude 2020-12-01T15:04:28.426700Z

what version of http-kit are you using?

2020-12-01T15:06:39.426900Z

2.5.0

borkdude 2020-12-01T15:07:58.427100Z

ok. not sure what the issue is and if it's related to http-kit then, sorry

2020-12-01T15:08:55.427300Z

np, I appreciate the input and I learned some stuff, so all good

dominicm 2020-12-01T15:45:34.427500Z

What mongo client do you use?

dominicm 2020-12-01T15:46:20.427700Z

It's probably worth getting metrics in place for JVM memory stats (heap, memory use, etc)

2020-12-01T16:02:08.427900Z

we’re at monger 3.5.0

2020-12-01T17:18:46.428500Z

sounds kind of like https://github.com/michaelklishin/monger/issues/166

2020-12-01T17:34:43.428800Z

That doesn’t seem to be our issue, it’s happening for us when we’re querying for data.

seancorfield 2020-12-01T18:08:52.429300Z

@manutter51 You won't get a lot of insight with New Relic on http-kit I'm afraid. We were using http-kit in production and we ended up switching (back) to Jetty because that has official support in New Relic and you get a lot more information.

seancorfield 2020-12-01T18:10:15.429500Z

We worked with New Relic quite a bit to try to get http-kit supported but in the end they just considered it too niche to expend any effort. We tried configuring transaction recognition and wrote some middleware to help with transaction boundary recognition, but in the end we just gave up.

seancorfield 2020-12-01T18:11:37.429700Z

All our apps are on Jetty now, except one built on Netty and we don't get a lot of the core web transaction metrics from New Relic on that either -- but we have a lot of custom metrics that we added ourselves, via New Relic's (now obsolete) metrics plugin library.

borkdude 2020-12-01T18:12:52.429900Z

@seancorfield out of curiosity, do you use ring-jetty or pedestal or ...?

seancorfield 2020-12-01T18:13:32.430100Z

ring-jetty -- we try to keep things as "stock" as possible so we get the best out of New Relic.

2020-12-01T18:17:34.430400Z

@seancorfield Wow, that's good to know, thanks for sharing that. I'll look into switching our app back to ring-jetty

rutledgepaulv 2020-12-01T18:21:15.430700Z

+1 newrelic and ring jetty also worked well for us, never saw a reason to pursue alternative http servers. even if you're using websockets there's good support for that in the sunng87 jetty adapter

seancorfield 2020-12-01T18:22:22.431Z

We went with Netty for our websocket app because our frontend is JS and uses http://Socket.IO so we needed to support that on the backend and it's "easy" with netty-socketio (and we use that with Netty directly via Java interop).

Renzo Tomlinson 2020-12-01T18:34:22.438800Z

anyone worked through Eric Normand's http://PurelyFunctional.tv series and can maybe shed a little light on a problem I'm working on? I'm on video LispCast - Intermediate Property-Based Testing with test.check - 3 Strategies for properties: generate the output and towards the end there are two functions lines and words that both fail on the input ["" ""] because the naive implementation of these functions use the built-in str/join and split functions which lose the empty string values. Eric gives some hints that we might want to slightly modify the generator in the defspec and that the solutions for the functions themselves would be recursive. I suppose I could just not allow the generator to produce empty strings, but I don't think that is the intention. My current line of thinking is to replace the empty strings with some kind of placeholder, but I don't really have any way to verify that this is the correct approach as I'm still trying to wrap my head around what it means to write a proper property-based test. Any guidance or insights would be greatly appreciated!

2020-12-01T18:36:42.440400Z

Hi. I have a record that implements this interface:

public interface MyAsyncService extends Function<Object, Future<Result>>
[...]
(defrecord MyService
   MyAsyncService
   ...)
but when I try to pass it to a Java method that expects Function I get a java.lang.ClassCastException: class MyService cannot be cast to class java.util.function.Function Is this not something that can be done? Do I have to implement Function separatly?

2020-12-01T18:40:58.441200Z

are you sure Function in the interface refers to java.util.function.Function ?

2020-12-01T18:42:30.442200Z

and are you sure the source for the interface definition you are looking at matches the bytecode you are running (was the bytecode compiled from the same version, was the source changed and not recompiled, etc)

2020-12-01T18:44:08.443300Z

@hiredman Yep, just confirmed on both sides. But your second point is a good clue, I'll double check that. Thanks

2020-12-01T18:51:17.443900Z

@hiredman rebuilt everything cleanly and it's all good. Many thanks!

seancorfield 2020-12-01T19:00:11.444100Z

I've been through that course but I don't recall the details of that example. When I get time later today I'll pull that lesson back up and see if I can provide guidance based on that.

Renzo Tomlinson 2020-12-01T19:03:16.444400Z

@seancorfield sounds great and looking forward to it. I'm really digging property-based testing so I'm hoping to learn as much as I can.

seancorfield 2020-12-01T19:06:33.444600Z

The three courses are really good. Some of the Advanced PBT stuff is pretty mind-blowing.

Renzo Tomlinson 2020-12-01T19:14:16.444800Z

I have Advanced PBT queued up next.

Michael 2020-12-01T19:40:27.452Z

I haven't seen any updates on spec.alpha2 / alpha.spec in a while. Is it still under active development? Trying to decide whether to embrace spec1, wait for spec2's release, or start using spec2 now despite its unreleased status

seancorfield 2020-12-01T19:43:24.453Z

Spec 2 is still being worked on in design mode. Rich is thinking about function specs and how those should work (differently to what's currently in Spec 1 / Spec 2 probably).

seancorfield 2020-12-01T19:43:41.453400Z

Spec 2 is definitely not ready to be used: it is quite buggy.

seancorfield 2020-12-01T19:44:21.453700Z

^ @michael608

Michael 2020-12-01T19:45:30.454200Z

gotcha, good to know.

vncz 2020-12-01T19:45:43.454500Z

What is the best way to filter a vector based on the properties of multiple events?

vncz 2020-12-01T19:46:08.455200Z

For example, take all the a and b whose f(a,b) holds true

dpsutton 2020-12-01T19:46:34.455700Z

You’d need to filter the Cartesian product

vncz 2020-12-01T19:47:11.456900Z

Yeah, compute the Cartesian product first I guess

dpsutton 2020-12-01T19:47:21.457400Z

If you want to see an example there’s a lambda island screencast of the solution to the first advent of code problem which can benefit from this approach :)

vncz 2020-12-01T19:47:36.457900Z

Right now I'm doing some sort of weird reduce

vncz 2020-12-01T19:48:02.458Z

:troll: Ahah that's where the question originated

vncz 2020-12-01T19:48:16.458200Z

This does not look cool, but it does the job.

alexmiller 2020-12-01T19:54:04.458600Z

spec 2 is still under active design. I would recommend using spec 1 for now.

Michael 2020-12-01T19:57:04.461300Z

thanks. It might be too soon to ask, but are there plans for some kind of compatibility shim or tool to ease the spec1 -> spec2 transition?

alexmiller 2020-12-01T19:57:57.461500Z

too early, but we will presumably have a guide, a tool, compatibility layer, something

Michael 2020-12-01T19:59:19.462700Z

that's what I figured. Looking forward to getting my hands on spec 2 whenever it's ready!

seancorfield 2020-12-01T21:55:56.463300Z

OK, just re-watched that... The "recursive" part is there already: the generators for lines (and for words) are built on top of lower-level generators (not strictly recursive except in the sense that the generators call other generators). The main issue he leaves open to the reader is the question of whether the generated output is what you would expect from processing the input: and the empty lines (or words) case is quite a tricky one to think about. Consider this behavior of clojure.string/split-lines:

user=> (str/split-lines "\n\n")
[]
user=> (str/split-lines "\n \n")
["" " "]
user=> (str/split-lines "\n \n\n")
["" " "]
user=> (str/split-lines "\n \n\n ")
["" " " "" " "]
Is that really what you want from a lines function? If so, how do you formalize that such that you can correctly generate that type of output? And then, how would you turn that output into the appropriate input? I think the real problem here is that splitting lines is "lossy" by which I mean that there are multiple inputs that can generate the same output (see the middle two items).

seancorfield 2020-12-01T21:57:37.463500Z

(a less charitable way to couch that is that he picked a really hard problem, ran into difficulty trying to specify the behavior, and punted on it 🙂 )

seancorfield 2020-12-01T21:58:31.463700Z

But the good part about this -- and he says this in the lesson -- is that trying to generatively test the function immediately uncovered several bizarre corner cases that are very hard to reason about! @rttomlinson

Renzo Tomlinson 2020-12-01T22:12:52.463900Z

I think the part I'm getting caught up on is determining what I actually want the lines function to do. Presumably I don't want it to be lossy since I lose the inverse property and this would also make it equivalent to the existing str/split-lines function. If lines is not lossy, then I suppose I'm checking before \n to determine if I should add an empty string?

Renzo Tomlinson 2020-12-01T22:14:06.464100Z

hmm, okay. I think I'll try that out and see where I get. Thanks for running through that, @seancorfield. Hopefully I'll have a satisfactory answer soon.

seancorfield 2020-12-01T22:15:44.464300Z

Yup, and that is exactly the hard part: should lines (or words) be lossy? Since words will throw away whitespace between items, it is definitely lossy: "foo bar" and "foo bar" must both yield ["foo" "bar"]

seancorfield 2020-12-01T22:16:55.464500Z

And then the question is how you convert from the generated output to an appropriate input, since one output should really generate a random set of inputs that should all map back to that one output.

seancorfield 2020-12-01T22:17:51.464700Z

And at that point, you'll be generating inputs from a seed output values (and now you're layering another set of generators on top of the two-or-more you already have).

pez 2020-12-01T22:38:58.464900Z

This is not perfect, but anyway:

(for [x (butlast report)
      y (rest report)
      :when (= 2020 (+ x y))]
  (* x y))

2020-12-01T23:13:37.466Z

Is there a function or macro to get the line\number file when called?

2020-12-01T23:15:23.466600Z

(defn my-func
  []
  (let [{:keys [file line]} (file-name-line-number)]
    (println file line)
    (do-something important)))

2020-12-01T23:24:11.466700Z

(defmacro info [x]
  (let [f  *file*
        md (meta &form)]
    (assoc md :file f)))

(info "hi")
=> {:line 10, :column 8, :file "/path/to/x.clj"}