Source code for rapidtools.core.cancellation

# Copyright (c) 2025 The University of Washington
#
# This file is part of rapidtools.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# You should have received a copy of the BSD 3-Clause License along with
# rapidtools. If not, see <http://www.opensource.org/licenses/>.
#
# Contributors:
# Barbaros Cetiner
#
# Last updated:
# 09-22-2026

"""
Cooperative cancellation for long-running rapidtools components.

Pipeline components accept an optional ``cancel_event`` (a
``threading.Event``). They call :func:`raise_if_cancelled` between units of
work (tiles, batches, assets) so that a caller running them on a background
thread can stop them promptly by setting the event.

Example:
    >>> import threading
    >>> from rapidtools import Gemma4AssetAnalyzer
    >>> stop = threading.Event()
    >>> analyzer = Gemma4AssetAnalyzer(prompt='...', cancel_event=stop)
    >>> # from another thread: stop.set()  -> analyzer raises OperationCancelled
"""

from __future__ import annotations

import threading


[docs] class OperationCancelled(RuntimeError): """ Raised by a component when its ``cancel_event`` has been set. The exception derives from :class:`RuntimeError` so that callers who do not care about cancellation specifically can still catch it with a generic handler, while callers that do can distinguish a user-requested stop from a genuine failure. Example: >>> import threading >>> from rapidtools.core import OperationCancelled, raise_if_cancelled >>> >>> stop = threading.Event() >>> stop.set() >>> try: ... raise_if_cancelled(stop, context='tiling') ... except OperationCancelled as exc: ... print(exc) Operation cancelled during tiling. """
[docs] def raise_if_cancelled(cancel_event: threading.Event | None, context: str = '') -> None: """ Raise :class:`OperationCancelled` if ``cancel_event`` has been set. Components call this between units of work so that a caller running them on a background thread can stop them promptly. Passing ``None`` disables cancellation entirely, which keeps the call sites free of ``if`` guards. Args: cancel_event (threading.Event | None): The event to inspect. ``None`` means cancellation is not enabled. context (str, optional): A short description of the work in progress (e.g. ``'tile 3/10'``) that is appended to the exception message. Defaults to ``''``. Raises: OperationCancelled: If ``cancel_event`` is not ``None`` and is set. Example: >>> import threading >>> from rapidtools.core import raise_if_cancelled >>> >>> stop = threading.Event() >>> raise_if_cancelled(stop) # not set: returns silently >>> raise_if_cancelled(None) # cancellation disabled: returns silently >>> stop.set() >>> raise_if_cancelled(stop, context='inference') Traceback (most recent call last): ... OperationCancelled: Operation cancelled during inference. """ if cancel_event is not None and cancel_event.is_set(): suffix = f' during {context}' if context else '' raise OperationCancelled(f'Operation cancelled{suffix}.')
[docs] def is_cancelled(cancel_event: threading.Event | None) -> bool: """ Return ``True`` if ``cancel_event`` exists and has been set. This is the non-raising counterpart of :func:`raise_if_cancelled`, useful in loops that want to exit gracefully and keep partial results. Args: cancel_event (threading.Event | None): The event to inspect. ``None`` means cancellation is not enabled. Returns: bool: ``True`` only when an event was supplied and it is set. Example: >>> import threading >>> from rapidtools.core import is_cancelled >>> >>> stop = threading.Event() >>> is_cancelled(None), is_cancelled(stop) (False, False) >>> stop.set() >>> is_cancelled(stop) True """ return cancel_event is not None and cancel_event.is_set()