5.9 Atomic Execution

► |  | The Racket Foreign Interface

1 | Overview
2 | Loading Foreign Libraries
3 | C Types
4 | Pointer Functions
5 | Derived Utilities
6 | Miscellaneous Support
7 | Unexported Primitive Functions
| Bibliography
| Index

▼ | 5 | Derived Utilities

5.1 | Safe Homogenous Vectors
5.2 | Safe C Vectors
5.3 | Tagged C Pointer Types
5.4 | Serializable C Struct Types
5.5 | Static Callout and Callback Cores
5.6 | Defining Bindings
5.7 | Allocation and Finalization
5.8 | Custodian Shutdown Registration
5.9 | Atomic Execution
5.10 | Speculatively Atomic Execution
5.11 | Thread Scheduling
5.12 | Ports
5.13 | Process-Wide and Place-Wide Registration
5.14 | Operating System Threads
5.15 | Garbage Collection Callbacks
5.16 | Objective-C FFI
5.17 | Cocoa Foundation
5.18 | COM (Common Object Model)
5.19 | File Security-Guard Checks
5.20 | Windows API Helpers
5.21 | Virtual Machine Primitives
5.22 | Declaring Foreign Libraries Needed at Run Time

On this page:

start-atomic
end-atomic
start-breakable-atomic
end-breakable-atomic
call-as-atomic
call-as-nonatomic
in-atomic-mode?
start-uninterruptible
end-uninterruptible
call-as-uninterruptible
make-uninterruptible-lock
uninterruptible-lock-acquire
uninterruptible-lock-release

Racket
top contents ← prev up next →

### 5.9 Atomic Execution🔗ℹ

(require ffi/unsafe/atomic) | package: base

Atomic mode evaluates a Racket expression without switching among Racket threads and with limited support for events. An atomic computation in this sense is not atomic with respect to other places, futures, or parallel threads, but only to coroutine threads within a place. Asynchronous break exceptions (in the sense of break-thread) are also disabled in atomic mode except as started by start-breakable-atomic.

Atomic mode is unsafe, because the Racket scheduler is not able to operate while execution is in atomic mode: (1) the scheduler cannot switch coroutine threads or poll certain kinds of events, which can lead to deadlock or starvation of other threads; and (2) calling a scheduler-related function in atomic mode has unspecified behavior, where misuse is not necessarily caught with a check. Functions that are directly scheduler-related include thread, sleep, semaphore-wait, semaphore-post, channel-put, channel-get, and sync. Beware that other operations can involve such synchronization, such as writing to an output port or using an equal?-based hash table. Even if an output target is known to be free of synchronization, beware that values can have arbitrary printing procedures attached through prop:custom-write. Successful use of atomic mode requires a detailed knowledge of any implementation that might be reached during atomic mode to ensure that it terminates and does not involve synchronization.

Uninterruptible mode is related to atomic mode. It is also unsafe and practically the same as atomic mode in a coroutine thread, but uninterruptible mode does not force a parallel thread to synchronize with all coroutine threads. Uninterruptible mode also allows the use of uncontested semaphores and equal?-based hash tables.

> procedure (start-atomic) → void?
> procedure (end-atomic) → void?

Starts and ends atomic mode by disabling/re-enabling concurrency with any Racket coroutine threads in the same place, and also suspends/resumes delivery of break exceptions (independent of the result of break-enabled). Calls to start-atomic and end-atomic can be nested.

Note that pairing start-atomic and end-atomic with dynamic-wind is useful only when

- the current exception handler is known to safely escape atomic mode, or else all possible escapes are through known continuation jumps or aborts (because breaks are disabled and no other exceptions are possible) that escape safely; and

- exception constructions, if any, avoid printing values in the exception message, or else the error value conversion handler is always used and known to be safe for atomic mode.

Using call-as-atomic is somewhat safer than using start-atomic and end-atomic, because call-as-atomic catches exceptions and re-raises them after exiting atomic mode, and it wraps any call to the error value conversion handler with call-as-nonatomic. The latter is safe for a particular atomic region, however, only if the region can be safely interrupted by a non-atomic exception construction. See also call-as-nonatomic-retry-point.

Unlike call-as-atomic, start-atomic and end-atomic can be called from any OS thread as supported by ffi/unsafe/os-thread, although the calls have no effect outside of Racket threads. Using start-atomic in a future that is not running in a Racket thread blocks the future until it is resumed by touch in a Racket thread. Using start-atomic in a parallel thread synchronizes with all coroutine threads in the same place, but not other parallel threads or futures.

