From e9e8f168fcc8013c5e79006eb54517432f9bc2d4 Mon Sep 17 00:00:00 2001 From: bobiqqq Date: Wed, 26 Aug 2026 13:49:22 +0300 Subject: [PATCH] =?UTF-8?q?tiktak:=20=D0=BE=D1=82=D0=BA=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20-threads=201,=20=D1=80=D0=B0=D0=B7=D0=B2=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20=D0=B1=D1=8E=D0=B4=D0=B6=D0=B5=D1=82=D1=8B?= =?UTF-8?q?=20probe=20=D0=B8=20ffmpeg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MUMBLE "couldn't create video" -- моя регрессия из 576baa3/753d21c. В логах у части загрузок есть Stream mapping и "Press [q]", но нет "Output #0": ffmpeg не доработал, его снял дедлайн. Две причины: - "-threads 1" душил многопоточное декодирование vp9. 12-секундное 640x360 в 2 секунды одним потоком не влезает. - checkGeometry вызывал ffprobe ВНУТРИ того же 2-секундного контекста, что и кодек, отбирая у него время. - убран -threads 1, -max_alloc оставлен (на скорость не влияет); - probe получил свой бюджет ProbeDeadline=1s, ffmpeg -- полные ProcessingDeadline=2.5s. Сумма с GetDuration укладывается в 7с контекста handleCreate с запасом; - семафор 16 -> 48: под флудом 16 слотов создавали очередь, и чекер выпадал по таймауту ещё до обработки. Бомбы теперь отсекаются дёшево, слоты освобождаются быстро. Co-Authored-By: Claude Opus 5 --- services/tiktak/video/preview.go | 36 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/services/tiktak/video/preview.go b/services/tiktak/video/preview.go index 443355b..2949a33 100644 --- a/services/tiktak/video/preview.go +++ b/services/tiktak/video/preview.go @@ -14,21 +14,19 @@ import ( ) var ( - ProcessingDeadline = 2 * time.Second - // 75 concurrent ffmpeg processes are enough to swap the box out on their - // own, bomb or no bomb. - semaphore = make(chan struct{}, 16) + // handleCreate даёт 7с на всё: probe + ffmpeg + GetDuration для субтитров. + // 1.0 + 2.5 + 2.5 = 6.0, секунда про запас. + ProcessingDeadline = 2500 * time.Millisecond + // Header-only ffprobe; cheap, но не должен отъедать бюджет у кодека. + ProbeDeadline = 1 * time.Second + semaphore = make(chan struct{}, 48) ) func generatePreview(ctx context.Context, inp string, out string) error { - // Reject decompression bombs before ffmpeg expands a frame into memory. - if err := checkGeometry(ctx, inp); err != nil { - return err - } // -max_alloc caps a single ffmpeg allocation: a 1080p frame needs ~8 MB, a // 16383x16383 bomb needs ~1 GB. Second line of defence behind checkGeometry, // and it works even when the probe told us nothing. - cmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-threads", "1", + cmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-max_alloc", "134217728", "-i", inp, "-vframes", "1", out) s := strings.Builder{} cmd.Stdout = &s @@ -43,12 +41,20 @@ func generatePreview(ctx context.Context, inp string, out string) error { func GeneratePreview(ctx context.Context, inp string, out string) error { select { case semaphore <- struct{}{}: - ctx, cancel := context.WithTimeout(ctx, ProcessingDeadline) - defer func() { - cancel() - <-semaphore - }() - return generatePreview(ctx, inp, out) + defer func() { <-semaphore }() + + // Probe gets its OWN budget. Sharing ProcessingDeadline with ffmpeg + // starved the encode and killed legit uploads ("cannot create video"). + pctx, pcancel := context.WithTimeout(ctx, ProbeDeadline) + err := checkGeometry(pctx, inp) + pcancel() + if err != nil { + return err + } + + fctx, fcancel := context.WithTimeout(ctx, ProcessingDeadline) + defer fcancel() + return generatePreview(fctx, inp, out) case <-ctx.Done(): return ctx.Err() }