File size: 5,283 Bytes
a5784e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""
FastAPI Dependencies Module
"""

import logging
from asyncio import Event, Lock, Queue
from typing import Any, Dict, List, Set

from api_utils.context_types import QueueItem


def get_logger() -> logging.Logger:
    from api_utils.server_state import state

    return state.logger


def get_log_ws_manager():
    from api_utils.server_state import state

    return state.log_ws_manager


def get_request_queue() -> "Queue[QueueItem]":
    from typing import cast

    from api_utils.server_state import state

    return cast("Queue[QueueItem]", state.request_queue)


def get_processing_lock() -> Lock:
    from typing import cast

    from api_utils.server_state import state

    return cast(Lock, state.processing_lock)


def get_worker_task():
    from api_utils.server_state import state

    return state.worker_task


def get_server_state() -> Dict[str, Any]:
    from api_utils.server_state import state

    # Return immutable snapshot to prevent downstream modifications to global references
    return dict(
        is_initializing=state.is_initializing,
        is_playwright_ready=state.is_playwright_ready,
        is_browser_connected=state.is_browser_connected,
        is_page_ready=state.is_page_ready,
    )


def get_page_instance():
    from api_utils.server_state import state

    return state.page_instance


def get_model_list_fetch_event() -> Event:
    from typing import cast

    from api_utils.server_state import state

    return cast(Event, state.model_list_fetch_event)


def get_parsed_model_list() -> List[Dict[str, Any]]:
    from api_utils.server_state import state

    return state.parsed_model_list


def get_excluded_model_ids() -> Set[str]:
    from api_utils.server_state import state

    return state.excluded_model_ids


def get_current_ai_studio_model_id() -> str:
    from typing import cast

    from api_utils.server_state import state

    return cast(str, state.current_ai_studio_model_id)


async def ensure_request_lock():
    """
    Dependency that acts as a 'Parking Lot' for requests.
    If Auth Rotation is in progress (Lock is cleared) or Quota is Exceeded (Rotation imminent),
    this will pause the request until the system is ready.
    """
    import asyncio
    import time

    from api_utils.server_state import state as server_state
    from config.global_state import GlobalState

    logger = server_state.logger

    # A request is considered "queued" if it has to wait for the lock.
    is_waiting = (
        GlobalState.IS_QUOTA_EXCEEDED or not GlobalState.AUTH_ROTATION_LOCK.is_set()
    )
    if is_waiting:
        GlobalState.queued_request_count += 1

    start_time = time.time()
    max_total_wait = 60.0  # 60 second hard timeout for request parking

    try:
        # Wait loop to handle both Lock and Quota states
        # We wait if:
        # 1. Lock is NOT set (Rotation in progress)
        # 2. Quota IS exceeded (Rotation about to start, or we need to wait for it)
        while (
            GlobalState.IS_QUOTA_EXCEEDED or not GlobalState.AUTH_ROTATION_LOCK.is_set()
        ):
            # Check for total timeout
            if time.time() - start_time > max_total_wait:
                logger.error(
                    f"🚨 Request parking timeout after {max_total_wait}s. Quota={GlobalState.IS_QUOTA_EXCEEDED}, LockSet={GlobalState.AUTH_ROTATION_LOCK.is_set()}"
                )
                from fastapi import HTTPException

                raise HTTPException(
                    status_code=530,  # Custom code for state resolution timeout
                    detail="System state resolution timeout - please try again later",
                )

            if not GlobalState.AUTH_ROTATION_LOCK.is_set():
                # Rotation in progress. Wait for lock to open with timeout.
                try:
                    await asyncio.wait_for(
                        GlobalState.AUTH_ROTATION_LOCK.wait(), timeout=30.0
                    )
                except asyncio.TimeoutError:
                    logger.warning(
                        "🚨 Lock wait timeout after 30s. Service may be unavailable."
                    )
                    from fastapi import HTTPException

                    raise HTTPException(
                        status_code=503,
                        detail="Service temporarily unavailable - timeout waiting for system lock",
                    )
            else:
                # Lock is Open, but Quota is still marked Exceeded.
                # This implies the Watchdog is about to rotate, or we are in a race.
                # We wait for the recovery event which signals rotation completion.
                try:
                    if GlobalState.IS_RECOVERING:
                        # If recovery is active, wait for it to finish
                        await asyncio.wait_for(
                            GlobalState.RECOVERY_EVENT.wait(), timeout=30.0
                        )
                    else:
                        # Watchdog hasn't started yet, wait briefly
                        await asyncio.sleep(0.1)
                except asyncio.TimeoutError:
                    await asyncio.sleep(0.1)
    finally:
        if is_waiting:
            GlobalState.queued_request_count -= 1