fix(common/misc_utils): allow once decorator retries after failure (#16174)

### What problem does this PR solve?
`common/misc_utils.once` set `executed=True` before invoking the wrapped
function, so an exception on the first call permanently disabled future
calls and returned the cached `None`.

This change marks `executed=True` only after a successful call, allowing
retries after transient failures while preserving once-only behavior
after success. It also adds regression tests for retry-after-exception
and thread-safe single execution.

### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Co-authored-by: Harsh Kashyap <harshkashyap@Harshs-MacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Harsh Kashyap
2026-07-31 20:13:18 +05:30
committed by GitHub
parent 1b5ce4c27e
commit d67d14e4c6
2 changed files with 74 additions and 2 deletions

View File

@@ -17,6 +17,7 @@ import asyncio
import contextvars
import hashlib
import sys
import threading
import types
import uuid
from contextlib import contextmanager
@@ -25,7 +26,7 @@ from unittest.mock import patch
import pytest
from common import ssrf_guard
from common.misc_utils import convert_bytes, download_img, get_uuid, hash_str2int, thread_pool_exec
from common.misc_utils import convert_bytes, download_img, get_uuid, hash_str2int, once, thread_pool_exec
class _Hdr:
@@ -539,3 +540,74 @@ class TestConvertBytes:
# Ensure we don't exceed available units
huge_value = 100 * 1125899906842624 # 100 PB (still within PB range)
assert "PB" in convert_bytes(huge_value)
@pytest.mark.p2
class TestOnce:
"""Test cases for the once() run-exactly-once decorator."""
def test_runs_only_once_on_success(self):
"""A successful function body runs once; later calls return the cached result."""
calls = {"n": 0}
@once
def init():
calls["n"] += 1
return calls["n"]
assert init() == 1
assert init() == 1 # cached, not re-executed
assert calls["n"] == 1
def test_exception_allows_retry(self):
"""A first-call exception must not permanently disable the function.
Previously ``executed`` was set to True before the wrapped function ran,
so a raising first call left the decorator stuck returning None forever.
After a failure the body should run again on the next call.
"""
calls = {"n": 0}
@once
def flaky():
calls["n"] += 1
if calls["n"] == 1:
raise RuntimeError("boom")
return "ok"
with pytest.raises(RuntimeError):
flaky()
# The first call failed, so the second call must retry and succeed.
assert flaky() == "ok"
assert calls["n"] == 2
def test_thread_safe_single_execution(self):
"""Concurrent callers must trigger exactly one successful execution."""
calls = {"n": 0}
counter_lock = threading.Lock()
barrier = threading.Barrier(8)
@once
def init():
with counter_lock:
calls["n"] += 1
return 42
results = []
results_lock = threading.Lock()
def worker():
barrier.wait()
value = init()
with results_lock:
results.append(value)
threads = [threading.Thread(target=worker) for _ in range(8)]
for t in threads:
t.start()
for t in threads:
t.join()
assert calls["n"] == 1
assert results == [42] * 8