1

I'm working on an extension that opens multiple models simultaneously. Models can be opened and closed dynamically. Models are opened as a LiteWorkspace so that widgets and such are displayed. Whenever you create a new LiteWorkspace, it starts up a JobThread and Lifeguard. The problem is that shutting down a GUIWorkspace (the parent class of LiteWorkspace) doesn't kill its Lifeguard or JobThread.

Possible solutions:

  1. The ideal solution would be a to have all models share a JobThread, as this would remove number of threads as a cap on how many models one can run. Doing this would have certain performance benefits as well. Is there a chance this is a possible? This solution still require being able to kill Lifeguard.

  2. Next would be to be able to kill the threads of course.

  3. As a last resort, we could keep around a pool of GUIWorkspaces. When the user closes a model, it's thrown back into the pool. Then, when the user loads a new model and there's something in the pool, we just reuse one of those GUIWorkspaces. This would allow us to open the same number of models as with 2. The only downside is that opening a new model (like completely new, from the file menu or whatever) still doesn't kill the threads, so there's this permanent resource baggage hanging around. We actually might have to institute a pool anyway for performance reasons, but it would be really nice to actually be able to free up the resources at some point.

Bryan Head
  • 12,360
  • 5
  • 32
  • 50

1 Answers1

1

Solution 1 isn't workable; the assumption of one JobThread per Workspace is deeply embedded in the code.

To get the JobThread to die, this should be sufficient:

workspace.jobManager.haltPrimary()
workspace.jobManager.die()

To get the GUIWorkspace.Lifeguard thread to die, we just need to call interrupt() on it, but even just getting a reference to it isn't so easy. Here's a transcript showing a successful attempt:

/Applications/NetLogo 5.0.5 % scala29 -Yrepl-sync -classpath NetLogo.jar
Welcome to Scala version 2.9.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).

scala> val applet = new org.nlogo.lite.Applet
applet: org.nlogo.lite.Applet = ...

scala> applet.init()
...

scala> val ws = applet.panel.workspace: org.nlogo.window.GUIWorkspace
ws: org.nlogo.window.GUIWorkspace = org.nlogo.lite.LiteWorkspace@69cafecd

scala> import collection.JavaConverters._
import collection.JavaConverters._

scala> Thread.getAllStackTraces().keySet.asScala.foreach(println)
Thread[JobThread,4,main]
Thread[Lifeguard,6,main]
...

scala> ws.jobManager.haltPrimary()

scala> ws.jobManager.die()

scala> import util.Try
import util.Try

scala> for {
         thread <- Thread.getAllStackTraces().keySet.asScala
         if thread.getName == "Lifeguard"
         outer = Try{ val field = thread.getClass.getDeclaredField("this$0")
                      field.setAccessible(true)
                      field.get(thread) }
         if outer.toOption == Some(ws)
       } {
         thread.interrupt()
         thread.join()
       }
Success(org.nlogo.lite.LiteWorkspace@69cafecd)

scala> Thread.getAllStackTraces().keySet.asScala.foreach(println)
...

(Note that I've answered in Scala; conversion to Java left to the reader. The reflection stuff for accessing the outer instance of an inner class comes from https://stackoverflow.com/a/763617/86485.)

A pull request that adds code implementing GUIWorkspace.dispose() in a cleaner way would be welcome.

Community
  • 1
  • 1
Seth Tisue
  • 29,985
  • 11
  • 82
  • 149