Reentrancy (computing)


In computing, a computer program or subroutine is called reentrant if multiple invocations can safely run concurrently. The concept applies even on a single processor system, where a reentrant procedure can be interrupted in the middle of its execution and then safely be called again before its previous invocations complete execution. The interruption could be caused by an internal action such as a jump or call, or by an external action such as an interrupt or signal. The previous invocations may resume correct execution before the reentered invocation completes, unlike recursion, where the previous invocations may only resume correct execution once the reentered invocation completes.
This definition originates from multiprogramming environments where the flow of control could be interrupted by an interrupt and transferred to an interrupt service routine or "handler" subroutine. Any subroutine used by the handler that could potentially have been executing when the interrupt was triggered should be reentrant. Often, subroutines accessible via the operating system kernel are not reentrant. Hence, interrupt service routines are limited in the actions they can perform; for instance, they are usually restricted from accessing the file system and sometimes even from allocating memory.
This definition of reentrancy differs from that of thread-safety in multi-threaded environments. A reentrant subroutine can achieve thread-safety, but being reentrant alone might not be sufficient to be thread-safe in all situations. Conversely, thread-safe code does not necessarily have to be reentrant.
Other terms used for reentrant programs include "pure procedure" or "sharable code". Reentrant subroutines are sometimes marked in reference material as being "signal safe".

Background

Reentrancy is not the same thing as idempotence, in which the function may be called more than once yet generate exactly the same output as if it had only been called once. Generally speaking, a function produces output data based on some input data. Shared data could be accessed by any function at any time. If data can be changed by any function, there is no guarantee to those that share a datum that that datum is the same as at any time before.
Data has a characteristic called scope, which describes where in a program the data may be used. Data scope is either global or local.
Local data is not shared by any routines, re-entering or not; therefore, it does not affect re-entrance. Global data is defined outside functions and can be accessed by more than one function, either in form of global variables, or as static variables. In object-oriented programming, global data is defined in the scope of a class and can be private, making it accessible only to functions of that class. There is also the concept of instance variables, where a class variable is bound to a class instance. For these reasons, in object-oriented programming, this distinction is usually reserved for the data accessible outside of the class, and for the data independent of class instances.
Reentrancy is distinct from, but closely related to, thread-safety. A function can be thread-safe and still not reentrant. For example, a function could be wrapped all around with a mutex, but, if that function were used in an interrupt service routine, it could starve waiting for the first execution to release the mutex. The key for avoiding confusion is that reentrant refers to only one thread executing. It is a concept from the time when no multitasking operating systems existed.

Rules for reentrancy

;Reentrant code may not hold any static or global non-constant data.
;Reentrant code may not modify itself.
;Reentrant code may not call non-reentrant computer programs or routines.
Reentrancy of a subroutine that operates on operating-system resources or non-local data depends on the atomicity of the respective operations. For example, if the subroutine modifies a 64-bit global variable on a 32-bit machine, the operation may be split into two 32-bit operations, and thus, if the subroutine is interrupted while executing, and called again from the interrupt handler, the global variable may be in a state where only 32 bits have been updated. The programming language might provide atomicity guarantees for interruption caused by an internal action such as a jump or call. Then the function in an expression like + ), where the order of evaluation of the subexpressions might be arbitrary in a programming language, would see the global variable either set to 1 or to its previous value, but not in an intermediate state where only part has been updated. The operating system might provide atomicity guarantees for signals, such as a system call interrupted by a signal not having a partial effect. The processor hardware might provide atomicity guarantees for interrupts, such as interrupted processor instructions not having partial effects.

Examples

To illustrate reentrancy, this article uses as an example a C utility function,, that takes two pointers and transposes their values, and an interrupt-handling routine that also calls the swap function.

Neither reentrant nor thread-safe

This is an example swap function that fails to be reentrant or thread-safe. Since the tmp variable is globally shared, without serialization, among any concurrent instances of the function, one instance may interfere with the data relied upon by another. As such, it should not have been used in the interrupt service routine isr:

int tmp;
void swap
void isr

Thread-safe but not reentrant

The function in the preceding example can be made thread-safe by making thread-local. It still fails to be reentrant, and this will continue to cause problems if is called in the same context as a thread already executing :

_Thread_local int tmp;
void swap
void isr

Reentrant but not thread-safe

The following modification of the swap function, which is careful to leave the global data in a consistent state at the time it exits, is reentrant; however, it is not thread-safe, since there are no locks employed, it can be interrupted at any time:

int tmp;
void swap
void isr

Reentrant and thread-safe

An implementation of that allocates on the stack instead of globally and that is called only with unshared variables as parameters is both thread-safe and reentrant. Thread-safe because the stack is local to a thread and a function acting just on local data will always produce the expected result. There is no access to shared data therefore no data race.

void swap
void isr

Reentrant interrupt handler

A reentrant interrupt handler is an interrupt handler that re-enables interrupts early in the interrupt handler. This may reduce interrupt latency. In general, while programming interrupt service routines, it is recommended to re-enable interrupts as soon as possible in the interrupt handler. This practice helps to avoid losing interrupts.

Further examples

In the following code, neither f nor g functions are reentrant.

int v = 1;
int f
int g

In the above, depends on a non-constant global variable ; thus, if is interrupted during execution by an ISR which modifies, then reentry into will return the wrong value of. The value of and, therefore, the return value of, cannot be predicted with confidence: they will vary depending on whether an interrupt modified during 's execution. Hence, is not reentrant. Neither is, because it calls, which is not reentrant.
These slightly altered versions are reentrant:

int f
int g

In the following, the function is thread-safe, but not reentrant:

int function

In the above, can be called by different threads without any problem. But, if the function is used in a reentrant interrupt handler and a second interrupt arises inside the function, the second routine will hang forever. As interrupt servicing can disable other interrupts, the whole system could suffer.

Works cited

*