Programs often want to provide REPLs to users in contexts when a) network communication is desired, b) capturing stdio is difficult, or c) when more than one REPL session is desired. In addition, tools that want to support REPLs and simultaneous conversations with the host are difficult with a single stdio REPL as currently provided by Clojure.
Tooling and users often need to enable a REPL on a program without changing the program, e.g. without asking author or program to include code to start a REPL host of some sort. Thus a solution must be externally and declaratively configured (no user code changes). A REPL is just a special case of a socket service. Rather than provide a socket server REPL, provide a built-in socket server that composes with the existing repl function.
For design background, see: http://dev.clojure.org/display/design/Socket+Server+REPL
Start a socket server by supplying an extra system property (classpath and clojure.main here, but this would generally be starting your own app instead - we won't use the repl it starts):
where options are:
address = host or address, defaults to loopback
port = port, required
accept = namespaced function to invoke on socket accept, required
args = sequential collection of args to pass to accept
bind-err = defaults to true, binds err to out stream
server-daemon = defaults to true, socket server thread doesn't block exit
client-daemon = defaults to true, socket client threads don't block exit
Run a repl client using telnet:
Ragnar, good comments. I've updated the patch. That stop code was broken in a couple other ways too but should be good now.
I don't feel like we have nailed down the requirements for the session feature, so hard to comment on that
attach/detach metaphor for sessions feels weird, e.g. I cannot set a dynamic var inside there and have the other session see my change. We would instead just session state query and eval-in-session APIs.
something needs to happen if the session you are attached to goes away out from under you
socket backlog arg of 0 means "use default", is there some reason 50 is better?
I think 'Clojure' better than 'Socket' more disinguishing as thread names
catch-all exception eating scares me, couldn't stop-servers call the uncaught exception handler?
vec + map can be replaced by mapv
start-client seems a very odd name to me, as this is the server side client handler
Added new -12 patch with the following changes per Stu's comments:
In the scenario where an attached session goes away, you now lose that attachment and get an exception tell you so the next time you try to eval an expression.
Changed socket backlog to 0 (50 is the default, but I wasn't aware that 0 would get the default)
Changed thread names
Changed stop-server to shutdown each server in a future independently - exceptions will bubble up to default uncaught exception handler
Replaced vec+map with mapv
Changed start-client to accept-connection
Deferred discussion of session intent.
The -13 patch strips out the session attach/detach/list functionality. Rescued description of those features in -12 here:
Now open a 2nd telnet client and "attach" to the session of the first client:
Note that when the session is attached, the prompt changes, showing the client id and namespace of the attached session instead. When attached to another session, your expressions are evaluated in the context of the attached session, so you can grab the current namespace or even the *1/*2/*3 results. This is an example of how a tool could attach and be "inside" the context of the user's repl.
Add socket-change.patch for change log update.