See also the caveat that atomic mode is unsafe.

> procedure (start-breakable-atomic) → void?
> procedure (end-breakable-atomic) → void?

Like start-atomic and end-atomic, but the delivery of break exceptions is not suspended.

These functions are not significantly faster than start-atomic and end-atomic, so they provide no benefit in a context where breaks are disabled.

> procedure (call-as-atomic thunk) → any
> thunk : (-> any)

Calls thunk in atomic mode, where call-as-nonatomic can be used during the dynamic extent of the call to revert to non-atomic mode for a nested computation.

When call-as-atomic is used in the dynamic extent of call-as-atomic, then thunk is called directly as a non-tail call.

If thunk raises an exception, the exception is caught and re-raised after exiting atomic mode. Any call to the current error value conversion handler is effectively wrapped with call-as-nonatomic.

See also the caveat that atomic mode is unsafe.

> procedure (call-as-nonatomic thunk) → any
> thunk : (-> any)

Within the dynamic extent of a call to call-as-atomic, calls thunk in non-atomic mode. Beware that the current thread may be suspended or terminated by other threads during the execution of thunk.

When used not in the dynamic extent of a call to call-as-atomic, call-as-nonatomic raises exn:fail:contract.

> procedure (in-atomic-mode?) → boolean?

Returns #t when in atomic mode or uninterruptible mode (within the current place), #f otherwise.

> procedure (start-uninterruptible) → void?
> procedure (end-uninterruptible) → void?

Like start-atomic and end-atomic, but the continuation after start-uninterruptible and before end-uninterruptible may run concurrently with other Racket threads (both coroutine threads and parallel threads), but in uninterruptible mode: the continuation will reach end-uninterruptible without interruption from other threads. Uninterruptable mode is unsafe, just like atomic mode is unsafe.

Unlike start-atomic, start-uninterruptible in the CS implementation does not block a future that is running concurrently with Racket threads, and it does not cause a parallel thread to synchronize with coroutine threads. At the same time, such a future or parallel thread must not perform any action that blocks the future or requires synchronization with coroutine threads; successful use of uninterruptible mode in a future or parallel thread thus requires knowledge of Racket’s internals.

As a special case, an equal?-based mutable hash table (created by make-hash, for example) or a semaphore (created by make-semaphore) can be used an in uninterruptible mode if it is used only in uninterruptible mode, never concurrently; in the case of a semaphore, the semaphore’s internal counter must be non-zero when waiting on the semaphore. Direct use of a semaphore in uninterrupted mode would not make sense, but the special case allows uninterrupted mode to use data structures that normally rely on semaphores for locking, as long as they are used consistently that way. See also make-uninterruptible-lock.

Calls to start-uninterruptible and end-uninterruptible can be nested, and they can be mutually nested with calls to start-atomic and end-atomic in a coroutine thread. Since start-atomic is blocking for futures and requires synchronization in a parallel thread, it cannot be used in uninterruptible mode in a future or parallel thread.

Added in version 8.17.0.7 of package base.
Changed in version 8.18.0.2: Constrain the use of uninterruptible mode in futures and parallel threads.

> procedure (call-as-uninterruptible thunk) → any
> thunk : (-> any)

Similar to call-as-atomic, but for uninterruptible mode.

Added in version 8.17.0.7 of package base.

> procedure (make-uninterruptible-lock) → any/c
> procedure (uninterruptible-lock-acquire lock) → void?
> lock : any/c
> procedure (uninterruptible-lock-release lock) → void?
> lock : any/c

An uninterruptible lock provides low-level synchronization that cooperates with uninterruptible mode across parallel threads and futures. In particular, since a hash table or semaphore can be used in uninterruptible mode but non-concurrently, an uninterruptible lock can be used to guard access to the hash table or semaphore.

An uninterruptible lock does not cooperate with coroutine thread scheduling. The uninterruptible-lock-acquire function enters uninterruptible mode immediately, before taking the lock or even waiting for it, and uninterruptible-lock-release exits uninterruptible mode only after releasing the lock. An uninterruptible lock is therefore useful only to guard predictably short actions that are reasonably considered atomic from the perspective of Racket threads.

Added in version 8.18.0.5 of package base.

top contents ← prev up next →