Spaces:
Paused
Keep register pool monitor alive across transient errors
Browse filesThe available/quota-mode register loop runs a background monitor thread
that re-checks the account pool every check_interval and resumes
registering when it drops below target. Two issues made that thread die
silently, leaving enabled=True (UI still shows "运行中") while nothing
re-evaluated the pool — only a manual stop→start recovered it:
- _run had no per-iteration try/except, so any transient error in the
loop body killed the whole thread.
- _bump persisted register.json to the /data FUSE bucket on every pool
check (~2 writes / interval). A single bucket write hiccup raised and
unwound the unguarded loop — the exact per-write-to-FUSE anti-pattern
CLAUDE.md already warns against.
Fixes:
- Wrap the loop body in try/except (log + sleep + continue); add a
finally that always sets enabled=False so the UI can't get stuck on a
dead thread.
- Drop _save() from _bump. Stats are ephemeral display state read from
in-memory _config over SSE and rebuilt by start(); persist only on
start/stop/update/reset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- services/register_service.py +47 -27
|
@@ -183,38 +183,58 @@ class RegisterService:
|
|
| 183 |
stats["avg_seconds"] = round(elapsed / success, 1) if success else 0
|
| 184 |
stats["success_rate"] = round(success * 100 / max(1, success + fail), 1)
|
| 185 |
self._config["stats"]["updated_at"] = _now()
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
def _run(self) -> None:
|
| 189 |
threads = int(self.get()["threads"])
|
| 190 |
submitted, done, success, fail = 0, 0, 0, 0
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
while self.get()["enabled"] and not self._target_reached(cfg, submitted) and len(futures) < threads:
|
| 196 |
-
submitted += 1
|
| 197 |
-
futures.add(executor.submit(openai_register.worker, submitted))
|
| 198 |
-
self._bump(running=len(futures), done=done, success=success, fail=fail)
|
| 199 |
-
if not futures and (not self.get()["enabled"] or str(cfg.get("mode") or "total") == "total"):
|
| 200 |
-
break
|
| 201 |
-
if not futures:
|
| 202 |
-
time.sleep(max(1, int(cfg.get("check_interval") or 5)))
|
| 203 |
-
continue
|
| 204 |
-
finished, futures = wait(futures, return_when=FIRST_COMPLETED)
|
| 205 |
-
for future in finished:
|
| 206 |
-
done += 1
|
| 207 |
try:
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
|
| 220 |
register_service = RegisterService(REGISTER_FILE)
|
|
|
|
| 183 |
stats["avg_seconds"] = round(elapsed / success, 1) if success else 0
|
| 184 |
stats["success_rate"] = round(success * 100 / max(1, success + fail), 1)
|
| 185 |
self._config["stats"]["updated_at"] = _now()
|
| 186 |
+
# 不在此落盘:stats 只是临时展示字段,SSE 读内存 _config 即可,start() 每次重建。
|
| 187 |
+
# _bump 每次号池检查都调用,旧实现会把 register.json 写到 /data FUSE 桶;
|
| 188 |
+
# 桶写一次抽风抛异常,就会杀死没有 try 的 _run 监控线程,enabled 卡在 True、
|
| 189 |
+
# 号池掉到目标下也不再触发注册(CLAUDE.md 已警告对象桶每次全量写的反模式)。
|
| 190 |
+
# 真正落盘只发生在 start/stop/update/reset。
|
| 191 |
|
| 192 |
def _run(self) -> None:
|
| 193 |
threads = int(self.get()["threads"])
|
| 194 |
submitted, done, success, fail = 0, 0, 0, 0
|
| 195 |
+
try:
|
| 196 |
+
with ThreadPoolExecutor(max_workers=threads) as executor:
|
| 197 |
+
futures: set = set()
|
| 198 |
+
while True:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
try:
|
| 200 |
+
cfg = self.get()
|
| 201 |
+
while self.get()["enabled"] and not self._target_reached(cfg, submitted) and len(futures) < threads:
|
| 202 |
+
submitted += 1
|
| 203 |
+
futures.add(executor.submit(openai_register.worker, submitted))
|
| 204 |
+
self._bump(running=len(futures), done=done, success=success, fail=fail)
|
| 205 |
+
if not futures and (not self.get()["enabled"] or str(cfg.get("mode") or "total") == "total"):
|
| 206 |
+
break
|
| 207 |
+
if not futures:
|
| 208 |
+
time.sleep(max(1, int(cfg.get("check_interval") or 5)))
|
| 209 |
+
continue
|
| 210 |
+
finished, futures = wait(futures, return_when=FIRST_COMPLETED)
|
| 211 |
+
for future in finished:
|
| 212 |
+
done += 1
|
| 213 |
+
try:
|
| 214 |
+
result = future.result()
|
| 215 |
+
success += 1 if result.get("ok") else 0
|
| 216 |
+
fail += 0 if result.get("ok") else 1
|
| 217 |
+
except Exception:
|
| 218 |
+
fail += 1
|
| 219 |
+
except Exception as loop_error:
|
| 220 |
+
# 单次循环出错(如 /data FUSE 桶写盘抽风、号池读取瞬时异常)绝不能杀死
|
| 221 |
+
# 监控线程:否则 enabled 卡在 True、徽章仍显示“运行中”,但号池掉到目标下
|
| 222 |
+
# 也永不再触发注册,只能手动 stop→start 才恢复。忽略本次,睡一个检查间隔后继续。
|
| 223 |
+
self._append_log(f"号池监控循环异常,已忽略本次并继续: {loop_error}", "red")
|
| 224 |
+
time.sleep(max(1, int(self.get().get("check_interval") or 5)))
|
| 225 |
+
finally:
|
| 226 |
+
# 无论正常结束还是异常退出,都把 enabled 落为 False,避免 UI 卡在“运行中”假象。
|
| 227 |
+
try:
|
| 228 |
+
self._bump(running=0, done=done, success=success, fail=fail, finished_at=_now())
|
| 229 |
+
except Exception:
|
| 230 |
+
pass
|
| 231 |
+
with self._lock:
|
| 232 |
+
self._config["enabled"] = False
|
| 233 |
+
try:
|
| 234 |
+
self._save()
|
| 235 |
+
except Exception:
|
| 236 |
+
pass
|
| 237 |
+
self._append_log(f"注册任务结束,成功{success},失败{fail}", "yellow")
|
| 238 |
|
| 239 |
|
| 240 |
register_service = RegisterService(REGISTER_FILE)
|