Permalink
Browse files

Added support for REPLs into Tomcat web applications by discovering a…

…lternate classloaders to start the repl in.

Removed repl-caught hack
  • Loading branch information...
1 parent e6f2ee7 commit 4bb056a219805fd0fc058428b477d755d9169335 @djpowell djpowell committed Oct 31, 2010
View
111 liverepl-agent/src/net/djpowell/liverepl/agent/Agent.java
@@ -1,25 +1,102 @@
package net.djpowell.liverepl.agent;
import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import net.djpowell.liverepl.client.Main;
+import net.djpowell.liverepl.discovery.ClassLoaderInfo;
+import net.djpowell.liverepl.discovery.Discovery;
public class Agent {
-
- private static ClassLoader pushClassLoader(List<URL> urls) {
+
+ private static final Discovery discovery = new Discovery();
+
+ public static interface ConnectNotifyingTask {
+ void run(ServerSocket server, AtomicBoolean connected);
+ }
+
+ private static Thread startKillerThread(final int connectTimeout, final AtomicBoolean connected, final ServerSocket server) {
+ Thread killer = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(connectTimeout);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ if (!connected.get()) {
+ // TRC.fine("Client connect timeout: terminating server");
+ try {
+ server.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }, "Killer Thread");
+ killer.start();
+ return killer;
+ }
+
+ public static void runAfterConnect(int port, int connectTimeout, String threadName, final ConnectNotifyingTask task) throws Exception {
+ final ServerSocket serverSocket = new ServerSocket(port, 0, Main.LOCALHOST);
+ final AtomicBoolean connected = new AtomicBoolean(false);
+ Thread taskThread = new Thread(new Runnable() {
+ public void run() {
+ task.run(serverSocket, connected);
+ }
+ }, threadName);
+ startKillerThread(connectTimeout, connected, serverSocket);
+ taskThread.start();
+ }
+
+ private static void printClassLoaderInfo(int port) {
+ try {
+ runAfterConnect(port, 5000, "ClassLoaderInfoThread", new ConnectNotifyingTask() {
+ public void run(ServerSocket server, AtomicBoolean connected) {
+ try {
+ Socket socket = server.accept();
+ connected.set(true);
+ try {
+ PrintStream out = new PrintStream(socket.getOutputStream());
+ try {
+ discovery.dumpList(out);
+ } finally {
+ out.close();
+ }
+ } finally {
+ socket.close();
+ }
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static ClassLoader pushClassLoader(List<URL> urls, String clId) {
TRC.fine("Creating new classloader with: " + urls);
ClassLoader old = Thread.currentThread().getContextClassLoader();
TRC.fine("Old classloader: " + old);
- URLClassLoader withClojure = new URLClassLoader(urls.toArray(new URL[urls.size()]), old); // TODO
+ ClassLoaderInfo cli = discovery.findClassLoader(clId);
+ if (cli == null) {
+ throw new RuntimeException("Unknown class loader: " + clId);
+ }
+ ClassLoader cl = cli.getClassLoader();
+ URLClassLoader withClojure = new URLClassLoader(urls.toArray(new URL[urls.size()]), cl); // TODO
Thread.currentThread().setContextClassLoader(withClojure);
return old;
}
@@ -41,17 +118,23 @@ private static boolean isClojureLoaded() {
public static void agentmain(String agentArgs, Instrumentation inst) {
TRC.fine("Started Attach agent");
-
+
StringTokenizer stok = new StringTokenizer(agentArgs, "\n");
- if (stok.countTokens() != 3) {
+ if (stok.countTokens() != 4) {
throw new RuntimeException("Invalid parameters: " + agentArgs);
}
int port = Integer.parseInt(stok.nextToken());
TRC.fine("Port: " + port);
String clojurePath = stok.nextToken();
String serverPath = stok.nextToken();
-
+ String classLoaderId = stok.nextToken();
+
+ if ("L".equals(classLoaderId)) {
+ printClassLoaderInfo(port);
+ return;
+ }
+
boolean clojureLoaded = isClojureLoaded();
TRC.fine("Clojure is " + (clojureLoaded ? "" : "not ") + "loaded");
@@ -61,8 +144,8 @@ public static void agentmain(String agentArgs, Instrumentation inst) {
} else {
urls = getJarUrls(clojurePath, serverPath);
}
-
- ClassLoader old = pushClassLoader(urls);
+
+ ClassLoader old = pushClassLoader(urls, classLoaderId);
try {
if (!clojureLoaded) { // if clojure wasn't loaded before, print current status
TRC.fine("Clojure is " + (isClojureLoaded() ? "" : "not ") + "loaded");
View
9 liverepl-agent/src/net/djpowell/liverepl/client/Console.java
@@ -13,10 +13,11 @@
public class Console {
- private static String NEWLINE = System.getProperty("line.separator");
+ private static final String NEWLINE = System.getProperty("line.separator");
- public static void main(InetAddress host, int port) throws Exception {
+ public static void main(InetAddress host, int port, boolean readInput) throws Exception {
final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
+ if (!readInput) cin.close();
final Writer cout = new OutputStreamWriter(System.out);
Socket s = new Socket(host, port);
try {
@@ -42,7 +43,7 @@ public void run() {
}
}
};
- ch.start();
+ if (readInput) ch.start();
Thread sh = new Thread("SocketHandler") {
@Override
@@ -68,7 +69,7 @@ public void run() {
// block until both threads finish
try {
sh.join();
- ch.join();
+ if (readInput) ch.join();
} catch (InterruptedException e) {
// if either thread dies, then allow the socket to
// close, terminating the other one
View
39 liverepl-agent/src/net/djpowell/liverepl/client/Main.java
@@ -26,7 +26,7 @@ private static int getFreePort(InetAddress host) {
// so that we can tell the server to listen on that port
// and we will know which port to connect to
try {
- ServerSocket server = new ServerSocket(0, 0, LOCALHOST);
+ ServerSocket server = new ServerSocket(0, 0, host);
int port = server.getLocalPort();
server.close();
return port;
@@ -48,28 +48,49 @@ private static void listPids() {
}
public static void main(String[] args) throws Exception {
- // Usage: <clojurepath> <agentjarpath> <serverjarpath> <jvmpid>
- if (args.length != 4) {
+ // Usage: <clojurepath> <agentjarpath> <serverjarpath> <jvmpid> <classloaderid>
+ if (args.length < 4) {
listPids();
System.exit(0);
}
String clojurepath = args[0];
String agentpath = args[1];
String serverpath = args[2];
String pid = args[3];
+ String classLoaderId;
+ if (args.length < 5) {
+ classLoaderId = "L";
+ System.out.println();
+ System.out.println("List of ClassLoaders for process #" + pid);
+ } else {
+ classLoaderId = args[4];
+ }
+
TRC.fine("Attaching to pid: " + pid);
- VirtualMachine vm = VirtualMachine.attach(pid);
+ final VirtualMachine vm = VirtualMachine.attach(pid);
int port = getFreePort(LOCALHOST);
// start the agent, which will create the server socket, then return
- String agentArgs = String.valueOf(port) + "\n" + clojurepath + "\n" + serverpath;
+ String agentArgs = String.valueOf(port) + "\n" + clojurepath + "\n" + serverpath + "\n" + classLoaderId;
vm.loadAgent(agentpath, agentArgs);
-
+
+ boolean listClassLoaders = "L".equals(classLoaderId);
// start the code that will connect to the server socket
- Console.main(LOCALHOST, port);
-
+ Console.main(LOCALHOST, port, !listClassLoaders);
+
// the server will shutdown when the client disconnects
- vm.detach();
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ try {
+ vm.detach();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
}
private static final Logger TRC = Logger.getLogger(Main.class.getName());
View
16 liverepl-agent/src/net/djpowell/liverepl/discovery/ClassLoaderDiscovery.java
@@ -0,0 +1,16 @@
+package net.djpowell.liverepl.discovery;
+
+import java.util.Collection;
+
+/**
+ * SPI Interface for providing implementations to discover ClassLoaders.
+ */
+public interface ClassLoaderDiscovery {
+ /**
+ * Return information about the available ClassLoaders.
+ * Implementations must register each ClassLoader with the application's ClassLoaderRegistry,
+ * and use the id returned by the registry in the ClassLoaderInfo instances returned.
+ */
+ Collection<ClassLoaderInfo> listClassLoaders();
+ String discoveryName();
+}
View
40 liverepl-agent/src/net/djpowell/liverepl/discovery/ClassLoaderInfo.java
@@ -0,0 +1,40 @@
+package net.djpowell.liverepl.discovery;
+
+import java.lang.ref.WeakReference;
+
+public class ClassLoaderInfo implements Comparable {
+
+ public final String id;
+ private final WeakReference<ClassLoader> classLoader;
+ public final String info;
+
+ public ClassLoaderInfo(String id, ClassLoader classLoader, String info) {
+ this.id = id;
+ this.classLoader = new WeakReference<ClassLoader>(classLoader);
+ this.info = info;
+ }
+
+ public ClassLoader getClassLoader() {
+ return classLoader.get();
+ }
+
+ public String getClassLoaderName() {
+ ClassLoader cl = classLoader.get();
+ if (cl == null) {
+ return "<null>";
+ } else {
+ return cl.getClass().getSimpleName();
+ }
+ }
+
+ public static final String header = String.format("#%-3s %-20s : %s", "Id", "ClassLoader", "Info");
+
+ public String toString() {
+ return String.format("#%-3s %-20s : %s", id, getClassLoaderName(), info);
+ }
+
+ public int compareTo(Object o) {
+ ClassLoaderInfo cli = (ClassLoaderInfo)o;
+ return id.compareTo(cli.id);
+ }
+}
View
29 liverepl-agent/src/net/djpowell/liverepl/discovery/ClassLoaderRegistry.java
@@ -0,0 +1,29 @@
+package net.djpowell.liverepl.discovery;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * ClassLoaderDiscovery implementations must register all ClassLoaders with this registry to give them an
+ * identifier which can be used to subsequently find the ClassLoader that they user asked for.
+ *
+ */
+public class ClassLoaderRegistry {
+
+ private final AtomicInteger clIdGenerator = new AtomicInteger(0);
+ private final Map<ClassLoader, String> clIdMap = new WeakHashMap<ClassLoader, String>();
+
+ public String registerClassLoader(ClassLoader classLoader) {
+ String id;
+ synchronized (clIdMap) {
+ id = clIdMap.get(classLoader);
+ if (id == null) {
+ id = String.valueOf(clIdGenerator.getAndIncrement());
+ clIdMap.put(classLoader, id);
+ }
+ }
+ return id;
+ }
+
+}
View
62 liverepl-agent/src/net/djpowell/liverepl/discovery/Discovery.java
@@ -0,0 +1,62 @@
+package net.djpowell.liverepl.discovery;
+
+import net.djpowell.liverepl.discovery.impl.SystemDiscovery;
+import net.djpowell.liverepl.discovery.impl.ThreadDiscovery;
+import net.djpowell.liverepl.discovery.impl.TomcatDiscovery;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * API for obtaining a list of ClassLoaders by using available implementations of ClassLoaderDiscovery
+ */
+public class Discovery implements ClassLoaderDiscovery {
+
+ private final List<ClassLoaderDiscovery> impls = new ArrayList<ClassLoaderDiscovery>();
+
+ public Discovery() {
+ ClassLoaderRegistry registry=new ClassLoaderRegistry();
+ impls.add(new SystemDiscovery(registry));
+ impls.add(new TomcatDiscovery(registry));
+ impls.add(new ThreadDiscovery(registry));
+ }
+
+ public Collection<ClassLoaderInfo> listClassLoaders() {
+ List<ClassLoaderInfo> ret = new ArrayList();
+ for (ClassLoaderDiscovery discovery : impls) {
+ ret.addAll(discovery.listClassLoaders());
+ }
+ return ret;
+ }
+
+ public String discoveryName() {
+ return "All ClassLoaders";
+ }
+
+ public void dumpList(PrintStream out) {
+ for (ClassLoaderDiscovery discovery : impls) {
+ Collection<ClassLoaderInfo> clis = discovery.listClassLoaders();
+ if (!clis.isEmpty()) {
+ out.println();
+ out.println(discovery.discoveryName() + ":");
+ out.println();
+ out.println(ClassLoaderInfo.header);
+ for (ClassLoaderInfo cli : clis) {
+ out.println(cli.toString());
+ }
+ }
+ }
+ }
+
+ public ClassLoaderInfo findClassLoader(String clId) {
+ for (ClassLoaderInfo cli : listClassLoaders()) {
+ if (cli.id.equals(clId)) {
+ return cli;
+ }
+ }
+ return null;
+ }
+
+}
View
7 liverepl-agent/src/net/djpowell/liverepl/discovery/Function.java
@@ -0,0 +1,7 @@
+package net.djpowell.liverepl.discovery;
+
+public interface Function<R, A> {
+
+ R invoke (A arg);
+
+}
View
67 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/JMXDiscovery.java
@@ -0,0 +1,67 @@
+package net.djpowell.liverepl.discovery.impl;
+
+import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
+import net.djpowell.liverepl.discovery.ClassLoaderInfo;
+import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
+import net.djpowell.liverepl.discovery.Function;
+
+import javax.management.*;
+import java.lang.management.ManagementFactory;
+import java.util.Collection;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Helper class to return ClassLoaders using JMX
+ */
+public class JMXDiscovery implements ClassLoaderDiscovery {
+
+ private final ClassLoaderRegistry registry;
+ private final MBeanServer mbs;
+ private final String searchName;
+ private final String attributeName;
+ private final Function<ClassLoader, Object> getClassLoader;
+ private final Function<String, ObjectName> getInfo;
+ private final String discoveryName;
+
+ public JMXDiscovery(ClassLoaderRegistry registry, String searchName, String attributeName, Function<ClassLoader, Object> getClassLoader, Function<String, ObjectName> getInfo, String discoveryName) {
+ this.registry = registry;
+ this.mbs = ManagementFactory.getPlatformMBeanServer();
+ this.searchName = searchName;
+ this.attributeName = attributeName;
+ this.getClassLoader = getClassLoader;
+ this.getInfo = getInfo;
+ this.discoveryName = discoveryName;
+ }
+
+ public Collection<ClassLoaderInfo> listClassLoaders() {
+ Set<ClassLoaderInfo> ret = new TreeSet<ClassLoaderInfo>();
+ Set<ObjectName> names;
+ try {
+ names = mbs.queryNames(new ObjectName(searchName), null);
+ } catch (MalformedObjectNameException e) {
+ throw new RuntimeException(e);
+ }
+
+ for (ObjectName name : names) {
+ Object obj;
+ try {
+ obj = mbs.getAttribute(name, attributeName);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ ClassLoader classLoader = getClassLoader.invoke(obj);
+ String id = registry.registerClassLoader(classLoader);
+ String info = getInfo.invoke(name);
+ ClassLoaderInfo cli = new ClassLoaderInfo(id, classLoader, info);
+ ret.add(cli);
+ }
+ return ret;
+ }
+
+ public String discoveryName() {
+ return discoveryName;
+ }
+}
View
32 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/SystemDiscovery.java
@@ -0,0 +1,32 @@
+package net.djpowell.liverepl.discovery.impl;
+
+import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
+import net.djpowell.liverepl.discovery.ClassLoaderInfo;
+import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Simple implementation of ClassLoaderDiscovery for returning the SystemClassLoader.
+ * This implementation should be registered first with Discovery to ensure that the
+ * well-known SystemClassLoader gets assigned 0 as its id.
+ */
+public class SystemDiscovery implements ClassLoaderDiscovery {
+
+ private final String id;
+
+ public SystemDiscovery(ClassLoaderRegistry registry) {
+ id = registry.registerClassLoader(ClassLoader.getSystemClassLoader());
+ }
+
+ public Collection<ClassLoaderInfo> listClassLoaders() {
+ ClassLoaderInfo cli = new ClassLoaderInfo(id, ClassLoader.getSystemClassLoader(), "<system>");
+ return Collections.singletonList(cli);
+ }
+
+ public String discoveryName() {
+ return "System Class Loader";
+ }
+
+}
View
52 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/ThreadDiscovery.java
@@ -0,0 +1,52 @@
+package net.djpowell.liverepl.discovery.impl;
+
+import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
+import net.djpowell.liverepl.discovery.ClassLoaderInfo;
+import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
+
+import java.util.*;
+
+/**
+ * Implementation of ClassLoaderDiscovery that makes available the Thread Context ClassLoaders
+ * associated with currently running threads.
+ */
+public class ThreadDiscovery implements ClassLoaderDiscovery {
+
+ private final ClassLoaderRegistry registry;
+
+ public ThreadDiscovery(ClassLoaderRegistry registry) {
+ this.registry = registry;
+ }
+
+ public Collection<ClassLoaderInfo> listClassLoaders() {
+ Collection<ClassLoaderInfo> ret = new ArrayList<ClassLoaderInfo>();
+ ClassLoader systemCl = ClassLoader.getSystemClassLoader();
+ Collection<Thread> threads = new HashSet<Thread>(Thread.getAllStackTraces().keySet());
+ Map<ClassLoader, String> classLoaders = new HashMap<ClassLoader, String>();
+ for (Thread thread : threads) {
+ ClassLoader classLoader = thread.getContextClassLoader();
+ if (classLoader == null) continue;
+ if (classLoader == systemCl) continue;
+ classLoaders.put(classLoader, thread.getName() + " #" + thread.getId() + " [" + thread.getThreadGroup().getName() + "]");
+ }
+ TreeSet<Map.Entry<ClassLoader, String>> entries = new TreeSet<Map.Entry<ClassLoader, String>>(
+ new Comparator<Map.Entry<ClassLoader, String>>() {
+ public int compare(Map.Entry<ClassLoader, String> o1, Map.Entry<ClassLoader, String> o2) {
+ return o1.getValue().compareTo(o2.getValue());
+ }
+ });
+ entries.addAll(classLoaders.entrySet());
+ for (Map.Entry<ClassLoader, String> entry : entries) {
+ ClassLoader classLoader = entry.getKey();
+ String threadName = entry.getValue();
+ String id = registry.registerClassLoader(classLoader);
+ ClassLoaderInfo cli = new ClassLoaderInfo(id, classLoader, threadName);
+ ret.add(cli);
+ }
+ return ret;
+ }
+
+ public String discoveryName() {
+ return "Thread Context Class Loaders";
+ }
+}
View
51 liverepl-agent/src/net/djpowell/liverepl/discovery/impl/TomcatDiscovery.java
@@ -0,0 +1,51 @@
+package net.djpowell.liverepl.discovery.impl;
+
+import net.djpowell.liverepl.discovery.ClassLoaderDiscovery;
+import net.djpowell.liverepl.discovery.ClassLoaderInfo;
+import net.djpowell.liverepl.discovery.ClassLoaderRegistry;
+import net.djpowell.liverepl.discovery.Function;
+
+import javax.management.ObjectName;
+import java.util.Collection;
+
+/**
+ * Implementation of ClassLoaderDiscovery which uses JMX to obtain the ClassLoaders associated
+ * with Tomcat web applications.
+ */
+public class TomcatDiscovery implements ClassLoaderDiscovery {
+
+ private final JMXDiscovery jmxDiscovery;
+
+ public TomcatDiscovery(ClassLoaderRegistry registry) {
+ this.jmxDiscovery = new JMXDiscovery(registry, "Catalina:j2eeType=WebModule,*", "loader",
+ new Function<ClassLoader, Object>() {
+ public ClassLoader invoke(Object obj) {
+ ClassLoader classLoader;
+ try {
+ classLoader = (ClassLoader) obj.getClass().getMethod("getClassLoader").invoke(obj);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return classLoader;
+ }
+ },
+ new Function<String, ObjectName>() {
+ public String invoke(ObjectName arg) {
+ String name = arg.getKeyProperty("name");
+ int pos = name.lastIndexOf('/');
+ if (pos != -1) name = name.substring(pos);
+ return name;
+ }
+ }, "Tomcat Web Applications");
+ }
+
+ public Collection<ClassLoaderInfo> listClassLoaders() {
+ return jmxDiscovery.listClassLoaders();
+ }
+
+ public String discoveryName() {
+ return jmxDiscovery.discoveryName();
+ }
+}
View
9 liverepl-server/src/net/djpowell/liverepl/server/Repl.java
@@ -10,14 +10,13 @@
public class Repl {
- final static public Symbol REPL_NS = Symbol.create("net.djpowell.liverepl.server.repl");
- final static public Namespace NS = Namespace.findOrCreate(REPL_NS);
- final static public Var REQUIRE = Var.intern(RT.CLOJURE_NS, Symbol.create("require"));
- final static public Var REPL = Var.intern(NS, Symbol.create("repl"));
+ private final static Symbol REPL_NS = Symbol.create("net.djpowell.liverepl.server.repl");
+ private final static Namespace NS = Namespace.findOrCreate(REPL_NS);
+ private final static Var REQUIRE = Var.intern(RT.CLOJURE_NS, Symbol.create("require"));
+ private final static Var REPL = Var.intern(NS, Symbol.create("repl"));
public static void main(InetAddress host, int port) throws Exception {
// not really needed in clojure 1.1, as context class loaders are the default
- // required so that clojure can load the repl code
Var.pushThreadBindings(new PersistentArrayMap(new Object[] {RT.USE_CONTEXT_CLASSLOADER, Boolean.TRUE}));
try {
REQUIRE.invoke(REPL_NS);
View
93 liverepl-server/src/net/djpowell/liverepl/server/repl.clj
@@ -1,50 +1,43 @@
-(ns net.djpowell.liverepl.server.repl
- (:require clojure.main)
- (:import (java.io InputStreamReader BufferedWriter OutputStreamWriter Writer)
- (java.net ServerSocket Socket)
- (java.util.logging Logger)
- (clojure.lang LineNumberingPushbackReader)))
-
-(def new-line (System/getProperty "line.separator"))
-
-(defn repl-caught
- "Override the default repl-caught to not rely on PrintStream"
- [e]
- (.write #^Writer *err* (str (clojure.main/repl-exception e) new-line)))
-
-;; The client should connect immediately after the server starts up,
-;; but in case there is a problem, we will kill the serversocket,
-;; terminating the server, if the client fails to connect.
-(def client-connect-timeout 10000)
-
-(def #^Logger trc (Logger/getLogger (str *ns*)))
-
-(defn repl
- "Start a single connection repl server"
- [port backlog host]
- (let [server (ServerSocket. port backlog host)
- got-connection (atom false)
- killer-thread (Thread.
- (fn []
- (Thread/sleep client-connect-timeout)
- (when-not @got-connection
- (.fine trc "Client connect timeout: terminating server")
- (.close server)))
- "Killer Thread")
- repl-thread (Thread.
- (fn []
- (with-open [server server
- socket (.accept server)
- in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream socket)))
- out (BufferedWriter. (OutputStreamWriter. (.getOutputStream socket)))]
- (swap! got-connection (constantly true))
- (binding [*in* in
- *out* out
- *err* out]
- (try
- (clojure.main/repl :caught repl-caught :init #(in-ns 'user))
- (catch Exception e ; discard errors caused by abrupt disconnection
- nil)))))
- "Repl Thread")]
- (.start killer-thread)
- (.start repl-thread)))
+(ns net.djpowell.liverepl.server.repl
+ (:require clojure.main)
+ (:import (java.io InputStreamReader BufferedWriter OutputStreamWriter Writer)
+ (java.net ServerSocket Socket)
+ (java.util.logging Logger)
+ (clojure.lang LineNumberingPushbackReader)))
+
+;; The client should connect immediately after the server starts up,
+;; but in case there is a problem, we will kill the serversocket,
+;; terminating the server, if the client fails to connect.
+(def client-connect-timeout 10000)
+
+(def #^Logger trc (Logger/getLogger (str *ns*)))
+
+(defn repl
+ "Start a single connection repl server"
+ [port backlog host]
+ (let [server (ServerSocket. port backlog host)
+ got-connection (atom false)
+ killer-thread (Thread.
+ (fn []
+ (Thread/sleep client-connect-timeout)
+ (when-not @got-connection
+ (.fine trc "Client connect timeout: terminating server")
+ (.close server)))
+ "Killer Thread")
+ repl-thread (Thread.
+ (fn []
+ (with-open [server server
+ socket (.accept server)
+ in (LineNumberingPushbackReader. (InputStreamReader. (.getInputStream socket)))
+ out (BufferedWriter. (OutputStreamWriter. (.getOutputStream socket)))]
+ (swap! got-connection (constantly true))
+ (binding [*in* in
+ *out* out
+ *err* out]
+ (try
+ (clojure.main/repl :init #(do (in-ns 'user) (println "Clojure" (clojure-version))))
+ (catch Exception e ; discard errors caused by abrupt disconnection
+ nil)))))
+ "Repl Thread")]
+ (.start killer-thread)
+ (.start repl-thread)))
View
16 liverepl.bat
@@ -1,8 +1,8 @@
-@echo off
-rem Starter script for Clojure liverepl
-
-set JDK_HOME=c:\jdk
-set LIVEREPL_HOME=%~dp0
-set CLOJURE_JAR=%LIVEREPL_HOME%\clojure.jar
-
-"%JDK_HOME%\bin\java" -cp "%LIVEREPL_HOME%\liverepl-agent.jar";"%JDK_HOME%\lib\tools.jar" net.djpowell.liverepl.client.Main "%CLOJURE_JAR%" "%LIVEREPL_HOME%\liverepl-agent.jar" "%LIVEREPL_HOME%\liverepl-server.jar" %1
+@echo off
+rem Starter script for Clojure liverepl
+
+set JDK_HOME=c:\jdk
+set LIVEREPL_HOME=%~dp0
+set CLOJURE_JAR=%LIVEREPL_HOME%\clojure.jar
+
+"%JDK_HOME%\bin\java" -cp "%LIVEREPL_HOME%\liverepl-agent.jar";"%JDK_HOME%\lib\tools.jar" net.djpowell.liverepl.client.Main "%CLOJURE_JAR%" "%LIVEREPL_HOME%\liverepl-agent.jar" "%LIVEREPL_HOME%\liverepl-server.jar" %*

0 comments on commit 4bb056a

Please sign in to comment.