FALSEafter PL_handle_signals() returned -1, indicating an exception was raised.
SIGUSR2to the signalled thread while the handler is an empty function. This causes most blocking system calls to return with
EINTR. See also the commandline option --sig-alert. On Windows, PL_handle_signals() is called when the user processes Windows messages.
If one or more signals are queued, the queue is processed. Processing the queue skips signals blocked due to sig_block/1 and stops after the queue does not contain any more non-blocked signals or processing a signal results in an exception. After an exception, other signals remain in the queue and will be processed after unwinding to the matching catch/3. Typically these queued signals will be processed during the Recover goal of the catch/3. Note that sig_atomic/1 may be used to protect the recovery goal.
mechanism is primarily used by the system to insert debugging goals into
the target thread (tspy/1, tbacktrace/1,
etc.) or to interrupt a thread using e.g.,
abort). Predicates from library
signals to stop workers for e.g. concurrent_maplist/2
if some call fails. Applications may use it, typically for similar
purposes such as asynchronously stopping tasks or inspecting the status
of a task. Below we describe the behaviour of thread signalling in more
detail. The following notes apply for
Goal executing in ThreadId
- The execution is protected by sig_atomic/1 and thus signal execution is not nested.
- If Goal succeeds, possible choice points are discarded. Changes to the Prolog stacks such as changes to backtrackable global variables remain.
- If Goal fails, no action is taken, i.e., failure is not considered a special condition.
- If Goal raises an exception the exeception is propagated into the environment. This allows for forcefully stopping the target thread. The system uses this to implement abort/0 and call_with_time_limit/2.
- Code into which signals may be injected must make sure to use setup_call_cleanup/3 and friends to ensure proper cleanup in the case of an exception. This is good practice anyway to guard against unpredicatable exceptions such as resource exhaustion.
- Goal may use stack inspection such as prolog_frame_attribute/3 to determine what the thread is doing.
- The goal injected using thread_signal/2, i.e., signals do not interrupt a running signal handler.
- The Setup call of setup_call_cleanup/3 and friends.
- The Cleanup call of call_cleanup/2 and friends.
- Compiling a file or loading a quick load file.
The call port of sig_atomic/1 does not handle signals. This may notably be used to prevent interruption of the catch/3 Recover goal. For example, we may ensure the recovery goal of a timeout is called using the code below. Without this precaution another signal may run before writeln/1 and raise an exception to prevent its execution. Note that catch/3 should generally not be used for cleanup of resources in case of an exception and thus it is typically fine if its Recover goal is interrupted. Use setup_call_cleanup/3 or one of the other predicates from the call_cleanup/2 family for cleanup.
..., catch(call_with_time_limit(Time, Goal), time_limit_exceeded, sig_atomic(writeln('Time limit exceeded'))).
Besides queues (section 10.3.1) threads can share and exchange data using dynamic predicates. The multithreaded version knows about two types of dynamic predicates. By default, a predicate declared dynamic (see dynamic/1) is shared by all threads. Each thread may assert, retract and run the dynamic predicate. Synchronisation inside Prolog guarantees the consistency of the predicate. Updates are logical: visible clauses are not affected by assert/retract after a query started on the predicate. In many cases primitives from section 10.4 should be used to ensure that application invariants on the predicate are maintained.
Besides shared predicates, dynamic predicates can be declared with the thread_local/1 directive. Such predicates share their attributes, but the clause list is different in each thread.
- thread_local +Functor/+Arity, ...
- This directive is related to the dynamic/1
directive. It tells the system that the predicate may be modified using assert/1, retract/1,
etc., during execution of the program. Unlike normal shared dynamic
data, however, each thread has its own clause list for the predicate. As
a thread starts, this clause list is empty. If there are still clauses
when the thread terminates, these are automatically reclaimed by the
system (see also volatile/1).
The thread_local property implies the properties dynamic and volatile.
Thread-local dynamic predicates are intended for maintaining thread-specific state or intermediate results of a computation.
It is not recommended to put clauses for a thread-local predicate into a file, as in the example below, because the clause is only visible from the thread that loaded the source file. All other threads start with an empty clause list.
:- thread_local foo/1. foo(gnat).
DISCLAIMER Whether or not this declaration is appropriate in the sense of the proper mechanism to reach the goal is still debated. If you have strong feelings in favour or against, please share them in the SWI-Prolog mailing list.