From aef811ed95c36f0f8bd7172881fca3dad5c832ec Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Fri, 9 Oct 2026 19:16:23 +0200 Subject: [PATCH] docs: clarify reusable flush and terminal shutdown guidance --- .../flush-shutdown-lifetime-guidance.md | 5 ++ posthog/__init__.py | 47 ++++++++++++--- posthog/client.py | 60 +++++++++++++------ .../posthog-python-references-7.67.0.json | 16 ++--- .../posthog-python-references-latest.json | 16 ++--- 5 files changed, 103 insertions(+), 41 deletions(-) create mode 100644 .sampo/changesets/flush-shutdown-lifetime-guidance.md diff --git a/.sampo/changesets/flush-shutdown-lifetime-guidance.md b/.sampo/changesets/flush-shutdown-lifetime-guidance.md new file mode 100644 index 000000000..a87fd12bf --- /dev/null +++ b/.sampo/changesets/flush-shutdown-lifetime-guidance.md @@ -0,0 +1,5 @@ +--- +pypi/posthog: patch +--- + +Clarify when to flush reusable clients versus shut down disposable clients, including timeout and delivery limitations. diff --git a/posthog/__init__.py b/posthog/__init__.py index d718906f0..66fb9ea19 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -1226,16 +1226,39 @@ def load_feature_flags(): def flush(timeout_seconds: Optional[float] = 10) -> None: """ - Tell the client to flush all queued events. + Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. + + Call ``flush()`` before a short-lived request or serverless invocation returns + when using the shared module-level client. Use ``shutdown()`` only for final + process cleanup or a client that will not be reused. Flush does not stop the + background workers or feature flag poller, and does not end open spans. + + Returning does not guarantee server receipt: pending work can remain after a + timeout or span export failure, and failed events may be dropped by consumers. + Calls made directly from SDK callbacks such as ``on_error`` are deferred + rather than blocking the worker that invoked the callback. Args: - timeout_seconds: Maximum seconds to wait for the queue to flush. - Defaults to 10 seconds. Pass ``None`` to wait indefinitely. + timeout_seconds: Seconds to wait for event queues to drain, shared across + event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a + time limit; this does not guarantee successful delivery. Queued spans + flush concurrently, waiting up to this many seconds for an in-flight + flush before starting a fresh drain budget. A lock wait timeout leaves + the in-flight flush to finish. At least one span request is attempted + after acquiring the lock, even with no budget left; retriable failures + are retried after backoff while budget remains, with one last attempt + at the deadline. The last span request is not cut short and is bounded + by the client's ``timeout`` in seconds, + so this is not a strict wall-clock limit for the whole call. With + ``None``, a retriable span failure is left for a later flush rather than + retried within this call. Examples: ```python - from posthog import flush - flush() + import posthog + posthog.capture('event_name', distinct_id='user_id') + # Before a serverless invocation returns; keep the global client open. + posthog.flush(timeout_seconds=10) ``` Category: @@ -1343,11 +1366,21 @@ def get_active_span() -> Optional[Span]: def shutdown() -> None: """ - Flush all messages and cleanly shutdown the client. + Attempt final delivery and permanently shut down the global client. + + Call this for final process cleanup, not at the end of each request or + serverless invocation that shares the module-level client. Use ``flush()`` + before returning from those invocations instead. Shutdown closes capture + lanes, stops background workers and the feature flag poller, and tears down + integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through - logging or ``on_error``; returning does not guarantee server receipt. + logging or ``on_error``; returning does not guarantee server receipt. Event + draining has no timeout. Queued spans get a final flush with up to 30 seconds + waiting for an in-flight flush, then a fresh 30-second drain budget; the last + HTTP request is not cut short. Unsent queued spans and still-open spans are + discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned diff --git a/posthog/client.py b/posthog/client.py index d74ea56e3..2f33a29ae 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -2715,25 +2715,41 @@ def get_active_span(self) -> Optional[Span]: def flush(self, timeout_seconds: Optional[float] = 10) -> None: """ - Force a flush from the internal queue to the server. Do not use directly, call `shutdown()` instead. + Attempt immediate delivery of queued events and ended spans, keeping the client reusable. + + For a client shared across requests or serverless invocations (including + the module-level client), call ``flush()`` before a short-lived invocation + returns. Use ``shutdown()`` instead only for final process cleanup or when + disposing of a client that will not be reused. Flush does not stop the + background workers or feature flag poller, and does not end open spans. + + Returning does not guarantee server receipt: pending work can remain after + a timeout or span export failure, and failed events may be dropped by the + consumers. Calls made directly from SDK callbacks such as ``on_error`` are + deferred rather than blocking the worker that invoked the callback. Args: - timeout_seconds: Maximum seconds to wait for the queue to flush. - Defaults to 10 seconds. Pass ``None`` to wait indefinitely. - Queued spans are sent at the same time, within the same - budget: at least one span request is attempted even when the - budget is already spent, a retriable failure is retried after - its backoff while budget remains, no other request starts once - it is spent, and each request is bounded by ``timeout``. The - wait for the last request is not cut short, so a flush can - take up to ``timeout_seconds`` plus ``timeout`` in the worst - case. A span flush already in flight for the whole wait is - left to finish instead. + timeout_seconds: Seconds to wait for event queues to drain, shared + across event lanes. Defaults to 10 seconds. Pass ``None`` to wait + without a time limit; this does not guarantee successful delivery. + Queued spans flush concurrently. The span exporter waits up to + this many seconds for an in-flight flush, then uses a fresh drain + budget once it acquires the lock. If the lock wait times out, the + in-flight flush is left to finish. At least one span request is + attempted after acquiring the lock, even with no budget left; + retriable failures are retried after backoff while budget remains, + with one last attempt at the deadline. Each span request is + bounded by the client's ``timeout`` in + seconds, and the last request is not cut short. This is therefore + not a strict wall-clock limit for the whole call. With ``None``, + a retriable span failure is left for a later flush rather than + retried within this call. Examples: ```python - posthog.capture('event_name') - posthog.flush() # Ensures the event is sent immediately + posthog.capture('event_name', distinct_id='user_id') + # Before a serverless invocation returns; keep the shared client open. + posthog.flush(timeout_seconds=10) ``` """ if self._defer_flush_from_callback(timeout_seconds): @@ -3130,14 +3146,22 @@ def join(self) -> None: @no_throw() def shutdown(self) -> None: """ - Flush all messages and cleanly shutdown the client. Call this before the process ends in serverless environments to avoid data loss. + Attempt final delivery and permanently shut down the client. + + Call this for final process cleanup or when disposing of a client that + will not be reused. It closes capture lanes, stops background workers and + the feature flag poller, and tears down integrations. Do not call it at the + end of each request or serverless invocation when the client is shared; + call ``flush()`` before returning instead. A shut-down client cannot be + reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee - server receipt. Queued spans get one final flush of up to 30 s (plus a - request already in flight); any it cannot send are discarded with a - warning, as are spans still open. + server receipt. Event draining has no timeout. Queued spans get one final + flush with up to 30 seconds waiting for an in-flight flush, then a fresh + 30-second drain budget; the last HTTP request is not cut short. Any spans + it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that diff --git a/references/posthog-python-references-7.67.0.json b/references/posthog-python-references-7.67.0.json index 600acb877..1124933fc 100644 --- a/references/posthog-python-references-7.67.0.json +++ b/references/posthog-python-references-7.67.0.json @@ -1001,13 +1001,13 @@ { "id": "flush", "title": "flush", - "description": "Force a flush from the internal queue to the server. Do not use directly, call `shutdown()` instead.", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", "details": "", "category": null, "params": [ { "name": "timeout_seconds", - "description": "Maximum seconds to wait for the queue to flush. Defaults to 10 seconds. Pass ``None`` to wait indefinitely. Queued spans are sent at the same time, within the same", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", "isOptional": true, "type": "float" } @@ -1022,7 +1022,7 @@ { "id": "example_1", "name": "Example 1", - "code": "posthog.capture('event_name')\nposthog.flush() # Ensures the event is sent immediately" + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" } ] }, @@ -2050,7 +2050,7 @@ { "id": "shutdown", "title": "shutdown", - "description": "Flush all messages and cleanly shutdown the client. Call this before the process ends in serverless environments to avoid data loss. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Queued spans get one final flush of up to 30 s (plus a request already in flight); any it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", "details": "", "category": null, "params": [], @@ -2489,13 +2489,13 @@ { "id": "flush", "title": "flush", - "description": "Tell the client to flush all queued events.", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", "details": "", "category": "Client management", "params": [ { "name": "timeout_seconds", - "description": "Maximum seconds to wait for the queue to flush. Defaults to 10 seconds. Pass ``None`` to wait indefinitely.", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", "isOptional": true, "type": "float" } @@ -2510,7 +2510,7 @@ { "id": "example_1", "name": "Example 1", - "code": "from posthog import flush\nflush()" + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" } ] }, @@ -3421,7 +3421,7 @@ { "id": "shutdown", "title": "shutdown", - "description": "Flush all messages and cleanly shutdown the client. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", "details": "", "category": "Client management", "params": [], diff --git a/references/posthog-python-references-latest.json b/references/posthog-python-references-latest.json index 600acb877..1124933fc 100644 --- a/references/posthog-python-references-latest.json +++ b/references/posthog-python-references-latest.json @@ -1001,13 +1001,13 @@ { "id": "flush", "title": "flush", - "description": "Force a flush from the internal queue to the server. Do not use directly, call `shutdown()` instead.", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the client reusable. For a client shared across requests or serverless invocations (including the module-level client), call ``flush()`` before a short-lived invocation returns. Use ``shutdown()`` instead only for final process cleanup or when disposing of a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by the consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", "details": "", "category": null, "params": [ { "name": "timeout_seconds", - "description": "Maximum seconds to wait for the queue to flush. Defaults to 10 seconds. Pass ``None`` to wait indefinitely. Queued spans are sent at the same time, within the same", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently. The span exporter waits up to this many seconds for an in-flight flush, then uses a fresh drain budget once it acquires the lock. If the lock wait times out, the in-flight flush is left to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. Each span request is bounded by the client's ``timeout`` in seconds, and the last request is not cut short. This is therefore not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", "isOptional": true, "type": "float" } @@ -1022,7 +1022,7 @@ { "id": "example_1", "name": "Example 1", - "code": "posthog.capture('event_name')\nposthog.flush() # Ensures the event is sent immediately" + "code": "posthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the shared client open.\nposthog.flush(timeout_seconds=10)" } ] }, @@ -2050,7 +2050,7 @@ { "id": "shutdown", "title": "shutdown", - "description": "Flush all messages and cleanly shutdown the client. Call this before the process ends in serverless environments to avoid data loss. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Queued spans get one final flush of up to 30 s (plus a request already in flight); any it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", + "description": "Attempt final delivery and permanently shut down the client. Call this for final process cleanup or when disposing of a client that will not be reused. It closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. Do not call it at the end of each request or serverless invocation when the client is shared; call ``flush()`` before returning instead. A shut-down client cannot be reused; create a new client if needed. Normally this method blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get one final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Any spans it cannot send are discarded with a warning, as are spans still open. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. When called directly from an SDK callback such as ``on_error``, shutdown is deferred to avoid blocking the worker that invoked the callback. If the callback must coordinate a blocking shutdown, have it signal an application-owned thread and return before that thread calls shutdown. Do not wait inside the callback for another thread or task that calls a lifecycle method.", "details": "", "category": null, "params": [], @@ -2489,13 +2489,13 @@ { "id": "flush", "title": "flush", - "description": "Tell the client to flush all queued events.", + "description": "Attempt immediate delivery of queued events and ended spans, keeping the global client reusable. Call ``flush()`` before a short-lived request or serverless invocation returns when using the shared module-level client. Use ``shutdown()`` only for final process cleanup or a client that will not be reused. Flush does not stop the background workers or feature flag poller, and does not end open spans. Returning does not guarantee server receipt: pending work can remain after a timeout or span export failure, and failed events may be dropped by consumers. Calls made directly from SDK callbacks such as ``on_error`` are deferred rather than blocking the worker that invoked the callback.", "details": "", "category": "Client management", "params": [ { "name": "timeout_seconds", - "description": "Maximum seconds to wait for the queue to flush. Defaults to 10 seconds. Pass ``None`` to wait indefinitely.", + "description": "Seconds to wait for event queues to drain, shared across event lanes. Defaults to 10 seconds. Pass ``None`` to wait without a time limit; this does not guarantee successful delivery. Queued spans flush concurrently, waiting up to this many seconds for an in-flight flush before starting a fresh drain budget. A lock wait timeout leaves the in-flight flush to finish. At least one span request is attempted after acquiring the lock, even with no budget left; retriable failures are retried after backoff while budget remains, with one last attempt at the deadline. The last span request is not cut short and is bounded by the client's ``timeout`` in seconds, so this is not a strict wall-clock limit for the whole call. With ``None``, a retriable span failure is left for a later flush rather than retried within this call.", "isOptional": true, "type": "float" } @@ -2510,7 +2510,7 @@ { "id": "example_1", "name": "Example 1", - "code": "from posthog import flush\nflush()" + "code": "import posthog\nposthog.capture('event_name', distinct_id='user_id')\n# Before a serverless invocation returns; keep the global client open.\nposthog.flush(timeout_seconds=10)" } ] }, @@ -3421,7 +3421,7 @@ { "id": "shutdown", "title": "shutdown", - "description": "Flush all messages and cleanly shutdown the client. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", + "description": "Attempt final delivery and permanently shut down the global client. Call this for final process cleanup, not at the end of each request or serverless invocation that shares the module-level client. Use ``flush()`` before returning from those invocations instead. Shutdown closes capture lanes, stops background workers and the feature flag poller, and tears down integrations. The global client is not recreated automatically after shutdown. This normally blocks until queued events have been attempted and cleanup finishes. Failed or undrainable events may be dropped and reported through logging or ``on_error``; returning does not guarantee server receipt. Event draining has no timeout. Queued spans get a final flush with up to 30 seconds waiting for an in-flight flush, then a fresh 30-second drain budget; the last HTTP request is not cut short. Unsent queued spans and still-open spans are discarded with a warning. Lifecycle cleanup is attempted once, and cleanup failures are logged without retry. Calls made directly from SDK callbacks such as ``on_error`` are deferred to avoid deadlocking the worker. If blocking completion is required, signal an application-owned thread, return from the callback, and call ``shutdown()`` from that thread. Do not wait inside a callback for another thread or task calling a lifecycle method.", "details": "", "category": "Client management", "params": [],