Fork me on GitHub
#babashka
<
2022-09-20
>
borkdude13:09:33

JDK19 virtual threads + bb: https://twitter.com/borkdude/status/1572222344684531717

‼️ 5
babashka 10
🎉 1
🚀 1
mkvlr13:09:37

will you default to JDK 19?

borkdude13:09:24

when this lands, I think so yes

borkdude14:09:50

Any objections?

borkdude14:09:11

The first thing I'll probably do is make the threads in the core.async go macro use this, currently it defaults to 1 normal thread

🔥 3
mkvlr14:09:19

I’m all for it

mkvlr14:09:44

> when this lands, I think so yes so JDK 19 is out but graal is still in preview?

borkdude14:09:00

well, they don't have an official build yet

mkvlr14:09:24

I see, did you need to build it yourself?

borkdude14:09:31

there is a dev build

borkdude14:09:16

I can send you my local build of bb if you want to play around with it :P

mkvlr14:09:17

will they even do one or are they waiting on the next LTS or is 19 an LTS?

borkdude14:09:32

I think the next release will have it

borkdude14:09:48

I think 19 is even an LTS right

borkdude14:09:11

hmm no it isn't

borkdude14:09:17

but they will have it, I'm fairly certain

mkvlr14:09:49

exciting to finally have this

Darin Douglass14:09:05

> # whoopps lol

teodorlu15:09:36

Exciting! Love the demo format 😁

pavlosmelissinos15:09:52

Great demo! Are you aware of any studies that compare the performance/scaling capabilities of Loom threads and virtual threads in other languages, e.g. goroutines?

borkdude15:09:37

@UEQPKG7HQ Not aware of those

👌 1
littleli16:09:39

good you're experimenting with this @U04V15CAJ but I'd be very careful

littleli16:09:45

if you have Vars with thread-bounded global namespace are you sure this will work as intended?

littleli16:09:33

If you remember few months back I wrote an optimistic article about Virtual thread and structured concurrency, but during night I have actually nightmares about this

borkdude16:09:49

I'm not sure what you're getting at

littleli16:09:12

I'm getting towards how Vars are implemented

borkdude16:09:27

bb won't replace all Threads with VirtualThreads

littleli16:09:36

they have thread-local semantics... and threadlocal is an enemy of cheap threads

borkdude16:09:06

dynamic vars, yes

littleli16:09:35

I'd like to understand Vars deeply so my nightmare goes away

borkdude16:09:22

I'm not sure what could go wrong, but feel free to make a Clojure JVM repro

littleli16:09:05

how def and defn work with thread-local? btw you're wildly overestimating my Clojure capabilities 🙂

borkdude16:09:09

@ales.najmann def and defn create globally mutable things. many things are mutable in Java. What is the problem? ;)

littleli16:09:33

well if def and defn create entries in thread-local and just by referring to these things they got duplicated per thread, you have a problem. if that's the case solutions based around idea that you can launch millions of cheap virtual threads simply falls apart, because they're no longer cheap. You said that only dynamic vars follow thread semantics and use thread local - this is where I'm losing it because I didn't have time to follow white rabbit down this hole.

littleli16:09:23

But you're probably right, there is no reason why Vars in general should use thread locals. I'm almost certainly exaggerating here.

borkdude16:09:33

@ales.najmann I tried this with both normal threads and virtual threads and they yielded the same result:

(def ^:dynamic *foo* 10)
(defn lots-of-tasks
  [concurrency]
  (binding [*foo* 111]
    (let [executor   #_(Executors/newFixedThreadPool concurrency)
          (Executors/newVirtualThreadPerTaskExecutor)
          tasks      (mapv (fn [_]
                             (bound-fn []
                               (Thread/sleep 1000)
                               *foo*))
                           (range concurrency))
          start-time (System/currentTimeMillis)
          sum        (->> (.invokeAll ^ExecutorService executor tasks)
                          (map #(.get ^Future %))
                          (reduce +))
          end-time   (System/currentTimeMillis)]
      {:sum     sum
       :time-ms (- end-time start-time)})))

borkdude16:09:56

At least, in bb ;)

borkdude16:09:20

You are right that one should be mindful about conveying bindings to virtual threads, that doesn't happen automatically, but it doesn't happen automatically with normal threads either

littleli16:09:57

This is quite convincing. You're smart man. I've to do some exercise during the weekend using Clojure (or bb) around this topic. It seems like a great thing to do.

borkdude16:09:38

From the docs:

Virtual threads support thread-local variables, synchronized blocks, and thread interruption; therefore, code working with Thread and currentThread won't have to change. Indeed, this means that existing Java code will easily run in a virtual thread without any changes or even recompilation!

borkdude16:09:03

