Common Error Patterns - Execution Order

Describes a few common error patterns based on thread priority and multiple active threads in an SMP environment.

The majority of errors found while testing SMP code are not actually caused by SMP but are multi-threaded code errors that become more likely to occur on an SMP system.

Many of these errors are caused by the developer making assumptions about how the scheduler will prioritise some threads, and suspend execution of other threads while the higher priority thread completes. Even on a single core system these assumptions can fail if the higher priority thread is not ready to run or has to wait for a resource, and the scheduler continues execution of the original thread.

Because many of the common error patterns are caused by the same thread execution assumptions and errors, the solution to many of them is the same: write good code that doesn't make assumptions but explicitly enforces execution order or waits until asynchronous events are complete.

Examples of common errors that are caused by thread execution order are:

Relying on linear execution

On a single-processor system, the scheduler usually grants execution to another thread when the active thread returns from a function or calls a waiting primitive. This can lead to two kinds of assumptions: assuming that the other thread will not execute until a later stage, or assuming that the active thread will block on an asynchronous function call. You must not rely on either of these, especially on an SMP system.

Deleting/re-creating kernel objects

Kernel objects are objects derived from RHandleBase, such as threads, semaphores, mutexes. processes and timers. They each have a name that must be unique within the system and are managed by the supervisor thread.

If you ask for such a kernel object to be deleted, there may be a delay before the asynchronous deletion completes. If you try to create an object with the same name before the kernel has deleted the original object, the creation will fail. In general it is not possible to determine whether the kernel has completed the deletion: although there are some APIs that can be used during development, they are not available for use on a live system.

For that reason it is important to not reuse names, but to always create kernel objects with a new unique name for each object. There are APIs available for many kernel objects to provide new unique names. It is also possible for you to write your own unique name generator.

Passing stack variables or temporary variables to an asynchronous service

Figure 1. Passing data on the stack

When execution leaves a function, the variables that are local to the function are deallocated from the stack. If you pass local variables as parameters to an asynchronous function, you must make sure that the other thread's execution happens before the data is removed from the stack. It is much better to use objects that persist over the lifetime of an asynchronous call: for example, a class member variable..

Likewise, deleting an object after it has been passed to an asynchronous function but before the service is provided will cause application errors and possibly device panics

Making asynchronous calls that pass a TRequestStatus without calling WaitForRequest() afterwards

On a single-processor system, calling a waiting primitive always relinquishes execution control to the scheduler. With adequate thread priorities, the scheduler may then run the thread in charge of the request. However, this assumption is not true on an SMP system, where both threads might execute at the same time.

Therefore, you must use a synchronisation primitive to ensure that the asynchronous request is complete before continuing on to any code that relies on the completion of that request.

As for any other asynchronous request, it is important to wait for the request to complete. Use WaitForRequest() to ensure that the other thread has finished processing the asynchronous call before the calling thread can safely progress.

Relying on thread priority for execution order

On an SMP system, you cannot assume execution order from thread priority. Use semaphores and mutexes to ensure that the execution order is safe.

Relying on thread priority for publish and subscribe execution order

As the notification thread and the subscription thread might execute at the same time, use synchronisation primitives to ensure that your listener is ready before publishing, and that it is notified before the publication channel is deleted.

See Common Error Patterns - Case Studies for an example of a Publish and Subscribe error pattern.

Other race conditions due to unexpected thread ordering

It is expected that there may be other race conditions between threads and as these are identified this document will be updated to describe them.

Using non-Symbian synchronisation APIs

The Symbian synchronisation and IPC primitives protect data and control thread execution on an SMP system. There is no guarantee that non-Symbian APIs will give the same protection. For SMP-safe code, only use the Symbian synchronisation APIs .

See Data Integrity and Memory Barriers , Atomic Operations and Locking for useful synchronisation and atomic operation APIs.

Related concepts
SMP Developer Tips