time module
Monty implements two functions from time, both answered by the host rather
than by the interpreter: time.time() and time.sleep().
asyncio.sleep() is documented in asyncio.md; it shares
time.sleep()’s handling of the delay argument.
Only time and sleep exist. Every other name — the monotonic clocks
(monotonic, perf_counter, process_time, thread_time and their _ns
forms), time_ns, struct_time, localtime, gmtime, mktime, strftime,
strptime, asctime, ctime, tzset, timezone, altzone, daylight,
tzname — raises AttributeError rather than being stubbed.
time.time() and time.sleep() suspend the sandbox with an OS call, the way
date.today() and datetime.now() do. A host that answers neither leaves them
raising:
- Through the bindings (
pydantic_monty,@pydantic/monty) they reach theos=handler. With no handler,RuntimeError: 'time.time' is not supported in this environment. A Rustmonty-poolcaller has noos=handler: each call arrives as aTurnEvent::OsCallto answer withresume. - Under standard (non-suspending) execution —
MontyRun::run, and so themontyCLI running a file without mounts —time.time()is answered from the runner’sHostClock(the machine’s clock by default,HostClock::DeniedraisingNotImplementedError), buttime.sleep()always raisesNotImplementedError: OS function 'time.sleep' not implemented with standard execution. Nothing waits inside the interpreter: a wait has to happen where a deadline can be enforced.
Since the host performs the wait, how long time.sleep() actually sleeps is the
host’s choice. A host may cap it, ignore it, or refuse it.
time.time() returns whatever the host answered with, so it need not agree with
the machine’s clock, need not advance between calls, and is not guaranteed to
move forward at all. It is a float of seconds since the Unix epoch, as in
CPython.
max_feed_duration and max_turn_duration measure execution time, and the clock
stops while the sandbox is suspended — so a sleep costs nothing against them,
however long it lasts. What bounds sleeping instead:
max_suspensions(default 1000), since each sleep is one suspension (two when anasyncio.sleep()answered with a future is awaited later). The pools and the CLI enforce it; a direct Rust host counts suspensions itself.- The host’s own turn deadline (
request_timeoutfor the pools).
A sandbox that sleeps in a loop therefore ends its turn on the host’s deadline
rather than on MemoryError/time limits. See
resource_limits.md.
The OverflowError past ~9223372036.85 seconds is CPython’s,
timestamp out of range for C PyTime_t. What does not happen is the OSError: [Errno 22] Invalid argument CPython’s platform sleep
raises for a delay just under that boundary: Monty accepts it and passes it
to the host, where it becomes a wait that outlives any turn deadline.
The hosts Monty ships cut long sleeps short rather than wait them out:
pydantic_monty’s OSAccess at its max_sleep (default 10 seconds, None
for no cap) and the monty CLI at --max-sleep (default 10). Sandboxed code
sees the call return early with no error, where CPython would have waited, so
time.time() advances by less than the sleep asked for.
A host may answer time.sleep() with a value, and it is discarded:
time.sleep() always evaluates to None. A host answering it with a future
instead gets RuntimeError: time.sleep cannot be answered with a future in the
sandbox — the call is a wait, so there is nothing to resume into.