Fork me on GitHub
#clojure
<
2022-02-16
>
pinkfrog12:02:40

I’d like to shutdown ALL go blocks upon program exit. The planned idea is to have a GLOBAL close-ch and every go block in the program will alts! on it. However, I vaguely remember due to the underlying implementation, a channel shouldn’t have too many go blocks listening to it. Given that, what’s the recommended approach to achieve the goal?

pinkfrog12:02:16

Maybe use a mult ?

tatut12:02:40

for doing some cleanup? you could also add JVM shutdown hooks where needed, or do you specifically need for every block

pinkfrog13:02:43

It’s actually not about shutdown. I am in a reload-able workflow. So I have to cleanup all goblocks before reload the workflow.

bortexz13:02:59

Are those go blocks reading from some channel? I think usually you would close the source chan for those go blocks, making the go blocks to also finish. You can ‘wait’ on the chans returned by each go block to make sure you don’t proceed until all of them are closed. then on lifecycle/start, a new source chan is created along new go blocks, on lifecycle/stop the source is closed making the entire pipeline to finish, potentially blocking until this happens

tatut13:02:58

wouldn't parked go blocks simply be garbage collected if they are blocking on a channel that is gc'd

ghadi13:02:14

auto-reload frameworks have a lot of issues

ghadi13:02:26

a nice search in slack is from:@seancorfield reload

pinkfrog13:02:26

> Are those go blocks reading from some channel? I think usually you would close the source chan for those go blocks, making the go blocks to also finish. You can ‘wait’ on the chans returned by each go block to make sure you don’t proceed until all of them are closed. then on lifecycle/start, a new source chan is created along new go blocks, on lifecycle/stop the source is closed making the entire pipeline to finish, potentially blocking until this happens Yes. That’s what I am going to achieve. I am going with a/mult for distributing the close signal, and the java.util.concurrent/Phaser to wait on all the go blocks to finish.

pinkfrog13:02:56

I normally perform this pattern in golang (which also has csp), and is now translating to clojure.

pinkfrog13:02:17

That said, golang csp-s has much fewer edge cases than clojure.

noisesmith00:02:36

seeing this late, but it got stuck in my head: https://clojurians.slack.com/archives/C03S1KBA2/p1645017958360719?thread_ts=1645015840.724989&amp;cid=C03S1KBA2 my understanding is that if parking on a go block wasn't enough to keep the channel from being garbage collected, I have a lot of code that should have never been collected. I think this code proves that parking is enough to prevent gc.

;; an experiment in channel garbage collection

(ns channels.gc-test
  (:require [clojure.core.async :refer [chan go-loop timeout <! >!]]))

(defn println'
  [& args]
  (print (apply str (concat args "\n")))
  (flush))

