fix: 防止 Worker Timeout 與改進錯誤訊息
- 加入 Oracle call_timeout (55s) 防止慢查詢阻塞 Gunicorn worker - 改進前端 fetchWithTimeout 錯誤訊息,顯示超時的 URL - 區分超時錯誤與網路錯誤,便於問題診斷 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -181,6 +181,7 @@ def get_db_connection():
|
||||
"""Create a direct oracledb connection.
|
||||
|
||||
Used for operations that need direct cursor access.
|
||||
Includes call_timeout to prevent long-running queries from blocking workers.
|
||||
"""
|
||||
try:
|
||||
conn = oracledb.connect(
|
||||
@@ -189,7 +190,10 @@ def get_db_connection():
|
||||
retry_count=1, # Retry once on connection failure
|
||||
retry_delay=1, # 1s delay between retries
|
||||
)
|
||||
logger.debug("Direct oracledb connection established")
|
||||
# Set call timeout to 55 seconds (must be less than Gunicorn's 60s worker timeout)
|
||||
# This prevents queries from blocking workers indefinitely
|
||||
conn.call_timeout = 55000 # milliseconds
|
||||
logger.debug("Direct oracledb connection established (call_timeout=55s)")
|
||||
return conn
|
||||
except Exception as exc:
|
||||
ora_code = _extract_ora_code(exc)
|
||||
|
||||
@@ -764,7 +764,11 @@
|
||||
|
||||
async function fetchWithTimeout(url, timeout = API_TIMEOUT, externalSignal = null) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
let timedOut = false;
|
||||
const timeoutId = setTimeout(() => {
|
||||
timedOut = true;
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
|
||||
// If external signal is provided, abort when it fires
|
||||
if (externalSignal) {
|
||||
@@ -782,9 +786,12 @@
|
||||
if (externalSignal && externalSignal.aborted) {
|
||||
throw error; // Re-throw AbortError for external cancellation
|
||||
}
|
||||
throw new Error('Request timeout');
|
||||
if (timedOut) {
|
||||
throw new Error(`Request timeout after ${timeout/1000}s: ${url}`);
|
||||
}
|
||||
throw error; // Other abort (shouldn't happen, but be safe)
|
||||
}
|
||||
throw error;
|
||||
throw new Error(`Network error for ${url}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -951,7 +951,11 @@
|
||||
|
||||
async function fetchWithTimeout(url, timeout = API_TIMEOUT, externalSignal = null) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
let timedOut = false;
|
||||
const timeoutId = setTimeout(() => {
|
||||
timedOut = true;
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
|
||||
// If external signal is provided, abort when it fires
|
||||
if (externalSignal) {
|
||||
@@ -969,9 +973,12 @@
|
||||
if (externalSignal && externalSignal.aborted) {
|
||||
throw error; // Re-throw AbortError for external cancellation
|
||||
}
|
||||
throw new Error('Request timeout');
|
||||
if (timedOut) {
|
||||
throw new Error(`Request timeout after ${timeout/1000}s: ${url}`);
|
||||
}
|
||||
throw error; // Other abort (shouldn't happen, but be safe)
|
||||
}
|
||||
throw error;
|
||||
throw new Error(`Network error for ${url}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user