Tilawa Server commited on
Commit
a8d14b8
·
1 Parent(s): 650d7e9

S64: fix 5 bugs — dedup v11.0, RAM guard, watchdog, engine_log, status

Browse files
Files changed (1) hide show
  1. app.py +32 -15
app.py CHANGED
@@ -96,13 +96,11 @@ ENGINE_SCRIPTS = {
96
  "v11.1": BASE / "true_engine_itiqan_v2_fixed.py",
97
  "v11.2": BASE / "engine_isteidad_v12.py",
98
  "v10.0": BASE / "engine_v100.py",
99
- "v11.0": BASE / "engine_tajalli_v1.py",
100
  "v9.0": BASE / "engine_v90.py",
101
  "v8.5": BASE / "engine_v85.py",
102
- "v8.4": BASE / "engine_v84.py",
103
  "v8.0": BASE / "engine_v80.py",
104
  "v7.0": BASE / "engine_v70.py",
105
- }
106
  REF_DIR = BASE / "reference_audio"
107
  CHUNK_SIZE = 4 * 1024 * 1024 # 4 MB upload chunks
108
  MAX_DOWNLOAD_CHUNK = 32 * 1024 * 1024 # 32 MB max per download_chunk call
@@ -519,10 +517,10 @@ def _run_engine(job_id):
519
 
520
  # ── RAM guard: require ≥ 3.5 GB available before launching subprocess ──
521
  ram_gb = _available_ram_gb()
522
- if ram_gb < 0.5:
523
  with JOBS_LOCK:
524
  job["status"] = "error"
525
- job["error"] = f"Insufficient RAM ({ram_gb:.1f} GB free, need 0.5 GB)"
526
  job["label"] = "خطأ: ذاكرة غير كافية — أعد المحاولة لاحقاً"
527
  return
528
 
@@ -555,16 +553,18 @@ def _run_engine(job_id):
555
  for rf in ref_files[:3]:
556
  cmd += ["--ref", str(rf)]
557
 
 
 
558
  proc = subprocess.Popen(
559
  cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
560
  )
561
 
562
- engine_lines = [] # DEBUG: capture all output
563
  for line in proc.stdout:
564
  line = line.strip()
565
- engine_lines.append(line) # DEBUG
566
- if len(engine_lines) > 60: # DEBUG: keep last 60 lines
567
- engine_lines.pop(0) # DEBUG
568
  # Progress markers — plain scalar writes, no lock needed
569
  if "Pass 1" in line or "[٧]" in line or "[١]" in line:
570
  job["progress"] = 45; job["label"] = "Pass 1 — تحليل الطيف..."
@@ -595,17 +595,31 @@ def _run_engine(job_id):
595
  except Exception:
596
  pass
597
 
598
- proc.wait()
 
 
 
 
 
 
 
 
 
 
 
 
599
  _out = Path(job["out_path"])
600
  # engine_v100 exits 1 for score<85 but still writes the file.
601
  # Use file existence + size as the real success signal.
602
  if _out.exists() and _out.stat().st_size > 0:
603
  success = True
604
  else:
605
- job["engine_rc"] = proc.returncode
 
606
 
607
  except Exception as exc:
608
  job["engine_error"] = str(exc)
 
609
 
610
  if not success:
611
  # Status transition: running → error (held under lock)
@@ -664,10 +678,13 @@ def status(job_id):
664
  if "error" in job:
665
  resp["error"] = job["error"]
666
 
667
- # DEBUG: expose last engine output lines so we can see the crash
668
- if job.get("status") == "error" and "engine_log" in job:
669
- resp["engine_log"] = job["engine_log"]
670
- resp["engine_rc"] = job.get("engine_rc")
 
 
 
671
 
672
  return jsonify(resp)
673
 
 
96
  "v11.1": BASE / "true_engine_itiqan_v2_fixed.py",
97
  "v11.2": BASE / "engine_isteidad_v12.py",