(defn channels-in-the-void
  []
  (let [left (chan 1)
        right (chan 1)]
    (do (go-loop [n 1]
                 (println' "loop 1: " n)
                 (>! right n)
                 (<! (timeout 1000))
                 (if (> n 1000000)
                   (println' "loop 1 exit: " n)
                   (recur (+ n (<! left)))))
        (go-loop [m 1]
                 (println' "loop 2: " m)
                 (>! left m)
                 (<! (timeout 1000))
                 (if (> m 1000000)
                   (println' "loop 2 exit: " m)
                   (recur (+ m (<! right)))))
        nil)))
last few lines of output:
loop 2: 131072
loop 1: 131072
loop 2: 262144
loop 1: 262144
loop 1: 524288
loop 2: 524288
loop 1: 1048576
loop 2: 1048576
loop 1 exit: 1048576
loop 2 exit: 1048576

noisesmith00:02:20

@U11SJ6Q0K I expected linking to your comment to do some magic it didn't do, the above is a response to your comment about parking and channel gc above

noisesmith00:02:10

(and I assumed you meant parking not blocking, as blocking shouldn't be done in go blocks)

vemv14:02:04

I'm getting a user report with the following:

java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol (json.clj:23)
json.clj is https://github.com/clojure/data.json/blob/v2.4.0/src/main/clojure/clojure/data/json.clj I can't make sense of the exception, any ideas?

emccue14:02:20

Did they give the code that gave the exception?

vemv14:02:14

There isn't more to repro, it fails right as the code is loading. Anyway it's https://github.com/rm-hull/nvd-clojure/issues/128

Alex Miller (Clojure team)14:02:55

maybe an error parsing a json file? that project is using a very old version of data.json (0.2.6) - newest is 2.4.0

Alex Miller (Clojure team)14:02:22

oh sorry, I looked at the wrong deps.edn, it's on latest

slipset14:02:35

at nvd.task.check$eval__24$loading__6309__auto____26.invoke(check.clj:23)
is somewhat further down the stacktrace

slipset14:02:20

But that doesn’t make any sense at all, as it’s in the middle of a require

slipset14:02:54

Oh, but here:

slipset14:02:09

Could it be some strange destructuring thing going on?

slipset14:02:30

Dependent on Clojure version?

🎯 1
vemv14:02:33

Finally the GH issue above is fixed, ~sorry for the noise (I was quite lucky to see the Clojure compiler warning! Otherwise it would have been a time sink)

seancorfield15:02:40

Do you know what version of Clojure it pulled in by default there?

vemv15:02:02

repro'ing what happened, turned out to be clojure 1.1.0. It's what Maven resolves if a Lein project only depends on this tree: https://github.com/rm-hull/nvd-clojure/blob/master/project.clj ...Surely it's pulling it from some transitive dep, because for a truly empty project, Leiningen will not add org.clojure/clojure at all

seancorfield15:02:13

Wow! Not what I expected.

p-himik16:02:44

Is there a document anywhere describing issues with the REPL-driven workflow? The page at https://clojure.org/guides/repl/enhancing_your_repl_workflow#writing-repl-friendly-programs has some info but it's very superficial. @seancorfield has mentioned a snippet that removes refers, aliases, and interns from a namespace because you can't remove and re-create a namespace in REPL-driven workflow since it'll create problems. So that would be another thing in the list. But are the other such things? I tried searching through the history of Slack messages with from:@seancorfield reload as suggested by @ghadi but 95% of the results seem to be just "don't use reloaded workflows". I'm asking because I've been using the reloaded workflow with integrant.repl/reset for a around 4 years now, and, while I do have a problem with it about once a month, all such problems are well-known and described at https://github.com/clojure/tools.namespace#warnings-and-potential-problems. And funny thing is, some if not most of those warnings are definitely applicable to the REPL-driven workflow, but it doesn't seem to be mentioned anywhere.

p-himik16:02:38

After the amount of messages on Slack that I have read on the topic, along with pretty much every article and video that I've stumbled upon about both REPL-driven and reloaded workflows, I'm under a very strong impression that the true enemy of the people here is not the reloaded workflow but rather... cognitive biases. REPL workflow is manual. When something is wrong, you're always the one to blame. But when something goes wrong with the reloaded workflow, it's always "that pesky magic function" (which actually isn't magic). It's much easier to blame something else when there is that something else.

JohnJ18:02:26

Are their issues with the 'reloaded workflow' orthogonal to using a component system? In like that they find it valuable to use DI in Clojure but just not for REPLing

seancorfield18:02:57

@U01KZDMJ411 Not quite sure what you're asking -- we use Component very heavily at work and we have a tight REPL workflow and we do not use reload with it.

vemv18:02:11

As a brief contribution, I've amassed enough "Reloaded" expertise to roll my own parallel implementation which has worked nicely over more than one year. The issues with Reloaded aren't as conceptual as they are implementational - if you give people a barely-maintained lib it sure will have issues. Oh and I like to mix and match workflows - it's not a binary thing :) I'd like to contribute my work at some point.

p-himik18:02:36

Thanks, I'll check it out! And yeah, I'd definitely check out your implementation as well.

JohnJ19:02:38

@seancorfield Ok, was just curious if you were against using something like Component altogether

Alex Miller (Clojure team)19:02:35

I'm not sure what exactly "REPL-driven workflow" is, but what I usually do is to reload my current namespace, which I'm guessing may be in between the two approaches being described here?

Alex Miller (Clojure team)19:02:28

(although I sometimes work in a form-at-a-time manner and have in some projects also used a reloaded pattern)

Alex Miller (Clojure team)19:02:39

I feel like reloading the current namespace addresses the majority of the scenarios listed in clojure-repl-sufficient-p (at least in many cases, not if stateful constructs are defined elsewhere). maybe this is just my more tactical version of reloaded, I don't know.

Alex Miller (Clojure team)19:02:57

truly though, I think any of these approaches are fine if you have the mental model of both your code and how Clojure works (and probably they are all bad if you don't)?

1
🙂 1
Alex Miller (Clojure team)19:02:25

is the real place to apply leverage not on prescriptive approaches of how to work, but thinking about how to provide better advice on structuring code and how vars and loading work?

p-himik19:02:01

My question is a precursor to exactly that. :)

seancorfield20:02:56

By "reload my current namespace" do you literally mean (require 'current.ns :reload) or whatever your editor does for the "load file" command?

Alex Miller (Clojure team)20:02:26

yes, I hit that like a monkey getting a treat

😆 1
seancorfield20:02:04

If the latter, yes, I use my editor's "load file" functionality when I first open a file -- but I generally eval top-level forms without even saving changes to the file. But doing "load file" after saving is pretty "safe" and a reasonable in-between that doesn't go anywhere near the full reset/refresh/reload workflow that some libraries seem to encourage (and some books and tutorials, unfortunately).

Alex Miller (Clojure team)20:02:53

there are only a few situations I get into (like renamed/deleted test) where I end up restarting my repl due to state. I'm well aware I can work my way out of even those, but it's easier to click restart than it is to type that out

seancorfield20:02:02

@U2FRKM4TW The main issue where "reload" tooling seems to cause breakage is by actually calling remove-ns -- which I see that repl-sufficient? repo does too in the tests @U45T93RA6 -- and that's something I deliberately avoid. I have a code snippet that "cleans up" a namespace but it doesn't remove it (it just unmaps and unaliases most things and the "load file" will get you a nice, pristine ns state) but I try to use that as rarely as possible -- mostly for removing old test definitions after a refactoring.

Alex Miller (Clojure team)20:02:18

yeah, if I get to that part I just restart :)

seancorfield20:02:20

Right now, I've "only" had my main work REPL running for about six and a half days (since the last restart):

dev=> (java.util.Date.)
#inst "2022-02-16T20:49:50.416-00:00"
dev=> (up-since)
#inst "2022-02-10T07:01:06.116-00:00"
dev=> 
It often runs for a month or so...

seancorfield20:02:31

(my HoneySQL development REPL has been up a few days longer)

p-himik20:02:03

@seancorfield Right, but unless my understanding is wrong, that's only problematic if you don't reload a namespace that depends the removed namespace - and a properly working reloaded workflow is supposed to do that for you. I've never experienced issues with removing namespaces, ever, and I hit the "reload button" hundreds times every day. Issues with protocols - yes, and it's fixable by manually loading the protocol namespace. But namespaces - not a single problem, it has been working flawlessly.

crinklywrappr21:02:14

does anyone know how to specify resource directories w/ deps.edn?

dpsutton21:02:58

i believe it is just another classpath root

dpsutton21:02:46

and example from the repo for tools.deps.alpha adding src/main/resources onto the classpath:

dpsutton21:02:49

oh neat. i’ve never seen the path aliases before.