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 asyncio
import os
import modal
from temporalio import activity, workflow
from temporalio.client import Client, TLSConfig
from temporalio.worker import Worker
@activity.defn
async def my_activity(name: str) -> str:
return f"Hello, {name}!"
@workflow.defn
class 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 err
RuntimeError: 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.