Releases: x42005e1f/aiologic
Releases · x42005e1f/aiologic
0.16.0
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.
- It now updates not only the
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
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 slee...
0.14.0
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
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
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
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
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
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
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.