98
  "v10.0": BASE / "engine_v100.py",
 
99
  "v9.0": BASE / "engine_v90.py",
100
  "v8.5": BASE / "engine_v85.py",
 
101
  "v8.0": BASE / "engine_v80.py",
102
  "v7.0": BASE / "engine_v70.py",
103
+ } # S64: removed duplicate v11.0 + dead v8.4
104
  REF_DIR = BASE / "reference_audio"
105
  CHUNK_SIZE = 4 * 1024 * 1024 # 4 MB upload chunks
106
  MAX_DOWNLOAD_CHUNK = 32 * 1024 * 1024 # 32 MB max per download_chunk call
 
517
 
518
  # ── RAM guard: require ≥ 3.5 GB available before launching subprocess ──
519
  ram_gb = _available_ram_gb()
520
+ if ram_gb < 1.5: # S64: raised from 0.5 — engines need ~3.5GB
521
  with JOBS_LOCK:
522
  job["status"] = "error"
523
+ job["error"] = f"Insufficient RAM ({ram_gb:.1f} GB free, need 1.5 GB)"
524
  job["label"] = "خطأ: ذاكرة غير كافية — أعد المحاولة لاحقاً"
525
  return
526
 
 
553
  for rf in ref_files[:3]:
554
  cmd += ["--ref", str(rf)]
555
 
556
+ # S64: watchdog — kill engine if it runs > 35 min
557
+ _ENGINE_TIMEOUT = 35 * 60 # 35 minutes
558
  proc = subprocess.Popen(
559
  cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
560
  )
561
 
562
+ engine_lines = [] # last 60 lines for debug
563
  for line in proc.stdout:
564
  line = line.strip()
565
+ engine_lines.append(line)
566
+ if len(engine_lines) > 60:
567
+ engine_lines.pop(0)
568
  # Progress markers — plain scalar writes, no lock needed
569
  if "Pass 1" in line or "[٧]" in line or "[١]" in line:
570
  job["progress"] = 45; job["label"] = "Pass 1 — تحليل الطيف..."
 
595
  except Exception:
596
  pass
597
 
598
+ # S64: timed wait — kill if engine hangs past timeout
599
+ try:
600
+ proc.wait(timeout=_ENGINE_TIMEOUT)
601
+ except subprocess.TimeoutExpired:
602
+ proc.kill()
603
+ proc.wait()
604
+ job["engine_error"] = "Engine timed out after 35 min — killed"
605
+ job["engine_log"] = engine_lines
606
+ with JOBS_LOCK:
607
+ job["status"] = "error"
608
+ job["error"] = "Engine timed out (35 min limit)"
609
+ job["label"] = "خطأ: انتهت مهلة المحرك — أعد المحاولة"
610
+ return
611
  _out = Path(job["out_path"])
612
  # engine_v100 exits 1 for score<85 but still writes the file.
613
  # Use file existence + size as the real success signal.
614
  if _out.exists() and _out.stat().st_size > 0:
615
  success = True
616
  else:
617
+ job["engine_rc"] = proc.returncode
618
+ job["engine_log"] = engine_lines # S64: persist last 60 lines
619
 
620
  except Exception as exc:
621
  job["engine_error"] = str(exc)
622
+ job["engine_log"] = engine_lines # S64: persist for /status debug
623
 
624
  if not success:
625
  # Status transition: running → error (held under lock)
 
678
  if "error" in job:
679
  resp["error"] = job["error"]
680
 
681
+ # S64: expose engine debug info on error
682
+ if job.get("status") == "error":
683
+ if "engine_log" in job:
684
+ resp["engine_log"] = job["engine_log"] # last 60 stdout lines
685
+ if "engine_error" in job:
686
+ resp["engine_error"] = job["engine_error"] # exception message
687
+ resp["engine_rc"] = job.get("engine_rc")
688
 
689
  return jsonify(resp)
690