#975

Let checkout limits override a restored dump's limits on load_session

Openalex-dropened 24d ago

Summary

load_session restores the dump's own ResourceLimits and ignores the checkout(limits=...) config. The stubs say so, so this is a request rather than a bug report: please give the host a way to make the checkout's limits win on restore (or to set limits after load_session).

The reason: resource limits are host policy, not session state. A host that stores dumps between turns cannot today

  • lower max_memory or max_feed_duration_secs in a release and have it apply to sessions that already exist, until each is reset;
  • treat limits as a per-deployment setting when the dumps live outside the host's control (e.g. in per-user storage). #873 rightly puts provenance and integrity on the caller, and we will sign our dumps, but even an authentic dump from last month carries last month's limits.

Reproduction (pydantic-monty 1.1.0, Python 3.13, Linux)

import asyncio, time
from pydantic_monty import AsyncMonty, MontyError

async def main():
    async with AsyncMonty(min_processes=1) as pool:
        async with pool.checkout(limits={"max_feed_duration_secs": 60.0, "max_memory": 400 * 2**20}) as s:
            await s.feed_run("x = 1")
            state = await s.dump()

    async with AsyncMonty(min_processes=1) as pool:
        async with pool.checkout(limits={"max_feed_duration_secs": 1.0, "max_memory": 10 * 2**20}) as s:
            await s.load_session(state)
            t = time.monotonic()
            try:
                await asyncio.wait_for(s.feed_run("while True:\n    pass"), timeout=5)
            except MontyError as exc:
                print(f"stopped after {time.monotonic() - t:.1f}s: {type(exc).__name__}")
            except asyncio.TimeoutError:
                print("not stopped in 5s: the dump's 60s limit won over the checkout's 1s")

    async with AsyncMonty(min_processes=1) as pool:
        async with pool.checkout(limits={"max_feed_duration_secs": 5.0, "max_memory": 10 * 2**20}) as s:
            await s.load_session(state)
            try:
                n = await s.feed_run("s = 'x' * (50 * 1024 * 1024)\nlen(s)")
                print(f"allocated {n} bytes under a 10 MiB checkout limit: the dump's 400 MiB won")
            except MontyError as exc:
                print(f"refused: {type(exc).__name__}")

asyncio.run(main())

Output:

not stopped in 5s: the dump's 60s limit won over the checkout's 1s
allocated 52428800 bytes under a 10 MiB checkout limit: the dump's 400 MiB won

A fresh session under the same 1 s / 10 MiB checkout stops the loop at 1.0 s with MontyRuntimeError. Same result on 0.0.23 with max_duration_secs.

What would help

Any of these would do:

  1. checkout(limits=...) applies on top of a restored dump, with the dump's limits used only where the checkout passes None. This seems like the least surprising default, since the checkout is what the host is asking for now.
  2. A load_session(state, limits=...) argument, or a session.set_limits(...) valid between feeds.
  3. If the current behaviour is deliberate, a note in the load_session docstring saying that hosts who need limits as policy must reject or re-dump sessions whose stored limits differ, so it is a documented contract rather than a surprise.

The pool's request_timeout is a workable backstop for duration in the meantime, but there is nothing equivalent for memory.

Activity

0

No activity yet.