fix(utils): keep the success callback enqueue on its own task

The synchronous enqueue spawned the logging worker loop inside the awaited
call and ran callbacks that per-call loops used to drop, which hung the SDK
callback tests on the autouse worker flush. The shutdown worker flush covers
the queued callback on its own, so the one-tick task is restored

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
joshua 2026-10-29 05:10:11 +00:00
parent 902dc2ceea
commit ca17402df9
2 changed files with 13 additions and 50 deletions

View file

@ -1540,12 +1540,14 @@ def _schedule_async_success_logging(
def _enqueue_async_logging() -> None:
logging_obj.mark_async_success_log_enqueued()
_client_async_logging_helper(
logging_obj=logging_obj,
result=result,
start_time=start_time,
end_time=end_time,
is_completion_with_fallbacks=is_completion_with_fallbacks,
asyncio.create_task(
_client_async_logging_helper(
logging_obj=logging_obj,
result=result,
start_time=start_time,
end_time=end_time,
is_completion_with_fallbacks=is_completion_with_fallbacks,
)
)
if not logging_obj.defer_async_logging:
@ -1556,13 +1558,13 @@ def _schedule_async_success_logging(
logging_obj.enqueue_deferred_logging = _enqueue_async_logging
def _client_async_logging_helper(
async def _client_async_logging_helper(
logging_obj: LiteLLMLoggingObject,
result: object,
start_time: datetime.datetime,
end_time: datetime.datetime,
result,
start_time,
end_time,
is_completion_with_fallbacks: bool,
) -> None:
):
if (
is_completion_with_fallbacks is False
): # don't log the parent event litellm.completion_with_fallbacks as a 'log_success_event', this will lead to double logging the same call - https://github.com/BerriAI/litellm/issues/7477

View file

@ -10884,45 +10884,6 @@ def test_get_optional_params_forwards_a_tool_config_cache_point_to_a_downstream_
assert optional_params["extra_body"] == {"kept": True, "cache_control_injection_points": [point]}
@pytest.mark.asyncio
@pytest.mark.parametrize("deferred", [False, True])
async def test_success_logging_is_enqueued_before_the_worker_is_flushed(deferred: bool) -> None:
from litellm.litellm_core_utils.litellm_logging import Logging
from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER
from litellm.utils import _schedule_async_success_logging
completed: Final[asyncio.Queue[object]] = asyncio.Queue()
async def callback(kwargs: object, response: object, start: object, end: object) -> None:
completed.put_nowait(response)
now: Final = datetime(2026, 1, 1, tzinfo=timezone.utc)
logging_obj: Final = Logging(
model="synthetic-model",
messages=[],
stream=False,
call_type="acompletion",
start_time=now.timestamp(),
litellm_call_id="logging-enqueue",
function_id="logging-enqueue",
dynamic_async_success_callbacks=[callback],
)
logging_obj.defer_async_logging = deferred
result: Final = litellm.ModelResponse(id="accounted-response")
try:
_schedule_async_success_logging(logging_obj, result, now, now, False)
if deferred:
assert completed.empty()
assert logging_obj.enqueue_deferred_logging is not None
logging_obj.enqueue_deferred_logging()
await GLOBAL_LOGGING_WORKER.flush()
assert completed.get_nowait() is result
assert completed.empty()
finally:
await asyncio.sleep(0)
await GLOBAL_LOGGING_WORKER.flush()
await GLOBAL_LOGGING_WORKER.stop()
@pytest.mark.parametrize(
("provider", "expected_warnings"),