I suspect a virtual thread will always be tied to the thread it started to run on, else you can't have the above reliably OR you will have to do a lot of magic

littleli16:09:33

There is a concept of pinning and my bet is thread-locals can be one cause for pinning virtual thread to an OS thread

littleli16:09:48

But let me tell you why I'm so scared. I've seen video where Oracle engineer created a loop over collection of more than 10 million items launching thread for each individual one of them. If you do this and you hit pinning,your VM is toast

littleli16:09:40

I wonder if it just blocks or explode... I'm not sure at this point.

borkdude16:09:42

The above example (in tweet) does something similar (but for 100k threads, which would normally also toast your running program)

borkdude16:09:47

it explodes with normal threads

littleli16:09:07

ah, ok! then it's a good, very good sign.

Crispin08:09:29

Holy expletive! Loom is exciting tech. In babashka so soon!

Crispin08:09:44

Never reach for Erlang ever again... 😆

teodorlu15:09:33

Follow-up question to an old thread: https://clojurians.slack.com/archives/CLX41ASCS/p1655276723468789. Is the motivation for providing a single function fs/modified-since rather than a whole makefile.edn thing that a single function is a better abstraction than inventing a different data format? So that with fs/modified-since, you can essentially get the makefile stuff easily, as bb tasks, a separate CLI, or something else? And to be able to have dynamically computed dependencies rather than static ones?

borkdude15:09:22

@teodorlu Providing a function and be able to code is more flexible than having a whatever.edn right? This is why tools.build is a library as well

👍 1
teodorlu15:09:11

I definitely don't disagree!

teodorlu15:09:36

Though it took me three months of writing code around makefile generation to see your point of view.

mkvlr15:09:56

came across https://andydote.co.uk/2022/09/19/make-content-hash/ on the orange site today.

💯 1
borkdude15:09:43

yeah, we've spoken about this @mkvlr that it could be interesting to do more with content-hashing in a bb lib

👍 1
teodorlu15:09:34

is the blog post sort of "in the absence of bb and bb tasks and fs/modified-since we can do lots of fancy stuff to get dynamic make-like behavior"?

borkdude15:09:23

For nextjournal I've already done some content-hashing stuff in bb, but it isn't public

👍 1
borkdude15:09:56

I also recently spoke about this with @robert-stuttaford who has similar problems with slow cljs builds and he asked if it was ok if I showed that code to him @mkvlr

robert-stuttaford05:09:11

yes pleeeeeaassse 🙂

mkvlr15:09:42

sure, we can also open it up though probably a bit early to commit to a stable api maybe?

borkdude15:09:56

maybe we could write a blog post about the approach or so

teodorlu15:09:56

I'd love to have a peek 🙂

borkdude15:09:41

The idea is basically to calculate all namespaces that are reachable from the main CLJS namespace(s) including macro namespaces and then content-hash those

borkdude15:09:54

and then upload the compiled CLJS under that hash on some store

borkdude15:09:07

and then next time you either download from the store or re-compile

teodorlu15:09:42

Huh. That sounds like it could have a big impact on CLJS build times.

mkvlr15:09:12

also using it in clerk

👍 1
robert-stuttaford05:09:31

i assume the 'compiled cljs' is the :none optimised file? how do you integrate this with shadow?

robert-stuttaford05:09:10

ok it looks like clerk's use is not for app cljs but for something else

robert-stuttaford05:09:39

to be clear, we have a 6 minute shadow-cljs :release build that we'd like to speed up, which builds 7 shadow builds. right now we're not doing anything in terms of conditional compilation; we simply build everything always. seems like this solution is about hashing the source tree, and then checking if that hash is on <storage>. if not found, build, otherwise, continue to reference existing assets in <storage>, and bypass building any cljs.

robert-stuttaford05:09:07

so if even one cljs or required cljc file changes, you're running your full build

escherize16:09:34

Does using sci from babashka usually work? I am getting Could not resolve symbol: sci/eval-string from: bb -e "(require '[sci.core :as sci]) (sci/eval-string \"32\")"

borkdude16:09:15

There are some SCI things in there, but not all. sci.core/stacktrace was added to get a stacktrace from an exception

borkdude16:09:27

could add more if you can explain your use case :)

escherize16:09:29

I’m writing a tool in nbb that takes a description of questions from stdin or a file, asks the questions, and outputs the result to a file. (Sort of re-awakening a pet project).

escherize16:09:50

One feature I want to add is something like:

{:type :number,
  :name :phone,
  :message "What is your phone number?",
  :validate '(fn [value]
               (or (= 10 (count (str value)))
                   (str "10 digits please, I got: " value " which is " (count (str value)) " digits.")))}

escherize16:09:06

where you can embed functions in your questions description.

borkdude16:09:31

