¿Es posible construir mediante programación una pila (uno o más marcos de pila) en CPython y comenzar la ejecución en un punto de código arbitrario? Imagine el siguiente escenario:¿Es posible construir programáticamente un marco de pila Python y comenzar la ejecución en un punto arbitrario en el código?
Tiene un motor de flujo de trabajo, donde los flujos de trabajo puede ser escrito en Python con algunas construcciones (por ejemplo, ramificación, en espera/unión) que son llamadas al motor de flujo de trabajo.
Una llamada de bloqueo, como esperar o unir establece una condición de oyente en un motor de envío de eventos con un almacén de respaldo persistente de algún tipo.
Tiene una secuencia de comandos de flujo de trabajo, que llama a la condición de espera en el motor, a la espera de alguna condición que se señalará más tarde. Esto configura el oyente en el motor de envío de eventos.
El estado del script de flujo de trabajo, los marcos de pila relevantes, incluido el contador del programa (o estado equivalente) se conservan, ya que la condición de espera podría ocurrir días o meses después.
Mientras tanto, el motor de flujo de trabajo podría detenerse y reiniciarse, lo que significa que debe ser posible almacenar y reconstruir el contexto del script de flujo de trabajo mediante programación.
El motor de envío de eventos activa el evento de que la condición de espera se activa.
El motor de flujo de trabajo lee el estado serializado y apila y reconstruye un hilo con la pila. A continuación, continúa la ejecución en el punto donde se llamó al servicio de espera.
La pregunta
se puede hacer esto con un intérprete de Python sin modificar? Mejor aún, ¿alguien puede indicarme algún tipo de documentación que pueda cubrir este tipo de cosas o un ejemplo de código que construye programáticamente un marco de pila y comienza la ejecución en algún lugar en medio de un bloque de código?
Editar: Para aclarar 'intérprete de python sin modificar', no me importa el uso de la API C (¿hay suficiente información en un PyThreadState hacer esto?), Pero no quiero ir a hurgar los componentes internos del intérprete de Python y tener que construir uno modificado.
Actualización: De alguna investigación inicial, uno puede obtener el contexto de ejecución con PyThreadState_Get()
. Esto devuelve el estado del hilo en un PyThreadState
(definido en pystate.h
), que tiene una referencia al marco de la pila en frame
. Un marco de pila se mantiene en una estructura typedef'd a PyFrameObject
, que se define en frameobject.h
. PyFrameObject
tiene un campo f_lasti
(apoyos a bobince) que tiene un contador de programa expresado como un desplazamiento desde el comienzo del bloque de código.
Esta última es una especie de buena noticia, porque significa que siempre que conserve el bloque de código compilado real, debería ser capaz de reconstruir los locales para tantos marcos de pila como sea necesario y reiniciar el código.Yo diría que esto significa que es teóricamente posible sin tener que hacer un intérprete de pitón modificado, aunque significa que el código probablemente todavía esté complicado y estrechamente vinculado a versiones específicas del intérprete.
Los tres problemas que quedan son:
estado de la transacción y el desmantelamiento 'saga', lo que probablemente se puede lograr mediante el tipo de piratería metaclase se podría usar para construir un asignador de O/R. Construí un prototipo una vez, así que tengo una buena idea de cómo se puede lograr esto.
Estado de transacciones de serialización robusta y locales arbitrarios. Esto se puede lograr leyendo
__locals__
(que está disponible desde el marco de pila) y construyendo programáticamente una llamada a pickle. Sin embargo, no sé qué podría haber aquí, si es que hay alguno.Versiones y actualizaciones de flujos de trabajo. Esto es un poco más complicado, ya que el sistema no proporciona ningún anclaje simbólico para los nodos de flujo de trabajo. Todo lo que tenemos es el ancla Para hacer esto, uno debería identificar las compensaciones de todos los puntos de entrada y asignarlos a la nueva versión. Probablemente sea factible hacerlo manualmente, pero sospecho que sería difícil automatizarlo. Este es probablemente el mayor obstáculo si desea soportar esta capacidad.
Actualización 2:PyCodeObject
(code.h
) tiene una lista de addr (f_lasti
) -> asignaciones de número de línea en PyCodeObject.co_lnotab
(corríjanme si mal aquí). Esto podría usarse para facilitar un proceso de migración para actualizar los flujos de trabajo a una nueva versión, ya que los punteros de instrucciones congelados podrían asignarse al lugar apropiado en el nuevo guión, hecho en términos de los números de línea. Aún bastante desordenado, pero un poco más prometedor.
Actualización 3: Creo que la respuesta podría ser Stackless Python. Puede suspender tareas y serializarlas. No he averiguado si esto también funcionará con la pila.
Gran pregunta - ¡De seguro que odio ser el que tiene que depurar este proyecto! –