See the org.openide.execution package.


Execution API

The Execution API controls the execution of long-lived processes. XXX need new documentation for this module!

ThreadDeath may be thrown by the execution engine if the process is manually stopped (by the user or via its ExecutorTask), so if you catch this it is best not to print its stack trace. Also, the executed code within this method may invoke System.exit to end the application, without shutting down NetBeans.

A class running in an external process whose output you wish to capture should generally have its streams redirected to the Output Window, e.g. by using Process.getInputStream(). The streams on the Output Window may be obtained from the execution engine (see below), using ExecutorTask.getInputOutput() on the task returned from ExecutionEngine.execute(...), and then InputOutput.getOut() and so on. Now you can just copy characters from one stream to the other.

Note that exceptions or errors occurring during the actual execution of the process, once it has been successfully started, should be handled within the scope of the ExecutorTask and normally this just means printing the stack trace to the Output Window via an InputOutput handle.

NbProcessDescriptor is an object representing the abstract command template. This class has a standard custom property editor which should be familiar to anyone who has used external execution, compilation, or debugging: it displays a dialog with a command name, some arguments in a panel, and an optional description key.

Typically both the command name and list of arguments are actually templates for the real thing, with embedded substitution codes; then the description key can explain to the user what the substitution codes mean.

The process descriptor itself does not include any information about how to substitute these codes, if there are any. Rather, it presents ways to create a running process from itself: NbProcessDescriptor.exec() simply runs the literally supplied string, assuming it does not need any substitutions; but NbProcessDescriptor.exec(Format) applies a (textual) format to the command name and arguments list before invoking Runtime.exec. The format can in principle be any format you like.

NbClassPath is an object used to represent a classpath, which is of course frequently used in Java-related applications, though implementors of executors unrelated to Java development will probably ignore it. Its main purpose is that it also has a custom property editor, making it convenient to use as a Bean property on service types such as executors.

Managing I/O

Typically, implementors of executors (or other external services such as compilation) will wish for any I/O via standard system streams (input, output, and error) to be redirected to the Output Window, rather than going to the NetBeans console (and logfile) where the user will not normally see it. In some cases, executed applications are also expected or permitted to invoke System.exit to terminate themselves, which should be prevented from terminating NetBeans itself.

NetBeans provides two forms of support for these considerations, which complement one another in that they cover both dynamic scope (code run within a certain thread group and time window) and quasi-lexical scope (code derived from a certain classloader). Both will accomplish automatic I/O redirection and trapping of exit calls; implementors may use either or both to ensure coverage of executed code.

First it is necessary to describe the InputOutput interface which is common to both forms of support. This interface is essentially an abstract description of the public capabilities of an Output Window tab. Thus, a certain level of control is afforded to users of this interface in terms of managing selection of the tab, getting its various I/O streams and using them, controlling whether the two output streams are mixed together, etc.

There are three ways to get a useful InputOutput implementation:

  1. You may use IOProvider.getIO(String,boolean) to create an Output Window tab with a specified name and return its InputOutput representative. Or, try to reuse an existing tab of the same name, if there is one.
  2. If ExecutionEngine.execute(...,InputOutput) is called and its third argument is null, this signifies that the engine itself should choose an appropriate Output Window tab, probably named after the supplied task name. The returned ExecutorTask will then already be using the proper InputOutput, and this may be retrieved if needed using ExecutorTask.getInputOutput().
  3. You may use the constant InputOutput.NULL to indicate that no I/O at all is desired for a task - that is, its I/O streams will be trapped, but writes will be discarded and reads will return end-of-file. This constant should be used if it is very clear that I/O will not be used and some overhead should be saved; or if the I/O is specifically not desired.
Note that if you simply implement the InputOutput interface yourself, it will not be "magic" and the execution engine will not be able to use it for managing I/O, so there is probably no reason to ever do so. Specifically this means that there is currently no support in the APIs for running a task and automatically redirecting its I/O to streams of the implementor's choice; they may only be redirected to the Output Window.

