Fork me on GitHub
#babashka
<
2023-01-24
>
didibus05:01:53

In babashka.http-client, it seems that when we use {:async true}, if an exception happen it's not isolated? I'm doing something like:

(->> ["garbage" ""]
     (mapv #(http/get % {:async true})))
java.lang.IllegalArgumentException: URI with undefined scheme [at <repl>:4:8]
Where it seems like the error is not thrown in an async way? Shouldn't this return CompletableFuture? How is it that it throws?

lispyclouds08:01:45

@U0K064KQV this is a result of creating the URI itself : https://github.com/babashka/http-client/blob/main/src/babashka/http_client/internal.clj#L150 and happens even before making the request. Since the URI is wrong its thrown right away. You should have the same behaviour with the raw java 11 http client which this uses too. using something like should have the behaviour you're expecting.

💡 2
lispyclouds08:01:17

Essentially for the java 11 http client, the request needs to be built before the client fires it in (a)sync and a valid URI is needed there.

borkdude08:01:42

Try:

(->> ["" ""]  (mapv #(http/get % {:async true})) (mapv deref))
it will throw, without the last form it doesn't

didibus17:01:13

Right, ok. It does make it a bit more cumbersome to handle errors, since the function is now both sync and async. You need to try/catch around the call and around the deref.

didibus17:01:14

I've also noticed that some errors are returned as exceptions? Like if you get a 503 it's going to be wrapped in an exception, instead of showing up on :status

Execution error (ExceptionInfo) at babashka.http-client.interceptors/fn (interceptors.clj:200).
 Exceptional status code: 503

didibus17:01:59

It seems the default interceptors throw on bad http status?

borkdude17:01:39

@U0K064KQV Yes, you can suppress this with :throw false

borkdude17:01:09

The exception info that is thrown still has the status code in the ex-data

didibus17:01:15

What are the assumptions I can make then? That all possibly thrown errors are going to be wrapped in "ex-info" ? And everything except for a :status 200 is returned as an exception?

lispyclouds17:01:03

@U0K064KQV the exception in the first code sample is gonna happen regardless of sync or async calls though. you can treat that separately. as its not being thrown by the interceptors and happens even before the request is started.

didibus18:01:08

So like, say for a robust run, I would want to query 1000 URLs, and I want to retry the ones that error on an error where it makes sense to retry, and I want to capture and log the ones that fail on an error that retrying has no chance of fixing. And for each I want to also know what on the error I need to capture to be able to debug it later. I would need to know like where can errors be thrown/returned, what exact errors, which one make sense to retry, which one makes sense to skip and log, and for each type of error, where in the error is the important debug information so I can extract it and log it. How would I go about doing that now? It seems I would at least need documented description of where errors are thrown/returned, the set of all possible type of errors at each point, and their structure. At the minimum maybe a more generic description that like all errors will always be an ex-info and the type of error will be on a :type key on the ex-data, with the set of possible types, and that there will always be an ex-data with details. That way I could try/catch at the call to get and on the deref, inspect the ex-data :type key, if it's a retryable type I could retry the get, if it's not I would log the ex-data and ex-message and continue. I need at least this kind of guaranteed structure on all errors to be able to handle all of them. If some errors might not be ex-info, might not have a :type on ex-data, etc., it gets very tricky for the user.

borkdude18:01:02

Feel free to post issues with concrete problems you experienced in real programs. Would also be good to compare with what other clients are doing at the moment.

didibus18:01:54

Well, that's a real issue I'm experiencing now 😛 I can't add robust error handling.

didibus18:01:53

I'm also confused why this throws:

(->> [""
      ""
      ""
      ""
      "garbage"]
     (mapv #(try (http/get % {:async true})
                 (catch Exception e (delay e))))
     (mapv deref))
I thought deref would return the exception as a value, but it seems it throws on exception?

borkdude18:01:56

I'm in a hurry right now, so I can't help you, but if you want to record any useful details in an issue along with a proposal, that might get us somewhere some time

didibus18:01:10

Sounds good, thanks!

borkdude18:01:19

deref-ing a future in which an exception happened throws, this is standard clojure / Java behavior

didibus18:01:49

Ah, maybe I was getting it confused with taking from a channel

lispyclouds19:01:38

Also if it helps these are the only two places where exceptions are explicitly thrown as ex-infos: • https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L200https://github.com/babashka/http-client/blob/main/src/babashka/http_client/internal.clj#L119 rest all would be an internal implementation exceptions if any, which may not be ex-infos. this does not anticipate every possible exceptions nor does it have a wrap all try

borkdude21:01:59

Thanks for the issue, I'll think more about it

2
borkdude11:01:26

A one-liner to run babashka tests with eftest (requires the latest bb version)

$ bb -Sdeps '{:paths ["src" "test"] :deps {eftest/eftest {:mvn/version "0.6.0"}}}' -e "(require '[eftest.runner :refer [find-tests run-tests]]) (run-tests (find-tests \"test\"))"

3/3   100% [==================================================]  ETA: 00:00

Ran 3 tests in 0.026 seconds
19 assertions, 0 failures, 0 errors.
{:test 3, :pass 19, :fail 0, :error 0, :type :summary, :duration 26.077792}

👀 2
💯 4
🆒 2
👍 2
Aljaz15:01:37

As a Babashka/Clojure newbie, I'm trying to translate my justfile into Babashka tasks. I got stuck translating this recipe:

restart:
    @ssh -t user@server "sudo sh -c 'service myservice restart'"
My Babashka translation:
restart {
    :task (shell "ssh -t user@server sudo sh -c \"service myservice restart\"")
}
The justfile version works, whereas the Babashka version asks for the password but then exits with an error. It prints: "Usage: service < option > | --status-all | [ service_name [ command | --full-restart ] ]" Any ideas about what the problem is?

borkdude15:01:29

you escaped the sudo string in Justfile, but not in bb tasks

borkdude15:01:04

Try this for debugging:

(shell "ssh -t user@server \"echo 'service myservice restart'\"")
and then work from there

Aljaz16:01:41

Ah, should've caught that, thanks. However, strangely enough, the fixed version yields the exact same error:

restart {
    :task (shell "ssh -t user@server \"sudo sh -c 'service myservice restart'\"")
}

Aljaz17:01:38

Ah, got it, this works:

restart {
    :task (shell "ssh -t user@server 'sudo sh -c \"service myservice restart\"'")
}

borkdude17:01:38

Ah, I think you may have hit a bug in the one you posted first after my reply:

user=> (p/tokenize "ssh -t user@server \"sudo sh -c 'service myservice restart'\"")
["ssh" "-t" "user@server" "sudo sh -c service myservice restart"]
user=> (p/tokenize "ssh -t user@server 'sudo sh -c \"service myservice restart\"'")
["ssh" "-t" "user@server" "sudo sh -c \"service myservice restart\""]
I'll fix it in the next version of bb

borkdude17:01:26

Another way to solve it is:

(shell "ssh" "-t" "user@server" ...)
Only the first string is tokenized

Aljaz17:01:39

Thanks - and a more general thanks for your work on Babashka, I've enjoyed using it so far!

❤️ 2