I played around with trying to run a Temporal worker on Modal. I didn’t do a ton of research upfront — I just kind of gave it a shot. I suspect this isn’t possible. Both use Python magic to do the things they do. This is what I tried.
import asyncioimport osimport modalfrom temporalio import activity, workflowfrom temporalio.client import Client, TLSConfigfrom temporalio.worker import Worker
@activity.defnasync def my_activity(name: str) -> str: return f"Hello, {name}!"
@workflow.defnclass MyWorkflow: @workflow.run async def run(self, name: str) -> str: return await workflow.execute_activity( my_activity, name, start_to_close_timeout=60 )
async def worker_main():
client = await Client.connect( "my.namespace.tmprl.cloud:7233", namespace="my.namespace", tls=TLSConfig( client_cert=bytes(os.environ["TEMPORAL_CLIENT_CERT"], "utf-8"), client_private_key=bytes(os.environ["TEMPORAL_CLIENT_KEY"], "utf-8"), ), ) worker = Worker( client, task_queue="modal-task-queue", workflows=[MyWorkflow], activities=[my_activity], ) await worker.run()
stub = modal.Stub("temporal-worker")
@stub.function( image=modal.Image.debian_slim().pip_install( [ "temporalio==1.5.1", ] ), secrets=[modal.Secret.from_name("modal-temporal-worker")],)def main(): asyncio.run(worker_main())
if __name__ == "__main__": with stub.run(): main.call()
Run with
modal run worker::main
The error trace looked like
RESPONSES: Mapping[int, Tuple[str, str]] = http.server.BaseHTTPRequestHandler.responses ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/temporalio/worker/workflow_sandbox/_restrictions.py", line 845, in __getattribute__ state.assert_child_not_restricted(__name) File "/usr/local/lib/python3.11/site-packages/temporalio/worker/workflow_sandbox/_restrictions.py", line 697, in assert_child_not_restricted raise RestrictedWorkflowAccessError(f"{self.name}.{name}")temporalio.worker.workflow_sandbox._restrictions.RestrictedWorkflowAccessError: Cannot access http.server.BaseHTTPRequestHandler.responses from inside a workflow. If this is code from a module not used in a workflow or known to only be used deterministically from a workflow, mark the import as pass through.
The above exception was the direct cause of the following exception:
Traceback (most recent call last): File "/pkg/modal/_container_io_manager.py", line 488, in handle_input_exception yield File "/pkg/modal/_container_entrypoint.py", line 134, in run_input res = imp_fun.fun(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/root/worker.py", line 52, in main asyncio.run(worker_main()) File "/usr/local/lib/python3.11/asyncio/runners.py", line 190, in run return runner.run(main) ^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/asyncio/runners.py", line 118, in run return self._loop.run_until_complete(task) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/asyncio/base_events.py", line 650, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "/root/worker.py", line 32, in worker_main worker = Worker( ^^^^^^^ File "/usr/local/lib/python3.11/site-packages/temporalio/worker/_worker.py", line 292, in __init__ self._workflow_worker = _WorkflowWorker( ^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/temporalio/worker/_workflow.py", line 118, in __init__ raise RuntimeError(f"Failed validating workflow {defn.name}") from errRuntimeError: Failed validating workflow MyWorkflow╭─ Error ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│ Failed validating workflow MyWorkflow │╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
I believe Temporal needs to patch the Python runtime in Worker executions to make Workflow code deterministic. It seems what Modal does may be interfering with that.
I don’t know if running a Temporal worker on Modal is possible or if I will trying to come back to this by it was interesting trying to mash these two together.