Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Commit messages are consistent with Conventional Commits.
0.17.1 - 2026-06-27
Fixed
aiologic.Lock(andaiologic.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 aRuntimeErrorfor 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()(andaiologic.lowlevel.async_seconds_per_timeout()) did not handle non-standardasyncioevent loops, which could lead to overflows on Pyodide (see pyodide/pyodide#6304; however, note that this does not apply toaiologic.lowlevel.async_sleep_forever()) and non-pure Python event loops. It now handles standard event loops,uvloop,winloop,pyodide.webloop.WebLoop, and others.
0.17.0 - 2026-06-14
Added
with_()methods to all awaitable primitives (for waiting with given parameters) and thetimeoutparameter 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 version0.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 withasyncio.wait_for()in some dependent repositories, which is clearly undesirable.aiologic.meta.replaces_when_imported()as a safer alternative towrapt.when_imported()+aiologic.meta.replaces().aiologic.meta.import_original()as anaiologic.meta.import_from()-like unified alternative toeventlet.patcher.original()andgevent.monkey.get_original().aiologic.meta.isgreenpatched()as a unified alternative toeventlet.patcher.is_monkey_patched()andgevent.monkey.is_module_patched().aiologic.meta.lookup_static()andaiologic.meta.resolve_special()to look for a name via the MRO without triggering user code. They are similar toinspect.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 anEnumsubclass) and have a simpler (more efficient) implementation. Unlikeaiologic.meta.lookup_static(),aiologic.meta.resolve_special()resolves descriptors using the passed arguments.aiologic.meta.isdatadescriptor_static()andaiologic.meta.ismethoddescriptor_static()to check whether an object is a descriptor of the specified type.aiologic.meta.isdatadescriptor_static()uses the same algorithm asaiologic.meta.lookup_static(), so it returnsTrueonly for objects that actually work as data descriptors (not prone to false positives, unlikeinspect.isdatadescriptor()). Note that unlikeinspect.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()andaiologic.meta.isclass_static()to check whether an object is a metaclass (subclass oftype) and a class (instance oftype), respectively. Unlikeinspect.isclass(), they behave in the same way as the functions below.aiologic.meta.issubclass_static()andaiologic.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(), andaiologic.meta.isasyncgenlike()asinspect-like functions that check whether an object implements a certain interface (corresponding to the function name).aiologic.meta.isgeneratorfactory(),aiologic.meta.iscoroutinefactory(), andaiologic.meta.isasyncgenfactory()as functions that differ frominspect.is*function()in that they have broader semantics and more flexible checks.aiologic.meta.markgeneratorfactory(),aiologic.meta.markcoroutinefactory(), andaiologic.meta.markasyncgenfactory()as a way to fine-tune the above (and some standard) functions regardless of the Python version.aiologic.meta.generator()andaiologic.meta.coroutine()functions for transforming generator/coroutine factories into the corresponding object functions. Unliketypes.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 (forsphinx.ext.autodoc).aiologic.meta.GeneratorCoroutineWrapperas a way to create universal objects that are similar to bothtypes.GeneratorTypeandtypes.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 theinspect.signature()implementation.
Changed
All functions that previously used
inspect.iscoroutinefunction()now rely onaiologic.meta.iscoroutinefactory(). This makes detecting asynchronous functions (coroutine factories) more accurate and also allows them to be marked on any version of Python.The
aiologic.meta.SingletonEnumconstructor 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.SingletonEnuminstances 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 aLookupErrorwith an informative message instead of aKeyErrorif 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 usestyping.Anyas the return type instead ofobjectto avoid usingtyping.cast()for dynamic imports in__init__modules.
Deprecated
The
copy()methods (see below).Pickling/copying by state (for queues and flags). This behavior was inconsistent with other primitives because these ones were interpreted as collections, which does more harm than good. It also makes it hard to optimize queues.
Fixed
Low-level Trio waiters allowed multiple reschedules for the same instance, which caused Trio to crash at its underlying
assert. Only direct use of them by the user was affected (low-level events already prevent multiplewake()calls in general, so no high-level primitives were affected). See fleming79/async-kernel#493 (comment) for details.Functions with
sphinx.ext.autodoc-specific paths did not have the expected behavior due to a peculiarity of module reloading. Now they also check environment variables.Since
aiologic.meta.export()includes all public names in__all__,__future__.annotationsandtyping.TYPE_CHECKINGwere also included (as ‘annotations’ and ‘TYPE_CHECKING’, respectively) if they were imported in__init__.py.aiologic.meta.export_dynamic()andaiologic.meta.export_deprecated()raised aRuntimeErrorwhen registering already registered symbolic links, which prevented the module from being reloaded. Now they allow re-registration for the same values.
0.16.0 - 2025-11-27
Added
aiologic.__version__andaiologic.__version_tuple__as a way to retrieve the package version at runtime.aiologic.meta.SingletonEnumas a base class that encapsulates common logic of all type-checker-friendly singleton classes (such asaiologic.meta.DefaultTypeandaiologic.meta.MissingType).aiologic.meta.resolve_name()as an alternative toimportlib.util.resolve_name()with more consistent behavior.aiologic.meta.import_module()as an alternative toimportlib.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 anImportErrorinstead of anAttributeError, 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 toaiologic.meta.export_deprecated()that does not raise aDeprecationWarning. Useful for optional features that may be missing at runtime but should be available at the package level.aiologic.meta.export()andaiologic.meta.export_deprecated()now support passing module objects, which should simplify their use for manually created module objects.Final classes from
aiologic.metanow support runtime introspection via the__final__attribute on all supported versions of Python.
Changed
aiologic.meta.export()has been redesigned:It now updates not only the
__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.Attributes are only updated if the object type matches one of the supported types, which protects against undefined behavior due to problematic types (such as singletons that provide a read-only
__module__attribute).When a class is encountered, its members are also updated (recursively). This allows its functions to be referenced safely. In addition, properties and class/static methods are now also processed.
All non-public attributes are now skipped, which should reduce the number of operations performed.
For each package, it now builds a human-readable
__all__, which gives the expected behavior offrom 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 newimportlib-like functions, which should make it safer to use.Non-public functions for importing original objects now implement
aiologic.meta.import_from()-like behavior, making them more predictable (relevant for a green-patched world).aiologic.Queueand its derivatives now raise aValueErrorwhen attempting to pass amaxsizeless than 1.All timeouts now raise a
ValueErrorfor values less than zero.
Removed
All deprecated features (see
0.15.0).
Fixed
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 anAttributeErrorbeing raised in such cases.aiologic.meta.copies()used a function copying technique that was incompatible with Nuitka, resulting in aRuntimeErrorwhen 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.
0.15.0 - 2025-11-05
Added
aiologic.synchronized()as an async-aware alternative towrapt.synchronized(). Related: GrahamDumpleton/wrapt#236.aiologic.SimpleLifoQueueas a simplified LIFO queue, i.e. a lightweight alternative toaiologic.LifoQueuewithoutmaxsizesupport.aiologic.BinarySemaphoreandaiologic.BoundedBinarySemaphoreas binary semaphores, i.e. semaphores restricted to the values 0 and 1 and using a more efficient implementation.aiologic.RBarrieras a reusable barrier, i.e. a barrier that can be reset to its initial state (async-aware alternative tothreading.Barrier).aiologic.lowlevel.ThreadLockclass (for typing purposes) andaiologic.lowlevel.create_thread_lock()factory function as a new way to obtain unpatchedthreading.Lock.aiologic.lowlevel.ThreadRLockclass (for typing purposes) andaiologic.lowlevel.create_thread_rlock()factory function as a unique way to obtain unpatchedthreading.RLock. Solves the problem of using reentrant thread-level locks in the gevent-patched world (due to the fact thatthreading._PyRLock.__globals__referenced the patched namespace, which made it impossible to use the original object because it used the patchedthreading.Lock). Note: likethreading._PyRLock, the fallback pure Python implementation is not signal-safe.aiologic.lowlevel.ThreadOnceLockclass (for typing purposes) andaiologic.lowlevel.create_thread_oncelock()factory function as a way to obtain a one-time reentrant lock. The interface mimics that ofaiologic.lowlevel.ThreadRLock, but the semantics are different: the first successfulrelease()call, which sets the internal counter to zero, wakes up all threads at once (just likeaiologic.Event), and all furtheracquire()calls become no-ops, which effectively turns the lock into a dummy primitive. And unlikeaiologic.lowlevel.ThreadRLock, this primitive is signal-safe, which, combined with the described semantics, makes the primitive suitable for protecting initialization sections.aiologic.lowlevel.ThreadDummyLockclass (for typing purposes) andaiologic.lowlevel.THREAD_DUMMY_LOCKsingleton object as a way to obtain a dummy lock.aiologic.lowlevel.oncedecorator to ensure that a function is executed only once (inspired bystd::sync::Oncefrom Rust). It usesaiologic.lowlevel.ThreadOnceLockunder the hood and stores the result in the wrapper’s closure, which makes the function both thread-safe and signal-safe whenreentrant=Trueis passed (note: this does not apply to side effects!).aiologic.lowlevel.lazydequeas a thread-safe/signal-safe wrapper forcollections.dequewith lazy initialization. It solves the problem of deques’ high memory usage: one empty instance ofcollections.dequetakes up 760 bytes on Python 3.11+ (for comparison, one empty list takes up only 56 bytes!). In contrast, one empty instance ofaiologic.lowlevel.lazydequetakes 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 ofaiologic.lowlevel.ThreadOnceLock).aiologic.lowlevel.lazyqueueas a thread-safe/signal-safe wrapper for_queue.SimpleQueue(when available) orcollections.dequewith lazy initialization. It provides a non-blocking queue and differs fromaiologic.lowlevel.lazydequein 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()andaiologic.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()andaiologic.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()andaiologic.lowlevel.create_async_event()as a new way to create low-level events.aiologic.lowlevel.enable_checkpoints()andaiologic.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()andaiologic.lowlevel.async_checkpoint_enabled()to determine if checkpoints are enabled for the current library.aiologic.lowlevel.green_checkpoint_if_cancelled()andaiologic.lowlevel.async_checkpoint_if_cancelled(). Currently,aiologic.lowlevel.async_checkpoint_if_cancelled()is equivalent to removedaiologic.lowlevel.checkpoint_if_cancelled(), andaiologic.lowlevel.green_checkpoint_if_cancelled()does nothing. However, these methods have a slightly different meaning: they are intended to accompanyaiologic.lowlevel.shield()calls to pre-check for cancellation, and do not guarantee actual checking.aiologic.lowlevel.green_clock()andaiologic.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()andaiologic.lowlevel.async_sleep()to suspend the current task for the given number of seconds.aiologic.lowlevel.green_sleep_until()andaiologic.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()andaiologic.lowlevel.async_sleep_forever()to suspend the current task until an exception occurs.aiologic.lowlevel.green_seconds_per_sleep()andaiologic.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()andaiologic.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()andgreen_borrowed()methods to capacity limiters,green_owned()andasync_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()andgreen_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.Reentrant primitives can now be acquired and released multiple times in a single call.
Multi-use barriers (cyclic and reusable) can now be used as context managers, which simplifies error handling.
for_()method to condition variables as an async analog ofwait_for().Conditional variables now support user-defined timers. They can be passed to the constructor, called via the
timerproperty, and used to pass the deadline to the notification methods.Low-level events can now support
aiologic.lowlevel.ThreadOnceLockmethods by passinglocking=True. This allows to synchronize related one-time operations with less memory overhead.Low-level events can now be shielded from external cancellation by passing
shield=True. This allows to implement efficient finalization strategies while preserving the one-time nature of low-level events.Low-level events can now be forced (like checkpoints) by passing
force=True. This allows to use existing event objects instead of checkpoints to minimize possible overhead.aiologic.lowlevel.SET_EVENTandaiologic.lowlevel.CANCELLED_EVENTas variants ofaiologic.lowlevel.DUMMY_EVENTfor a set event and a cancelled event respectively. In fact,aiologic.lowlevel.SET_EVENTis just a copy ofaiologic.lowlevel.DUMMY_EVENT, but to avoid confusion both variants will coexist (maybe temporarily, maybe not).aiologic.metasubpackage for metaprogramming purposes.aiologic.meta.MISSINGas a marker for parameters that, when not passed, specify special default behavior.aiologic.meta.DEFAULTas 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_CHECKPOINTSandAIOLOGIC_ASYNC_CHECKPOINTSenvironment variables.AIOLOGIC_PERFECT_FAIRNESSenvironment variable.
Changed
In previous versions,
aiologicimplicitly 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 callrelease()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 versionaiologicprovides theAIOLOGIC_PERFECT_FAIRNESSenvironment variable to explicitly enable or disable it.To avoid cubic complexity, token removal in perfect fairness style (cannot be disabled in: multi-time events, multi-use barriers, and condition variables) is now synchronized via
aiologic.lowlevel.ThreadOnceLockmethods in the free-threaded mode.Signal-safety is now also explicit and can be configured via the universal decorators. When enabled, code behaves as if it were running outside the context in the same thread (as in a separate thread but with the same thread identifiers), allowing both notifying and waiting to be performed safely from inside signal handlers and destructors (affects library detection and low-level waiters).
All timeouts now support the full range of int and float values, and correctly handle NaN and infinity (in particular,
timeout=infis equivalent totimeout=None). A side effect is that large timeouts (exceeding the maximum) increase the number of checkpoints.All primitives now use
aiologic.lowlevel.lazydequeinstead ofcollections.dequeto reduce memory usage. This makes their memory usage a bit closer toasyncioprimitives (more lightweight than somethreadingprimitives!), 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 viaaiologic.lowlevel.ThreadOnceLockat the first addition to the queue in free-threading.Shallow copying now relies on the
__copy__()method instead of pickle methods. This makes copying faster at the cost of additional overriding in subclasses.Flags are now a high-level primitive, available as
aiologic.Flag, withweakrefsupport.Reentrant primitives now have checkpoints on reentrant acquires. This should make their behavior more predictable. Previously, checkpoints were not called if a primitive had already been acquired (for performance reasons).
Interfaces and type hints have been improved:
Queues, capacity limiters, and flags are now generic types not only in stubs but also at runtime, making it possible to use subscriptions on Python 3.8. Also, capacity limiters’ and flags’ type parameters now have default values.
The use of markers as default parameter values has been expanded.
Noneis used when it disables a particular feature (e.g. timeouts or maxsize).aiologic.meta.MISSINGis used when it specifies a special default behavior.aiologic.meta.DEFAULTis used when an existing value that is compatible in type will be taken.aiologic.lowlevel.Eventis now a protocol not only in stubs but also at runtime.Calling
flag.set()without arguments is now only allowed foraiologic.lowlevel.Flag[object]. Previously it ignored subscriptions.aiologic.meta.MissingTypeis now a subclass ofenum.Enum, so static analysis tools now correctly recognizeaiologic.meta.MISSINGas a singleton instance.aiologic.lowlevel.current_green_library()andaiologic.lowlevel.current_async_library()now returnOptional[str]when passingfallback=True. Previously,strwas returned, which was not the expected behavior.aiologic.lowlevel.AsyncLibraryNotFoundErrorandaiologic.lowlevel.current_async_library_tlocalare now exactly the same assniffio.AsyncLibraryNotFoundErrorandsniffio.thread_local. This allows them to be used interchangeably.In all modules, type information is now also inline. This allows such information to be used in cases where stubs are not supported. In particular, for
sphinx.ext.autodoc. Stubs are still preserved to reduce issues with type checkers.Overload introspection is now available at runtime on all supported versions of Python.
Thread-related functions have been rewritten:
aiologic.lowlevel.current_thread()now raisesRuntimeErrorfor threads started outside of thethreadingmodule instead of returningNone. 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.The fallback
_thread._localimplementation now works not only with thread objects but also with main greenlet objects, so it now supportsgevent’s pool of native worker threads.
Checkpoints have been rewritten:
aiologic.lowlevel.repeat_if_cancelled()has been replaced byaiologic.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.threadingcheckpoints now useos.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.They now skip the current library detection when it is not required (for example, when checkpoints are not enabled for any of the imported libraries). This also affects checkpoints in low-level events, which no longer access context variables before using checkpoints, making them much faster.
They can now only be enabled dynamically (without environment variables) at the thread level. This prevents checkpoints from being enabled in created worker threads.
Low-level events have been rewritten:
They are now built on waiters (new low-level primitives), due to which they determine the current library and access the specific API only in wait methods and only if they have not been pre-set. This allows them to be created outside the event loop, and gives more predictable behavior and some performance gains.
They now prevent concurrent calls to the wait method by raising a
RuntimeErrorin such situations, making them safer. Previously, this was not handled in any way on theaiologicside, which required special care and could lead to undefined behavior if the conditions for their use were not met.Placeholder events now inherit
aiologic.lowlevel.GreenEventandaiologic.lowlevel.AsyncEvent, allowing them to be used in code sections where wait methods are called.event.is_cancelled()has been renamed toevent.cancelled(). This makes them more similar toasynciofutures and thus more familiar to new users.They are now always cancelled on timeouts, and the
event.cancel()method has been removed to avoid redundancy. This should make it easier to work with them outside ofaiologicand simplify some things, since now there is no need to callevent.cancel(). Previously, green events were not cancelled when a timeout was passed.They are no longer considered set after being cancelled. This affects the return value of
bool(event)andevent.is_set(), which now returnFalsefor cancelled events.They now return
Falseafter waiting again if they were previously cancelled. PreviouslyTruewas returned, which could be considered unexpected behavior.
Events have been rewritten:
They no longer save their state when being pickled/copied. So they now share the same behavior as the other synchronization primitives.
The
valueparameter ofaiologic.CountdownEventhas been renamed toinitial_value. Accordingly, a property with the same name has also been added.
Barriers have been rewritten:
The
partiesparameter now has a default value of0. This allows barriers to be used directly as default factories.They now allow passing
partiesequal to0, with which they ignore the waiting queue length (they only wake up tasks whenabort()is called directly or indirectly, e.g. on cancellation or timeout).Single-use barriers can now be used correctly in a Boolean context:
Trueif the current state is not filling,Falseotherwise.They now do not prevent concurrent
abort()calls from affecting successful task wakeup. This change is due to the fact that they cannot suppress cancellation in principle, making the prevention ofBrokenBarrierErroron successful wakeup meaningless. As a consequence, single-use barriers can now be broken after use (e.g. via anabort()call).
Semaphores have been rewritten:
aiologic.Semaphorenow disallow passingmax_sizeother thanNonefrom subclasses. Previously it was ignored, which could violate user expectations intending to getaiologic.BoundedSemaphorebehavior.aiologic.BoundedSemaphore(and consequentlyaiologic.Semaphore) now createsaiologic.BoundedBinarySemaphorewhenmax_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 disallowscount=0. Previously, it allowed threads to participate in waking up others during race conditions, butaiologic.Semaphore.release()no longer has such semantics.
Capacity limiters have been rewritten:
CapacityLimiter.borrowersis now a read-only property that returns a read-only mapping proxy, which increases safety when working with it.The
total_tokensparameter now has a default value of1. This allows capacity limiters to be used directly as default factories and makes their interface a bit closer to semaphores.They can now be used correctly in a Boolean context:
Trueif at least one token has been borrowed,Falseotherwise.
Locks have been rewritten:
They now set
owner(andcount) 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 wasNone).
Condition variables have been rewritten:
They now only support passing low-level (thread-level) locks (synchronous mode), binary semaphores and high-level locks (mixed mode), and
None(lockless mode). This change was made to simplify their implementation. For special cases it is recommended to use low-level events directly.Waiting for a predicate now supports delegating its checking to notifiers and is the default (
delegate=True). This reduces the number of context switches to the minimum necessary.For high-level primitives, all waits are now truly fair and always run with exactly one checkpoint (exactly two in case of cancel), just like all other
aiologicfunctions. This works by using a new reparking mechanism and solves the well-known resource starvation issue.They now count the number of lock acquires to avoid redundant
release()calls when used as context managers. Because of this, they will now never throw aRuntimeErrorwhen await()call fails (e.g. due to aKeyboardInterruptwhile trying to reacquireaiologic.lowlevel.ThreadLock) except in the case of concurrentnotify()calls. This makes it safe (with some caveats) to use condition variables even when shielding from external cancellation is not guaranteed.They now shield lock state restoring not only in async methods, but also in green methods. It is still not guaranteed that a
wait()call cannot be cancelled in any unpredictable way (e.g. when a greenlet is killed by an exception other thanGreenletExit), but now condition variables can be safely used in more scenarios.They now check that the current task is the owner of the lock before starting the wait. This corresponds to the behavior of the standard condition variables, and allows exceptions to be raised with more meaningful messages.
They now check that the current task is the owner of the lock before starting notification for predicates for all variations, and in general for any calls for high-level primitives. While this change reduces the number of scenarios in which condition variables can be used, it provides the thread-safety needed for the new features to work.
They now always yield themselves when used as context managers. This diverges from the standard condition variables, but makes the interface more consistent.
They now return the original value of the predicate, allowing the
wait_for()andfor_()methods to be used in more scenarios. Previously, a value of typeboolwas returned.They are now more accurately interpreted as
bool. For locks from thethreadingmodule,Trueis returned if the lock is locked andFalseotherwise, which matches the behavior of locks from theaiologicmodule. ForNone,Falseis always returned. With this change, condition variables can now be used to determine the status of an operation (just like normalaiologiclocks) regardless of the lock used. Previously,Truewas always returned for both cases.
Resource guards have been rewritten:
They are now interpreted in a Boolean context in the same way as locks. Previously, they returned opposite values.
Queues have been rewritten:
They no longer support the
_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 ofaiologic, to create full-featured custom queues.
The representation of primitives has been changed. All instances now include the module name, use the correct class name in subclasses (except for private classes) and show their status in representation.
aiologic.lowlevel.current_green_token()now returns the current thread, andaiologic.lowlevel.current_green_token_ident()now uses the current thread ID forthreading. This makes these functions more meaningful, and leads to the expected behavior in group-level locks. Previously, constant values were returned forthreading.aiologic.lowlevel.current_green_token()andaiologic.lowlevel.current_green_task()now return main greenlet objects forgevent’s pool of native worker threads (and for any dummy threads whengreenletis imported). This allows these functions to be used withgeventwithout additional handlers.The first
aiologic.lowlevel.current_thread()call no longer patches thethreadingmodule for PyPy (to fix the race inThread.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.The first
aiologic.lowlevel.GreenEventinstantiation no longer injectsdestroy()intoeventlethubs. This is delegated to a separate patch as it gives more predictable behavior. However,aiologicstill injectsschedule_call_threadsafe()since eventlet/eventlet#1023 is still unresolved.curioevents now use lockless futures instead ofconcurrent.futures.Future. This makes the implementation ofcuriosupport completely non-blocking (like the rest of the concurrency libraries), which has a positive impact on performance.sniffiois now a required dependency. This is done to simplify the code logic (which previously treatedsniffioas an optional dependency) and should not introduce any additional complexity.typing-extensionsis now a required dependency on Python < 3.13. This is done to make it easier to work with stubs and use new features (such aswarnings.deprecated) on older versions of Python.The build system has been changed from
setuptoolstouv+hatch. It keeps the samepyproject.tomlformat, but has better performance, better logging, and builds cleaner source distributions (withoutsetup.cfg). Dependencies specific to the development process have been redefined using PEP 735.The version identifier is now generated dynamically and includes the latest commit information for development versions, which simplifies bug reporting. It is also passed to archives generated by GitHub (via
.git_archival.txt) and source distributions (viaPKG-INFO).
Deprecated
timeout<0in all primitives, as they differ from the semantics of the standard library, which could lead to their incorrect use (since they are equivalenttimeout=0, but not to no timeout).actionas a positional parameter inaiologic.ResourceGuardin favor of using it as a keyword-only parameter.maxsize<=0in complex queue constructors in favor ofmaxsize=None: support formaxsize<0is not pythonic, goes against common style, andmaxsize=0may in the future be used to create special empty queues.aiologic.PLockin favor ofaiologic.BinarySemaphore.aiologic.RLock.levelin favor ofaiologic.RLock.count.aiologic.lowlevel.MISSINGin favor ofaiologic.meta.MISSING.aiologic.lowlevel.GreenEventandaiologic.lowlevel.AsyncEventdirect creation in favor ofaiologic.lowlevel.create_green_event()andaiologic.lowlevel.create_async_event(): they will become protocols in the future.aiologic.lowlevel.Flagin favor ofaiologic.Flag.aiologic.lowlevel.checkpoint()in favor ofaiologic.lowlevel.async_checkpoint()(previously alias): checkpoints are now strictly separated into green and async checkpoints.
Removed
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_setparameter from one-time and reusable events (aiologic.Eventandaiologic.REvent).aiologic.lowlevel.<library>_running(): these functions have not been used and could be misleading.aiologic.lowlevel.checkpoint_if_cancelled()andaiologic.lowlevel.cancel_shielded_checkpoint(): they only supported asynchronous libraries and were not actually used in high-level primitives.aiologic.lowlevel.<library>_checkpoints_cvarin favor ofaiologic.lowlevel.enable_checkpoints()andaiologic.lowlevel.disable_checkpoints().AIOLOGIC_GREEN_LIBRARYandAIOLOGIC_ASYNC_LIBRARYenvironment variables: they could be confusing because they did not affectsniffio.current_async_library(). The alternative of setting the default directly insniffio(viasniffio.thread_local.__class__.name) affectsanyio, which refuses to make a successfulanyio.run()call when the current async library is set.
Fixed
Slotted classes lacked
__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.In
aiologic.lowlevel.shield()(previouslyaiologic.lowlevel.repeat_if_cancelled()):Hangs could occur when using
anyio.CancelScope()with theasynciobackend. Now this case is handled in a special way. Related: agronholm/anyio#884.Reference cycles were possible when another exception was raised after a
asyncio.CancelledErrorwas caught: in this case, the lastasyncio.CancelledErrorwas not removed from the frame.
aiologic.lowlevel.current_thread()returned:another main thread object after monkey patching the
threadingmodule witheventlet(now the same as fromthreading.main_thread()).Nonefor the main thread after monkey patching thethreadingmodule withgevent(now the same as fromthreading.main_thread()).green thread objects for dummy threads whose identifier matched the running greenlets (now raises an exception according to the new behavior).
The initialization of low-level waiter classes was protected from concurrent execution using a mutex, which could lead to deadlock if it was interrupted and retried in the same thread. Now,
aiologic.lowlevel.ThreadOnceLockis used for this (viaaiologic.lowlevel.once()), which ensures signal-safety.Using
aiologic.RLockfrom inside a signal handler or destructor could result in a false release if the execution occurred inside an*_acquire()call after setting theownerproperty but before setting thecountproperty. The order of operations is now inverted. This makesaiologic.RLocka bit more signal-safe thanthreading._PyRLock.Using checkpoints for
threadingcould cause hub spawning in worker threads whenaiologicis imported after monkey patching thetimemodule witheventletorgevent. As a result, the open files limit could have been exceeded.Blocking
eventletcalls did not check the context, which could lead to incorrect behavior when executing blocking calls in the hub context (as part of scheduled calls).The locks and semaphores (and the capacity limiters and simple queues based on them) did not handle exceptions at checkpoints, so that cancelling at checkpoints (
triocase by default) did not release the primitive.The methods of complex queues (all except
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.Complex queues did not remove the event from the secondary internal queue on unsuccessful cancellation, which could lead to memory leaks in some situations.
In very rare cases,
curioevents would set the future attribute after theset()method was completed and thus cause a hang.In very rare cases, lock acquiring methods did not notify newcomers due to calling a non-existent method when racing during cancellation, causing a hang (
0.14.0regression).A non-existent function was imported for
triotokens, which resulted in inability to useaiologic.lowlevel.current_async_token()andaiologic.lowlevel.current_async_token_ident()fortrio(0.14.0regression).The
_localclass was imported directly from the_threadmodule, which caused the current library to be set only for the current greenlet and not for the whole thread after monkey patching (0.14.0regression).Semaphores with value > 1 incorrectly handled the optimistic acquire case after adding an event to the queue, resulting in excessive decrements in a free-threading mode (
0.2.0regression).
0.14.0 - 2025-02-12
Added
Experimental
curiosupport.
Changed
Support for libraries has been redefined with
wraptvia 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.Support for greenlet-based libraries has been simplified. Detecting the current green library is now done by hub: if any
eventletorgeventfunction 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.Corrected type annotations:
aiologic.BoundedSemaphoreextendsaiologic.Semaphore, andaiologic.Semaphorereturns an instance ofaiologic.BoundedSemaphorewhen passingmax_value. Previously, the classes were independent in stubs, which was inconsistent with the behavior added in0.2.0.aiologic.lowlevel.current_thread()returnsOptional[threading.Thread]. Previously,threading.Threadwas returned, which was inconsistent with the special handling ofthreading._DummyThread.
Removed
Aliases to old modules (affects objects pickled before
0.13.0).Patcher-related exports (such as
aiologic.lowlevel.start_new_thread()).aiologic.lowlevel.current_async_library_cvar: thesniffioequivalent is deprecated and not used by modern libraries, and the performance impact of using it is only negative.
Fixed
asynciowas not considered running when the current task wasNone. 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.There was a missing branch for the optimistic case of non-waiting lock acquiring during race condition, which caused hangs in a free-threaded mode (
0.13.1regression).
0.13.1 - 2025-01-24
Fixed
Optimized the event removal in locks and semaphores. Previously, removing an event from the waiting queue was performed even when it was successfully set, which caused serious slowdown due to expensive O(n) operations. Now the removal is only performed in case of failure — on cancellation, in particular timeouts, and exceptions. (#5).
0.13.0 - 2025-01-19
Added
Type annotations via stubs. They are tested via
mypyandpyright, but may cause ambiguities in some IDEs that do not support overloading well.
Changed
The source code tree has been significantly changed for better IDEs support. The same applies to exports, which are now checker-friendly. Objects pickled in previous versions refer to the old tree, so aliases to old modules have been temporarily added. If you have these, it is strongly recommended to repickle them with this version to support future versions.
0.12.0 - 2024-12-14
Changed
Support for cancellation and timeouts has been dramatically improved. The
cancel()method (and accompanyingis_cancelled()) has been added to low-level events, which always returnsTrueafter the first successful call beforeset()andFalseotherwise. Previously, cancellation handling used the sameset()call, which resulted in false negatives, in particular unnecessary wakes.
Fixed
Added missing
awaitkeyword inaiologic.Condition, without which it did not restore the state of the wrappedaiologic.RLock(0.11.0regression).Priority queue initialization now ensures the heap invariant for the passed list. Previously, passing a list with an incorrect order of elements violated the order in which the priority queue elements were returned.
Low-level event classes are now created in exclusive mode, eliminating possible slowdown in method resolution.
0.11.0 - 2024-11-08
Added
aiologic.lowlevel.async_checkpoint()as an alias foraiologic.lowlevel.checkpoint().
Changed
The capabilities of
aiologic.Conditionhave been extended. Since there is no clear definition of which locks it should wrap, support for sync-only locks such asthreading.Lockhas been added. Passing another instance ofaiologic.Conditionis now supported too, and implies copying a reference to its lock. Also, passingNonenow specifies a different behavior wherebyaiologic.Conditionacts as lockless: its methods ignore the lock, which should help to useaiologic.Conditionas a simple replacement for removedaiologic.ParkingLot.
0.10.0 - 2024-11-04
Changed
aiologic.lowlevel.shield()function, which protects the call from cancellation, has been replaced byaiologic.lowlevel.repeat_if_cancelled(), which, depending on the library, either has the same action or repeats the call until it completes (successfully or unsuccessfully). Previouslyanyio.CancelScopewas used, which did not really shield the call from cancellation in ways outside ofanyio, such astask.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 affectsaiologic.Condition.
0.9.0 - 2024-11-02
Changed
aiologic.CountdownEventis significantly improved. Instead of using the initial value for the representation, it now uses the current value, so that its state is saved when thepicklemodule 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 newclear()method, which atomically resets the current value, has the same purpose.Changed the detection of the current green library after applying the monkey patch. Now the library that applied the patch is set only for the main thread, and the default library (usually
threading) is used for all others. This should eliminate redundancy when using worker threads, which previously had to explicitly setthreadingto avoid accidentally creating a new hub and hence an event loop.
0.8.1 - 2024-10-30
Fixed
The future used by the
asyncioevent could be canceled due to the event loop shutdown, causingInvalidStateErrorto be raised due to the callback execution. In particular, this was detected when usingcall_soon()for notification methods.
0.8.0 - 2024-10-26
Added
aiologic.CountdownEventas a countdown event, i.e. an event that wakes up all tasks when its value is down to zero (inspired byCountdownEventfrom .NET Framework 4.0).aiologic.RCapacityLimiteras a reentrant version ofaiologic.CapacityLimiter.
0.7.1 - 2024-10-20
Fixed
aiologic.Conditionwas referring to the wrong variable in its notification methods, which caused hangs (0.2.0regression).
0.7.0 - 2024-10-19
Added
aiologic.Barrieras a cyclic barrier, i.e. a barrier that tasks can pass through repeatedly.aiologic.SimpleQueueas a simplified queue withoutmaxsizesupport.aiologic.PriorityQueueas a priority queue, i.e. a queue that returns its smallest element (using theheapqmodule).
Changed
Once again the queues have been significantly changed.
aiologic.Queueand 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 commonwaitingproperty returning the number of all waiting ones has been added, andput_waitingandget_waitinghave been renamed toputtingandgetting.
0.6.0 - 2024-09-29
Added
forceparameter for green checkpoints (allowing to ignore context variables and environment variables).
Fixed
Added the missing
returnkeyword in queue implementations, without which they always returnedNoneingreen_get()andasync_get()methods (0.4.0regression).aiologic.Latchnow preserves the original task order for greenlets. Previously, the last greenlet always passed the barrier first when checkpoints were disabled.
0.5.0 - 2024-09-28
Added
aiologic.CapacityLimiteras a primitive similar toaiologic.Semaphore, but with ownership checking on release likeaiologic.Lock(thread-aware alternative toanyio.CapacityLimiter).aiologic.Latchas a single-use barrier, useful for ensuring that no thread or task sleeps after passing the barrier (inspired bystd::latchfrom C++20).forceparameter for async checkpoints (allowing to ignore context variables and environment variables).
0.4.0 - 2024-09-23
Changed
aiologic.SimpleQueuehas been replaced byaiologic.Queue(FIFO) andaiologic.LifoQueue(LIFO) with the same performance. Unlikeaiologic.SimpleQueue, they supportmaxsizeand their methods havegreen_andasync_prefixes, which follows the common naming convention. And since they have two different waiting queues, thewaitingproperty has been replaced withput_waitingandget_waiting.The build system has been changed to
pyproject.toml-only instead ofpyproject.toml+setup.cfg+setup.py. It should be more trusted by new users, since it does not contain executable code. (#1).
0.3.0 - 2024-09-23
Added
weakrefsupport for all high-level primitives.
Fixed
In the implementation of asynchronous checkpoints, the
awaitkeyword was missing, causing the checkpoints to have no effect (0.2.0regression).In
aiologic.REvent, woken tasks now inherit the timestamp of the one that woke them up, which eliminates false wakes in theset()+clear()scenario.The
setup.pycode has been simplified to avoidSetuptoolsDeprecationWarning.
0.2.0 - 2024-09-10
Added
eventletandgeventsupport.aiologic.PLockas a primitive lock, i.e. the fastest exclusive lock that does not do checks inrelease()methods.aiologic.REventas a reusable event, i.e. an event that supports theclear()method (async-aware alternative tothreading.Event).aiologic.ResourceGuardfor ensuring that a resource is only used by a single task at a time (thread-safe alternative toanyio.ResourceGuard).release()method toaiologic.Semaphoreandaiologic.BoundedSemaphore.max_valueparameter toaiologic.Semaphoreto allow creating an instance ofaiologic.BoundedSemaphorewithout importing it.waitingproperty to all primitives that returns the length of waiting queue (number of waiting threads and tasks).Non-blocking (non-waiting) mode for asynchronous lock acquiring methods. In this mode, it is guaranteed that there is no context switching even with checkpoints enabled (they are ignored). Unlike synchronous methods, lock acquiring is done on behalf of an asynchronous task. It is enabled by passing
blocking=False.The ability to specify the library used via environment variables, local thread fields, context variables.
Patch for the
threadingmodule that fixes the race inThread.join()on PyPy. Automatically applied when creating an instance of the threading event.Patch for the
eventletmodule that adds the necessary methods to support it. Automatically applied when creating an instance of eventlet event.Optional dependencies to guarantee support for third-party libraries (by specifying the minimum supported version).
Changed
Checkpoints are now optional. They can be enabled or disabled via environment variables or context variables. They are enabled by default for Trio.
_as_threadand_as_tasksuffixes are replaced bygreen_andasync_prefixes. Other options were considered, but only such prefixes ensure that the two different APIs are equal in convenience.aiologic.Lockandaiologic.RLockno longer support upgrading from synchronous to asynchronous access, which avoids some issues in complex scenarios.put()andaput()methods inaiologic.SimpleQueueare replaced by the commonput()method (withoutblockingandtimeoutparameters).aiologic.SimpleQueuenow throws its ownaiologic.QueueEmptyexception instead ofqueue.Empty.aiologic.lowlevel.ThreadEventis renamed toaiologic.lowlevel.GreenEventandaiologic.lowlevel.TaskEventis renamed toaiologic.lowlevel.AsyncEvent.set()method of low-level events now returnsbool:Trueif called for the first time,Falseotherwise.Identification functions are changed due to support for new libraries.
aiologic.lowlevel.current_thread()now returns athreading.Threadinstance instead of its identifier, and a new functionaiologic.lowlevel.current_thread_ident()has been added to get the identifier instead. The oldaiologic.lowlevel.current_token()andaiologic.lowlevel.current_task()are now differentiated by library type (current_green_andcurrent_async_). Additional functions with the_identsuffix have also been added to get an identifier instead of an object.
Removed
aiologic.ParkingLot: now each primitive uses its own lightweight code to handle the waiting queue.aiologic.lowlevel.AsyncioEventandaiologic.lowlevel.TrioEvent: they are now private.aiologic.lowlevel.Flags.markers: it is now private.
Fixed
In very rare cases, notification methods (
notify(),release(), and so on) did not notify newcomers due to a race, causing a hang. Now they are fully thread-safe.aiologic.Conditionnow uses timestamps for wakeups, which provides expected behavior in complex scenarios (and eliminates resource starvation). The same is implemented inaiologic.REvent.The
threadingevent now callslock.acquire(blocking=False)instead oflock.acquire(timeout=timeout)on a negative timeout, which is consistent with the behavior ofthreading.Event.The
default_factory()call inaiologic.lowlevel.Flag.get()is now performed outside the except block, which simplifies stack traces.Eager imports are replaced by lazy imports, so that only necessary ones are imported at runtime. This avoids some issues related to incompatibility of library versions.
0.1.1 - 2024-08-08
Fixed
aiologic.ParkingLotis now fair (and hence all other primitives too).
0.1.0 - 2024-08-06
Added
A mixed build system (
setup.py+setup.cfg+pyproject.toml): supports both direct (viasetup.py) and indirect (viabuild+pip) installation.Low-level functions:
sniffio-likeaiologic.lowlevel.current_async_library()function.anyio-likeaiologic.lowlevel.checkpoint()function.anyio-likeaiologic.lowlevel.checkpoint_if_cancelled()function.anyio-likeaiologic.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.
Low-level primitives:
aiologic.lowlevel.Flagas a one-slot alternative todict.setdefault().aiologic.lowlevel.TaskEventas a thread-safe async event.aiologic.lowlevel.ThreadEventas a one-time thread event.
Synchronization primitives:
aiologic.ParkingLotas a waiting queue for all other primitives (inspired bytrio.lowlevel.ParkingLot).aiologic.Semaphoreas a async-aware alternative tothreading.Semaphore.aiologic.BoundedSemaphoreas a async-aware alternative tothreading.BoundedSemaphore.aiologic.Lockas a thread-aware alternative toanyio.Lock.aiologic.RLockas a async-aware alternative tothreading.RLock.aiologic.Conditionas a async-aware alternative tothreading.Condition.aiologic.Eventas a thread-aware alternative toasyncio.Event.
Communication primitives:
aiologic.SimpleQueueas a queue that works in a semaphore style (async-aware alternative toqueue.SimpleQueue).