## Idle


## idle.IdleResource


A lazily-loaded value that unloads itself once idle past its TTL.


Usage

``` python
idle.IdleResource(
    load,
    unload,
    *,
    ttl_s,
)
```


[load](config.md#athome.config.load) is awaited at most once per loaded lifetime, under a lock, so concurrent first uses trigger exactly one load; a load that raises leaves the resource unloaded and the next [use()](idle.md#athome.idle.IdleResource.use) retries fresh. `unload` closes over the consumer's own state -- the resource holds only the loaded `T` -- and runs from the reaper ([sweep()](idle.md#athome.idle.IdleResource.sweep) / [run()](train.md#athome.train.run)) once no use is in flight and the idle window has elapsed, or immediately from [discard()](idle.md#athome.idle.IdleResource.discard).


#### Attributes


`load: Callable[[], Awaitable[T]]`  
Awaited to produce the value; called once per loaded lifetime.

`unload: Callable[[], Awaitable[None]]`  
Awaited to release the consumer's state after the value reference is dropped.

`ttl_s: float`  
Idle seconds a loaded value must sit unused before the reaper unloads it.


#### Example

``` python
>>> resource = IdleResource(spawn_server, teardown_server, ttl_s=300.0)
>>> async with resource.use() as server:
...     await server.query(prompt)
```


#### Attributes

| Name | Description |
|----|----|
| [loaded](#athome.idle.IdleResource.loaded) | Whether a value is currently held. |


##### loaded


Whether a value is currently held.


`loaded: bool`


#### Methods

| Name | Description |
|----|----|
| [discard()](#athome.idle.IdleResource.discard) | Drop the value and await `unload` now, bypassing both the TTL and the in-flight count. |
| [run()](#athome.idle.IdleResource.run) | Reaper loop: every `interval_s` seconds, sweep the resource. Runs until cancelled. |
| [sweep()](#athome.idle.IdleResource.sweep) | Unload the value iff it is loaded, no use is in flight, and it has been idle past `ttl_s`. |
| [use()](#athome.idle.IdleResource.use) | Yield the loaded value, loading it first if necessary and counting the use in flight. |


##### discard()


Drop the value and await `unload` now, bypassing both the TTL and the in-flight count.


Usage

``` python
discard()
```


For a value known bad: in-flight holders keep their own local references and fail on their own, so the reaper's guards are deliberately skipped here.


##### run()


Reaper loop: every `interval_s` seconds, sweep the resource. Runs until cancelled.


Usage

``` python
run(*, interval_s=DEFAULT_REAP_INTERVAL_S)
```


Each iteration isolates its own sweep: a [sweep](idle.md#athome.idle.IdleResource.sweep) that raises -- a failed `unload`, most likely -- is logged and the loop keeps running, so one transient failure never silently kills the reaper. Cancellation still ends the loop: anyio raises it as a `BaseException`, which `except Exception` cannot catch.


##### sweep()


Unload the value iff it is loaded, no use is in flight, and it has been idle past `ttl_s`.


Usage

``` python
sweep(*, now=None)
```


The value reference is dropped before `unload` is awaited, with no backup local ref (a ref would pin MLX weights and defeat eviction). So a failed `unload` leaves the resource unloaded while the underlying resource is possibly still alive; the next [load](config.md#athome.config.load) must tolerate that by re-adopting or reloading it.


##### Parameters


`now: float | None = None`  
The monotonic time to measure the idle window against; resolves the clock itself when omitted.


##### use()


Yield the loaded value, loading it first if necessary and counting the use in flight.


Usage

``` python
use()
```


Loads under the lock on first use so concurrent callers share one load. The in-flight count is held for the body's duration, blocking the reaper; on exit it records the completion time against the monotonic clock so the idle window starts from the last release.
