# # Copyright 2025 The InfiniFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import os import queue import threading from typing import Any, Callable, Coroutine, Optional, Type, Union import asyncio import trio from functools import wraps from flask import make_response, jsonify from common.constants import RetCode TimeoutException = Union[Type[BaseException], BaseException] OnTimeoutCallback = Union[Callable[..., Any], Coroutine[Any, Any, Any]] def timeout(seconds: float | int | str = None, attempts: int = 2, *, exception: Optional[TimeoutException] = None, on_timeout: Optional[OnTimeoutCallback] = None): if isinstance(seconds, str): seconds = float(seconds) def decorator(func): @wraps(func) def wrapper(*args, **kwargs): result_queue = queue.Queue(maxsize=1) def target(): try: result = func(*args, **kwargs) result_queue.put(result) except Exception as e: result_queue.put(e) thread = threading.Thread(target=target) thread.daemon = True thread.start() for a in range(attempts): try: if os.environ.get("ENABLE_TIMEOUT_ASSERTION"): result = result_queue.get(timeout=seconds) else: result = result_queue.get() if isinstance(result, Exception): raise result return result except queue.Empty: pass raise TimeoutError(f"Function '{func.__name__}' timed out after {seconds} seconds and {attempts} attempts.") @wraps(func) async def async_wrapper(*args, **kwargs) -> Any: if seconds is None: return await func(*args, **kwargs) for a in range(attempts): try: if os.environ.get("ENABLE_TIMEOUT_ASSERTION"): with trio.fail_after(seconds): return await func(*args, **kwargs) else: return await func(*args, **kwargs) except trio.TooSlowError: if a < attempts - 1: continue if on_timeout is not None: if callable(on_timeout): result = on_timeout() if isinstance(result, Coroutine): return await result return result return on_timeout if exception is None: raise TimeoutError(f"Operation timed out after {seconds} seconds and {attempts} attempts.") if isinstance(exception, BaseException): raise exception if isinstance(exception, type) and issubclass(exception, BaseException): raise exception(f"Operation timed out after {seconds} seconds and {attempts} attempts.") raise RuntimeError("Invalid exception type provided") if asyncio.iscoroutinefunction(func): return async_wrapper return wrapper return decorator def construct_response(code=RetCode.SUCCESS, message="success", data=None, auth=None): result_dict = {"code": code, "message": message, "data": data} response_dict = {} for key, value in result_dict.items(): if value is None and key != "code": continue else: response_dict[key] = value response = make_response(jsonify(response_dict)) if auth: response.headers["Authorization"] = auth response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Method"] = "*" response.headers["Access-Control-Allow-Headers"] = "*" response.headers["Access-Control-Allow-Headers"] = "*" response.headers["Access-Control-Expose-Headers"] = "Authorization" return response