Threading in the Open APIs

Overview

# Contents

- Introduction
- Threading-Model Domains
  - Thread-safe
  - Unsynchronized
  - AWT Event Queue
  - Startup
  - Mutexes
  - Engines
  - Tasks
- Division of APIs into Domains

# Introduction

The Open APIs have a well-defined threading model, i.e. a set of conventions and requirements that should be used in regards to use of threads and long-running computations by all users of the Open APIs. This model applies both to API code itself; code in the IDE's private implementation; and all code present in modules.

Rather than attempt to make all calls in the APIs unconditionally thread-safe, which is not practical for the same reasons that much Java Platform code is not thread-safe in this way, the APIs are designed to have different threading requirements depending on which section of the APIs is being used. For the most part the division corresponds to API packages, although there are other considerations.

This document will begin by outlining several different domains, each of which has its own threading characteristics. Then, the APIs will be divided functionally into the different domains. Programmers who pay attention to this division and use the appropriate threading models throughout their code are of course not guaranteed freedom from deadlocks, UI freezes, race conditions, and multithreaded data structure corruption, but the risk of these types of errors is greatly reduced. Where appropriate, notes are included as to how extensively a domain has been tested for safety.

# Threading-Model Domains

## Thread-safe

Several areas of the Open APIs are designed to be completely thread-safe, which is to say that calls to these APIs may be made from any thread without concern for deadlocks or corruptions. Most importantly, the Filesystems API, Datasystems API, and Java Hierarchy API fall into this category. All code in these APIs is designed to expect concurrent access from multiple threads, and is automatically synchronized against such a possibility.

Implementations of abstract API objects in these packages should also follow this convention and use language-level synchronization where needed. Such implementations include custom filesystems, data loaders, data objects, cookie supports, hierarchy element implementations, etc. Note that `AbstractFileSystem` assumes much of the burden of synchronization for typical file system implementations, including the standard `LocalFileSystem`.

Swing abstract text documents are assumed to be thread-safe for callers, so implementors of editors must take care to synchronize these objects to prevent corruption. For atomicity of transactions on documents, callers should use `NbDocument.runAtomic(...)` or `NbDocument.runAtomicAsUser(...)` as needed. Note that this thread-safety does not extend to GUI components built using the documents, such as `EditorSupport.Editor`.

Note that these packages do not generally protect the programmer against delays, i.e. some calls may require a user-perceptible amount of time to complete. In such cases, GUI code should be careful when using the APIs, lest temporary screen freezes or jumpiness occur. That said, all calls in these packages which are expected to be slow provide a mechanism alleviating the problem:

- Filesystems calls are generally not slow, unless the underlying OS implementation of files has long delays.
- Datasystems calls are usually fast, except when directly waiting for Filesystems. Note that `DataFolder.getChildren()` is a synchronous call which ought not be used directly from GUI code or other sensitive areas: when necessary, it finds new files on disk and recognizes them with the data loader pool, which can be slow. However, the corresponding folder node does not block when yielding its list of children - this list may represent an in-progress computation, as you can see when in the Explorer a new folder with many files in it is expanded for the first time, and objects appear as they are available.
- `CloneableEditorSupport.prepareDocument()` may be used to load a text document asynchronously, as some editor implementations may not do this quickly (e.g. may begin syntax analysis at load time).
- A combination of the last two: if you already have a parse of a source file which is present on disk but not open in an Editor (more generally, does not have a document loaded), and then attempt to make a modification using the Java Hierarchy API - the method will block while the document is loaded from disk (even though no editor pane is open), then the modification will be made to the document. (Then the data object must be saved using `SaveCookie.save()` if you wish the changes to be written back to disk; otherwise the data object will be left in the modified state, which means an editor component opened on it in the future will have an asterisk in its name, and the user action to Save All will save it, etc.)

Filesystems has been extensively tested for thread safety and appears to be quite robust. Datasystems has not undergone as extensive testing but still has no known threading problems. The implementation of thread-safe nodes is quite new as of this writing (build 383), but it is expected that the reimplementation will solve a number of previously intractable threading problems, as well as simplifying the caller-side semantics. Thread-safe documents are entirely up to the editor implementation, though there is associated API code such as updates `Line.Set`s which should be safe.

## Unsynchronized

Some utility APIs are unsynchronized and are only suitable for access from within a single thread, as is common in utility classes such as the Java Collections API. Such APIs do not interact directly with other APIs and so this is reasonable. Such APIs include enumerations, safe I/O, and data transfer.

## AWT Event Queue

A number of pieces of the API are designed to operate as GUI components; like in any AWT/Swing GUI application, care must be taken to only access these APIs within the AWT event queue, and not to consume an unreasonable amount of time or block when running code within them. Most importantly, the entire Explorer API is AWT-threaded, as well as the Window System API.

To make calls into these APIs from non-event-queue threads, the normal Swing mechanisms should be used, e.g. `SwingUtilities.invokeLater(...)`.

Note that nodes are not accessed in the event queue, they are mutexed; however, Explorer views are (and must be, since they are visual components). To interface between these domains, the `Visualizer` class and some associated models such as `NodeTreeModel` are used by Explorer views to provide event-queue-safe calling conventions while being asynchronously updated from the node hierarchy. Other code which must run in the event queue could also use these classes to interact safely with the node hierarchy. The visualizer layer is expected to be removed in the future.

## Startup

