nrepl

Hasnain Naeem 2024-05-08T01:49:04.242939Z

What's best way to achieve isolation between clients in nREPL? For example, if we have several clients connected to same server and each one can see variables and functions defined by others. But, aren't sessions supposed to provide some level of isolation?

Michael Griffiths 2024-05-08T16:23:44.703399Z

Clojure namespaces and vars are generally https://clojure.org/reference/vars. Only vars declared dynamic (and hence returned by clojure.core/get-thread-bindings) can be set! – if this is done in an nREPL session it will be invisible to other sessions

1
Michael Griffiths 2024-05-08T16:31:04.539519Z

Some isolation is possible by manipulating JVM classloaders – examples that spring to mind are classlojure, clj-embed and clojail. It’s fraught with complexity though and Clojure isn’t designed to support this so you can run into problems if you need to share anything between the runtimes

Michael Griffiths 2024-05-08T16:35:28.756879Z

I don’t know all the details of your use case, but if process isolation is an option then it’s very possibly less painful than the classloader route

👍 1
phronmophobic 2024-05-08T02:04:04.085839Z

https://nrepl.org/nrepl/design/middleware.html#sessions

phronmophobic 2024-05-08T02:04:51.142829Z

It depends on what you mean by isolation, but sessions probably don't provide the kind of isolation you're looking for.

phronmophobic 2024-05-08T02:05:03.996869Z

What kind of isolation are you looking for? What's your use case?

Hasnain Naeem 2024-05-08T02:11:44.111719Z

@smith.adriane thank you for your response. The use case requires each session to be a clean slate and other sessions shouldn't be able to access whatever variables/functions are defined inside it. I can enforce usage of a fixed namespace for each session and limit access to others to achieve this. But, I was curious what kinda isolation nREPL already offers. I guess it doesn't offer any at all.

Hasnain Naeem 2024-05-08T02:12:08.004829Z

Does nREPL offer any isolation at all?

phronmophobic 2024-05-08T02:18:58.329219Z

I'm not aware of that kind of isolation being provided by nrepl. I think it's primary goal is just evaling forms. You would need some other mechanism to provide isolation. I think that could theoretically be done via middleware, but any architecture that wants to provide isolation between multiple clients running arbitrary code in the same process seems fairly dubious.

Hasnain Naeem 2024-05-08T02:18:59.915729Z

Documentation states: > Sessions persist dynamic vars (collected by get-thread-bindings) against a unique lookup. This allows you to have a different value for *e from different REPL clients (e.g. two separate REPL-y instances). I tried following:

(defn concurrent-interactions-with-same-namespaces-using-dynamic-vars []
  (with-open [conn (repl/connect :port 7888)]
    (try
      (let [client1 (repl/client conn 1000)
            clone-response1 (repl/message client1 {:op "clone"})
            session-id1 (:new-session (first clone-response1))

            client2 (repl/client conn 1000)
            clone-response2 (repl/message client2 {:op "clone"})
            session-id2 (:new-session (first clone-response2))

            op-response1 (first (repl/message client1 {:op "eval"  :code "(def ^:dynamic *todo-file* \".session1todo\")" :session session-id1}))
            op-response2 (first (repl/message client1 {:op "eval"  :code "*todo-file*" :session session-id1}))
            op-response3 (first (repl/message client2 {:op "eval"  :code "*todo-file*" :session session-id2}))]

        ;; clone
        (println "\nClone Response1 from nREPL: " clone-response1)
        (println "\nClone Response2 from nREPL: " clone-response2)

        ;; eval
        (println "\nResult2 from nREPL: " op-response1)
        (println "\nResult4 from nREPL: " op-response2)
        (println "\nResult5 from nREPL: " op-response3)
      (catch Exception e
        (println "An error occurred: " e)))))
Dynamic variable defined for session 1 is accessible in session 2.

phronmophobic 2024-05-08T02:20:00.911599Z

Yes, you could have two separate repls in the same environment.

phronmophobic 2024-05-08T02:20:13.950099Z

That's not the same thing as two separate repls in separate environments.

phronmophobic 2024-05-08T02:29:23.387709Z

Is the isolation for security or just so sessions don't clobber each other?

Hasnain Naeem 2024-05-08T02:32:18.785589Z

To prevent clobbering

phronmophobic 2024-05-08T02:35:31.323849Z

ah ok. that makes sense. You can do that. def alters the var's root. You would need the middleware to use binding to create thread local values for vars for them to be separate in separate sessions.

Hasnain Naeem 2024-05-08T02:37:46.247909Z

I see. Thank you. Documentation says: > Sessions persist dynamic vars (collected by get-thread-bindings) against a unique lookup. What exactly is this about if not about allowing isolation of dynamic variables for various sessions.

phronmophobic 2024-05-08T02:39:20.730739Z

I think that is allowing isolation of dynamic variables.

phronmophobic 2024-05-08T02:39:52.639509Z

However, you have to create thread locals for the dynamic variables. Updating the var root is a global operation (eg. def).

👍 1
phronmophobic 2024-05-08T02:41:49.355259Z

I'm trying to find a relevant example, but most repls will setup thread locals for common dynamic variables.