Fork me on GitHub
#clojure
<
2024-01-11
>
P Alexander Schofield08:01:56

I'm using clojure as an embedded scripting language in a scala project, and it's generally working well-- I am able to load clojure code via scala, and call a function defined in clojure code, and parse out the returned clojure map to my domain model in scala without too much difficulty. The one thing I have not been able to do is set the namespace-- either in clojure itself or through the java api. The error I get is: "Can't change/establish root binding of: ns with set". Does anyone know what this could be?

mpenet08:01:55

you can use in-ns to do this

🎯 1
borkdude12:01:13

you have to bind the *ns* dynamic var in the current thread to be able to use set! on it, which in-ns is doing

borkdude12:01:59

pushThreadBindings will indeed help here (since that is what binding is also doing, + pop)

hiredman13:01:25

Unless you are evaling code you shouldn't need to set *ns*

borkdude13:01:12

The OP is evaluating clojure code, at least that is what I understood from the original post

P Alexander Schofield07:01:35

Thank you @U2FRKM4TW and @U04V15CAJ (I use Jet constantly btw), this got me rolling. For future reference of anyone who may have a similar problem I have posted the code I came up with below. I am able to make 2 identical calls to execClojure without error, and get a correct return result, but this code is being used in a console utility where I only make one call for the life of the application, and I don't greatly care about the hygiene of the clojure environment after I've got my results, so everyone please use at your own risk:

def execClojure(
  clojurePath: Path,
  fullyQualifiedFnName: String,
  args: AnyRef*
): Either[Throwable, AnyRef] = {
  val attempt = Try {
    Var.pushThreadBindings(
      RT.mapUniqueKeys(RT.CURRENT_NS, RT.CURRENT_NS.deref)
    )
    val eval = Clojure.`var`("clojure.core", "eval")
    val clojureFn = Clojure.`var`(fullyQualifiedFnName)
    val clojureCode = s"(do ${os.read(clojurePath)})"
    val readString = Clojure.`var`("clojure.core", "read-string")
    val clojureForm = readString.invoke(clojureCode)
    eval.invoke(clojureForm)
    val parameterClasses = List.fill(args.size)(classOf[Object])
    val clojureFnInvokeMethod =
      clojureFn.getClass.getMethod("invoke", parameterClasses *)
    val result = clojureFnInvokeMethod.invoke(clojureFn, args *)
    Var.popThreadBindings()
    result
  }
  attempt.toEither
}

borkdude09:01:55

@U06DE7HR6US If you run into other difficulties, you could also use #C015LCR9MHD for evaluation/scripting. It runs as a sandboxed environment so it doesn't pollute the global Clojure environment and it's configurable in the sense that you can plug in libraries that other can call

pesterhazy14:01:51

Why is there force when deref seems to do the same thing for a delay?

βž• 1
tomd14:01:16

I think this is the main difference:

user> (def things [3 (delay 4) 5])
#'user/things
user> (map force things)
(3 4 5)
user> (map deref things)
Error printing return value (ClassCastException) at clojure.core/deref-future (core.clj:2315).
class java.lang.Long cannot be cast to class java.util.concurrent.Future...

pesterhazy14:01:12

Ah it's like deref-if-delay, make sense

πŸ‘ 1
jpmonettas14:01:29

yeah, it is the same but with that condition :

public class Delay implements IDeref, IPending{
 ...
 static public Object force(Object x) {
	return (x instanceof Delay) ?
	       ((Delay) x).deref()
	       : x;
  }
}

ghadi15:01:36

rich has considered making deref work like that - bottoming out at returning its argument when not IDeref

πŸ‘ 1
pesterhazy15:01:37

I guess I might as well ask my real question then:

(def ec2-instance-id
  "Is only supposed to be called when running on an EC2 instance."
  (delay (EC2MetadataUtils/getInstanceId)))

(defn server-name
  "Does an http request to retrieve the metadata, if running outside of an EC2 instance context,
  it will fail."
  []
  (try
    (force ec2-instance-id)
    (catch Exception _e
      (log/info {:at :exception-tracker-cant-fetch-ec2-id})
      ;; return nil then to not include the :server-name key in Sentry config
      nil)))
We're seeing a com.amazonaws.SdkClientException bubbling up (and causing server startup to fail) in the prod logs pointing to the (force ec2-instance-id) call. But I don't see how this is possible – shouldn't the blanket try/catch swallow any exceptions?

jpmonettas15:01:45

Maybe it has to do with the sneakyThrow in Delay deref ?

public Object deref() {
	if(fn != null)
		{
	        synchronized(this)
	        {
	        //double check
	        if(fn!=null)
	            {
	                try
	                    {
	                    val = fn.invoke();
	                    }
	                catch(Throwable t)
	                    {
	                    exception = t;
	                    }
	                fn = null;
	            }
	        }
		}
	if(exception != null)
		throw Util.sneakyThrow(exception);
	return val;
}