Certain code is run (in the NetBeans private implementation of the APIs) in the application main thread during IDE startup time. The main impact of this on module authors is that some code may be run in this main thread, specifically module install code. Generally such code should avoid waiting for the event queue (though non-Window-System GUI objects such as dialogs may be created at install or restore item, and Window System top components at install time only).

Module authors should be cautious about code run from `ModuleInstall` implementation methods, using only thread-safe calls or tasks.

`TopComponent` externalization methods may also be called in this main thread, during Window System startup, as well as potentially in the event queue, and thus should be as cautious.

## Mutexes

Certain APIs are thread-safe, but for implementation purposes require internal synchronization, and this synchronization is also exposed to caller-side code which requires transactional operations; this is generally done using `Mutex`es.

Each mutex follows the usual model of permitting any number of simultaneous readers, or a single writer, to exist in the mutex block. (Also, re-entering a mutex with the same type of access is harmless, as is entering in read mode while already holding write mode. Trying to re-enter in write mode while holding a read mode lock will cause a quick deadlock; to catch these, run the IDE with the system property `-Dnetbeans.debug.threads=true`.) There are four calls which permit threads to access a mutex: `Mutex.readAccess(Runnable)`, `Mutex.readAccess(Mutex.Action)`, `Mutex.writeAccess(Runnable)`, and `Mutex.writeAccess(Mutex.Action)`. Collectively these provide not only read-versus-write access to the mutex, but also the ability to either run code asynchronously whenever the mutex is next available, versus running code with a return value and blocking for it. (Compare similar calls in `EventQueue` and `SwingUtilities`.)

Typically an API subsystem has an associated public mutex or mutexes; other mutexes may be constructed if needed. For example, the project system has a single public mutex.

There is a special pseudo-mutex which has an interesting implementation, namely to synchronize with the AWT event queue. `Mutex.EVENT` makes no distinction between read and write access, permitting only one thread at a time in each. `Mutex.EVENT.readAccess(Runnable)` (or `Mutex.EVENT.writeAccess(Runnable)`) has an effect equivalent to `SwingUtilities.invokeLater`, while `Mutex.EVENT.readAccess(Mutex.Action)` (or `Mutex.EVENT.writeAccess(Mutex.Action)`) is equivalent to `SwingUtilities.invokeAndWait`. However, if the calling thread is already in the event queue, this mutex executes the code immediately (rather than waiting for other pending events to be processed).

The advantage of Mutex.EVENT over directly calling `EventQueue` methods (besides the special behavior for code already in the event queue) is that it is possible to construct a large amount of code to expect some mutex, and then quickly switch between the event-queue mutex and some independent mutex. This was used to advantage when creating an independent mutex for node children (which previously had been an alias for the event-queue mutex)!

## Engines

There are a couple places in the APIs where certain types of operations are clearly expected to take macroscopic time, if they terminate at all, and thus have a special isolated threading model. This applies mainly to running Ant. There are no special API considerations for this since Ant is normally single-threaded and does not normally call back to NetBeans code. Where this does happen (from a custom in-VM task), write code conservatively.

## Tasks

For explicit control over the performance of miscellaneous long-running tasks, which require their own threads, the APIs provides the utility class `Task` which occurs throughout the APIs as the result of various asynchronous operations. In and of itself it does not do much interesting except provide thread-safe status information, an exit status, and the ability to fire termination events.

However, it is typically used in conjunction with a `RequestProcessor` which does provide a separate thread or thread group to contain the running tasks, as well as other functionality including task priorities, and delayed scheduling (with the option to cancel a pending job). Although callers can create their own request processors, typical usage is to use a single instance in the system with `RequestProcessor.postRequest(Runnable)`; this ensures that all long-running code in the IDE (parsing, internal database updates, and so forth) is serialized and prioritized, rather than contending all at once for processor time and triggering virtual memory thrashes (as might result from the naive use of background threads).

It is also useful to post a request to the processor in the middle of GUI code, if the request might take a perceptible amount of time to complete.

# Division of APIs into Domains

The Open APIs are divided into threading domains as follows:

Modules
  Unsynchronized (though there is little implementation in this API). Most code to really handle modules is present in the private implementation, and thus has no documented model.
`DialogDisplayer`
  Follows Swing Threading Model - e.g. it is generally safe to realize the components from non event queue thread, however any callback made then has to be handled in event queue only. `DialogDisplayer.notify(...)` and similar calls (customize and select nodes) use the event queue in GUI mode, however, and is assumed to block (so do not hold any locks when you call it, nor wait from event queue for result of such operation).
`ErrorManager.notify(...)`
  Thread-safe.
Filesystems
  Thread-safe.
Datasystems
  Thread-safe. But `org.openide.util.datatransfer` is unsynchronized.
Explorer
  Event queue only. Includes event-queue-safe interface to node hierarchy.
Nodes
  Generally should all run in event queue, though this is not yet enforced.
Actions
  `Action.actionPerformed(ActionEvent)` must be called from the event thread. The action may choose to do all of its work synchronously in this call, or if necessary do some work in another thread, at its sole discretion.
Window System
  Event queue.
Options
  Thread-safe.
Editor
  Mostly thread-safe. Text documents should be locked using special locking provisions if desired. Document preparation may also be run as a task. Contains a default editor pane implementation which is displayed in the event queue, though document-related operations are still thread-safe.
Utility Classes
  Variable: `org.openide.awt` should be used from the event queue; `org.openide.util.Enumerations` and `org.openide.util.io` are unsynchronized. `org.openide.util` is generally thread-safe.

@FOOTER@