|
@@ -3,14 +3,16 @@ import time
|
|
|
import json
|
|
import json
|
|
|
import threading
|
|
import threading
|
|
|
import random
|
|
import random
|
|
|
-import redis
|
|
|
|
|
-from typing import List, Dict, Callable, Any, Optional
|
|
|
|
|
|
|
+from datetime import datetime
|
|
|
|
|
+from typing import List, Dict, Callable, Any
|
|
|
|
|
|
|
|
from vs_types import GroupConfig, VSPlgConfig, Task, VSQueryResult, AppointmentType, AvailabilityStatus
|
|
from vs_types import GroupConfig, VSPlgConfig, Task, VSQueryResult, AppointmentType, AvailabilityStatus
|
|
|
from vs_plg_factory import VSPlgFactory
|
|
from vs_plg_factory import VSPlgFactory
|
|
|
from toolkit.thread_pool import ThreadPool
|
|
from toolkit.thread_pool import ThreadPool
|
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
|
from toolkit.backoff import ExponentialBackoff
|
|
from toolkit.backoff import ExponentialBackoff
|
|
|
|
|
+from utils.safe_redis_cli import SafeRedisClient
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class OrderBookerGCO:
|
|
class OrderBookerGCO:
|
|
|
"""
|
|
"""
|
|
@@ -27,7 +29,8 @@ class OrderBookerGCO:
|
|
|
self.m_lock = threading.RLock()
|
|
self.m_lock = threading.RLock()
|
|
|
self.m_stop_event = threading.Event()
|
|
self.m_stop_event = threading.Event()
|
|
|
|
|
|
|
|
- self.redis_client = redis.Redis(**redis_conf)
|
|
|
|
|
|
|
+ self.redis_client = SafeRedisClient(redis_conf, self.m_logger)
|
|
|
|
|
+
|
|
|
self.m_pending_order_by_queue: Dict[str, int] = {}
|
|
self.m_pending_order_by_queue: Dict[str, int] = {}
|
|
|
self.m_last_spawn_times: Dict[str, float] = {}
|
|
self.m_last_spawn_times: Dict[str, float] = {}
|
|
|
self.m_task_data_cache: Dict[str, dict] = {}
|
|
self.m_task_data_cache: Dict[str, dict] = {}
|
|
@@ -41,6 +44,8 @@ class OrderBookerGCO:
|
|
|
def _log(self, message):
|
|
def _log(self, message):
|
|
|
if self.m_logger:
|
|
if self.m_logger:
|
|
|
self.m_logger(f'[ORDER-BOOKER] [{self.m_cfg.identifier}] {message}')
|
|
self.m_logger(f'[ORDER-BOOKER] [{self.m_cfg.identifier}] {message}')
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f'[ORDER-BOOKER] [{self.m_cfg.identifier}] {message}')
|
|
|
|
|
|
|
|
def start(self):
|
|
def start(self):
|
|
|
if not self.m_cfg.enable:
|
|
if not self.m_cfg.enable:
|
|
@@ -60,15 +65,28 @@ class OrderBookerGCO:
|
|
|
self._log("Stopping Booker...")
|
|
self._log("Stopping Booker...")
|
|
|
self.m_stop_event.set()
|
|
self.m_stop_event.set()
|
|
|
self._cleanup_all_tasks("booker stop")
|
|
self._cleanup_all_tasks("booker stop")
|
|
|
|
|
+
|
|
|
|
|
+ def update_config(self, new_cfg: GroupConfig):
|
|
|
|
|
+ """
|
|
|
|
|
+ 动态更新配置
|
|
|
|
|
+ """
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ if self.m_cfg.enable and not new_cfg.enable:
|
|
|
|
|
+ self._log("Config dynamically updated: Group DISABLED. Will stop creating new tasks.")
|
|
|
|
|
+ elif not self.m_cfg.enable and new_cfg.enable:
|
|
|
|
|
+ self._log("Config dynamically updated: Group ENABLED.")
|
|
|
|
|
+ else:
|
|
|
|
|
+ self._log("Config dynamically updated: Parameters refreshed.")
|
|
|
|
|
+
|
|
|
|
|
+ self.m_cfg = new_cfg
|
|
|
|
|
|
|
|
def _cleanup_task(self, task: Task, reason: str = ""):
|
|
def _cleanup_task(self, task: Task, reason: str = ""):
|
|
|
try:
|
|
try:
|
|
|
- instance = getattr(task, 'instance', None)
|
|
|
|
|
- if instance and hasattr(instance, 'cleanup'):
|
|
|
|
|
- instance.cleanup()
|
|
|
|
|
- self._log(f"🧹 Cleaned up instance for task={getattr(task, 'task_ref', None)}. Reason: {reason}")
|
|
|
|
|
|
|
+ if task and task.instance:
|
|
|
|
|
+ task.instance.cleanup()
|
|
|
|
|
+ self._log(f"🧹 Cleaned up instance for task={task.task_ref}. Reason: {reason}")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- self._log(f"Cleanup failed for task={getattr(task, 'task_ref', None)}. Reason: {reason}. Error: {e}")
|
|
|
|
|
|
|
+ self._log(f"Cleanup failed for task={task.task_ref}. Reason: {reason}. Error: {e}")
|
|
|
|
|
|
|
|
def _remove_task(self, task: Task, reason: str = "", cleanup: bool = True):
|
|
def _remove_task(self, task: Task, reason: str = "", cleanup: bool = True):
|
|
|
removed = False
|
|
removed = False
|
|
@@ -76,7 +94,7 @@ class OrderBookerGCO:
|
|
|
if task in self.m_tasks:
|
|
if task in self.m_tasks:
|
|
|
self.m_tasks.remove(task)
|
|
self.m_tasks.remove(task)
|
|
|
removed = True
|
|
removed = True
|
|
|
- task_id = str(getattr(task, 'task_ref', ''))
|
|
|
|
|
|
|
+ task_id = task.task_ref
|
|
|
self.m_task_data_cache.pop(task_id, None)
|
|
self.m_task_data_cache.pop(task_id, None)
|
|
|
|
|
|
|
|
if cleanup and removed:
|
|
if cleanup and removed:
|
|
@@ -96,26 +114,20 @@ class OrderBookerGCO:
|
|
|
|
|
|
|
|
def _maintain_loop(self):
|
|
def _maintain_loop(self):
|
|
|
self._log("Maintain loop started.")
|
|
self._log("Maintain loop started.")
|
|
|
- heartbeat_interval = 30
|
|
|
|
|
while not self.m_stop_event.is_set():
|
|
while not self.m_stop_event.is_set():
|
|
|
- for _ in range(heartbeat_interval):
|
|
|
|
|
- if self.m_stop_event.is_set():
|
|
|
|
|
- return
|
|
|
|
|
- time.sleep(1.0)
|
|
|
|
|
-
|
|
|
|
|
- with self.m_lock:
|
|
|
|
|
- tasks_to_check = list(self.m_tasks)
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ time.sleep(1)
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ tasks_to_check = list(self.m_tasks)
|
|
|
|
|
+
|
|
|
|
|
+ if not tasks_to_check:
|
|
|
|
|
+ continue
|
|
|
|
|
|
|
|
- if not tasks_to_check:
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- healthy_tasks = []
|
|
|
|
|
- dead_tasks = []
|
|
|
|
|
- now = time.time()
|
|
|
|
|
-
|
|
|
|
|
- for t in tasks_to_check:
|
|
|
|
|
- if now >= t.next_remote_ping:
|
|
|
|
|
- try:
|
|
|
|
|
|
|
+ healthy_tasks = []
|
|
|
|
|
+ dead_tasks = []
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ for t in tasks_to_check:
|
|
|
|
|
+ if now >= t.next_remote_ping:
|
|
|
t.instance.keep_alive()
|
|
t.instance.keep_alive()
|
|
|
if t.instance.health_check():
|
|
if t.instance.health_check():
|
|
|
healthy_tasks.append(t)
|
|
healthy_tasks.append(t)
|
|
@@ -125,78 +137,67 @@ class OrderBookerGCO:
|
|
|
else:
|
|
else:
|
|
|
dead_tasks.append(t)
|
|
dead_tasks.append(t)
|
|
|
self._log(f"♻️ Instance for task={t.task_ref} unhealthy.")
|
|
self._log(f"♻️ Instance for task={t.task_ref} unhealthy.")
|
|
|
- except Exception as e:
|
|
|
|
|
- dead_tasks.append(t)
|
|
|
|
|
- self._log(f"♻️ Instance for task={t.task_ref} keep-alive failed: {e}.")
|
|
|
|
|
- else:
|
|
|
|
|
- healthy_tasks.append(t)
|
|
|
|
|
-
|
|
|
|
|
- if healthy_tasks:
|
|
|
|
|
- try:
|
|
|
|
|
- pipeline = self.redis_client.pipeline()
|
|
|
|
|
|
|
+ else:
|
|
|
|
|
+ healthy_tasks.append(t)
|
|
|
|
|
+
|
|
|
|
|
+ if healthy_tasks:
|
|
|
new_deadline = time.time() + self.heartbeat_ttl
|
|
new_deadline = time.time() + self.heartbeat_ttl
|
|
|
- for t in healthy_tasks:
|
|
|
|
|
- if t.task_ref is not None:
|
|
|
|
|
- pipeline.zadd(self.m_tracker_key, {str(t.task_ref): new_deadline})
|
|
|
|
|
- pipeline.execute()
|
|
|
|
|
- self._log(f"💓 Heartbeat sent. Renewed {len(healthy_tasks)} tasks.")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- self._log(f"Redis Heartbeat update failed: {e}")
|
|
|
|
|
|
|
+ mapping = {str(t.task_ref): new_deadline for t in healthy_tasks if t.task_ref is not None}
|
|
|
|
|
+ self.redis_client.bulk_zadd(self.m_tracker_key, mapping)
|
|
|
|
|
+ # self._log(f"💓 Heartbeat sent. Renewed {len(healthy_tasks)} tasks.")
|
|
|
|
|
|
|
|
- if dead_tasks:
|
|
|
|
|
- try:
|
|
|
|
|
- pipeline = self.redis_client.pipeline()
|
|
|
|
|
- for t in dead_tasks:
|
|
|
|
|
- if t.task_ref is not None:
|
|
|
|
|
- pipeline.zadd(self.m_tracker_key, {str(t.task_ref): 0})
|
|
|
|
|
- pipeline.execute()
|
|
|
|
|
|
|
+ if dead_tasks:
|
|
|
|
|
+ mapping = {str(t.task_ref): 0 for t in dead_tasks if t.task_ref is not None}
|
|
|
|
|
+ self.redis_client.bulk_zadd(self.m_tracker_key, mapping)
|
|
|
self._log(f"🗑️ Handed over {len(dead_tasks)} dead tasks to Sweeper.")
|
|
self._log(f"🗑️ Handed over {len(dead_tasks)} dead tasks to Sweeper.")
|
|
|
- except Exception as e:
|
|
|
|
|
- pass
|
|
|
|
|
-
|
|
|
|
|
- if dead_tasks:
|
|
|
|
|
- with self.m_lock:
|
|
|
|
|
- current_tasks = list(self.m_tasks)
|
|
|
|
|
- self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
|
|
|
|
|
- for t in dead_tasks:
|
|
|
|
|
- if t in current_tasks:
|
|
|
|
|
- self._cleanup_task(t, "unhealthy or keep-alive failed")
|
|
|
|
|
- else:
|
|
|
|
|
- with self.m_lock:
|
|
|
|
|
- self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if dead_tasks:
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ current_tasks = list(self.m_tasks)
|
|
|
|
|
+ self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
|
|
|
|
|
+ for t in dead_tasks:
|
|
|
|
|
+ if t in current_tasks:
|
|
|
|
|
+ self._cleanup_task(t, "unhealthy or keep-alive failed")
|
|
|
|
|
+ else:
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ self.m_tasks = [t for t in self.m_tasks if t in healthy_tasks]
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self._log(f'Maintain loop exception:{e}')
|
|
|
|
|
|
|
|
def _cache_refresh_loop(self):
|
|
def _cache_refresh_loop(self):
|
|
|
self._log("Cache refresh loop started.")
|
|
self._log("Cache refresh loop started.")
|
|
|
- refresh_interval = 15*60
|
|
|
|
|
|
|
+ refresh_interval = 15 * 60
|
|
|
|
|
|
|
|
while not self.m_stop_event.is_set():
|
|
while not self.m_stop_event.is_set():
|
|
|
- for _ in range(refresh_interval):
|
|
|
|
|
- if self.m_stop_event.is_set():
|
|
|
|
|
- return
|
|
|
|
|
- time.sleep(1.0)
|
|
|
|
|
- with self.m_lock:
|
|
|
|
|
- task_ids = list(self.m_task_data_cache.keys())
|
|
|
|
|
- if not task_ids:
|
|
|
|
|
- continue
|
|
|
|
|
- for tid in task_ids:
|
|
|
|
|
- if self.m_stop_event.is_set():
|
|
|
|
|
- break
|
|
|
|
|
- try:
|
|
|
|
|
- fresh_data = VSCloudApi.Instance().get_vas_task(tid)
|
|
|
|
|
- if fresh_data:
|
|
|
|
|
- with self.m_lock:
|
|
|
|
|
- if tid in self.m_task_data_cache:
|
|
|
|
|
- self.m_task_data_cache[tid] = fresh_data
|
|
|
|
|
- except Exception:
|
|
|
|
|
- pass
|
|
|
|
|
- time.sleep(0.5)
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ time.sleep(1)
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ tasks_to_check = {
|
|
|
|
|
+ tid: data.get('_last_refresh', 0)
|
|
|
|
|
+ for tid, data in self.m_task_data_cache.items()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if not tasks_to_check:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ for tid, last_refresh in tasks_to_check.items():
|
|
|
|
|
+ if now - last_refresh >= refresh_interval:
|
|
|
|
|
+ fresh_data = VSCloudApi.Instance().get_vas_task(tid)
|
|
|
|
|
+ if fresh_data:
|
|
|
|
|
+ fresh_data['_last_refresh'] = time.time()
|
|
|
|
|
+
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ if tid in self.m_task_data_cache:
|
|
|
|
|
+ self.m_task_data_cache[tid] = fresh_data
|
|
|
|
|
+ time.sleep(0.5)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self._log(f'Cache refresh loop exception: {e}')
|
|
|
|
|
|
|
|
- def _is_date_of_interest(self, task, query_result) -> bool:
|
|
|
|
|
|
|
+ def _is_date_of_interest(self, task, query_result: VSQueryResult) -> bool:
|
|
|
"""
|
|
"""
|
|
|
- 判断 query_result 中的可用日期,
|
|
|
|
|
- 是否在 task 的意向日期范围内。
|
|
|
|
|
|
|
+ 判断 query_result 中的可用日期,是否在 task 的意向日期范围内。
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
if query_result.availability_status != AvailabilityStatus.Available:
|
|
if query_result.availability_status != AvailabilityStatus.Available:
|
|
|
return True
|
|
return True
|
|
|
task_id = task.task_ref
|
|
task_id = task.task_ref
|
|
@@ -211,7 +212,8 @@ class OrderBookerGCO:
|
|
|
or '2100-01-01'
|
|
or '2100-01-01'
|
|
|
)
|
|
)
|
|
|
available_date = query_result.earliest_date
|
|
available_date = query_result.earliest_date
|
|
|
- return available_date <= expected_end_date
|
|
|
|
|
|
|
+ dt = available_date.strftime("%Y-%m-%d")
|
|
|
|
|
+ return dt <= expected_end_date
|
|
|
|
|
|
|
|
def _booking_trigger_loop(self):
|
|
def _booking_trigger_loop(self):
|
|
|
self._log("Trigger loop started.")
|
|
self._log("Trigger loop started.")
|
|
@@ -256,8 +258,7 @@ class OrderBookerGCO:
|
|
|
for t in threads:
|
|
for t in threads:
|
|
|
t.join()
|
|
t.join()
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- self._log(f"Trigger loop error: {e}")
|
|
|
|
|
- time.sleep(2)
|
|
|
|
|
|
|
+ self._log(f"Booking trigger loop exception: {e}")
|
|
|
|
|
|
|
|
def _execute_book_job(self, task: Task, query_result: VSQueryResult):
|
|
def _execute_book_job(self, task: Task, query_result: VSQueryResult):
|
|
|
task_id = task.task_ref
|
|
task_id = task.task_ref
|
|
@@ -270,7 +271,6 @@ class OrderBookerGCO:
|
|
|
self._log(f"Bound Task={task_id} is no longer valid or already processed. Removing instance.")
|
|
self._log(f"Bound Task={task_id} is no longer valid or already processed. Removing instance.")
|
|
|
self._remove_task(task, "bound task no longer valid")
|
|
self._remove_task(task, "bound task no longer valid")
|
|
|
self.redis_client.zrem(self.m_tracker_key, task_id)
|
|
self.redis_client.zrem(self.m_tracker_key, task_id)
|
|
|
- return
|
|
|
|
|
|
|
|
|
|
order_id = task_data.get('order_id')
|
|
order_id = task_data.get('order_id')
|
|
|
user_input = task_data.get('user_inputs', {})
|
|
user_input = task_data.get('user_inputs', {})
|
|
@@ -333,33 +333,37 @@ class OrderBookerGCO:
|
|
|
except Exception as cloud_err:
|
|
except Exception as cloud_err:
|
|
|
self._log(f"Failed to update task meta: {cloud_err}")
|
|
self._log(f"Failed to update task meta: {cloud_err}")
|
|
|
ThreadPool.getInstance().enqueue(_update_cloud_meta)
|
|
ThreadPool.getInstance().enqueue(_update_cloud_meta)
|
|
|
|
|
+
|
|
|
t_cd = self.task_backoff.calculate(t_fails)
|
|
t_cd = self.task_backoff.calculate(t_fails)
|
|
|
self._log(f"⏳ Task={task_id} (Booking Attempt {t_fails}) suspended for {t_cd:.1f}s.")
|
|
self._log(f"⏳ Task={task_id} (Booking Attempt {t_fails}) suspended for {t_cd:.1f}s.")
|
|
|
self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + t_cd})
|
|
self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + t_cd})
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
def _creator_loop(self):
|
|
def _creator_loop(self):
|
|
|
self._log("Creator loop started.")
|
|
self._log("Creator loop started.")
|
|
|
spawn_interval = 10.0
|
|
spawn_interval = 10.0
|
|
|
while not self.m_stop_event.is_set():
|
|
while not self.m_stop_event.is_set():
|
|
|
- time.sleep(2.0)
|
|
|
|
|
- now = time.time()
|
|
|
|
|
- for apt in self.m_cfg.appointment_types:
|
|
|
|
|
- r_key = apt.routing_key
|
|
|
|
|
-
|
|
|
|
|
- queue_cd_key = f"vs:queue:cooldown:{r_key}"
|
|
|
|
|
- if self.redis_client.exists(queue_cd_key):
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- with self.m_lock:
|
|
|
|
|
- active = sum(1 for t in self.m_tasks if getattr(t, 'source_queue', '') == r_key)
|
|
|
|
|
- pending = self.m_pending_order_by_queue.get(r_key, 0)
|
|
|
|
|
- target = self.m_cfg.booker.target_instances
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ time.sleep(1)
|
|
|
|
|
+ now = time.time()
|
|
|
|
|
+ for apt in self.m_cfg.appointment_types:
|
|
|
|
|
+ r_key = apt.routing_key
|
|
|
|
|
+ queue_cd_key = f"vs:queue:cooldown:{r_key}"
|
|
|
|
|
|
|
|
- if (active + pending) < target:
|
|
|
|
|
- last_spawn = self.m_last_spawn_times.get(r_key, 0.0)
|
|
|
|
|
- if now - last_spawn >= spawn_interval:
|
|
|
|
|
- self.m_last_spawn_times[r_key] = now
|
|
|
|
|
- self._spawn_worker(r_key)
|
|
|
|
|
|
|
+ if self.redis_client.exists(queue_cd_key):
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ with self.m_lock:
|
|
|
|
|
+ active = sum(1 for t in self.m_tasks if t.source_queue == r_key)
|
|
|
|
|
+ pending = self.m_pending_order_by_queue.get(r_key, 0)
|
|
|
|
|
+ target = self.m_cfg.booker.target_instances
|
|
|
|
|
+
|
|
|
|
|
+ if (active + pending) < target:
|
|
|
|
|
+ last_spawn = self.m_last_spawn_times.get(r_key, 0.0)
|
|
|
|
|
+ if now - last_spawn >= spawn_interval:
|
|
|
|
|
+ self.m_last_spawn_times[r_key] = now
|
|
|
|
|
+ self._spawn_worker(r_key)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self._log(f'Creator loop exception:{e}')
|
|
|
|
|
|
|
|
def _spawn_worker(self, target_routing_key: str):
|
|
def _spawn_worker(self, target_routing_key: str):
|
|
|
with self.m_lock:
|
|
with self.m_lock:
|
|
@@ -396,12 +400,7 @@ class OrderBookerGCO:
|
|
|
acceptable_keys = [target_routing_key]
|
|
acceptable_keys = [target_routing_key]
|
|
|
if self.m_cfg.need_proxy:
|
|
if self.m_cfg.need_proxy:
|
|
|
proxy = VSCloudApi.Instance().get_next_proxy(self.m_cfg.proxy_pool, self.m_cfg.proxy_cd)
|
|
proxy = VSCloudApi.Instance().get_next_proxy(self.m_cfg.proxy_pool, self.m_cfg.proxy_cd)
|
|
|
- plg_cfg.proxy.id = proxy['id']
|
|
|
|
|
- plg_cfg.proxy.ip = proxy['ip']
|
|
|
|
|
- plg_cfg.proxy.port = proxy['port']
|
|
|
|
|
- plg_cfg.proxy.proto = proxy['proto']
|
|
|
|
|
- plg_cfg.proxy.username = proxy['username']
|
|
|
|
|
- plg_cfg.proxy.password = proxy['password']
|
|
|
|
|
|
|
+ plg_cfg.proxy = type(plg_cfg.proxy)(**proxy)
|
|
|
|
|
|
|
|
instance = self.m_factory.create(self.m_cfg.identifier, self.m_cfg.plugin_config.plugin_name)
|
|
instance = self.m_factory.create(self.m_cfg.identifier, self.m_cfg.plugin_config.plugin_name)
|
|
|
instance.set_log(self.m_logger)
|
|
instance.set_log(self.m_logger)
|
|
@@ -421,9 +420,10 @@ class OrderBookerGCO:
|
|
|
next_remote_ping=time.time() + random.randint(55, 65)
|
|
next_remote_ping=time.time() + random.randint(55, 65)
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
- queue_fail_key = f"vs:queue:failures:{target_routing_key}"
|
|
|
|
|
- self.redis_client.delete(queue_fail_key)
|
|
|
|
|
|
|
+
|
|
|
success = True
|
|
success = True
|
|
|
|
|
+ queue_fail_key = f"vs:queue:failures:{target_routing_key}"
|
|
|
|
|
+ self.redis_client.delete(queue_fail_key)
|
|
|
self._log(f"+++ Order Booker spawned: {plg_cfg.account.username} (Target: {acceptable_keys})")
|
|
self._log(f"+++ Order Booker spawned: {plg_cfg.account.username} (Target: {acceptable_keys})")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
err_str = str(e)
|
|
err_str = str(e)
|
|
@@ -445,23 +445,28 @@ class OrderBookerGCO:
|
|
|
is_rate_limited = True
|
|
is_rate_limited = True
|
|
|
queue_fail_key = f"vs:queue:failures:{target_routing_key}"
|
|
queue_fail_key = f"vs:queue:failures:{target_routing_key}"
|
|
|
queue_cd_key = f"vs:queue:cooldown:{target_routing_key}"
|
|
queue_cd_key = f"vs:queue:cooldown:{target_routing_key}"
|
|
|
|
|
+
|
|
|
q_fails = self.redis_client.incr(queue_fail_key)
|
|
q_fails = self.redis_client.incr(queue_fail_key)
|
|
|
q_cd = self.queue_backoff.calculate(q_fails)
|
|
q_cd = self.queue_backoff.calculate(q_fails)
|
|
|
self.redis_client.set(queue_cd_key, "1", ex=int(q_cd))
|
|
self.redis_client.set(queue_cd_key, "1", ex=int(q_cd))
|
|
|
self._log(f"📉 [Rate Limited] Queue '{target_routing_key}' failed {q_fails} times. Global Backoff: {q_cd:.1f}s.")
|
|
self._log(f"📉 [Rate Limited] Queue '{target_routing_key}' failed {q_fails} times. Global Backoff: {q_cd:.1f}s.")
|
|
|
|
|
+
|
|
|
if task_id is not None:
|
|
if task_id is not None:
|
|
|
task_meta = task_data.get('meta') or {}
|
|
task_meta = task_data.get('meta') or {}
|
|
|
t_fails = task_meta.get('spawn_failures', 0) + 1
|
|
t_fails = task_meta.get('spawn_failures', 0) + 1
|
|
|
task_meta['spawn_failures'] = t_fails
|
|
task_meta['spawn_failures'] = t_fails
|
|
|
|
|
|
|
|
- try:
|
|
|
|
|
- VSCloudApi.Instance().update_vas_task(str(task_id), {"meta": task_meta})
|
|
|
|
|
- except Exception as cloud_err:
|
|
|
|
|
- self._log(f"Failed to update task meta: {cloud_err}")
|
|
|
|
|
|
|
+ def _update_cloud_meta():
|
|
|
|
|
+ try:
|
|
|
|
|
+ VSCloudApi.Instance().update_vas_task(str(task_id), {"meta": task_meta})
|
|
|
|
|
+ except Exception as cloud_err:
|
|
|
|
|
+ self._log(f"Failed to update task meta: {cloud_err}")
|
|
|
|
|
+ ThreadPool.getInstance().enqueue(_update_cloud_meta)
|
|
|
|
|
|
|
|
t_cd = self.account_backoff.calculate(t_fails)
|
|
t_cd = self.account_backoff.calculate(t_fails)
|
|
|
self._log(f"⏳ Task={task_id} (Attempt {t_fails}) suspended for {t_cd:.1f}s.")
|
|
self._log(f"⏳ Task={task_id} (Attempt {t_fails}) suspended for {t_cd:.1f}s.")
|
|
|
- self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + t_cd})
|
|
|
|
|
|
|
+ self.redis_client.zadd(self.m_tracker_key, {str(task_id): time.time() + t_cd})
|
|
|
|
|
+
|
|
|
finally:
|
|
finally:
|
|
|
with self.m_lock:
|
|
with self.m_lock:
|
|
|
self.m_pending_order_by_queue[target_routing_key] = max(0, self.m_pending_order_by_queue[target_routing_key] - 1)
|
|
self.m_pending_order_by_queue[target_routing_key] = max(0, self.m_pending_order_by_queue[target_routing_key] - 1)
|
|
@@ -469,7 +474,6 @@ class OrderBookerGCO:
|
|
|
if not success and task_id is not None and not is_rate_limited:
|
|
if not success and task_id is not None and not is_rate_limited:
|
|
|
self.redis_client.zadd(self.m_tracker_key, {str(task_id): 0})
|
|
self.redis_client.zadd(self.m_tracker_key, {str(task_id): 0})
|
|
|
self._log(f"♻️ Task={task_id} failed normal spawn. Instantly handed over to Sweeper.")
|
|
self._log(f"♻️ Task={task_id} failed normal spawn. Instantly handed over to Sweeper.")
|
|
|
-
|
|
|
|
|
with self.m_lock:
|
|
with self.m_lock:
|
|
|
self.m_task_data_cache.pop(str(task_id), None)
|
|
self.m_task_data_cache.pop(str(task_id), None)
|
|
|
|
|
|