Skip to main content

Task scopes for AnyIO

Project description

This library implements scoped taskgroups / nurseries.

Rationale

Large programs often consist of building blocks which depend on each other. Those dependencies may be non-trivial, aren’t always linear, and generally form some sort of directed acyclic graph instead of a nice linear or hierarchical set of relationships.

Let’s invent an example.

Your main code runs some admin module, which requires a support library, which connects to a database. Halfway through it encounters an error, thus loads an error handler, which also uses the database.

Another part of your program also needs the error handler database connections and loading a handler are expensive, so you want to re-use them.

Later the admin code terminates. It can’t unload the error handler because that other code still needs it.

This is a problem because you like to use Structured Programming principles. Thus you need to jump through interesting hoops getting all of this connected up and keeping track of each module’s users.

Worse, assume that your code dies with a fatal exception. The exception typically propagates through your code and cancels the database connection before the error handler has a chance to log the problem. This happens randomly, depending on which cancelled task runs first, so you have a lot of fun trying to reproduce the problem and debug all of this.

AsyncScope can help you.

AsyncScope keeps track of your program’s building blocks. It remembers which parts depend on which other parts, prevents cyclic dependencies, and terminates a scope as soon as nobody uses it any more.

Now your error handler stays around exactly as long as you need it, your database connection won’t die while the error handler (or any other code, for that matter) requires it, your error gets logged correctly, and you find the problem easily.

Usage

Wrap your main code in async with main_scope(): ....

Start a service task (i.e. something you depend on) like this:

from asyncscope import service

srv = await service(name, some_service, *params)

srv is whatever object the service intends you to use.

Note that service is not used as an async context manager. This is intentional; it will be closed when you leave its scope.

The service’s setup code typically looks like this:

from asyncscope import scope

async def some_service(*params):
   s = await start_your_service(*params)
   await scope.register(s)
   await scope.no_more_dependents()
   s.close_your_service_cleanly()

Alternately, if the service is created by an async context manager:

async def some_service(*params):
   async with your_service_context(*params) as s:
      await scope.register(s)
      await scope.no_more_dependents()

Every scope has a separate taskgroup which you can access by calling scope.spawn(). This function returns a cancel scope that wraps the new tasks, so that you can cancel it if you need to. All tasks started this way are also auto-cancelled when the scope exits.

Your service must call scope.register() exactly once, otherwise the scopes waiting for it to start will wait forever. (They’ll get cancelled if your scope’s main task exits before doing so.)

The current scope is available as the scope context variable.

The examples directory contains some sample code.

Cancellation semantics

When a scope exits (either cleanly or when it raises an error that escapes its taskgroup), all scopes depending on it are cancelled immediately, in parallel. Then those it itself depends on are terminated cleanly and in-order.

This also happens when a scope’s main task ends.

“Clean termination” means that the scope’s call to no_more_dependents() returns. If there is no such call, the scope’s tasks are cancelled.

TODO: write a service which your code can use to keep another service alive for a bit.

Code structure

A scope’s main code typically looks like this:

  • do whatever you need to start the service. This code may start other scopes it depends on. Note that if the scope is already running, service simply returns its existing service object.

  • call “register(serice_object)”

  • await no_more_dependents() (subordinate task) or wait for SIGTERM (daemon main task) or terminate (main task’s job is done)

  • cleanly stop your service.

If no_more_dependents is not used, the scope will be cancelled.

You don’t need to manage the scopes you started in the first step. asyncscopes handles this for you.

Scopes typically don’t need to access its own scope object. It’s stored in a contextvar and can be retrieved via scope.get() if you need it. For most uses, however, asyncscope’s global scope object accesses the current scope transparently.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

asyncscope-0.5.5.tar.gz (9.6 kB view details)

Uploaded Source

Built Distribution

asyncscope-0.5.5-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

File details

Details for the file asyncscope-0.5.5.tar.gz.

File metadata

  • Download URL: asyncscope-0.5.5.tar.gz
  • Upload date:
  • Size: 9.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.8.2 requests/2.25.1 setuptools/63.2.0 requests-toolbelt/0.9.1 tqdm/4.57.0 CPython/3.10.5

File hashes

Hashes for asyncscope-0.5.5.tar.gz
Algorithm Hash digest
SHA256 0b6aa61ae449e2f573665acffc6772a6c5e07c5bfc1aa04988f423809664b8cd
MD5 e5698e1c1eafbdd9304a987117689e26
BLAKE2b-256 590f9e1980cb61d0294434b1c99596e10f1ef9d7aca3d151d3b26713b2ea5533

See more details on using hashes here.

Provenance

File details

Details for the file asyncscope-0.5.5-py3-none-any.whl.

File metadata

  • Download URL: asyncscope-0.5.5-py3-none-any.whl
  • Upload date:
  • Size: 8.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.8.2 requests/2.25.1 setuptools/63.2.0 requests-toolbelt/0.9.1 tqdm/4.57.0 CPython/3.10.5

File hashes

Hashes for asyncscope-0.5.5-py3-none-any.whl
Algorithm Hash digest
SHA256 f8b76138e862d3a34d6aefccb163bd8281e6badec6dd44f91629e24a72f7fc67
MD5 ea92a6b841fee28d24f5d5c829d4922d
BLAKE2b-256 39fe0600ee041fc705e1a035efa10c86846ea611e21d94588cdd2c27624ad494

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page