python / python/mypy

Feature Design Discussion: Blocking Analysis

Abierto
#12,649 3 comentarios 4 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

feature topic-async
Lenguaje dominante
Python
Estrellas
20.6k
Forks
3.3k
Merge medio
1 d 18 h
PR fusionados (30 d)
54

Descripción

We would like to facilitate a migration from a large (formerly python 2 codebase) synchronous codebase to asyncio. Our codebase will forever probably contain a mixture of asyncio and synchronous code.

However, asyncio can be crippled if someone accidentally adds something like a time.sleep that blocks the event loop, or any synchronous network operation - or even a subprocess.run. In order to combat this, we are banning the function time.sleep and replacing it with something like this:

def blocking_sleep(t: float) -> None:
    assert_no_running_loop()
    time.sleep(t)

This covers the runtime case - instead of blocking at runtime we will raise an exception. This means our tests are likely to catch such issues. But not all of our code has tests. We would like to catch issues in mypy as well. One could imagine it is fairly simple to use either a mypy plugin or similar analysis tool to prevent things like this:

async def foo() -> None:
    blocking_sleep(10)

But the call to blocking_sleep may not be directly within the function foo and we still want to catch this.

One solution would be to make a monad-like construct Blocking like this:

from mypy_extensions import Blocking

def blocking_sleep(t: float) -> Blocking[None]:
    assert_no_running_loop()
    time.sleep(t)

The semantics would be simple - a call to a blocking callable would error if it is not itself within a blocking function. The only way to call it from outside a blocking function would be to use something like asyncio.to_thread or some utiltiy like allow_unsafe_blocking_call().

It would probably be feasible to annotate only a few function e.g. blocking_sleep and write a tool that automatically propagates all of the Blocking annotations and rewrites the code. This would work. The main problem is that the Blocking annotations may be annoying to developers/cause developer friction until more codebase is async.

The upside to this approach is that it facilites a migration to asyncio through a relatively simple substitution, blocking functions become async and calls to blocking functions get an await. One might note that this is mostly because annotating the blocking-ness is essentially isomorphic to annotating async-ness, but annotating the blocking-ness is much safer than a bulk conversion to asyncio becuase it won't have any runtime effect.

A second solution would be for a new mypy pass that, given a few root functions like blocking_sleep and other known blocking functions, automatically propagates the blocking-ness throughout and raises errors on invalid callsites. This has the advantage that we don't have to annotate everything. The disadvantages are that you can't tell if a function is blocking from reading it (although mypy could perhaps expose a query interface to ask it). Error generation may also be more difficult because we will need to produce some sort of callstack at which a function could block. We may need some way to be able to tell it that it is wrong, e.g. annotate a function as explicitly NotBlocking or similar.

I believe some sort of this feature will be required for asyncio to work on our large codebase. To make it will integrated with builtins and typeshed we would need a PEP, but I think it is fine for us to discuss here what the ideal state would be (internally we may use a mypy plugin or similar to rewrite typeshed stuff that should be blocking) without worrying about things like whether a PEP is necessary.

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Empieza comparando el enfoque propuesto de anotaciones Blocking con un nuevo pase de análisis de mypy, incluido cómo encajarían los plugins, typeshed y las anulaciones explícitas de NotBlocking. Se considera terminado cuando se haya llegado a un acuerdo sobre un diseño concreto, el comportamiento de los informes de errores y la ruta de integración, en lugar de implementar un archivo o una prueba identificados.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
compilers, tooling
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Estancado
Claridad
Necesita aclaración
Aptitud para principiantes
20/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.