NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #2228 most downloaded on PyPI
GIL-powered* locking library for Python
Last release 3 months ago
27 Jun 2026
Ships unpredictably
gaps range from 8 days to 9 months
Nearly every release is documented
notes for 7 of 7 stable releases
15 versions withdrawn
withdrawn after publishing
2 years old
22 releases · first in 2024
One column per month.
aiologic.Lock (and aiologic.RLock ) was unable to detect mixed deadlocks (for example, when a lock has already been acquired by an asynchronous task a
aiologic.Lock (and aiologic.RLock) was unable to detect mixed deadlocks (for example, when a lock has already been acquired by an asynchronous task and there is an attempt to acquire it synchronously), resulting in hangs. It now raises a RuntimeError for simple mixed deadlocks (attempting to acquire a lock as a thread when it has already been acquired in the same thread by any green/async task), which, although only a subset of all possible cases, covers the most common scenarios. Related: #38.aiologic.lowlevel.async_seconds_per_sleep() (and aiologic.lowlevel.async_seconds_per_timeout()) did not handle non-standard asyncio event loops, which could lead to overflows on Pyodide (see pyodide/pyodide#6304; however, note that this does not apply to aiologic.lowlevel.async_sleep_forever()) and non-pure Python event loops. It now handles standard event loops, uvloop, winloop, pyodide.webloop.WebLoop, and others.aiologic.meta.export_dynamic() and aiologic.meta.export_deprecated() raised a RuntimeError when registering already registered symbolic links, which p…
with_() methods to all awaitable primitives (for waiting with given parameters) and the timeout parameter to all blocking asynchronous functions (semantically equivalent to that of blocking green functions). This unifies the interface and provides safe handling of large timeouts (which is particularly relevant for Curio) and zero timeouts (which is particularly relevant for Trio) across all supported libraries; in this sense, it is a logical continuation of the extensive work on timeouts carried out in version 0.15.0. The main reason for this change is the observation that the lack of timeouts as parameters for asynchronous operations leads to boilerplate code with asyncio.wait_for() in some dependent repositories, which is clearly undesirable.aiologic.meta.replaces_when_imported() as a safer alternative to wrapt.when_imported() + aiologic.meta.replaces().aiologic.meta.import_original() as an aiologic.meta.import_from()-like unified alternative to eventlet.patcher.original() and gevent.monkey.get_original().aiologic.meta.isgreenpatched() as a unified alternative to eventlet.patcher.is_monkey_patched() and gevent.monkey.is_module_patched().aiologic.meta.lookup_static() and aiologic.meta.resolve_special() to look for a name via the MRO without triggering user code. They are similar to inspect.getattr_static(), but never search via the MRO of the passed object's type (to avoid confusion between, for example, EnumType.__call__() and user-defined __call__() in an Enum subclass) and have a simpler (more efficient) implementation. Unlike aiologic.meta.lookup_static(), aiologic.meta.resolve_special() resolves descriptors using the passed arguments.aiologic.meta.isdatadescriptor_static() and aiologic.meta.ismethoddescriptor_static() to check whether an object is a descriptor of the specified type. aiologic.meta.isdatadescriptor_static() uses the same algorithm as aiologic.meta.lookup_static(), so it returns True only for objects that actually work as data descriptors (not prone to false positives, unlike inspect.isdatadescriptor()). Note that unlike inspect.ismethoddescriptor(), aiologic.meta.ismethoddescriptor_static() follows the PEP 590 definition, where method descriptors associate the __get__() method with the __call__() method (a special case of such descriptors are functions).aiologic.meta.ismetaclass_static() and aiologic.meta.isclass_static() to check whether an object is a metaclass (subclass of type) and a class (instance of type), respectively. Unlike inspect.isclass(), they behave in the same way as the functions below.aiologic.meta.issubclass_static() and aiologic.meta.isinstance_static() as analogues to built-in functions but without triggering user code. Unlike the latter, they also ignore pseudo-classes (arbitrary objects providing the __bases__ attribute), virtual subclasses, and the __class__ attribute (which can be replaced by object proxies).aiologic.meta.isgeneratorlike(), aiologic.meta.iscoroutinelike(), and aiologic.meta.isasyncgenlike() as inspect-like functions that check whether an object implements a certain interface (corresponding to the function name).aiologic.meta.isgeneratorfactory(), aiologic.meta.iscoroutinefactory(), and aiologic.meta.isasyncgenfactory() as functions that differ from inspect.is*function() in that they have broader semantics and more flexible checks.aiologic.meta.markgeneratorfactory(), aiologic.meta.markcoroutinefactory(), and aiologic.meta.markasyncgenfactory() as a way to fine-tune the above (and some standard) functions regardless of the Python version.aiologic.meta.generator() and aiologic.meta.coroutine() functions for transforming generator/coroutine factories into the corresponding object functions. Unlike types.coroutine(), they always return a new function, and it is always a native object function (unless they have been cythonized). Mainly added to define the __await__() method via an asynchronous function (and subsequent transformation into a generator function) with no overhead when calling, with annotation updates (for sphinx.ext.autodoc).aiologic.meta.GeneratorCoroutineWrapper as a way to create universal objects that are similar to both types.GeneratorType and types.CoroutineType. Useful when you need to use a coroutine in generator functions or a generator in coroutine functions, and for some reason you cannot transform the function itself.aiologic.meta.getsro() as a way to analyze the signature resolution order, that is, the chain of (probably) callable objects that have a direct impact on the signature of the passed callable object. Inspired by the inspect.signature() implementation.inspect.iscoroutinefunction() now rely on aiologic.meta.iscoroutinefactory(). This makes detecting asynchronous functions (coroutine factories) more accurate and also allows them to be marked on any version of Python.aiologic.meta.SingletonEnum constructor now reverts to the original behavior if there are zero or more than one members, or if additional parameters are passed, which should make it safer.aiologic.meta.SingletonEnum instances now allow setting attributes for any properties (descriptors) that have a setter. Previously, only slot descriptors (member descriptors) were supported, which prevented any user-defined setter from being called.aiologic.meta.replaces() now raises a LookupError with an informative message instead of a KeyError if there is no function of the same name in the namespace, which should make debugging easier.aiologic.meta.replaces() now preserves the original function type in annotations, while requiring the __name__ attribute to be present in the passed function, which should make it more convenient and safer to use for custom callable objects.aiologic.meta.copies() now performs shallow copying of mutable objects such as __kwdefaults__ and __annotations__ to avoid propagating changes to the copy's signature when the original function's signature changes.aiologic.meta.copies() now forces copying when both functions are the same object, which increases its scope of application.aiologic.meta.import_from() now uses typing.Any as the return type instead of object to avoid using typing.cast() for dynamic imports in __init__ modules.copy() methods (see below).assert. Only direct use of them by the user was affected (low-level events already prevent multiple wake() calls in general, so no high-level primitives were affected). See fleming79/async-kernel#493 (comment) for details.sphinx.ext.autodoc-specific paths did not have the expected behavior due to a peculiarity of module reloading. Now they also check environment variables.aiologic.meta.export() includes all public names in __all__, __future__.annotations and typing.TYPE_CHECKING were also included (as 'annotations' and 'TYPE_CHECKING', respectively) if they were imported in __init__.py.aiologic.meta.export_dynamic() and aiologic.meta.export_deprecated() raised a RuntimeError when registering already registered symbolic links, which prevented the module from being reloaded. Now they allow re-registration for the same values.aiologic.meta.export_dynamic() as an alternative to aiologic.meta.export_deprecated() that does not raise a DeprecationWarning . Useful for optional f…
aiologic.__version__ and aiologic.__version_tuple__ as a way to retrieve the package version at runtime.aiologic.meta.SingletonEnum as a base class that encapsulates common logic of all type-checker-friendly singleton classes (such as aiologic.meta.DefaultType and aiologic.meta.MissingType).aiologic.meta.resolve_name() as an alternative to importlib.util.resolve_name() with more consistent behavior.aiologic.meta.import_module() as an alternative to importlib.import_module() with more consistent behavior.aiologic.meta.import_from() as a way to import attributes from modules. It differs from more naive solutions in that it raises an ImportError instead of an AttributeError, while not allowing names like *, and also in that it attempts to import submodules to achieve the expected behavior.aiologic.meta.export_dynamic() as an alternative to aiologic.meta.export_deprecated() that does not raise a DeprecationWarning. Useful for optional features that may be missing at runtime but should be available at the package level.aiologic.meta.export() and aiologic.meta.export_deprecated() now support passing module objects, which should simplify their use for manually created module objects.aiologic.meta now support runtime introspection via the __final__ attribute on all supported versions of Python.aiologic.meta.export() has been redesigned:
__module__ attribute, but also name-related attributes. This provides the expected representation in cases where functions are created dynamically in a different context with a different name.__module__ attribute).__all__, which gives the expected behavior of from package import * (in particular, public subpackages are now excluded) and also simplifies analysis.aiologic.meta.export_deprecated() now does additional checks before registering a link, and also relies on the new importlib-like functions, which should make it safer to use.aiologic.meta.import_from()-like behavior, making them more predictable (relevant for a green-patched world).aiologic.Queue and its derivatives now raise a ValueError when attempting to pass a maxsize less than 1.ValueError for values less than zero.0.15.0).aiologic.meta.replaces() performed replacement by the name of the replaced function, which could lead to replacing an arbitrary object in the case of a different __name__ attribute value.aiologic.meta.replaces() did not handle cases of parallel application to the same function, which could lead to an AttributeError being raised in such cases.aiologic.meta.copies() used a function copying technique that was incompatible with Nuitka, resulting in a RuntimeError when attempting to call the copied function after compilation.aiologic.meta.copies() used the default keyword argument values of the replaced function, which could lead to unexpected behavior when the default values were different.aiologic.meta.export_deprecated() to export deprecated content via custom __getattr__() .
aiologic.synchronized() as an async-aware alternative to wrapt.synchronized(). Related: GrahamDumpleton/wrapt#236.aiologic.SimpleLifoQueue as a simplified LIFO queue, i.e. a lightweight alternative to aiologic.LifoQueue without maxsize support.aiologic.BinarySemaphore and aiologic.BoundedBinarySemaphore as binary semaphores, i.e. semaphores restricted to the values 0 and 1 and using a more efficient implementation.aiologic.RBarrier as a reusable barrier, i.e. a barrier that can be reset to its initial state (async-aware alternative to threading.Barrier).aiologic.lowlevel.ThreadLock class (for typing purposes) and aiologic.lowlevel.create_thread_lock() factory function as a new way to obtain unpatched threading.Lock.aiologic.lowlevel.ThreadRLock class (for typing purposes) and aiologic.lowlevel.create_thread_rlock() factory function as a unique way to obtain unpatched threading.RLock. Solves the problem of using reentrant thread-level locks in the gevent-patched world (due to the fact that threading._PyRLock.__globals__ referenced the patched namespace, which made it impossible to use the original object because it used the patched threading.Lock). Note: like threading._PyRLock, the fallback pure Python implementation is not signal-safe.aiologic.lowlevel.ThreadOnceLock class (for typing purposes) and aiologic.lowlevel.create_thread_oncelock() factory function as a way to obtain a one-time reentrant lock. The interface mimics that of aiologic.lowlevel.ThreadRLock, but the semantics are different: the first successful release() call, which sets the internal counter to zero, wakes up all threads at once (just like aiologic.Event), and all further acquire() calls become no-ops, which effectively turns the lock into a dummy primitive. And unlike aiologic.lowlevel.ThreadRLock, this primitive is signal-safe, which, combined with the described semantics, makes the primitive suitable for protecting initialization sections.aiologic.lowlevel.ThreadDummyLock class (for typing purposes) and aiologic.lowlevel.THREAD_DUMMY_LOCK singleton object as a way to obtain a dummy lock.aiologic.lowlevel.once decorator to ensure that a function is executed only once (inspired by std::sync::Once from Rust). It uses aiologic.lowlevel.ThreadOnceLock under the hood and stores the result in the wrapper's closure, which makes the function both thread-safe and signal-safe when reentrant=True is passed (note: this does not apply to side effects!).aiologic.lowlevel.lazydeque as a thread-safe/signal-safe wrapper for collections.deque with lazy initialization. It solves the problem of deques' high memory usage: one empty instance of collections.deque takes up 760 bytes on Python 3.11+ (for comparison, one empty list takes up only 56 bytes!). In contrast, one empty instance of aiologic.lowlevel.lazydeque takes up 128 bytes in total, and after initialization (first addition) takes up 832 bytes on Python 3.11+. Free-threading adds an additional 16 bytes in all cases (due to the internal use of aiologic.lowlevel.ThreadOnceLock).aiologic.lowlevel.lazyqueue as a thread-safe/signal-safe wrapper for _queue.SimpleQueue (when available) or collections.deque with lazy initialization. It provides a non-blocking queue and differs from aiologic.lowlevel.lazydeque in that it is more memory efficient at the cost of less functionality. Instead of 128 and 832 bytes, it takes up 120 and 200 bytes on Python 3.13+.aiologic.lowlevel.create_green_waiter() and aiologic.lowlevel.create_async_waiter() as functions to create waiters, i.e. new low-level primitives that encapsulate library-specific wait-wake logic. Unlike low-level events, they have no state, and thus have less efficiency for multiple notifications (in particular, they schedule calls regardless of whether the wait has been completed or not). And, of course, for the same reason, they are even less safe (they require more specific conditions for their correct operation).aiologic.lowlevel.enable_signal_safety() and aiologic.lowlevel.disable_signal_safety() universal decorators to enable and disable signal-safety in the current thread's context. They support awaitable objects, coroutine functions, and green functions, and can be used directly as context managers.aiologic.lowlevel.signal_safety_enabled() to determine if signal-safety is enabled.aiologic.lowlevel.create_green_event() and aiologic.lowlevel.create_async_event() as a new way to create low-level events.aiologic.lowlevel.enable_checkpoints() and aiologic.lowlevel.disable_checkpoints() universal decorators to enable and disable checkpoints in the current thread's context. They support awaitable objects, coroutine functions, and green functions, and can be used directly as context managers.aiologic.lowlevel.green_checkpoint_enabled() and aiologic.lowlevel.async_checkpoint_enabled() to determine if checkpoints are enabled for the current library.aiologic.lowlevel.green_checkpoint_if_cancelled() and aiologic.lowlevel.async_checkpoint_if_cancelled(). Currently, aiologic.lowlevel.async_checkpoint_if_cancelled() is equivalent to removed aiologic.lowlevel.checkpoint_if_cancelled(), and aiologic.lowlevel.green_checkpoint_if_cancelled() does nothing. However, these methods have a slightly different meaning: they are intended to accompany aiologic.lowlevel.shield() calls to pre-check for cancellation, and do not guarantee actual checking.aiologic.lowlevel.green_clock() and aiologic.lowlevel.async_clock() as a way to get the current time according to the current library's internal monotonic clock (useful for sleep-until functions).aiologic.lowlevel.green_sleep() and aiologic.lowlevel.async_sleep() to suspend the current task for the given number of seconds.aiologic.lowlevel.green_sleep_until() and aiologic.lowlevel.async_sleep_until() to suspend the current task until the given deadline (relative to the current library's internal monotonic clock).aiologic.lowlevel.green_sleep_forever() and aiologic.lowlevel.async_sleep_forever() to suspend the current task until an exception occurs.aiologic.lowlevel.green_seconds_per_sleep() and aiologic.lowlevel.async_seconds_per_sleep() as a way to get the number of seconds during which sleep guarantees exactly one checkpoint. If sleep exceeds this time, it will use multiple calls (to bypass library limitations).aiologic.lowlevel.green_seconds_per_timeout() and aiologic.lowlevel.async_seconds_per_timeout() that are the same as their sleep equivalents, but for timeouts (applies to low-level waiters/events, as well as all high-level primitives).copy() method to flags and queues as a way to create a shallow copy without additional imports.async_borrowed() and green_borrowed() methods to capacity limiters, green_owned() and async_owned() methods to locks. They allow to reliably check if the current task is holding the primitive (or any of its tokens) without importing additional functions.async_count() and green_count() to reentrant primitives for the same purpose, but returning how many releases need to be made before the primitive is actually released by the current task.for_() method to condition variables as an async analog of wait_for().timer property, and used to pass the deadline to the notification methods.aiologic.lowlevel.ThreadOnceLock methods by passing locking=True. This allows to synchronize related one-time operations with less memory overhead.shield=True. This allows to implement efficient finalization strategies while preserving the one-time nature of low-level events.force=True. This allows to use existing event objects instead of checkpoints to minimize possible overhead.aiologic.lowlevel.SET_EVENT and aiologic.lowlevel.CANCELLED_EVENT as variants of aiologic.lowlevel.DUMMY_EVENT for a set event and a cancelled event respectively. In fact, aiologic.lowlevel.SET_EVENT is just a copy of aiologic.lowlevel.DUMMY_EVENT, but to avoid confusion both variants will coexist (maybe temporarily, maybe not).aiologic.meta subpackage for metaprogramming purposes.aiologic.meta.MISSING as a marker for parameters that, when not passed, specify special default behavior.aiologic.meta.DEFAULT as a marker for parameters with default values.aiologic.meta.copies() to replace a function with a copy of another.aiologic.meta.replaces() to replace a function of the same name in a certain namespace.aiologic.meta.export() to export all module content on behalf of the module itself (by updating __module__).aiologic.meta.export_deprecated() to export deprecated content via custom __getattr__().aiologic.meta.await_for() to use awaitable primitives via functions that only accept asynchronous functions.AIOLOGIC_GREEN_CHECKPOINTS and AIOLOGIC_ASYNC_CHECKPOINTS environment variables.AIOLOGIC_PERFECT_FAIRNESS environment variable.aiologic implicitly provided a strong fairness guarantee, informally called "perfect fairness". The point of this guarantee is to ensure the fairness of wakeups when they are parallel in nature. This had strong effects such as resuming all threads at once (no one is sleeping) on barrier wakeups and deterministic callback scheduling order in event loops when multiple threads call release() at the same time. This behavior is now explicit, optional, and disabled by default when GIL is also disabled. The reason for this change is that the behavior was not efficiently implemented at the Python level via existing atomic operations: deque.remove() increases worst-case time complexity from linear to cubic and gives a noticeable overhead that, in particular, makes barriers significantly slower with a huge number of threads in the free-threaded mode. Nevertheless, with implementation flaws, "perfect fairness" can still be useful, so since this version aiologic provides the AIOLOGIC_PERFECT_FAIRNESS environment variable to explicitly enable or disable it.aiologic.lowlevel.ThreadOnceLock methods in the free-threaded mode.timeout=inf is equivalent to timeout=None). A side effect is that large timeouts (exceeding the maximum) increase the number of checkpoints.aiologic.lowlevel.lazydeque instead of collections.deque to reduce memory usage. This makes their memory usage a bit closer to asyncio primitives (more lightweight than some threading primitives!), and especially affects complex queues, which now consume only ~0.5 KiB instead of ~3 KiB per instance. But the cost of this is synchronization via aiologic.lowlevel.ThreadOnceLock at the first addition to the queue in free-threading.__copy__() method instead of pickle methods. This makes copying faster at the cost of additional overriding in subclasses.aiologic.Flag, with weakref support.None is used when it disables a particular feature (e.g. timeouts or maxsize). aiologic.meta.MISSING is used when it specifies a special default behavior. aiologic.meta.DEFAULT is used when an existing value that is compatible in type will be taken.aiologic.lowlevel.Event is now a protocol not only in stubs but also at runtime.flag.set() without arguments is now only allowed for aiologic.lowlevel.Flag[object]. Previously it ignored subscriptions.aiologic.meta.MissingType is now a subclass of enum.Enum, so static analysis tools now correctly recognize aiologic.meta.MISSING as a singleton instance.aiologic.lowlevel.current_green_library() and aiologic.lowlevel.current_async_library() now return Optional[str] when passing fallback=True. Previously, str was returned, which was not the expected behavior.aiologic.lowlevel.AsyncLibraryNotFoundError and aiologic.lowlevel.current_async_library_tlocal are now exactly the same as sniffio.AsyncLibraryNotFoundError and sniffio.thread_local. This allows them to be used interchangeably.sphinx.ext.autodoc. Stubs are still preserved to reduce issues with type checkers.aiologic.lowlevel.current_thread() now raises RuntimeError for threads started outside of the threading module instead of returning None. This is to prevent situations where the function is used for identification without proper handling of dummy threads, since in such cases dummy threads would share the same "identifier" — None._thread._local implementation now works not only with thread objects but also with main greenlet objects, so it now supports gevent's pool of native worker threads.aiologic.lowlevel.repeat_if_cancelled() has been replaced by aiologic.lowlevel.shield(). Unlike the pre-0.10.0 function of the same name, it is now a universal decorator, and it combines both semantics: it shields both the called function and the calling task from being cancelled. It supports awaitable objects, coroutine functions, and green functions: timeouts are suppressed, and are re-raised after the call completes.threading checkpoints now use os.sched_yield() (when available) as a way to quickly switch the GIL. This makes them cheaper, but may slightly alter their behavior in free-threading.RuntimeError in such situations, making them safer. Previously, this was not handled in any way on the aiologic side, which required special care and could lead to undefined behavior if the conditions for their use were not met.aiologic.lowlevel.GreenEvent and aiologic.lowlevel.AsyncEvent, allowing them to be used in code sections where wait methods are called.event.is_cancelled() has been renamed to event.cancelled(). This makes them more similar to asyncio futures and thus more familiar to new users.event.cancel() method has been removed to avoid redundancy. This should make it easier to work with them outside of aiologic and simplify some things, since now there is no need to call event.cancel(). Previously, green events were not cancelled when a timeout was passed.bool(event) and event.is_set(), which now return False for cancelled events.False after waiting again if they were previously cancelled. Previously True was returned, which could be considered unexpected behavior.value parameter of aiologic.CountdownEvent has been renamed to initial_value. Accordingly, a property with the same name has also been added.parties parameter now has a default value of 0. This allows barriers to be used directly as default factories.parties equal to 0, with which they ignore the waiting queue length (they only wake up tasks when abort() is called directly or indirectly, e.g. on cancellation or timeout).True if the current state is not filling, False otherwise.abort() calls from affecting successful task wakeup. This change is due to the fact that they cannot suppress cancellation in principle, making the prevention of BrokenBarrierError on successful wakeup meaningless. As a consequence, single-use barriers can now be broken after use (e.g. via an abort() call).aiologic.Semaphore now disallow passing max_size other than None from subclasses. Previously it was ignored, which could violate user expectations intending to get aiologic.BoundedSemaphore behavior.aiologic.BoundedSemaphore (and consequently aiologic.Semaphore) now creates aiologic.BoundedBinarySemaphore when max_size <= 1. This makes it possible to use an implementation that is more efficient in both time and memory without importing new classes.aiologic.BoundedSemaphore.release() now disallows count=0. Previously, it allowed threads to participate in waking up others during race conditions, but aiologic.Semaphore.release() no longer has such semantics.CapacityLimiter.borrowers is now a read-only property that returns a read-only mapping proxy, which increases safety when working with it.total_tokens parameter now has a default value of 1. This allows capacity limiters to be used directly as default factories and makes their interface a bit closer to semaphores.True if at least one token has been borrowed, False otherwise.owner (and count) on release rather than on wakeup. This gives the expected values of these parameters when locks are used cooperatively. Previously, it was not possible to determine the next lock owner after release in the same task (it was None).None (lockless mode). This change was made to simplify their implementation. For special cases it is recommended to use low-level events directly.delegate=True). This reduces the number of context switches to the minimum necessary.aiologic functions. This works by using a new reparking mechanism and solves the well-known resource starvation issue.release() calls when used as context managers. Because of this, they will now never throw a RuntimeError when a wait() call fails (e.g. due to a KeyboardInterrupt while trying to reacquire aiologic.lowlevel.ThreadLock) except in the case of concurrent notify() calls. This makes it safe (with some caveats) to use condition variables even when shielding from external cancellation is not guaranteed.wait() call cannot be cancelled in any unpredictable way (e.g. when a greenlet is killed by an exception other than GreenletExit), but now condition variables can be safely used in more scenarios.wait_for() and for_() methods to be used in more scenarios. Previously, a value of type bool was returned.bool. For locks from the threading module, True is returned if the lock is locked and False otherwise, which matches the behavior of locks from the aiologic module. For None, False is always returned. With this change, condition variables can now be used to determine the status of an operation (just like normal aiologic locks) regardless of the lock used. Previously, True was always returned for both cases._qsize() (returns queue size) and _items() (returns a list of queue items) overrides, and they have been removed accordingly. Unlike the other overridden methods, they required thread-safety on the user side, which could cause additional difficulties. It is now recommended to use queues from culsans, a derivative of aiologic, to create full-featured custom queues.aiologic.lowlevel.current_green_token() now returns the current thread, and aiologic.lowlevel.current_green_token_ident() now uses the current thread ID for threading. This makes these functions more meaningful, and leads to the expected behavior in group-level locks. Previously, constant values were returned for threading.aiologic.lowlevel.current_green_token() and aiologic.lowlevel.current_green_task() now return main greenlet objects for gevent's pool of native worker threads (and for any dummy threads when greenlet is imported). This allows these functions to be used with gevent without additional handlers.aiologic.lowlevel.current_thread() call no longer patches the threading module for PyPy (to fix the race in Thread.join()). This is done to eliminate side effects and possible conflicts with debuggers. Use PyPy 7.3.18 or higher instead, or apply a separate patch yourself.aiologic.lowlevel.GreenEvent instantiation no longer injects destroy() into eventlet hubs. This is delegated to a separate patch as it gives more predictable behavior. However, aiologic still injects schedule_call_threadsafe() since eventlet/eventlet#1023 is still unresolved.curio events now use lockless futures instead of concurrent.futures.Future. This makes the implementation of curio support completely non-blocking (like the rest of the concurrency libraries), which has a positive impact on performance.sniffio is now a required dependency. This is done to simplify the code logic (which previously treated sniffio as an optional dependency) and should not introduce any additional complexity.typing-extensions is now a required dependency on Python < 3.13. This is done to make it easier to work with stubs and use new features (such as warnings.deprecated) on older versions of Python.setuptools to uv + hatch. It keeps the same pyproject.toml format, but has better performance, better logging, and builds cleaner source distributions (without setup.cfg). Dependencies specific to the development process have been redefined using PEP 735..git_archival.txt) and source distributions (via PKG-INFO).timeout<0 in all primitives, as they differ from the semantics of the standard library, which could lead to their incorrect use (since they are equivalent timeout=0, but not to no timeout).action as a positional parameter in aiologic.ResourceGuard in favor of using it as a keyword-only parameter.maxsize<=0 in complex queue constructors in favor of maxsize=None: support for maxsize<0 is not pythonic, goes against common style, and maxsize=0 may in the future be used to create special empty queues.aiologic.PLock in favor of aiologic.BinarySemaphore.aiologic.RLock.level in favor of aiologic.RLock.count.aiologic.lowlevel.MISSING in favor of aiologic.meta.MISSING.aiologic.lowlevel.GreenEvent and aiologic.lowlevel.AsyncEvent direct creation in favor of aiologic.lowlevel.create_green_event() and aiologic.lowlevel.create_async_event(): they will become protocols in the future.aiologic.lowlevel.Flag in favor of aiologic.Flag.aiologic.lowlevel.checkpoint() in favor of aiologic.lowlevel.async_checkpoint() (previously alias): checkpoints are now strictly separated into green and async checkpoints.aiologic.CapacityLimiter.*_on_behalf_of() methods: they did not provide the proper thread-safety level (capacity limiters need to be higher-level primitives for this), but also made the implementation more complex and thus degraded performance.is_set parameter from one-time and reusable events (aiologic.Event and aiologic.REvent).aiologic.lowlevel.<library>_running(): these functions have not been used and could be misleading.aiologic.lowlevel.checkpoint_if_cancelled() and aiologic.lowlevel.cancel_shielded_checkpoint(): they only supported asynchronous libraries and were not actually used in high-level primitives.aiologic.lowlevel.<library>_checkpoints_cvar in favor of aiologic.lowlevel.enable_checkpoints() and aiologic.lowlevel.disable_checkpoints().AIOLOGIC_GREEN_LIBRARY and AIOLOGIC_ASYNC_LIBRARY environment variables: they could be confusing because they did not affect sniffio.current_async_library(). The alternative of setting the default directly in sniffio (via sniffio.thread_local.__class__.name) affects anyio, which refuses to make a successful anyio.run() call when the current async library is set.__getstate__(), which caused internal fields to be copied during pickling even if __getnewargs__() was defined. As a result, it was impossible to copy primitives while using them.aiologic.lowlevel.shield() (previously aiologic.lowlevel.repeat_if_cancelled()):
anyio.CancelScope() with the asyncio backend. Now this case is handled in a special way. Related: agronholm/anyio#884.asyncio.CancelledError was caught: in this case, the last asyncio.CancelledError was not removed from the frame.aiologic.lowlevel.current_thread() returned:
threading module with eventlet (now the same as from threading.main_thread()).None for the main thread after monkey patching the threading module with gevent (now the same as from threading.main_thread()).aiologic.lowlevel.ThreadOnceLock is used for this (via aiologic.lowlevel.once()), which ensures signal-safety.aiologic.RLock from inside a signal handler or destructor could result in a false release if the execution occurred inside an *_acquire() call after setting the owner property but before setting the count property. The order of operations is now inverted. This makes aiologic.RLock a bit more signal-safe than threading._PyRLock.threading could cause hub spawning in worker threads when aiologic is imported after monkey patching the time module with eventlet or gevent. As a result, the open files limit could have been exceeded.eventlet calls did not check the context, which could lead to incorrect behavior when executing blocking calls in the hub context (as part of scheduled calls).trio case by default) did not release the primitive.aiologic.SimpleQueue) used an incorrect condition for cancellation handling, which could break thread-safety after cancellation. Now the handling is changed to match that of locks and semaphores, which additionally speeds up methods by reducing operations.curio events would set the future attribute after the set() method was completed and thus cause a hang.0.14.0 regression).trio tokens, which resulted in inability to use aiologic.lowlevel.current_async_token() and aiologic.lowlevel.current_async_token_ident() for trio (0.14.0 regression)._local class was imported directly from the _thread module, which caused the current library to be set only for the current greenlet and not for the whole thread after monkey patching (0.14.0 regression).0.2.0 regression).aiologic.lowlevel.current_async_library_cvar : the sniffio equivalent is deprecated and not used by modern libraries, and the performance impact of us…
curio support.wrapt via post import hooks. Previously, available libraries were determined after the first use, which could lead to unexpected behavior in interactive scenarios. Now support for a library is activated when the corresponding library is imported.eventlet or gevent function was called in the current thread, the current thread starts using the corresponding library. This eliminates the need to specify a green library both before and after monkey patching.aiologic.BoundedSemaphore extends aiologic.Semaphore, and aiologic.Semaphore returns an instance of aiologic.BoundedSemaphore when passing max_value. Previously, the classes were independent in stubs, which was inconsistent with the behavior added in 0.2.0.aiologic.lowlevel.current_thread() returns Optional[threading.Thread]. Previously, threading.Thread was returned, which was inconsistent with the special handling of threading._DummyThread.0.13.0).aiologic.lowlevel.start_new_thread()).aiologic.lowlevel.current_async_library_cvar: the sniffio equivalent is deprecated and not used by modern libraries, and the performance impact of using it is only negative.asyncio was not considered running when the current task was None. This resulted in the inability to use any async functions in asyncio REPR without explicitly setting the current async library. Related: python-trio/sniffio#35.0.13.1 regression).Optimized the event removal in locks and semaphores. Previously, removing an event from the waiting queue was performed even when it was successfully
Type annotations via stubs. They are tested via mypy and pyright , but may cause ambiguities in some IDEs that do not support overloading well.
mypy and pyright, but may cause ambiguities in some IDEs that do not support overloading well.Support for cancellation and timeouts has been dramatically improved. The cancel() method (and accompanying is_cancelled() ) has been added to low-lev
cancel() method (and accompanying is_cancelled()) has been added to low-level events, which always returns True after the first successful call before set() and False otherwise. Previously, cancellation handling used the same set() call, which resulted in false negatives, in particular unnecessary wakes.await keyword in aiologic.Condition, without which it did not restore the state of the wrapped aiologic.RLock (0.11.0 regression).aiologic.lowlevel.async_checkpoint() as an alias for aiologic.lowlevel.checkpoint() .
aiologic.lowlevel.async_checkpoint() as an alias for aiologic.lowlevel.checkpoint().aiologic.Condition have been extended. Since there is no clear definition of which locks it should wrap, support for sync-only locks such as threading.Lock has been added. Passing another instance of aiologic.Condition is now supported too, and implies copying a reference to its lock. Also, passing None now specifies a different behavior whereby aiologic.Condition acts as lockless: its methods ignore the lock, which should help to use aiologic.Condition as a simple replacement for removed aiologic.ParkingLot.aiologic.lowlevel.shield() function, which protects the call from cancellation, has been replaced by aiologic.lowlevel.repeat_if_cancelled() , which,
aiologic.lowlevel.shield() function, which protects the call from cancellation, has been replaced by aiologic.lowlevel.repeat_if_cancelled(), which, depending on the library, either has the same action or repeats the call until it completes (successfully or unsuccessfully). Previously anyio.CancelScope was used, which did not really shield the call from cancellation in ways outside of anyio, such as task.cancel(), so its use was abandoned. However, asyncio.shield() starts a new task, which has a negative impact on performance and does not match the expected single-switch behavior. This is why repeat instead of shield was chosen. This change directly affects aiologic.Condition.aiologic.CountdownEvent is significantly improved. Instead of using the initial value for the representation, it now uses the current value, so that i
aiologic.CountdownEvent is significantly improved. Instead of using the initial value for the representation, it now uses the current value, so that its state is saved when the pickle module is used, just like the other events. Also added the ability to increase the current value by more than one per call, which should help with performance in some scenarios. The new clear() method, which atomically resets the current value, has the same purpose.threading) is used for all others. This should eliminate redundancy when using worker threads, which previously had to explicitly set threading to avoid accidentally creating a new hub and hence an event loop.The future used by the asyncio event could be canceled due to the event loop shutdown, causing InvalidStateError to be raised due to the callback exec
asyncio event could be canceled due to the event loop shutdown, causing InvalidStateError to be raised due to the callback execution. In particular, this was detected when using call_soon() for notification methods.aiologic.CountdownEvent as a countdown event, i.e. an event that wakes up all tasks when its value is down to zero (inspired by CountdownEvent from .N
aiologic.CountdownEvent as a countdown event, i.e. an event that wakes up all tasks when its value is down to zero (inspired by CountdownEvent from .NET Framework 4.0).aiologic.RCapacityLimiter as a reentrant version of aiologic.CapacityLimiter.aiologic.Condition was referring to the wrong variable in its notification methods, which caused hangs (0.2.0 regression).
aiologic.Condition was referring to the wrong variable in its notification methods, which caused hangs (0.2.0 regression).aiologic.Barrier as a cyclic barrier, i.e. a barrier that tasks can pass through repeatedly (thread-aware alternative to asyncio.Barrier).
aiologic.Barrier as a cyclic barrier, i.e. a barrier that tasks can pass through repeatedly (thread-aware alternative to asyncio.Barrier).aiologic.SimpleQueue as a simplified queue without maxsize support.aiologic.PriorityQueue as a priority queue, i.e. a queue that returns its smallest element (using the heapq module).aiologic.Queue and its derivatives now use a unique architecture that guarantees exclusive access to inherited methods without increasing the number of context switches: implicit lock and wait queue are combined. Overridable methods are now part of the public API, and fairness now covers all accesses. The properties returning the length of waiting queues have also been changed: a common waiting property returning the number of all waiting ones has been added, and put_waiting and get_waiting have been renamed to putting and getting.force parameter for green checkpoints (allowing to ignore context variables and environment variables).
force parameter for green checkpoints (allowing to ignore context variables and environment variables).return keyword in queue implementations, without which they always returned None in green_get() and async_get() methods (0.4.0 regression).aiologic.Latch now preserves the original task order for greenlets. Previously, the last greenlet always passed the barrier first when checkpoints were disabled.aiologic.CapacityLimiter as a primitive similar to aiologic.Semaphore, but with ownership checking on release like aiologic.Lock (thread-aware alterna
aiologic.CapacityLimiter as a primitive similar to aiologic.Semaphore, but with ownership checking on release like aiologic.Lock (thread-aware alternative to anyio.CapacityLimiter).aiologic.Latch as a single-use barrier, useful for ensuring that no thread or task sleeps after passing the barrier (inspired by std::latch from C++20).force parameter for async checkpoints (allowing to ignore context variables and environment variables).aiologic.SimpleQueue has been replaced by aiologic.Queue (FIFO) and aiologic.LifoQueue (LIFO) with the same performance. Unlike aiologic.SimpleQueue,
aiologic.SimpleQueue has been replaced by aiologic.Queue (FIFO) and aiologic.LifoQueue (LIFO) with the same performance. Unlike aiologic.SimpleQueue, they support maxsize and their methods have green_ and async_ prefixes, which follows the common naming convention. And since they have two different waiting queues, the waiting property has been replaced with put_waiting and get_waiting.pyproject.toml-only instead of pyproject.toml + setup.cfg + setup.py. It should be more trusted by new users, since it does not contain executable code. (#1).The setup.py code has been simplified to avoid SetuptoolsDeprecationWarning.
weakref support for all high-level primitives.await keyword was missing, causing the checkpoints to have no effect (0.2.0 regression).aiologic.REvent, woken tasks now inherit the timestamp of the one that woke them up, which eliminates false wakes in the set() + clear() scenario.setup.py code has been simplified to avoid SetuptoolsDeprecationWarning.aiologic.PLock as a primitive lock, i.e. the fastest exclusive lock that does not do checks in release() methods.
eventlet and gevent support.aiologic.PLock as a primitive lock, i.e. the fastest exclusive lock that does not do checks in release() methods.aiologic.REvent as a reusable event, i.e. an event that supports the clear() method (async-aware alternative to threading.Event).aiologic.ResourceGuard for ensuring that a resource is only used by a single task at a time (thread-safe alternative to anyio.ResourceGuard).release() method to aiologic.Semaphore and aiologic.BoundedSemaphore.max_value parameter to aiologic.Semaphore to allow creating an instance of aiologic.BoundedSemaphore without importing it.waiting property to all primitives that returns the length of waiting queue (number of waiting threads and tasks).blocking=False.threading module that fixes the race in Thread.join() on PyPy. Automatically applied when creating an instance of the threading event.eventlet module that adds the necessary methods to support it. Automatically applied when creating an instance of eventlet event._as_thread and _as_task suffixes are replaced by green_ and async_ prefixes. Other options were considered, but only such prefixes ensure that the two different APIs are equal in convenience.aiologic.Lock and aiologic.RLock no longer support upgrading from synchronous to asynchronous access, which avoids some issues in complex scenarios.put() and aput() methods in aiologic.SimpleQueue are replaced by the common put() method (without blocking and timeout parameters).aiologic.SimpleQueue now throws its own aiologic.QueueEmpty exception instead of queue.Empty.aiologic.lowlevel.ThreadEvent is renamed to aiologic.lowlevel.GreenEvent and aiologic.lowlevel.TaskEvent is renamed to aiologic.lowlevel.AsyncEvent.set() method of low-level events now returns bool: True if called for the first time, False otherwise.aiologic.lowlevel.current_thread() now returns a threading.Thread instance instead of its identifier, and a new function aiologic.lowlevel.current_thread_ident() has been added to get the identifier instead. The old aiologic.lowlevel.current_token() and aiologic.lowlevel.current_task() are now differentiated by library type (current_green_ and current_async_). Additional functions with the _ident suffix have also been added to get an identifier instead of an object.aiologic.ParkingLot: now each primitive uses its own lightweight code to handle the waiting queue.aiologic.lowlevel.AsyncioEvent and aiologic.lowlevel.TrioEvent: they are now private.aiologic.lowlevel.Flags.markers: it is now private.notify(), release(), and so on) did not notify newcomers due to a race, causing a hang. Now they are fully thread-safe.aiologic.Condition now uses timestamps for wakeups, which provides expected behavior in complex scenarios (and eliminates resource starvation). The same is implemented in aiologic.REvent.threading event now calls lock.acquire(blocking=False) instead of lock.acquire(timeout=timeout) on a negative timeout, which is consistent with the behavior of threading.Event.default_factory() call in aiologic.lowlevel.Flag.get() is now performed outside the except block, which simplifies stack traces.aiologic.ParkingLot is now fair (and hence all other primitives too).
aiologic.ParkingLot is now fair (and hence all other primitives too).A mixed build system (setup.py + setup.cfg + pyproject.toml): supports both direct (via setup.py) and indirect (via build + pip) installation.
setup.py + setup.cfg + pyproject.toml): supports
both direct (via setup.py) and indirect (via build + pip) installation.sniffio-like aiologic.lowlevel.current_async_library() function.anyio-like aiologic.lowlevel.checkpoint() function.anyio-like aiologic.lowlevel.checkpoint_if_cancelled() function.anyio-like aiologic.lowlevel.cancel_shielded_checkpoint() function.aiologic.lowlevel.current_thread() to get the current thread identifier.aiologic.lowlevel.current_token() to get the current async token.aiologic.lowlevel.current_task() to get the current async task.aiologic.lowlevel.Flag as a one-slot alternative to dict.setdefault().aiologic.lowlevel.TaskEvent as a thread-safe async event.aiologic.lowlevel.ThreadEvent as a one-time thread event.aiologic.ParkingLot as a waiting queue for all other primitives (inspired
by trio.lowlevel.ParkingLot).aiologic.Semaphore as a async-aware alternative to threading.Semaphore.aiologic.BoundedSemaphore as a async-aware alternative to
threading.BoundedSemaphore.aiologic.Lock as a thread-aware alternative to anyio.Lock.aiologic.RLock as a async-aware alternative to threading.RLock.aiologic.Condition as a async-aware alternative to threading.Condition.aiologic.Event as a thread-aware alternative to asyncio.Event.aiologic.SimpleQueue as a queue that works in a semaphore style
(async-aware alternative to queue.SimpleQueue).Your coding agent can read these notes before it upgrades. Set up the MCP server →