Можно ли программно создать стек (один или несколько кадров стека) в CPython и начать выполнение с произвольной кодовой точки? Представьте себе следующий сценарий:
У вас есть механизм рабочего процесса, в котором рабочие процессы могут быть написаны в сценарии на Python с некоторыми конструкциями (например, ветвлением, ожиданием/присоединением), которые являются вызовами механизма рабочего процесса.
Блокирующий вызов, такой как ожидание или соединение, устанавливает условие прослушивателя в механизме диспетчеризации событий с каким-либо постоянным резервным хранилищем.
У вас есть сценарий рабочего процесса, который вызывает условие ожидания в движке, ожидая некоторого условия, о котором будет сообщено позже. Это устанавливает прослушиватель в механизме диспетчеризации событий.
Состояние сценария рабочего процесса, соответствующие кадры стека, включая счетчик программ (или эквивалентное состояние), сохраняются, поскольку состояние ожидания может возникнуть через несколько дней или месяцев.
Тем временем механизм рабочего процесса может быть остановлен и перезапущен, а это означает, что должна быть предусмотрена возможность программного сохранения и восстановления контекста сценария рабочего процесса.
Механизм диспетчеризации событий запускает событие, которое подхватывается условием ожидания.
Механизм рабочего процесса считывает сериализованное состояние и стек и реконструирует поток со стеком. Затем он продолжает выполнение в точке, где была вызвана служба ожидания.
Вопрос
Можно ли это сделать с помощью немодифицированного интерпретатора Python? Более того, может ли кто-нибудь указать мне какую-нибудь документацию, которая может охватывать подобные вещи, или пример кода, который программно создает кадр стека и начинает выполнение где-то в середине блока кода?
Редактировать: Чтобы уточнить «немодифицированный интерпретатор Python», я не возражаю против использования C API (достаточно ли информации в PyThreadState для этого?), но я не хочу ковыряться в внутренности интерпретатора Python и необходимость создания модифицированного.
Обновление: в результате некоторого начального исследования можно получить контекст выполнения с помощью PyThreadState_Get()
. Это возвращает состояние потока в PyThreadState
(определено в pystate.h
), которое имеет ссылку на кадр стека в frame
. Фрейм стека хранится в структуре с типом PyFrameObject
, который определен в frameobject.h
. PyFrameObject
имеет поле f_lasti
(поддерживает bobince), у которого программный счетчик выражен как смещение от начала кодового блока.
Это последнее в некотором роде хорошая новость, потому что это означает, что пока вы сохраняете фактически скомпилированный блок кода, вы должны быть в состоянии реконструировать локальные переменные для стольких кадров стека, сколько необходимо, и перезапускать код. Я бы сказал, что это означает, что это теоретически возможно без необходимости создавать модифицированный интерпретатор Python, хотя это означает, что код все еще, вероятно, будет неудобным и тесно связанным с конкретными версиями интерпретатора.
Три оставшиеся проблемы:
Состояние транзакции и откат «saga», которые, вероятно, могут быть достигнуты путем взлома метакласса, который можно использовать для создания O/R-преобразователя. Однажды я создал прототип, поэтому у меня есть четкое представление о том, как это можно сделать.
Надежная сериализация состояния транзакций и произвольных локальных переменных. Этого можно добиться, прочитав
__locals__
(доступный из кадра стека) и программно сконструировав вызов pickle. Тем не менее, я не знаю, какие здесь могут быть ошибки, если таковые имеются.Версионирование и обновление рабочих процессов. Это несколько сложнее, так как система не предоставляет никаких символических привязок для узлов рабочего процесса. Все, что у нас есть, это якорь. Для этого нужно определить смещения всех точек входа и сопоставить их с новой версией. Вероятно, это можно сделать вручную, но я подозреваю, что это будет сложно автоматизировать. Это, вероятно, самое большое препятствие, если вы хотите поддерживать эту возможность.
Обновление 2: PyCodeObject
(code.h
) содержит список сопоставлений адресов (f_lasti
)-> номеров строк в PyCodeObject.co_lnotab
(поправьте меня, если я ошибаюсь). Это может быть использовано для облегчения процесса миграции для обновления рабочих процессов до новой версии, поскольку указатели замороженных инструкций могут быть сопоставлены с соответствующим местом в новом сценарии, выполненным с точки зрения номеров строк. Все еще довольно грязно, но немного более многообещающе.
Обновление 3: я думаю, что ответом на этот вопрос может быть Stackless Python. Вы можете приостановить задачи и сериализовать их. Я не понял, будет ли это работать и со стеком.