ghadi15:01:21

@U06F82LES always start with your real question πŸ™‚

ghadi15:01:35

does the exception's complete stack trace indicate that it is thrown via this defn?

pesterhazy15:01:42

Yes, although when I'm stuck it takes me a while to figure out what my real question is :person_in_lotus_position:

ghadi15:01:56

it's always the question you were working on when you became stuck (corollary of "you'll find it in the last place you look" πŸ˜†)

ghadi15:01:19

paste the stacktrace if you can

pesterhazy15:01:42

apologies, the stacktrace is messed up, partial and reversed due to bad parsing

pesterhazy15:01:17

Hypothesis based on @U0739PUFQ's comment β€’ The exception gets rethrown (sneakily?) as a Throwable β€’ This Throwable isn't caught as an Exception Has some plausibility to it but I can't understand it

pesterhazy15:01:02

Trouble is, I can't repro in the REPL

dev=> (try (force (delay (throw (com.amazonaws.SdkClientException. "boom")))) (catch Exception _e (prn :caught)))
:caught
nil

ghadi15:01:31

the same exception is rethrown, it's not rethrown as anything

ghadi15:01:28

I don't see a problem yet

arnaud_bos15:01:39

Could you try to log into the instance and try to retrieve the metadata manually, just to rule out any "environment" issue ? My googling returns a few results hinting that not all EC2 instances are created equal (meta-data access restrictions due to VPC, public/private ip, etc). "instance-id" seems pretty benign but worth a check.

arnaud_bos16:01:14

"Failed to connect" is kinda weird because it's supposed to only access the metadata locally.

pesterhazy16:01:15

The fact that retrieving metadata fails – and that an exception is thrown – is expected (it's a bug that I need to fix). This job is running on ECS, which does not have access to EC2 metadata. What's puzzling to me is that the try/catch form doesn't manage to catch the exception

pesterhazy16:01:09

(Sorry, should've explained that earlier)

arnaud_bos16:01:25

Oops, my bad, didn't read properly πŸ™‚

ghadi16:01:06

are you running any instrumentation agents? are you sure what is running in prod is what you expect? (decompile the server_name class)

pesterhazy16:01:56

> are you sure what is running in prod is what you expect? Fairly sure, but yeah I could inspect the docker image.

arnaud_bos16:01:48

Not running on JDK21 + virtual threads are you? The (1.11) synchronized block could cause the JDK to print a stack trace in case the carrier thread is pinned, not sure about the actual behavior because it depends on flag. Granted this would not explain the startup failure, it would only be a dump. Just wondering if you're very very (sorry πŸ˜„) sure that this is the real startup failure cause and not a smoke screen.

arnaud_bos16:01:48

Nah, scratch that, your stack doesn't seem to be related to that, sorry for the noise.

pesterhazy16:01:46

You might be onto something It might be that startup isn't actually failing because of this, and that the surprise is just that the exception gets printed to the logs for some reason (but it's properly caught)

pesterhazy16:01:00

Maybe the AWS SDK is printing the exception, or something else I'm not thinking of

ghadi16:01:50

could be, but that doesn't explain the failure to startup

pesterhazy16:01:54

the failure to start up may be something else altogether – in fact I just removed the call to server-name and it's failing health checks for some other reason

pesterhazy16:01:40

thanks for your help everyone, I may have been chasing a wild goose here

valerauko01:01:34

(Sticking some marketing in there, give https://github.com/studistcorporation/sleepydog a try for datadog instrumentation)

pesterhazy15:01:37

I guess I might as well ask my real question then:

(def ec2-instance-id
  "Is only supposed to be called when running on an EC2 instance."
  (delay (EC2MetadataUtils/getInstanceId)))

(defn server-name
  "Does an http request to retrieve the metadata, if running outside of an EC2 instance context,
  it will fail."
  []
  (try
    (force ec2-instance-id)
    (catch Exception _e
      (log/info {:at :exception-tracker-cant-fetch-ec2-id})
      ;; return nil then to not include the :server-name key in Sentry config
      nil)))
We're seeing a com.amazonaws.SdkClientException bubbling up (and causing server startup to fail) in the prod logs pointing to the (force ec2-instance-id) call. But I don't see how this is possible – shouldn't the blanket try/catch swallow any exceptions?

Ingy dΓΆt Net18:01:58

I'm getting this error from a graalvm native-image build in windows. From what I've read it seems like a java error found in many places. Asking here as maybe more people have run into it:

Fatal error: java.lang.UnsatisfiedLinkError: no management in system library path: C:\Users\ingy\AppData\Local\Temp\graalvm-oracle-21\bin
        at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2432)
        at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:916)
        at java.base/java.lang.System.loadLibrary(System.java:2059)