alright, you can already do this with eval I think

❤️ 1
escherize16:09:56

Thanks I’ll give it a try

borkdude16:09:35

you might want to wrap this in (js/Promise.resolve ...) - in the future this might change to a promise so then you will be prepared in case it changes

borkdude16:09:22

I'm not sure if it will change

borkdude16:09:48

Ah, this works right now:

$ nbb -e '(eval (quote (+ 1 2 3 )))'
6

🙏 1
escherize16:09:49

It appears that eval returns a string. But I want the datastructure 🙂

user=> (type (eval "3"))
java.lang.String

borkdude16:09:50

evaluating a string returns a string

borkdude16:09:55

this is normal :)

borkdude16:09:36

I think you are after:

$ nbb -e '(nbb.core/load-string "3")'
3

borkdude16:09:43

if you want to eval a string

borkdude16:09:50

this does return a promise btw

borkdude16:09:10

If anyone want to play around with JDK 19 + virtual threads: https://github.com/graalvm/graalvm-ce-dev-builds/releases/tag/22.3.0-dev-20220915_2039 Bb branch: jdk19-loom Dev docs: https://github.com/babashka/babashka/blob/master/doc/dev.md /cc @ales.najmann I will post an example program in the thread

1
borkdude16:09:39

(import '[java.util.concurrent ExecutorService Executors Future])

(defn lots-of-tasks
  [concurrency]
  (let [executor   #_(Executors/newFixedThreadPool concurrency)
        ;; this uses one thread per concurrent operation...  too many when you
        ;; want to create 100.000!!!!  now let's swap the executor out with
        ;; virtual threads aka loom on JDK19!!!!
        (Executors/newVirtualThreadPerTaskExecutor)
        tasks      (mapv #(fn []
                            (Thread/sleep 1000)
                            %)
                         (range concurrency))
        start-time (System/currentTimeMillis)
        sum        (->> (.invokeAll ^ExecutorService executor tasks)
                        (map #(.get ^Future %))
                        (reduce +))
        end-time   (System/currentTimeMillis)]
    {:sum     sum
     :time-ms (- end-time start-time)}))

(lots-of-tasks 100000)

👀 1
teodorlu17:09:01

I'm really enjoying unix pipes + lines of EDN + bb -I -O .... (https://book.babashka.org/#_input_and_output_flags) ❤️ Here's an example:

$ seq 8 | bb -IO '(map (fn [n] {:n n :double (* 2 n)}) *input*)'
{:n 1, :double 2}
{:n 2, :double 4}
{:n 3, :double 6}
{:n 4, :double 8}
{:n 5, :double 10}
{:n 6, :double 12}
{:n 7, :double 14}
{:n 8, :double 16}
... and here's the (single line) I just used to set "language english" for 65 files that didn't have a specified language (https://github.com/teodorlu/play.teod.eu/commit/28d21f9f284a28a7a9f8315e0e831f88d656a4e7), all because I could work with "lines of EDN":
$ ./play.clj relations :from files :to lines | bb -I -O '(map #(assoc % :lang :en) *input*)' | ./play.clj relations :from lines :to files

👍 2
dabrazhe18:09:31

has smth changed?

(babashka.tasks/shell {:out :string} (babashka.process/tokenize "ls -la"))

[java.lang.IllegalArgumentException
 "No implementation of method: :as-file of protocol: #' found for class: clojure.lang.PersistentVector"
 nil]

borkdude20:09:02

@dennisa babashka.tasks/shell never received a vector, you can just write:

(babashka.tasks/shell {:out :string} "ls -la")
or
(babashka.tasks/shell {:out :string} "ls" "-la")
This has always been the case. Many babashka.process functions take a vector though

👍 1
dabrazhe09:09:13

Is there are way to read the error in bb shell without throwing an exception ? smth like

(shell {:out :string :err :string } "ls err") ; fails now
Read :out when success and error in case

borkdude09:09:14

yeah, you can do :continue true

borkdude09:09:17

to not throw

dabrazhe09:09:52

you are a star of replying to messages with the speed of light !

dabrazhe09:09:50

this one works, cheers

(shell {:out :string :err :string :continue true} "ls err")

leobm20:09:56

Hi, how can I access the "this" object in SCI (skittle) ? Ist there any macro for it?

(defn Foo [z]
   #js {:x 1
        :y 2
        :z z
        :test #(. (js* "this") -z)})

(set! (. js/window -Foo) Foo)  

leobm20:09:32

I want to access the object property -z in the test method

leobm20:09:19

In the javascript environment (browser), I would actually like to be able to call the following: Foo(100).test()

borkdude21:09:47

@leobm Hello! Let's continue in #scittle

borkdude21:09:28

I gave the answer over there now