Given an InputOutput, you can use it in either of the two ways given below, or both at the same time, and I/O streams will be trapped.

  1. ExecutionEngine.execute(...) is the primitive means for providing I/O services to a block of code using dynamic scope. (You may obtain the ExecutionEngine via ExecutionEngine.getDefault().) The supplied Runnable is run asynchronously in its own thread and thread group, i.e. well-isolated from the rest of NetBeans. Any uses of the system I/O streams within that thread group will automatically be trapped and redirected to the InputOutput. Note that code which creates new threads will normally create them in the same thread group as the calling code, so it is fine for there to be complex multithreaded code in the supplied runnable; it will all be handled. Runnable-spawned code which is destined for other thread groups will not be handled, however; for example, will execute code in NetBeans' main thread group, not the one created by the execution engine.

    The supplied task name is optional and may be null. If it is supplied, it will be used as a display name that may show up e.g. in the Execution View, so that the user may see that the process is running and stop it if needed. If null, the task will not be visible to the user.

    As mentioned above, you may pass an existing InputOutput to this method so as to ask the execution engine to use that I/O; or you may leave this argument null to request that a suitable I/O tab be created for you. In either case, the I/O tab actually in use will be available via ExecutorTask.getInputOutput().

    The task returned from the engine permits you to find its I/O implementation; let the runnable continue asynchronously; stop it at any time; or wait for it to finish (i.e. block) and get its return status. As far as return status goes, zero means success, nonzero numbers mean failure. The execution engine's default task implementation just considers natural termination of the Runnable to be success, and aborted tasks to have failed (and some unspecified nonzero number returned as the status). For some uses, such as execution of external applications which may return a meaningful exit status, you may need to create a special wrapper ExecutorTask implementation which provides the correct exit status (since the execution engine is not aware of such codes).

    The dynamic scope of execute(...) also covers attempted uses of System.exit(int) (or Runtime.exit(int)). If an exit is attempted within the task's dynamic scope (i.e. thread group), this is caught by the NetBeans security manager implementation, and the task is instead stopped (as if by ExecutorTask.stop()). In practice this means that all living threads in the thread group will receive ThreadDeath to stop them. So executor implementations should be prepared to have thread death thrown on them, and anyone catching Throwable should specifically consider whether the throwable is a thread death; in this case, the surrounding code should be stopped promptly, and there is no need to print a stack trace. The thread death may be thrown either because of an attempted exit call, or because of an explicit use of ExecutorTask.stop(). (Note that there is no way to recover the exit status which the attempted exit call used.)

  2. new NbClassLoader(InputOutput) creates a special classloader that is aware of an InputOutput obtained as above. Normally NbClassLoader is just used to load classes from the Repository. This constructor, however, is "magic" in that it will still load classes from the Repository (always giving preference to its parent class loader), but I/O calls quasi-lexically contained in such classes (i.e. made directly by such a class, or by other invoked code when such a class is on the stack) will be redirected to the supplied I/O.

    Note that this works regardless of thread group, so that for example a runnable loaded from this classloader which is posted to the RequestProcessor will still use I/O redirection, unlike with ExecutionEngine.execute(...).

    NbClassLoader does not specially handle System.exit calls since such code need not be in any particular thread group, so it does not make sense to try to stop some task. Rather, any code in the system which is outside the NetBeans standard trusted codebase which tries to exit the VM will receive a security exception. Note that this exception specifically does nothing in response to printStackTrace(), which is usually desirable because general-purpose exception catching code such as is common in executors just prints any received stack traces, whereas System.exit should simply end the task without triggering a noisy and confusing SecurityException trace.

    The NetBeans trusted codebase specifies that Java platform code, code loaded from modules (including test modules), and code loaded from an NbClassLoader with the InputOutput constructor is to be trustworthy; other Filesystems code will typically fall outside of these codebases and so is subject to the security manager. Such "untrusted" code is probably restricted from security-sensitive calls (but do not count on it). Such code can still call LifecycleManager.exit() to explicitly exit NetBeans.

Each of the three I/O streams from the generated process (created by createProcess which is described in detail above) are assigned to one of the I/O streams associated with the InputOutput. Each such pairing is implemented by a separate thread; this runs in the thread group created by the execution engine, since it was created by the runnable.

The external executor actually keeps track of the entire ExecutorTask provided by the execution engine, not just its InputOutput, since it cannot rely on the execution engine to kill the external process directly. (The execution engine normally just kills all threads in the thread group it created, in order to stop a task it created.) Rather, the internal runnable creates a new ExecutorTask which provides the additional needed behavior and returns it in proxy, while keeping the original for its own use.

The main trick involves program termination (from NetBeans). If the code calling ProcessExecutor.execute pays attention to its returned task and directly calls ExecutorTask.stop on this task, then there is no problem: this method is directly implemented to stop both the external process itself, and all I/O proxy threads. Stopping the process causes the result method to unblock and get an exit status, which also notifies task listeners that the task is finished; and when the last proxy thread stops, then the execution engine sees that the thread group is dead (no live threads remain in it) and so it also knows that its own task is finished, causing (among other things) that entry to disappear from the list of processes in the Execution View.

But if the task is stopped from the Execution View (i.e. stop is called on the execution engine's own task, rather than the synthetic task from ProcessExecutor), then the synthetic task needs to be stopped as well. This is implemented by having the synthetic task attach a task listener to the original one; when the original one finishes, the synthetic one stops itself as well (and consequently shuts down the external process and the copy threads, if they have not been killed already).