jiayi.xie Claude Opus 4.8 (1M context) commited on
Commit
29b5866
·
1 Parent(s): 8bb27a3

Keep register pool monitor alive across transient errors

Browse files

The 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>

Files changed (1) hide show
  1. services/register_service.py +47 -27
services/register_service.py CHANGED
@@ -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
- self._save()
 
 
 
 
187
 
188
  def _run(self) -> None:
189
  threads = int(self.get()["threads"])
190
  submitted, done, success, fail = 0, 0, 0, 0
191
- with ThreadPoolExecutor(max_workers=threads) as executor:
192
- futures = set()
193
- while True:
194
- cfg = self.get()
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
- result = future.result()
209
- success += 1 if result.get("ok") else 0
210
- fail += 0 if result.get("ok") else 1
211
- except Exception:
212
- fail += 1
213
- self._bump(running=0, done=done, success=success, fail=fail, finished_at=_now())
214
- with self._lock:
215
- self._config["enabled"] = False
216
- self._save()
217
- self._append_log(f"注册任务结束,成功{success},失败{fail}", "yellow")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)