Concurrency in the Debug Platform

Last revised: Nov 26, 2004. Author: Darin Wright.

Problem

Many debug architectures require communicating with a remote debug target. The debug user interface needs to be robust in the face of latent and unreliable connections. To avoid blocking the user interface, the debug platform is exploiting the use of background jobs to perform operations and communicate with debuggers. As the opportunity for concurrent requests against debuggers increases, the platform must provide support for serializing conflicting requests against debuggers - for example a read operation to render a stack frame and a request that would invalidate the stack frame by resuming its thread. The debug platform needs to provide API and a set of guidelines to follow when scheduling concurrent operations and requests against debuggers.

Proposal

The classic cuncurrency issues

There are similarities between resources and debug elements and between resource modification and and performing operations on debug elements. Both are hierarchical in nature: a project contains folders and files, a debug target contains threads and stack frames. To support concurrent operations on resources care must be taken to properly lock the appropriate resources for updates. To properly support concurrent operations on debug elements, a similar locking scheme should be used. For example, in order to write to a file, a lock must be obtained on that resource. In order to step in a thread, a lock should be obtained on the thread.

Using the platform pattern

The resource platform uses scheduling rules to achieve resource locking, via a rule factory that generates rules for different types of operations. Similarly, the debug platform could provide a rule factory to create scheduling rules for operations on debug elements. There are two basic types of operations that can be performed on a debug element: access operations and modify operations.

Specializing concurrency support

Since different architectures may have different concurrency capabilities, the rules for performing concurrent operations must be provided by the architecture. The debug platform can provide a pessimistic rule factory as a default implementation for targets that do not provide their own rule factory. Debuggers that want to leverage extra concurrency must provide their own rule factory. The pessimistic rule factory would provide rules for serialized modify and access operations.

What does it mean to the debug platform?

The debug platform's actions that modify and access debug elements should do so using the appropriate scheduling rules. Note that the use of scheduling rules does not mean that jobs have to be used. For example, one can simply use the beginRule/endRule support on the job manager. An operation would consult a debug target's rule factory to obtain the appropriate rule. For example, a step operation on a stack frame would obtain the modify rule for the stack frame, and schedule a job to perform the step.

What does it mean to the debug implementor?

To maintain backwards compatibility, debug implementations are not required to do anything to work in the 3.1 debug platform. By default, a rule factory will be provided that serializes access and modification operations. To enable concurrent operations, a debugger can provide its own rule factory. For example the Java debugger would provide a rule factory to allow concurrent access operations within a debug target, and allow concurrent modification operations in different threads.

Debug models may use scheduling rules within their implementations, so that clients calling debug model APIs are enforced to use the proper rules. For example, the step implementation would use beginRule/endRule calls to the job manager when performing a step.

Why do we need this?

As we add perform more background operations in the debugger, and move communication out of the UI thread to the background, we need a mechanism so support concurrent operations on debuggers, and we have to let debuggers tell us what the concurrency rules are for their architectures.

For example, in a step operation the following sequence of events occurs:

In the 3.1 debug platform the step request will be performed in a background job. As well, the debug and variables view content providers will retrieve content and generate labels in background jobs. Source lookup will also be performed in a background job. All of this is done to avoid blocking the UI thread while communicating with a debug target. As updating the variables view can trigger evaluations (effectively resuming the thread that has just suspended to compute toString()'s), this can interfere with rendering labels in the debug view, as it invalidates stack frames. As well, the step action re-enables when the step end event is received, allowing the user to initiate another step before the views update. This can cause the views to show partial information, and information in an error state as the thread resumes while labels are being generated. We either want all views to update before we allow another step to occur (via serialization of jobs), or allow the next step request to effectively cancel jobs that it makes obsolete. We may be able to leverage the shouldSchedule/shouldRun support in jobs to cancel jobs that are obsolete.

Findings

Last updated: Dec 3, 2004

Experimenting with a rule factory that clients can use to perform locking when communicating with (accessing and modifying) a debugger revealed the following:

Based on these findings, we believe it is up to debug model implementations to assure they are thread safe, performing their own synchronization as needed. Models that require serialized communication with a back end are also required to implement such behavior in their model. In terms of API, this is not a breaking change. In terms of behavior, it is a change that may expose concurrency issues in existing debug model implementations. Debug model implementations need to ensure they are thread safe.