babashka

https://github.com/babashka/babashka. Also see #sci, #nbb and #babashka-circleci-builds .
pbaille 2021-06-08T09:21:12.446700Z

Hello, i'm trying to use babashka.process for a little thing. I would like to be able to launch a process that do not terminate and do something whenever some new output occurs from it. Is there a builtin way to do this? I've try this example from the documentation:

(future
  (loop []
    (spit "log.txt" (str (rand-int 10) "\n") :append true)
    (Thread/sleep 10)
    (recur)))

(pipeline (pb '[tail -f "log.txt"])
          (pb '[cat])
          (pb '[grep "5"] {:out :inherit}))
but I do no see anything printing out, is it intended ?

borkdude 2021-06-08T09:25:23.447800Z

@pbaille The readme explains what the problem is: buffering.

borkdude 2021-06-08T09:25:32.448100Z

oh now you are using pipeline ok

pbaille 2021-06-08T09:25:42.448300Z

sorry I've edited the message

borkdude 2021-06-08T09:26:19.448500Z

Yes, so this works:

(require '[babashka.process :refer [pipeline pb]])

(future
  (loop []
    (spit "log.txt" (str (rand-int 10) "\n") :append true)
    (Thread/sleep 10)
    (recur)))


(-> (pipeline (pb '[tail -f "log.txt"])
              (pb '[cat])
              (pb '[grep "5"] {:out :inherit}))
    last
    deref)

pbaille 2021-06-08T09:31:26.450400Z

thank you @borkdude, I've just tried this snippet in my editor (intellij cursive) on Java 15 and clojure 1.10.0. Am I supposed to see something printed in the repl ?

borkdude 2021-06-08T09:32:20.450900Z

@pbaille :inherit prints directly to stdout (`System/out`)

borkdude 2021-06-08T09:33:34.452100Z

Just try (process ["ls"] {:inherit true}) to try it out what happens in your editor

pbaille 2021-06-08T09:34:36.452600Z

yes it prints the output

pbaille 2021-06-08T09:34:39.452800Z

in the repl

borkdude 2021-06-08T09:35:19.453200Z

ok, then that works correctly.

pbaille 2021-06-08T09:36:56.453900Z

ok, so I should be able to see some output from your first snippet ?

borkdude 2021-06-08T09:37:11.454100Z

yes, I think so

borkdude 2021-06-08T09:38:22.454500Z

maybe simplify the example for debugging purposes

pbaille 2021-06-08T09:38:22.454600Z

it hangs on the last expression

borkdude 2021-06-08T09:38:54.454900Z

you can remove the deref to make it not hang. I just added that to make the bb script not terminate

borkdude 2021-06-08T09:39:01.455100Z

let's continue in thread

pbaille 2021-06-08T09:39:10.455300Z

ok

pbaille 2021-06-08T09:40:38.455500Z

I got this but nothing prints

borkdude 2021-06-08T09:47:25.455900Z

Can you please try this in a standalone bb script and then execute it from the terminal?

borkdude 2021-06-08T09:47:47.456100Z

Or clojure script if you are using this lib on the JVM

pbaille 2021-06-08T09:49:25.456300Z

yes I will do this

pbaille 2021-06-08T09:56:44.456500Z

it works nicely as a script

borkdude 2021-06-08T09:57:33.456700Z

Perhaps try :out *out* in your editor for the last process in the pipeline

borkdude 2021-06-08T09:57:39.456900Z

else, I don't know :)

pbaille 2021-06-08T09:57:46.457100Z

I've tried this

pbaille 2021-06-08T09:58:02.457300Z

but didn't change anything

pbaille 2021-06-08T09:58:37.457500Z

ok, thank you for your time @borkdude! I will investigate on my own and let you know if I find something

dabrazhe 2021-06-08T16:54:31.459500Z

Can I connect to the bb repl in VSC Calva without manually entering it every time? it does not pick it up from .nrepl-port file

borkdude 2021-06-08T17:20:17.460400Z

@dennisa Babashka does not write an .nrepl-port file since that can conflict with a Clojure process.

dabrazhe 2021-06-08T17:22:50.461600Z

I can write it myself, just need Calva to pick it up from somewhere, otherwise getting this promt with no port. I have to restart the bb repl rather often as it slows down and it's a chore to type every time 1667 : )

borkdude 2021-06-08T17:24:05.462200Z

@dennisa There is a snippet about writing this port file here: https://book.babashka.org/#_nrepl

borkdude 2021-06-08T17:24:45.462700Z

(I think this script can be simplified/refactored now since there is babashka.process)

dabrazhe 2021-06-08T17:26:11.464100Z

Have someone managed to Calva to pick it up? Or am I in the wrong channe?l :)

borkdude 2021-06-08T17:26:40.464300Z

@pez ^

dabrazhe 2021-06-08T19:01:21.464700Z

what's the babashka.process : ) ?

dabrazhe 2021-06-08T19:22:05.464800Z

nevemind, found it in the BBook )

pez 2021-06-08T20:27:21.466600Z

I haven’t tried this myself, but you can probably configure a connect sequence, nReplPortFile with the name of whatever nrepl-port file you write. See: https://calva.io/connect-sequences/

dabrazhe 2021-06-09T08:36:16.483Z

It won’t work for me. I can see ‘bb’ in the ‘connect to repl’ prompt but the port is still empty after localhost: The .bb-nrepl-port is created fine with 1667 port in it. @pez

pez 2021-06-09T08:44:32.485100Z

And you are selecting bb, not just seeing it?

pez 2021-06-09T08:45:47.485500Z

Also, which directory are you starting babashka in?

dabrazhe 2021-06-09T12:05:14.485800Z

Yes, selecting. BB starts in the root dir or the project, where the repl file is.

dabrazhe 2021-06-09T12:05:43.486Z

Restarted VSC just in case

pez 2021-06-09T13:03:56.486200Z

I now tried this on my Windows machine, and it just works there as well… Please file an issue about this using the Help menu in vscode. Attach the setting and the command line you are using.

dabrazhe 2021-06-10T09:34:23.499800Z

It's on the mac. Do you want me to file issue to Microsoft about calva )? What's the chance to resolve it? )

dabrazhe 2021-06-10T09:35:16Z

The will forward me to the maker of the extension, at best

pez 2021-06-10T09:40:59.000300Z

There is an option there to file the issue on the extension, so it will land in Calva’s GiHub repo. The point with doing it this way is that some system info will be attached to the issue.

borkdude 2021-06-08T20:27:52.466800Z

^ @dennisa

❤️ 1
pez 2021-06-08T20:35:17.467100Z

Now tried it. This setting works:

"calva.replConnectSequences": [
        {
            "name": "bb",
            "projectType": "generic",
            "nReplPortFile": [".bb-nrepl-port"],
            "cljsType": "none"
        }
    ]
Then start babashka like so:
echo 1667 > .bb-nrepl-port && bb --nrepl-server

2021-06-08T20:44:48.468500Z

Continuing from #clojure in a thread: I'd like to add babashka support to https://github.com/IGJoshua/farolero The key thing that would prevent it from working as-is is the requirement to be able to throw an object that isn't caught by ex-info catch clauses.

2021-06-08T20:46:27.470500Z

If there's a way to throw an arbitrary object or extend a throwable marker protocol or something that'd be ideal, but since babashka is likely to be used in smaller projects with fewer dependencies, I may if need be just use ex-info, but I'd prefer to not as there's a requirement to re-throw exceptions from catch blocks based on a condition, and a normal catch block for ex-infos won't re-throw based on that condition.

borkdude 2021-06-09T13:48:35.486400Z

@suskeyhose I've got dosync etc working. I'm also looking into .wait and .notify: this does work if you use an ^Object type hint, so you could try that as a workaround meanwhile.

borkdude 2021-06-09T13:48:44.486600Z

I also made this GraalVM issue: https://github.com/oracle/graal/discussions/3456

borkdude 2021-06-09T13:49:06.486800Z

If you want to try a binary with dosync etc, let me know your OS

2021-06-09T17:41:41.494600Z

Awesome, I'll give it a try today.

2021-06-09T17:52:21.495Z

I'm using nixos

borkdude 2021-06-09T17:58:03.495200Z

ok, then the static linux binary probably works for you: https://19891-201467090-gh.circle-artifacts.com/0/release/babashka-0.4.5-SNAPSHOT-linux-amd64-static.tar.gz

2021-06-09T20:30:32.496300Z

@borkdude the commute and alter functions would be great to have too. I know farolero uses at least alter. I'll get to actually testing it once my workday is done.

borkdude 2021-06-09T20:47:30.496500Z

I already added alter. Adding commute now as well.

2021-06-09T20:55:28.496800Z

Thanks

borkdude 2021-06-10T10:05:16.000500Z

@suskeyhose Now the .notify / .wait stuff also works :)

borkdude 2021-06-10T10:15:10.000900Z

$ time bb -e '(def x (cons 1 nil)) (locking x (.wait x 2000))'
bb -e '(def x (cons 1 nil)) (locking x (.wait x 2000))'   0.01s  user 0.01s system 1% cpu 2.033 total

borkdude 2021-06-10T10:27:25.001100Z

I will just make a new release so you can use that

2021-06-10T14:05:14.008700Z

Thanks! Sorry I didn't get to try it out last night, life just kinda got in the way. Got a kitten recently and she requires a lot of attention.

borkdude 2021-06-10T14:05:55.008900Z

No worries at all, I'm glad I got it working after all

borkdude 2021-06-10T14:06:10.009100Z

and if you need something then we can just make another release

2021-06-10T14:39:20.009400Z

Sounds good

2021-06-08T20:47:58.470700Z

@borkdude

borkdude 2021-06-08T20:49:26.470800Z

How do you solve this in normal Clojure?

borkdude 2021-06-08T20:49:28.471Z

I'll brb

2021-06-08T20:51:43.471200Z

I solve this by making the farolero-signal artifact on maven that contains a single class that extends java.lang.Error (which extends from Throwable but not Exception), https://github.com/IGJoshua/farolero/blob/master/signal/src/farolero/signal/Signal.java Then in the actual farolero lib I just extend-protocol to this class to add the functionality for storing the objects that need to get passed around.

2021-06-08T20:53:50.471500Z

And the reason that it's in a different artifact altogether is to allow git dependencies on farolero.

borkdude 2021-06-08T21:10:44.471700Z

@suskeyhose So your library, does it catch only this specific exception and handle that one? Or does it install some global exception handler?

2021-06-08T21:11:29.471900Z

It only catches this one specific exception, and it never throws the exception except if it knows for a fact that it's currently in a dynamic context where it will be caught.

2021-06-08T21:11:53.472100Z

For the CLJS support it catches all objects thrown and re-throws them if they don't implement the protocol.

2021-06-08T21:15:16.472400Z

In particular there is exactly one construct that throws or catches this exception, and it is block, and everything else is implemented on top of that.

(block the-block
  (some code)
  (return-from the-block :returned)
  :unreachable)
return-from throws the exception, and block catches it

borkdude 2021-06-08T21:16:07.472600Z

In CLJS, can you throw any arbitrary object?

2021-06-08T21:17:16.472800Z

Yeah, it's a feature of JS.

borkdude 2021-06-08T21:17:42.473Z

A workaround I can come up with for bb is: Make one singleton Exception instance, e.g.:

(defonce FaroleroEx (Exception. "my-special"))
and then in the catch clause check (identical? FaroleroEx ex)

borkdude 2021-06-08T21:17:56.473200Z

or just use ex-info with a special field in it to mark it as a farolero exception

borkdude 2021-06-08T21:18:20.473400Z

I think that may just work best for bb

2021-06-08T21:19:02.473700Z

That's a good thought, I might do that. The key reason that I would want to throw arbitrary objects or something would just be to prevent catch blocks from interfering with this mechanism, but if I can't do that in bb, that's fine.

2021-06-08T21:19:39.473900Z

The features already won't be 100% parity anyway because I doubt bb implements locking and so the debugger won't be the same.

borkdude 2021-06-08T21:19:54.474100Z

bb does implement locking

borkdude 2021-06-08T21:21:16.474300Z

btw another thought. I see you mix specs with your code. If they are non-essential to your lib, e.g. only for development, I recommend putting them aside in a farolero.specs namespace so they can be optionally loaded. This saves startup time

borkdude 2021-06-08T21:21:56.474500Z

bb currently doesn't come with clojure.spec.alpha since ... it's alpha

borkdude 2021-06-08T21:22:13.474700Z

but there is https://github.com/borkdude/spartan.spec as a drop-in replacement

2021-06-08T21:22:16.475Z

Oh, cool, maybe it can be on part with the debugger then. The main part I was concerned about is this highlighted portion: https://github.com/IGJoshua/farolero/blob/master/src/cljc/farolero/core.cljc#L1192-L1269 I guess it's worthwhile to see if it'll run unmodified on bb though. Thanks! As for the exception mechanism, I think I can just do what you suggested with an extra field on an ex-info

2021-06-08T21:22:45.475400Z

Oh, yeah, the specs. Good to know. The reason the specs are there is just to help with macroexpansion, they aren't used at runtime at all. Moving them to a separate namespace is far from out of the question.

borkdude 2021-06-08T21:25:12.475700Z

Hmm, dosync and ref etc are not in babashka. Nobody ever asked for this. Those features were cool in the beginning of Clojure but nowadays pretty much everyone uses atoms

2021-06-08T21:25:46.475900Z

Yeah, this was the first time I'd ever found a use for refs.

borkdude 2021-06-08T21:26:32.476100Z

What is the debugger object?

2021-06-08T21:26:33.476300Z

I might be able to refactor it to use atoms, but it could be a challenge, this is all pretty sensitive code and prone to race conditions on changes.

borkdude 2021-06-08T21:26:49.476500Z

you call e.g. .wait and .notify on it, what is the type?

2021-06-08T21:27:39.476700Z

It's a unique cons of the condition and arguments.

2021-06-08T21:27:54.476900Z

line 1283

borkdude 2021-06-08T21:30:28.477300Z

I don't think .wait and .notify currently work, but I think they could be made to work, with some work ;)

borkdude 2021-06-08T21:30:37.477500Z

not sure how hard it is to port dosync etc

borkdude 2021-06-08T21:31:08.477700Z

probably it's possible

2021-06-08T21:31:33.477900Z

Fair enough. I'd like to take a peek in the source of babashka/sci to see how hard adding notify and wait are. dosync etc I might be able to avoid by refactoring to use an atom, although we'll see if that ends up worth it.

borkdude 2021-06-08T21:32:53.478100Z

oh gosh it does work already:

(locking 1 (.wait 1))

2021-06-08T21:32:59.478300Z

Oh nice!

2021-06-08T21:33:16.478500Z

Are numbers interned objects in bb?

borkdude 2021-06-08T21:36:12.478700Z

hm, this didn't work:

user=> (def c (cons 1 2))
user=> (locking c (.wait c))
java.lang.IllegalArgumentException: No matching method wait found taking 0 args for class clojure.lang.PersistentList [at <repl>:1:8]
The interop in bb is implemented using reflection and reflection inside a native image needs some special attention sometimes

borkdude 2021-06-08T21:36:58.478900Z

Are numbers interned: I don't do anything special with numbers, so I don't know

2021-06-08T21:37:09.479100Z

Ah, I see.

2021-06-08T21:37:22.479300Z

Yeah, I was able to get just a bare object to work as expected with wait and notify

borkdude 2021-06-08T21:37:26.479500Z

user=> (identical? 1 1)
true

borkdude 2021-06-08T21:37:52.479700Z

user=> (identical? 1 (+ 1 0))
true

2021-06-08T21:38:36.479900Z

Fair enough. Anyway, it looks like wait and notify don't work on clojure's collections, yeah, so that'd probably be where the work on wait and notify would have to be put.

borkdude 2021-06-08T21:39:38.480100Z

I'm going afk now, as it's getting late. Let's revisit some other time and make issues for the things that could be examined / improved

2021-06-08T21:39:47.480300Z

Also I'm looking at this and while the ref code could be re-expressed in terms of atoms, it'd be clunky at best.

2021-06-08T21:39:50.480500Z

Sounds good!

borkdude 2021-06-08T21:41:42.480700Z

btw, I think I know what the issue is. bb supports reflection on a selection of classes. e.g. this works:

user=> (def c (java.io.File. "foo"))
#'user/c
user=> (locking c (.wait c))

borkdude 2021-06-08T21:41:58.480900Z

so if I add explicit support for a type then it should work

borkdude 2021-06-08T21:42:26.481100Z

it just doesn't include the wait/notify methods for PersistentList

2021-06-08T21:43:31.481300Z

Fair enough. I'll make an issue for this now.

2021-06-08T21:43:43.481500Z

As well as a discussion issue about ref/dosync

borkdude 2021-06-08T21:44:24.481700Z

:thumbsup:

2021-06-08T21:55:40.481900Z

As an aside, farolero kind of completed my personal trek through the multithreading facilities that clojure provides, since before this I'd used atoms of course, core.async channels, reducers, and agents for per-endpoint ratelimits on a discord client. Finally with farolero I've also used refs. Now I guess it's time for me to learn manifold, lol.

borkdude 2021-06-08T21:56:14.482200Z

I think manifold could use some help, so that would be super cool

2021-06-08T21:57:46.482400Z

Honestly the one thing that I felt was really missing from core.async though was a promise channel that you could deref. So I've had to write one. 😛 https://github.com/IGJoshua/discljord/blob/master/src/discljord/util.clj#L93-L151