Attaching a repl to an arbitrary java application

I recently watched a very interesting talk by Stu Halloway on repl driven development:

I have a question about a remark Stu made. Here is the remark, as transcribed:

… This is how easy it is to start the socket REPL. clojure.server and then call start-server. Pass in a port and a name. So this is a name, so that if you start more than one of them you can keep track of the ones you have started. And then the accept function is the function that runs the server. clojure.server/repl is a REPL entry point. So this is just starting a REPL. And then you can telnet in. And you can do this anywhere.

Also, here is a super interesting thing. There is a Java system property, or a set of Java system properties, that allow you to specify these things, in which case the REPL will start automatically when Clojure loads. Which means that if you wanted to use a REPL to debug a Scala application, all you would have to do is have the Clojure JAR on the classpath, and start with the right flags, and it would now be a Scala application with a Clojure REPL running in it. And then you could just sort of tool around in there and do whatever you wanted to.

The ‘super interesting thing’ suggests that I might be able to attach a Clojure repl to any java application, in particular without having modify the application for said purpose. Can anyone point me to a working example of this technique?

I searched and found examples of two related ideas:

  • Cases wherein the java application is modified to launch a Clojure repl. Sure, but not what I understand Stu to be suggesting.

  • Writing a javaagent in Clojure to use the java.lang.instrument package (e.g. clojure-javaagent )

Is the latter technique what Stu is alluding to? Otherwise, I am puzzled. Say I want to attach a repl to Cassandra. I can put a Clojure jar into classpath used by Cassandra, and pass in various java system properties when I start Cassandra, but what invokes Clojure to start the repl?

Thank you for your help.

David

2 Likes

If you have the revelant system properties set you just need to load the clojure.lang.RT class somehow. The socket REPL is started in a static initializer for the class. Just referencing it somewhere in your Java code should be enough.

You can also start it manually without the system properties but thats a bit more complicated.

1 Like

Thank you for the reply.

You suggest that “Just referencing it somewhere in your Java code should be enough.” My (mis?)understanding of Stu’s remark is precisely that I do not have to modify the java code. If I have the java code and am willing/able/allowed to modify it, I can indeed attach a Clojure a repl, no doubt. But what if I don’t have the code?

Ah I misunderstood. No I do not think its possible to start the Clojure REPL by just having it on the classpath. The class needs to be loaded somehow. I think the Scala example Stu mentions implies that the Scala code would be loading the clojure.lang.RT class. I’m not aware of any mechanism to force loading extra classes on startup.

1 Like

It might work to have the Clojure jar on the classpath of the Java application, and start it with the property:

-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"

At least that is one way to start a socket repl in a Clojure application. To me, this is what he suggests in the talk.

2 Likes

I tried exactly that with but had no luck. I modified the Cassandra startup script to include a Clojure jar in the classpath, and I set the property as mentioned above. Cassandra started, but nothing was listening on port 5555. My take was that setting the ‘clojure.server.repl’ property is necessary but not sufficient to launch the repl. In particular, once the property is set, something has to subsequently read the property and use its value to launch a repl.

1 Like

Hm, okay. I never tried doing anything like this, so it was really just a guess. Which Clojure version did you try with?

I tried with Clojure 1.9.

Did a bit more experimentation, and met with some success. Let me back up, to provide some context. Stu’s talk gave me the idea that I would like to try opening a Clojure repl on some bit of java software that I use regularly. No particular goal, just hacking around.

I deal daily with both Cassandra and Datastax DSE clusters. DSE is basically an enterprise version of Cassandra. It has a management tool called OpsCenter. I remembered having seen Clojure error messages in the OpsCenter logs, so I figured that OpsCenter was using Clojure. A closer look at its startup script revealed a somewhat complex process which calls java to run jython to start up a Python app. Hmmm…

I decided to have a look at the Datastax agents, which route traffic between the DSE nodes and the OpsCenter. I unpacked the datastax-agent jar, and saw Clojure was in the jar. So I wrote a shell script, below, to start the agent with an additional property (4th line):

#!/bin/sh
/usr/java/jdk1.8.0_171/jre/bin/java
-Xmx1024M
-Dclojure.server.repl="{:accept clojure.core.server/repl :port 6666}"
-Dagent-pidfile=/var/run/datastax-agent/datastax-agent.pid
-Dlog4j.configuration=file:/etc/datastax-agent/log4j.properties
-Djava.security.auth.login.config=/etc/datastax-agent/kerberos.config
-jar datastax-agent-6.5.0-standalone.jar /var/lib/datastax-agent/conf/address.yaml

(BTW, the preview function on the clojureverse software is not showing me the backslashes present at the end of most lines in the above script, so if you don’t see them once I publish, imagine they are there. It is logically a one line script, but I prefer to be able to read it, and so wrote it with line continuations.)

Once the agent started, I could see a java process listening on port 6666. I tried telnetting to it, boom! I had a Clojure repl. Did not have much time to poke around, but I did run (all-ns) and saw much opportunity for nosing around. Very cool.

David

3 Likes

I built this exact thing for our microservices. I did have to add code to each app, but I just bundled it as a library. I also used nrepl instead of socket because everyone uses IntelliJ and it’s supported by more editors.

Basic idea was to grab anything from the IoC container (we use both Spring and Guice). So you can grab a service or Dao and manipulate it in the REPL directly. I also provided utils to query the db and convert to/from Clojure data structures and JSON. With all that, it’s easy to craft some useful debugging utils.

I also added nrepl over http so I didn’t have to open any additional ports in preprod. Unfortunately we were not allowed to use it beyond a local env, but if your org allows it then I see it as a great tool for a preproduction env.

1 Like

I also created a python-Clojure shell tool since most of the devs didn’t know Clojure but did know python. It dynamically translates and evaluates code inside the repl.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.