Safety of running and stopping dynamically loaded code


Luke Gorrie (luke@javagroup.org)
18 Mar 1999 10:13:13 +1000


Hi all, Bill invited me to poke my head in and mention a few ideas I've gathered about running and stopping dynamically loaded code, based on some mucking about I've done with "Echidna" (www.javagroup.org/echidna/), which is a program for squashing programs into a VM together. All I can offer is opinion, since my own work in this direction has just been hacking up a little tool a long time ago when I needed to compile files faster and save memory on my slow computer. I hope that my experiences can be of some help with the "Dealing with dynamically loaded code" topic. Here's some background, feel free to skip a few paragraphs to just get my misinformed ideas about where things should be going, rather than information about my misadventures. Firstly, what I've done. Basically, it's a program which can load java applications into "processes", which are a collection of threads, a classloader, and "resources". The classloader is one created for a process which is used to load its main class, and a ThreadGroup is then created to own a thread which invokes the main(String[]) method of that class. By knowing which classloader is associated with the process, I can use calls to the SecurityManager to discover on which process' behalf a call is being made. The "resources" are objects which which are associated with a process, and are either disposed of when the process finishes, keep the process from being considered finished while they exist, or both. The other mechanism employed is what I call a "lightweight namespace", which provides mapping from classes to instances for accessing shared resources. These lightweight namespaces are my means of avoiding conflicts in static state, like two processes each wanting System.out to write to a different stream. With a nested, recursive tree of lightweight namespaces, having a branch for each process from its parent, I can traverse the tree to find the object for a given class which a certain process is supposed to use. In the case of standard i/o streams, I have a "StandardIO" class with getIn(), getOut(), getErr() methods. When a process wants to access its standard IO streams, the top-level namespace is asked for an instance of "StandardIO", which delegates to its child which is the highest process-aware namespace. From here, there is a separate sub-namespace for each process, and with a call to the security manager to discover which process is running, it knows which branch to take and finds the StandardIO object registered for that process -- or if none exists, it keeps going back up the tree to find one, for which there is a default at the top-level. These mechanisms generally lend themselves to cooperative programs which will declare their resource use and always politely ask for appropriate shared objects instead of accessing globally available ones. What I do though is hack a little code into a few available hooks: System.{in,out,err} streams are replaced by streams which delegate to an appropriate StandardIO object, the "checkTopLevelWindow(Window)" method of SecurityManager is used to discover which process is responsible for which Windows so that they can be disposed of when the process finishes, and so that a process won't be considered to have "died of natural causes" while it has Windows which might make calls to it using AWT threads. Also, because I know which resources and threads a process owns, I can "kill" processes by stopping their threads and disposing of their resources. Echidna's kill mechanism is unsafe in the usual sense of Thread.stop(), and only provided because it can be useful, rather than as a real solution to stopping dynamically loaded code. // End of talking about Echidna, and on to more general topics. So, the main problems I've found with using separate programs in the same JVM are: 1. The core API uses a lot of shared state, without hooks to control access to it. e.g., there is no way to intercept calls asking for System properties and make decisions about the value to be returned based on context information. 2. stop()ing threads, for the reasons outlined in the deprecation rationale, is unsafe and there is no general case safe alternative. _From_ where I sit, these both seem like border control issues for when calls are made between shared objects and those inside the "sandbox" of the process. The first issue demands that calls from a process to a shared object - like System.getProperty("my.property") - can be intercepted and handled according to context, such as delegating to a different Properties object based on the ClassLoader context. Some kind of nested, recursive namespace seems to be what's needed, preferably I think based on some general border control mechanism to avoid making assumptions about requirements. The second issue also seems to relate to border control. I think that if we want to kill a process, then we can consider all the objects inside its sandbox to be dead, and all the threads associated with them to be dead. In Thread.stop() terms, we needn't worry about putting objects inside the process' sandbox into an inconsistent state, since they are dead and no longer supposed to be used. Instead we want to protect shared objects to ensure that killing the process' threads won't force them into an inconsistent state. One way to handle this would be at the border, if we used separate dispatch threads for calls from a process going outside its sandbox. For example, if a process calls "System.mySensitiveOperation()", a call which will leave shared state in an inconsistent state if ended prematurely, then the process' thread "A" would wait at the border, while a separate thread "B" makes the invocation. When "B" returns, it gives the result to "A" which reports it back to the process, or if "A" has been stopped then the result is ignored. Examples of where this type of thing could be implemented seem to be CORBA, where calls between objects which exist inside the same JVM could be made using the caller thread and normal method invocation, but also a request-level interceptor could be used to control how the call is made. The interceptor could presumably handle both the namespace-related issue and the safety issue, by using a separate dispatch thread and also having the opportunity to re-route the call based on context. Eiffel's SCOOP concurrency model also seems like it would fare well with the safety of stop()ing things issue, in the same distributed-system style way by not allowing threads which could be killed to make invocations into objects which must outlive them. Another possibility seems to be having the JVM give each process a separate actual namespace, with its own copy of all static state associated with classes. This would basically be the same thing as running multiple VMs, though presumably by sharing actual class definitions and only making copies of state it could be more memory efficient. Presumably then an RMI-like API would be used for communication between processes in different namespaces. I'm aware that if request interceptors did something like spawn a new Thread and used introspection to make invocations between sandboxes, then the overhead would be unthinkable - I say all this in the hope that people who understand JVMs might know a way to implement it well. Well, that's about all I have to say. As you've probably gathered this is just idle speculation, but it feels good to get it off my chest. Getting rid of the assumption that a JVM is only designed to run one application, which can do what it likes, is a topic very close to my heart. Cheers, Luke ---------------------------------------------------------------------------- To unsubscribe (or other requests) mailto:majordomo@media.mit.edu List archives, FAQ, member list, etc. http://gee.cs.oswego.edu/dl/javares/



This archive was generated by hypermail 2.0b3 on Wed Mar 17 1999 - 20:37:55 EST