curio v1.0 Release Notes

Release Date: 2020-02-20 // about 4 years ago
  • **** SPECIAL NOTE ****: For the past few years, Curio has been been an ๐Ÿšš experimental project. However, as it moves towards having a more ๐Ÿš€ "stable" release, I feel that it is better served by being small as opposed to providing every possible feature like a framework. Thus, a wide range of ๐Ÿ”จ minor features have either been removed or refactored. If this broke ๐Ÿšš your code, sorry. Some features have been moved to the examples ๐Ÿšš directory. If something got removed and you'd like to lobby for its return, please submit a bug report. -- Dave

    ๐Ÿšš 02/19/2020 The Activation base class has been moved into curio.kernel.

    ๐Ÿ‘ 02/14/2020 Modified UniversalEvent to also support asyncio for completeness with UniversalQueue. This requires Curio and asyncio to be running in separate threads.

    โฑ 02/10/2020 Removed absolute time-related functions wake_at(), timeout_at(), and ignore_at(). This kind of functionality (if needed) can be reimplemented using the other sleep/timeout functions.

    02/10/2020 Added daemon flag to TaskGroup.spawn(). This can be used to create a disregarded task that is ignored for the purpose of reporting results, but which is cancelled when the TaskGroup goes away.

    02/10/2020 Added spawn_thread() method to TaskGroup. Can be used to create an AsyncThread that's attached to the group. AsyncThreads follow the same semantics as tasks.

    โฑ 02/09/2020 Removed schedule() function. Use sleep(0).

    ๐Ÿšฆ 02/07/2020 Removed all support for signal handling. Signal handling, by its very nature, is a tricky affair. For instance, signals can only be handled in the main execution thread and there are all sorts of other issues that can arise when mixed with threads, subprocesses, locks, and other things. Curio already provides all of the necessary support to implement signal handling if you rely on UniversalEvent or UniversalQueue objects. Here is a simple example::

               import signal
               import curio
    
               evt = curio.UniversalEvent()
    
               def signal_handler(signo, frame):
                   evt.set()
    
               async def wait_for_signal():
                   await evt.wait()
                   print("Got a signal!")
    
               signal.signal(signal.SIGHUP, signal_handler)
               curio.run(wait_for_signal)
    

    ๐Ÿšš 02/07/2020 Removed iteration support from queues. Queues in the standard library don't support iteration.

    ๐Ÿšš 02/06/2020 Removed metaprogramming features not used in the implementation of Curio itself. These include:

               @async_thread
               @cpubound
               @blocking
               @sync_only
               AsyncABC
    
           Libraries/frameworks that use Curio should be responsible
           for their own metaprogramming.  Their removal is meant to
           make Curio smaller and easier to reason about.
    

    ๐Ÿ‘ป 02/06/2020 Added exception and exceptions attributes to TaskGroup. Can be used to check for errors. For example:

               async with TaskGroup(wait=all) as g:
                   await g.spawn(coro1)
                   await g.spawn(coro2)
                   ...
               if any(g.exceptions):
                   print("An error occurred")
    
           Obviously, you could expand that to be more detailed. 
    

    ๐Ÿšš 02/04/2020 Removed TaskGroupError exception and simplified the error handling behavior of task groups. If tasks exit with an exception, that information is now obtained on the task itself or on the .result attribute of a task group. For example:

               async with TaskGroup() as g:
                   t1 = await g.spawn(coro1)
                   t2 = await g.spawn(coro2)
                   ...
    
               try:
                   r = t1.result
               except WhateverError:
                   ...
    
               # Alternatively
               try:
                   r = g.result
               except WhateverError:
                   ...
    
           This simplifies both the implementation of task groups as well as 
           a lot of code that utilizes task groups.  Exceptions are no longer
           wrapped up in layer-upon-layer of other exceptions.  There is a risk
           of exceptions passing silently if you don't actually check the result
           of a task group.  Don't do that. 
    

    02/03/2020 Added convenience properties to TaskGroup. If you want the result of the first completed task, use .result like this:

               async with TaskGroup(wait=any) as g:
                   await g.spawn(coro1)
                   await g.spawn(coro2)
                   ...
    
               print('Result:', g.result)
    
           If you want a list of all results *in task creation order*
           use the .results property:
    
               async with TaskGroup(wait=all) as g:
                   await g.spawn(coro1)
                   await g.spawn(coro2)
                   ...
    
               print('Results:', g.results)
    
           Note: Both of these are on the happy path.  If any kind of
           exception occurs, task groups still produce a
           TaskGroupError exception.
    

    ๐Ÿ‘ 01/29/2020 Added support for contextvars. The behavior of context variables in the context of Curio and threads is not always well defined. As such, this is a feature that requires explicit user opt-in to enable. To do it, you need provide an alternative Task class definition to the kernel like this:

               from curio.task import ContextTask
               from curio import Kernel
    
               with Kernel(taskcls=ContextTask) as kernel:
                    kernel.run(coro)
    
           Alternatively, you can use:
    
               from curio import run
               run(coro, taskcls=ContextTask)
    

    01/29/2020 Added optional keyword-only argument taskcls to Kernel. This can be used to provide alternative implementations of the internal Task class used to wrap coroutines. This can be useful if you want to subclass Task or implement certain task-related features in a different way.

    ๐Ÿ”จ 10/15/2019 Refactored task.py into separate files for tasks and time management.

    ๐Ÿšš 09/29/2019 Removed Promise. Not documented, but also want to rethink the whole design/implementation of it. The "standard" way that Python implements "promises" is through the Future class as found in the concurrent.futures module. However, Futures are tricky. They often have callback functions attached to them and they can be cancelled. Some further thought needs to be given as to how such features might integrate with the rest of Curio. Code for the old Promise class can be found the examples directory.

    ๐Ÿšš 09/26/2019 Removed support for the Asyncio bridge. It wasn't documented and there are many possible ways in which Curio might potentially interact with an asyncio event loop. For example, using queues. Asyncio interaction may be revisited in the future.

    ๐Ÿ‘ 09/13/2019 Support for context-manager use of spawn_thread() has been withdrawn. It's a neat feature, but the implementation is pretty hairy. It also creates a situation where async functions partially run in coroutines and partially in threads. All things equal, it's probably more sane to make this kind of distinction at the function level, not at the level of code blocks.

    ๐Ÿšš 09/11/2019 Removed AsyncObject and AsyncInstanceType. Very specialized. Not used elsewhere in Curio. Involves metaclasses. One less thing to maintain.

    09/11/2019 I want to use f-strings. Now used for string formatting everywhere except in logging messages. Sorry Python 3.5.

    ๐Ÿšš 09/11/2019 Removed the allow_cancel optional argument to spawn(). If a task wants to disable cancellation, it should explicitly manage that on its own.

    ๐Ÿšš 09/11/2019 Removed the report_crash option to spawn(). Having it as an optional argument is really the wrong place for this. On-by-default crash logging is extremely useful for debugging. However, if you want to disable it, it's annoying to have to go change your source code on a task-by-task basis. A better option is to suppress it via configuration of the logging module. Better yet: write your code so that it doesn't crash.

    ๐Ÿ”จ 09/10/2019 Some refactoring of some internal scheduling operations. The SchedFIFO and SchedBarrier classes are now available for more general use by any code that wants to implement different sorts of synchronization primitives.

    ๐Ÿšš 09/10/2019 Removed the abide() function. This feature was from the earliest days of Curio when there was initial thinking about the interaction of async tasks and existing threads. The same functionality can still be implemented using run_in_thread() or block_in_thread() instead. In the big picture, the problem being solved might not be that common. So, in the interest of making Curio smaller, abide() has ridden off into the sunset.

    ๐Ÿšš 09/08/2019 Removed BoundedSemaphore.

    ๐Ÿšš 09/03/2019 Removed the experimental aside() functionality. Too much magic. Better left to others.

    ๐Ÿšš 09/03/2019 Removed the gather() function. Use TaskGroup instead.

    09/03/2019 Removed get_all_tasks() function.

    ๐Ÿšš 09/03/2019 Removed the Task.pdb() method.

    ๐Ÿšš 09/03/2019 Removed the Task.interrupt() method.

    09/03/2019 The pointless (and completely unused) name argument to TaskGroup() has been removed.

    08/09/2019 Exceptions raised inside the Curio kernel itself are no longer reported to tasks. Instead, they cause Curio to die. The kernel is never supposed to raise exceptions on its own--any exception that might be raised is an internal programming error. This change should not impact user-level code, but might affect uncontrolled Ctrl-C handling. If a KeyboardInterrupt occurs in the middle of kernel-level code, it will cause an uncontrolled death. If this actually matters to you, then modify your code to properly handle Ctrl-C via signal handling.

    04/14/2019 The Channel.connect() method no longer implements auto-retry. In practice, this kind of feature can cause interference. Better to let the caller do the retry if they want.

    04/14/2019 Simplified the implementation and semantics of cancellation control. The enable_cancellation() function has been removed. It is now only possible to disable cancellation. Nesting is still allowed. Pending cancellation exceptions are raised on the first blocking call executed when reenabled. The check_cancellation() function can be used to explicitly check for cancellation as before.

    ๐Ÿ›  03/09/2019 Fixed a bug in network.open_connection() that was passing arguments incorrectly. Issue #291.

    ๐Ÿšš 11/18/2018 Removed code that attempted to detect unsafe async generator functions. Such code is now executed without checks or warnings. It's still possible that code in finally blocks might not execute unless you use curio.meta.finalize() or a function such as async_generator.aclosing() (third party). The @safe_generator decorator is now also unnecessary.

    ๐Ÿšš 11/11/2018 Removed the wait argument to TaskGroup.join(). The waiting policy for task groups is specified in the TaskGroup constructor. For example:

               with TaskGroup(wait=any) as group:
                    ...
    

    ๐ŸŒฒ 09/05/2018 Tasks submitted to Kernel.run() no longer log exceptions should they crash. Such tasks already return immediately to the caller with the exception raised.

    09/05/2018 Refinement to Kernel.exit() to make sure the kernel shuts down regardless of any exceptions that have occurred. See Issue #275.

    04/29/2018 New task-related function. get_all_tasks() returns a list of all active Tasks. For example:

               tasks = await get_all_tasks()
    
           Tasks also have a where() method that returns a (filename, lineno) tuple
           indicating where they are executing.
    

    04/29/2018 Curio now properly allows async context managers to be defined using context.asynccontextmanager. Issue #247.

    04/29/2018 Removed the cancel_remaining keyword argument from TaskGroup.next_done()

    04/28/2018 Added new "object" wait mode to TaskGroup. It waits for the first non-None result to be returned by a task. Then all remaining tasks are cancelled. For example:

               async def coro1():
                   await sleep(1)
                   return None
    
               async def coro2():
                   await sleep(2)
                   return 37
    
               async def coro3():
                   await sleep(3)
                   return 42
    
               async with TaskGroup(wait=object) as tg:
                   await tg.spawn(coro1)      # Ignored (returns None)
                   await tg.spawn(coro2)      # Returns result
                   await tg.spawn(coro3)      # Cancelled
    
               print(tg.completed.result)  # -> 37
    

    ๐Ÿšš 04/27/2018 Removed the ignore_result keyword argument to TaskGroup.spawn(). It's not really needed and the extra complexity isn't worth it.

    04/27/2018 Added TaskGroup.next_result() function. It's mostly a convenience function for returning the result of the next task that completes. If the task failed with an exception, that exception is raised.

    04/14/2018 Changed the method of spawning processes for run_in_process to use the "spawn" method of the multiprocessing module. This prevents issues of having open file-descriptors cloned by accident via fork(). For example, as in Issue #256.