ElJoker63 commited on
Commit
eddd40a
·
1 Parent(s): 92d60cf

fix: cancelacion forzosa inmediata de FFmpeg y tracking robusto por PID

Browse files
Files changed (2) hide show
  1. internal/ffmpeg/ffmpeg.go +36 -2
  2. main.go +51 -29
internal/ffmpeg/ffmpeg.go CHANGED
@@ -270,8 +270,6 @@ func wmOverlayExpr(pos string) string {
270
  }
271
 
272
  // Compress ejecuta la compresión usando FFmpeg y reporta el progreso en vivo.
273
- // wmImagePath es la ruta local a un logo de marca de agua (opcional); si se
274
- // indica, tiene prioridad sobre la marca de agua de texto.
275
  func Compress(
276
  ctx context.Context,
277
  inputPath, outputPath string,
@@ -279,6 +277,19 @@ func Compress(
279
  wmImagePath string,
280
  totalDuration float64,
281
  onProgress func(p protocol.TaskProgress),
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  ) error {
283
  settings = SanitizeSettings(settings)
284
 
@@ -388,6 +399,13 @@ func Compress(
388
  )
389
 
390
  cmd := exec.CommandContext(ctx, "ffmpeg", args...)
 
 
 
 
 
 
 
391
 
392
  stderr, err := cmd.StderrPipe()
393
  if err != nil {
@@ -398,6 +416,22 @@ func Compress(
398
  return fmt.Errorf("error iniciando ffmpeg: %w", err)
399
  }
400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  scanner := bufio.NewScanner(stderr)
402
  // FFmpeg usa retorno de carro '\r' para actualizar la línea de estado
403
  scanner.Split(scanCRLFOrCR)
 
270
  }
271
 
272
  // Compress ejecuta la compresión usando FFmpeg y reporta el progreso en vivo.
 
 
273
  func Compress(
274
  ctx context.Context,
275
  inputPath, outputPath string,
 
277
  wmImagePath string,
278
  totalDuration float64,
279
  onProgress func(p protocol.TaskProgress),
280
+ ) error {
281
+ return CompressWithCmd(ctx, inputPath, outputPath, settings, wmImagePath, totalDuration, nil, onProgress)
282
+ }
283
+
284
+ // CompressWithCmd ejecuta la compresión y permite capturar el puntero a *exec.Cmd para terminación forzosa inmediata
285
+ func CompressWithCmd(
286
+ ctx context.Context,
287
+ inputPath, outputPath string,
288
+ settings protocol.VideoSettings,
289
+ wmImagePath string,
290
+ totalDuration float64,
291
+ onCmd func(cmd *exec.Cmd),
292
+ onProgress func(p protocol.TaskProgress),
293
  ) error {
294
  settings = SanitizeSettings(settings)
295
 
 
399
  )
400
 
401
  cmd := exec.CommandContext(ctx, "ffmpeg", args...)
402
+ cmd.Cancel = func() error {
403
+ if cmd.Process != nil {
404
+ return cmd.Process.Kill()
405
+ }
406
+ return nil
407
+ }
408
+ cmd.WaitDelay = 1 * time.Second
409
 
410
  stderr, err := cmd.StderrPipe()
411
  if err != nil {
 
416
  return fmt.Errorf("error iniciando ffmpeg: %w", err)
417
  }
418
 
419
+ if onCmd != nil {
420
+ onCmd(cmd)
421
+ }
422
+
423
+ stopWatch := make(chan struct{})
424
+ defer close(stopWatch)
425
+ go func() {
426
+ select {
427
+ case <-ctx.Done():
428
+ if cmd.Process != nil {
429
+ _ = cmd.Process.Kill()
430
+ }
431
+ case <-stopWatch:
432
+ }
433
+ }()
434
+
435
  scanner := bufio.NewScanner(stderr)
436
  // FFmpeg usa retorno de carro '\r' para actualizar la línea de estado
437
  scanner.Split(scanCRLFOrCR)
main.go CHANGED
@@ -12,6 +12,7 @@ import (
12
  "net/http"
13
  "net/url"
14
  "os"
 
15
  "os/signal"
16
  "path/filepath"
17
  "runtime"
@@ -42,6 +43,7 @@ type CurrentJob struct {
42
  FileName string
43
  UploadURL string
44
  Cancel context.CancelFunc
 
45
  Done bool
46
  }
47
 
@@ -53,7 +55,7 @@ type Worker struct {
53
 
54
  conn *websocket.Conn
55
  connMu sync.Mutex
56
- currentJob *CurrentJob
57
  jobMu sync.RWMutex
58
 
59
  connected bool
@@ -118,6 +120,7 @@ func main() {
118
  managerURL: managerURL,
119
  secret: secret,
120
  workDir: workDir,
 
121
  }
122
 
123
  ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
@@ -285,15 +288,15 @@ func (w *Worker) run(ctx context.Context) error {
285
  // 1. Enviar Handshake (incluyendo cualquier tarea activa si reconecta tras reinicio del Manager)
286
  w.jobMu.RLock()
287
  var activeTasks []protocol.TaskState
288
- if w.currentJob != nil {
289
  status := "converting"
290
- if w.currentJob.Done {
291
  status = "completed"
292
  }
293
  activeTasks = append(activeTasks, protocol.TaskState{
294
- TaskID: w.currentJob.TaskID,
295
  Status: status,
296
- UploadURL: w.currentJob.UploadURL,
297
  })
298
  }
299
  w.jobMu.RUnlock()
@@ -359,19 +362,37 @@ func (w *Worker) run(ctx context.Context) error {
359
  case protocol.TypeTaskAssign:
360
  var task protocol.TaskAssign
361
  if err := json.Unmarshal(msg.Payload, &task); err == nil {
362
- go w.executeTask(ctx, task)
 
 
 
 
 
 
 
 
 
 
 
363
  }
364
 
365
  case protocol.TypeTaskCancel:
366
  var cancel protocol.TaskCancel
367
  if err := json.Unmarshal(msg.Payload, &cancel); err == nil {
368
  w.jobMu.Lock()
369
- if w.currentJob != nil && w.currentJob.TaskID == cancel.TaskID {
370
- log.Printf("⛔ Cancelando tarea %s...", cancel.TaskID)
371
- if w.currentJob.Cancel != nil {
372
- w.currentJob.Cancel()
 
373
  }
374
- w.currentJob = nil
 
 
 
 
 
 
375
  }
376
  w.jobMu.Unlock()
377
  }
@@ -379,25 +400,16 @@ func (w *Worker) run(ctx context.Context) error {
379
  }
380
  }
381
 
382
- func (w *Worker) executeTask(parentCtx context.Context, task protocol.TaskAssign) {
383
  log.Printf("📥 Nueva tarea recibida: %s (Archivo: %s)", task.TaskID, task.FileName)
384
 
385
- taskCtx, cancel := context.WithCancel(parentCtx)
386
- w.jobMu.Lock()
387
- w.currentJob = &CurrentJob{
388
- TaskID: task.TaskID,
389
- FileName: task.FileName,
390
- UploadURL: task.UploadURL,
391
- Cancel: cancel,
392
- Done: false,
393
- }
394
- w.jobMu.Unlock()
395
-
396
  defer func() {
397
  w.jobMu.Lock()
398
- w.currentJob = nil
399
  w.jobMu.Unlock()
400
- cancel()
 
 
401
  }()
402
 
403
  ext := filepath.Ext(task.FileName)
@@ -482,21 +494,31 @@ func (w *Worker) executeTask(parentCtx context.Context, task protocol.TaskAssign
482
  log.Printf("[%s] Iniciando compresión con FFmpeg (Duración: %.1fs)...", task.TaskID, duration)
483
  startTime := time.Now()
484
 
485
- err = ffmpeg.Compress(taskCtx, inputPath, outputPath, task.Settings, wmImagePath, duration, func(p protocol.TaskProgress) {
 
 
 
 
 
 
 
486
  p.TaskID = task.TaskID
487
  _ = w.sendMessage(protocol.TypeTaskProgress, p)
488
  })
489
 
490
  if err != nil {
 
 
 
 
 
491
  log.Printf("[%s] Error en compresión: %v", task.TaskID, err)
492
  w.reportFailed(task.TaskID, fmt.Sprintf("falló compresión: %v", err))
493
  return
494
  }
495
 
496
  w.jobMu.Lock()
497
- if w.currentJob != nil {
498
- w.currentJob.Done = true
499
- }
500
  w.jobMu.Unlock()
501
 
502
  outStat, err := os.Stat(outputPath)
 
12
  "net/http"
13
  "net/url"
14
  "os"
15
+ "os/exec"
16
  "os/signal"
17
  "path/filepath"
18
  "runtime"
 
43
  FileName string
44
  UploadURL string
45
  Cancel context.CancelFunc
46
+ Cmd *exec.Cmd
47
  Done bool
48
  }
49
 
 
55
 
56
  conn *websocket.Conn
57
  connMu sync.Mutex
58
+ activeJobs map[string]*CurrentJob
59
  jobMu sync.RWMutex
60
 
61
  connected bool
 
120
  managerURL: managerURL,
121
  secret: secret,
122
  workDir: workDir,
123
+ activeJobs: make(map[string]*CurrentJob),
124
  }
125
 
126
  ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
 
288
  // 1. Enviar Handshake (incluyendo cualquier tarea activa si reconecta tras reinicio del Manager)
289
  w.jobMu.RLock()
290
  var activeTasks []protocol.TaskState
291
+ for _, j := range w.activeJobs {
292
  status := "converting"
293
+ if j.Done {
294
  status = "completed"
295
  }
296
  activeTasks = append(activeTasks, protocol.TaskState{
297
+ TaskID: j.TaskID,
298
  Status: status,
299
+ UploadURL: j.UploadURL,
300
  })
301
  }
302
  w.jobMu.RUnlock()
 
362
  case protocol.TypeTaskAssign:
363
  var task protocol.TaskAssign
364
  if err := json.Unmarshal(msg.Payload, &task); err == nil {
365
+ taskCtx, cancel := context.WithCancel(ctx)
366
+ job := &CurrentJob{
367
+ TaskID: task.TaskID,
368
+ FileName: task.FileName,
369
+ UploadURL: task.UploadURL,
370
+ Cancel: cancel,
371
+ }
372
+ w.jobMu.Lock()
373
+ w.activeJobs[task.TaskID] = job
374
+ w.jobMu.Unlock()
375
+
376
+ go w.executeTask(taskCtx, job, task)
377
  }
378
 
379
  case protocol.TypeTaskCancel:
380
  var cancel protocol.TaskCancel
381
  if err := json.Unmarshal(msg.Payload, &cancel); err == nil {
382
  w.jobMu.Lock()
383
+ job, exists := w.activeJobs[cancel.TaskID]
384
+ if exists {
385
+ log.Printf("⛔ [Worker] Cancelando tarea %s...", cancel.TaskID)
386
+ if job.Cancel != nil {
387
+ job.Cancel()
388
  }
389
+ if job.Cmd != nil && job.Cmd.Process != nil {
390
+ log.Printf("💀 [Worker] Matando proceso FFmpeg PID %d de tarea %s...", job.Cmd.Process.Pid, cancel.TaskID)
391
+ _ = job.Cmd.Process.Kill()
392
+ }
393
+ delete(w.activeJobs, cancel.TaskID)
394
+ } else {
395
+ log.Printf("⚠️ [Worker] Tarea %s no encontrada en activeJobs para cancelar", cancel.TaskID)
396
  }
397
  w.jobMu.Unlock()
398
  }
 
400
  }
401
  }
402
 
403
+ func (w *Worker) executeTask(taskCtx context.Context, job *CurrentJob, task protocol.TaskAssign) {
404
  log.Printf("📥 Nueva tarea recibida: %s (Archivo: %s)", task.TaskID, task.FileName)
405
 
 
 
 
 
 
 
 
 
 
 
 
406
  defer func() {
407
  w.jobMu.Lock()
408
+ delete(w.activeJobs, task.TaskID)
409
  w.jobMu.Unlock()
410
+ if job.Cancel != nil {
411
+ job.Cancel()
412
+ }
413
  }()
414
 
415
  ext := filepath.Ext(task.FileName)
 
494
  log.Printf("[%s] Iniciando compresión con FFmpeg (Duración: %.1fs)...", task.TaskID, duration)
495
  startTime := time.Now()
496
 
497
+ err = ffmpeg.CompressWithCmd(taskCtx, inputPath, outputPath, task.Settings, wmImagePath, duration, func(cmd *exec.Cmd) {
498
+ w.jobMu.Lock()
499
+ job.Cmd = cmd
500
+ w.jobMu.Unlock()
501
+ if cmd.Process != nil {
502
+ log.Printf("[%s] Proceso FFmpeg iniciado con PID %d", task.TaskID, cmd.Process.Pid)
503
+ }
504
+ }, func(p protocol.TaskProgress) {
505
  p.TaskID = task.TaskID
506
  _ = w.sendMessage(protocol.TypeTaskProgress, p)
507
  })
508
 
509
  if err != nil {
510
+ if taskCtx.Err() != nil {
511
+ log.Printf("[%s] Compresión cancelada por solicitud del usuario/manager.", task.TaskID)
512
+ w.reportFailed(task.TaskID, "compresión cancelada")
513
+ return
514
+ }
515
  log.Printf("[%s] Error en compresión: %v", task.TaskID, err)
516
  w.reportFailed(task.TaskID, fmt.Sprintf("falló compresión: %v", err))
517
  return
518
  }
519
 
520
  w.jobMu.Lock()
521
+ job.Done = true
 
 
522
  w.jobMu.Unlock()
523
 
524
  outStat, err := os.Stat(outputPath)