Skip to content

Python Interrupt API

Stopping a run. doppler.interrupt is the one flag every blocking wait in doppler consults, and the scoped guard that arms it.

The flag is process-wide, so Interrupt is a handle to a facility rather than an instance of one: two guards observe the same flag. What a guard scopes is the arming — which signals it installed and the latency it overrode — so both can be undone exactly, by the code that did them.

import numpy as np
from doppler.interrupt import Interrupt

it = Interrupt(np.array([], dtype=np.int32))   # a handle, arms nothing
it.interrupt()
assert it.interrupted()
it.resume()

Construction is what arms, and it clears the flag as it does — a stale one would refuse the first wait inside the very block that just armed:

import signal

import numpy as np

from doppler.interrupt import Interrupt

with Interrupt(np.array([signal.SIGINT], dtype=np.int32)) as it:
    while not it.interrupted():
        ...  # a receive, a ring wait, a generate loop
        # Ctrl+C is what ends this loop in a real run. The request is made
        # in-process here because the guard CHAINS to the interpreter's own
        # SIGINT handler, so a genuine signal would raise KeyboardInterrupt
        # out of this page rather than let it finish.
        it.interrupt()
    assert it.interrupted()

# The flag is process-wide and outlives the guard, so a page that sets it
# puts it back. Inside your own program that is exactly what you do NOT
# want -- see below.
Interrupt(np.array([], dtype=np.int32)).resume()

Leaving the block restores every handler the guard displaced. It does not clear the flag: a caller that was interrupted still needs to see that it was, after the block that noticed has exited.

See Ending a wait for the contract this serves on all three transports, and why the primitive is an object rather than the free functions it used to be.

Interrupt

Clear the flag, optionally install handlers, and remember what to undo.

Parameters:

Name Type Description Default
signals NDArray[int32]

Signals to install on; empty arms nothing and the guard is only a handle to the flag.

required
latency_ms int

Wait-slice override; 0 leaves the process setting alone, and only a non-zero value is restored.

0

Raises:

Type Description
OSError

If construction fails. The exception message is cannot install a handler for one of the signals requested.

Examples:

>>> from doppler.interrupt import Interrupt
>>> it = Interrupt([])
>>> it.interrupted()
0

interrupt

interrupt() -> None

Ask every blocking wait in this process to stop.

The object's face onto dp_interrupt(). It takes a guard because that is how a method is called, not because the request is scoped to one -- the flag is process-wide, and a request through any guard is seen by every waiter.

Examples:

>>> from doppler.interrupt import Interrupt
>>> it = Interrupt([])
>>> it.interrupt()
>>> it.interrupted()
1

interrupted

interrupted() -> int

Non-zero once a stop has been requested.

Returns:

Type Description
int

Non-zero if interrupted.

Examples:

>>> from doppler.interrupt import Interrupt
>>> import numpy as np
>>> it = Interrupt(np.array([], dtype=np.int32))
>>> it.interrupted()
0
>>> it.interrupt()
>>> it.interrupted()
1

resume

resume() -> None

Clear the flag so waits proceed again.

Examples:

>>> from doppler.interrupt import Interrupt
>>> it = Interrupt([])
>>> it.interrupt()
>>> it.resume()
>>> it.interrupted()
0

latency_ms

latency_ms() -> int

The wait slice every blocking wait in this process uses.

The readback for the constructor's latency_ms, and it reads the PROCESS setting rather than what this guard asked for -- those differ when the guard passed 0, which means "leave it alone". A value a caller can set and not read back is a value they cannot reason about.

Returns:

Type Description
int

Milliseconds.

Examples:

>>> import numpy as np
>>> from doppler.interrupt import Interrupt
>>> it = Interrupt(np.array([], dtype=np.int32), latency_ms=25)
>>> it.latency_ms()
25

destroy

destroy() -> None

Release the underlying C resources immediately.

Ordinarily unnecessary: the resources are freed when the object is garbage-collected. Call this to release them at a definite point instead, or use the object as a context manager, which calls it on exit.

Idempotent: calling it again on an already-released object does nothing. Every other method raises RuntimeError once it has run.

__enter__

__enter__() -> Interrupt

Enter a context manager, returning this object.

Lets a Interrupt be used in a with statement so its C resources are released deterministically on exit rather than at collection time.

Returns:

Type Description
Interrupt

This same object, not a copy.

__exit__

__exit__(
    exc_type: object | None = ...,
    exc: object | None = ...,
    tb: object | None = ...,
) -> None

Exit a context manager, releasing the Interrupt.

Equivalent to calling destroy(). Returns None, so an exception raised inside the with body propagates normally; this never suppresses one.

Parameters:

Name Type Description Default
exc_type object | None

Exception class, or None. Ignored.

...
exc object | None

Exception instance, or None. Ignored.

...
tb object | None

Traceback object, or None. Ignored.

...

DesignEnding a capture — spooling an endless stream to disk while reading it back, Ending a wait — one contract for network, memory and disk