# Pools, deadlines and retries All importable from `agensgraph`, except where a module is named. ## Pools See [Pooling](../../production/pooling.md). ```{py:currentmodule} agensgraph ``` ````{py:class} ConnectionPool(conninfo: ConninfoSource = '', *, graph: str | None = None, min_size: int = 4, max_size: int | None = 16, kwargs: dict[str, Any] | None = None, configure: Callable[[Connection[Any]], Awaitable[None]] | None = None, setup: Callable[[Connection[Any]], Awaitable[None]] | None = None, reset: Callable[[Connection[Any]], Awaitable[None]] | None = None, timeout: float = 30.0, max_waiting: int = 0, max_lifetime: float = 3600.0, max_idle: float = 600.0, reconnect_timeout: float = 300.0, reconnect_failed: Callable[[Any], None] | None = None, num_workers: int = 3, check_connections: bool = True, name: str | None = None, statement_timeout_gap: float = 0.5) A pool of `Connection`s, wrapping psycopg's. `graph` is selected on each new connection; `configure` runs once per connection, `setup` on each borrow, `reset` on each return. The other arguments are psycopg's. Creating it connects nothing: call `open()`. ```{py:method} open(*, wait: bool = False, timeout: float = 30.0) Check the server with one connection of its own, then start the pool; with `wait`, until `min_size` exist. ``` ```{py:method} wait(timeout: float = 30.0) Wait until `min_size` connections exist. ``` ```{py:method} close(timeout: float = 5.0) Close the pool. ``` ```{py:method} connection(*, timeout: float | None = None, deadline: Deadline | None = None) -> Generator[Connection[Any]] Borrow a connection for a `with` block, within `timeout` or a `Deadline`. ``` ```{py:method} execute_query(query: Statement, params: Params | None = None, *, timeout: float | None = None, deadline: Deadline | None = None, **kwargs: Any) -> Result Borrow, run one statement with `Connection.execute_query()`, return. ``` ```{py:method} invalidate() -> int Retire every connection now held; returns the new generation. ``` ```{py:method} drain() Close every connection and open the same number again. ``` ```{py:method} check() Check the idle connections now, replacing any that fail. ``` ```{py:method} resize(min_size: int, max_size: int | None = None) Change the pool's size. ``` ```{py:method} get_stats() -> dict[str, int] psycopg's counters with the pool's own; every key always present. ``` ```{py:method} pop_stats() -> dict[str, int] The same, with the accumulating counters reset. ``` ```{py:property} generation :type: int The generation now in force. ``` ```{py:property} retired :type: int How many connections were closed for an older generation. ``` ```{py:property} interrupted :type: int How many were closed because a statement on them was interrupted. ``` ```{py:property} closed :type: bool ``` ```{py:property} min_size :type: int ``` ```{py:property} max_size :type: int ``` ```{py:property} name :type: str ``` ```` ```{py:class} NullConnectionPool(conninfo: ConninfoSource = '', *, graph: str | None = None, min_size: int = 0, max_size: int | None = None, kwargs: dict[str, Any] | None = None, configure: Callable[[Connection[Any]], Awaitable[None]] | None = None, setup: Callable[[Connection[Any]], Awaitable[None]] | None = None, reset: Callable[[Connection[Any]], Awaitable[None]] | None = None, timeout: float = 30.0, max_waiting: int = 0, max_lifetime: float = 3600.0, max_idle: float = 600.0, reconnect_timeout: float = 300.0, reconnect_failed: Callable[[Any], None] | None = None, num_workers: int = 3, check_connections: bool = True, name: str | None = None, statement_timeout_gap: float = 0.5) A pool that keeps no connections: one per borrow, closed after. The same interface. ``` ```{py:class} AsyncConnectionPool(conninfo: AsyncConninfoSource = '', *, graph: str | None = None, min_size: int = 4, max_size: int | None = 16, kwargs: dict[str, Any] | None = None, configure: Callable[[AsyncConnection[Any]], Awaitable[None]] | None = None, setup: Callable[[AsyncConnection[Any]], Awaitable[None]] | None = None, reset: Callable[[AsyncConnection[Any]], Awaitable[None]] | None = None, timeout: float = 30.0, max_waiting: int = 0, max_lifetime: float = 3600.0, max_idle: float = 600.0, reconnect_timeout: float = 300.0, reconnect_failed: Callable[[Any], None] | None = None, num_workers: int = 3, check_connections: bool = True, name: str | None = None, statement_timeout_gap: float = 0.5) `ConnectionPool` for `asyncio`. Its hooks are coroutine functions. `open`, `wait`, `close`, `check`, `drain`, `resize` and `execute_query` are awaited; `connection()` is an `async with` block. ``` ```{py:class} AsyncNullConnectionPool(conninfo: AsyncConninfoSource = '', *, graph: str | None = None, min_size: int = 0, max_size: int | None = None, kwargs: dict[str, Any] | None = None, configure: Callable[[AsyncConnection[Any]], Awaitable[None]] | None = None, setup: Callable[[AsyncConnection[Any]], Awaitable[None]] | None = None, reset: Callable[[AsyncConnection[Any]], Awaitable[None]] | None = None, timeout: float = 30.0, max_waiting: int = 0, max_lifetime: float = 3600.0, max_idle: float = 600.0, reconnect_timeout: float = 300.0, reconnect_failed: Callable[[Any], None] | None = None, num_workers: int = 3, check_connections: bool = True, name: str | None = None, statement_timeout_gap: float = 0.5) `NullConnectionPool` for `asyncio`. ``` ```{py:currentmodule} agensgraph.pool ``` ```{py:data} COUNTERS :value: ('requests_num', 'requests_queued', 'requests_wait_ms', 'requests_errors', 'usage_ms', 'returns_bad', 'connections_num', 'connections_ms', 'connections_errors', 'connections_lost') The names of the pool counters that accumulate. ``` ## Deadlines See [Timeouts and cancellation](../../production/timeouts.md). ```{py:currentmodule} agensgraph ``` ````{py:class} Deadline(seconds: float | None = None, *, commit_reserve: float = 0.0) A budget of `seconds`, counted from when it is created. `commit_reserve` keeps part of it back from the statements. ```{py:method} none() -> Deadline :classmethod: A budget with no limit. ``` ```{py:method} remaining() -> float | None Seconds left, or `None` without a limit; negative once spent. ``` ```{py:method} spent() -> float Seconds used. ``` ```{py:method} available() -> float | None What the statements may spend: what is left, less the reserve. ``` ```{py:method} bounded(seconds: float | None) -> float | None A wait of `seconds`, cut to what is left. ``` ```{py:method} check(doing: str) Raise `Expired`, naming `doing`, if the budget is spent. ``` ```{py:method} statement_timeout_ms(*, gap: float = 0.5) -> int | None The `statement_timeout` it becomes, `gap` inside what is available; raises `Expired` if that is nothing. ``` ```{py:property} total :type: float | None ``` ```{py:property} commit_reserve :type: float ``` ```{py:property} unlimited :type: bool ``` ```{py:property} expired :type: bool ``` ```{py:property} can_commit :type: bool Whether the reserve is still there. ``` ```` ```{py:exception} Expired A budget ran out before anything was sent. A subclass of `TimeoutError`, with `spent` and `total`. ``` ## Retries See [Retries](../../production/retries.md). ````{py:class} Retryability What to do about a failure. ```{py:attribute} SAFE Run it again on the same connection. ``` ```{py:attribute} RECONNECT Run it again on a new connection. ``` ```{py:attribute} BACKPRESSURE Run it again, after a longer wait. ``` ```{py:attribute} RESET_STATE Clear the stale state, keep the connection, run it again. ``` ```{py:attribute} UNKNOWN Find out what happened before doing anything. ``` ```{py:attribute} FATAL Do not run it again. ``` ```{py:property} is_retryable :type: bool True for the first four. ``` ```{py:property} needs_new_connection :type: bool ``` ```{py:property} keeps_connection :type: bool ``` ```{py:property} clears_prepared_statements :type: bool ``` ```{py:property} wants_longer_delay :type: bool ``` ```` ```{py:function} retryability(exc: BaseException, *, wrote: bool = False, merging: bool = False) -> Retryability Classify an exception. `wrote=True` makes a lost connection `UNKNOWN`; `merging=True` makes a unique violation or a duplicate label from a `MERGE` race `SAFE`. ``` ```{py:function} is_retryable(exc: BaseException, *, wrote: bool = False, merging: bool = False) -> bool `retryability(...).is_retryable`. ``` ````{py:class} RetryPolicy(*, attempts: int = 3, base: float = 0.05, cap: float = 2.0, bucket: TokenBucket | None = None, rng: random.Random | None = None) Decides whether and when to try again; the caller keeps the loop. `attempts` counts the first try. Waits are full jitter between zero and a ceiling doubling from `base` up to `cap`. ```{py:method} decide(exc: BaseException, *, number: int, wrote: bool = False, merging: bool = False, remaining: float | None = None) -> Attempt An `Attempt` for the failure of attempt `number`. ``` ```{py:method} succeeded() Credit the allowance after a success. ``` ```{py:method} exhausted(exc: BaseException, *, attempts: int, previous: Sequence[BaseException]) -> BaseException Mark `exc` with its history and return it, for raising after the last attempt. ``` ```{py:property} attempts :type: int ``` ```{py:property} bucket :type: TokenBucket ``` ```` ```{py:currentmodule} agensgraph.retry ``` ```{py:class} Attempt `RetryPolicy.decide()`'s answer: `number`, `recovery`, `retry`, `delay` in seconds, and `reason` in words. ``` ```{py:currentmodule} agensgraph ``` ````{py:class} TokenBucket(capacity: int = 100, *, refill: float = 120.0) An allowance of retries that time refills, in `refill` seconds from empty. Every `RetryPolicy` shares one per process unless given its own. ```{py:method} tokens() -> float How many tokens are left. ``` ```{py:method} spend(recovery: Retryability) Spend for a retry: 14 tokens, or 5 for `BACKPRESSURE`. ``` ```{py:method} credit() Pay one back. ``` ```{py:property} allows_retry :type: bool Whether more than half is left. ``` ```{py:property} capacity :type: int ``` ```` ## Commit outcome See [Losing a connection while committing](../../production/retries.md#losing-a-connection-while-committing). ````{py:class} CommitOutcome What `resolve_commit()` found. ```{py:attribute} COMMITTED It landed. ``` ```{py:attribute} ABORTED It did not. ``` ```{py:attribute} IN_PROGRESS Not finished yet. ``` ```{py:attribute} UNKNOWN The server no longer knows. ``` ```{py:property} is_settled :type: bool `COMMITTED` or `ABORTED`. ``` ```{py:property} safe_to_retry :type: bool Only `ABORTED`. ``` ````