Skip to content

Commit 8e2aa7a

Browse files
committed
fix render timeout on windows
1 parent e892182 commit 8e2aa7a

File tree

1 file changed

+59
-24
lines changed

1 file changed

+59
-24
lines changed

render/src/main.rs

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use serde::{Deserialize, Serialize};
1414
use std::path::PathBuf;
1515
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1616
use std::sync::{Arc, OnceLock};
17-
use tempfile::TempDir;
1817

1918
use crate::ffmpeg::{AudioPlanResolved, SegmentWriter, mux_audio_plan_into_mp4};
2019

@@ -30,6 +29,7 @@ struct CancelResponse {
3029
}
3130

3231
static CHROMIUM_EXECUTABLE: OnceLock<Option<PathBuf>> = OnceLock::new();
32+
const FRAME_DIRECTORY: &str = "frames";
3333

3434
fn parse_bool_token(value: &str) -> Option<bool> {
3535
match value.trim().to_ascii_lowercase().as_str() {
@@ -60,13 +60,12 @@ fn resolve_chromium_executable() -> Option<PathBuf> {
6060
}
6161

6262
async fn spawn_browser_instance(
63-
profile_id: usize,
63+
profile_dir: PathBuf,
6464
width: u32,
6565
height: u32,
6666
) -> Result<(Browser, Handler), Box<dyn std::error::Error>> {
67-
// 一時ディレクトリをブラウザプロファイルとして使う
68-
let tmp = TempDir::new()?; // ライフタイム管理は適宜
69-
let user_data_dir: PathBuf = tmp.path().join(format!("profile-{}", profile_id));
67+
// Keep profile dir alive for the whole worker lifetime.
68+
std::fs::create_dir_all(&profile_dir)?;
7069

7170
let mut builder = BrowserConfig::builder()
7271
.new_headless_mode()
@@ -79,8 +78,10 @@ async fn spawn_browser_instance(
7978
has_touch: false,
8079
})
8180
//.arg("--use-angle=swiftshader")
81+
.arg("--no-first-run")
82+
.arg("--no-default-browser-check")
8283
.request_timeout(Duration::from_hours(24))
83-
.user_data_dir(user_data_dir); // ★ インスタンスごとに別のディレクトリ
84+
.user_data_dir(profile_dir);
8485

8586
if let Some(path) = resolve_chromium_executable() {
8687
builder = builder.chrome_executable(path);
@@ -298,14 +299,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
298299
.unwrap_or_else(|_| "http://localhost:5174/render".to_string());
299300

300301
let mut tasks = FuturesUnordered::new();
302+
let mut launched_worker_ids = Vec::new();
301303

302-
static DIRECTORY: &'static str = "frames";
303304
let output_path =
304305
std::env::var("RENDER_OUTPUT_PATH").unwrap_or_else(|_| "output.mp4".to_string());
305306
let output_path = PathBuf::from(output_path);
306307

307-
tokio::fs::remove_dir_all(DIRECTORY).await.ok();
308-
tokio::fs::create_dir(DIRECTORY).await?;
308+
tokio::fs::remove_dir_all(FRAME_DIRECTORY).await.ok();
309+
tokio::fs::create_dir(FRAME_DIRECTORY).await?;
309310

310311
let start = Instant::now();
311312

@@ -326,6 +327,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
326327
}
327328

328329
for (worker_id, (start, end)) in ranges.into_iter().enumerate() {
330+
launched_worker_ids.push(worker_id);
329331
let encode_clone = encode.clone();
330332
let preset_clone = preset.clone();
331333
let ffmpeg_threads_clone = ffmpeg_threads;
@@ -335,13 +337,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
335337
let completed_clone = completed.clone();
336338
let is_canceled_clone = is_canceled.clone();
337339
tasks.push(tokio::spawn(async move {
338-
let (mut browser, mut handler) = spawn_browser_instance(worker_id, width, height)
340+
let profile_dir =
341+
PathBuf::from(format!("{}/profiles/profile-{:03}", FRAME_DIRECTORY, worker_id));
342+
343+
let (mut browser, mut handler) = spawn_browser_instance(profile_dir, width, height)
339344
.await
340-
.unwrap();
345+
.map_err(|error| format!("failed to launch browser worker {worker_id}: {error}"))?;
341346

342347
tokio::spawn(async move { while handler.next().await.is_some() {} });
343348

344-
let out = format!("{}/segment-{worker_id:03}.mp4", DIRECTORY);
349+
let out = format!("{}/segment-{worker_id:03}.mp4", FRAME_DIRECTORY);
345350

346351
let mut writer = SegmentWriter::new(
347352
&out,
@@ -356,10 +361,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
356361
ffmpeg_low_memory_clone,
357362
)
358363
.await
359-
.unwrap();
364+
.map_err(|error| format!("worker {worker_id}: failed to create ffmpeg writer: {error}"))?;
360365

361-
let page = browser.new_page(page_url).await.unwrap();
362-
page.wait_for_navigation().await.unwrap();
366+
let page = browser
367+
.new_page(page_url)
368+
.await
369+
.map_err(|error| format!("worker {worker_id}: failed to open page: {error}"))?;
370+
page.wait_for_navigation()
371+
.await
372+
.map_err(|error| format!("worker {worker_id}: navigation failed: {error}"))?;
363373
wait_for_frame_api(&page).await;
364374
wait_for_animation_ready(&page).await;
365375
wait_for_draw_text_ready(&page).await;
@@ -379,7 +389,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
379389
"#,
380390
frame
381391
);
382-
page.evaluate(js).await.unwrap();
392+
page.evaluate(js)
393+
.await
394+
.map_err(|error| format!("worker {worker_id}: setFrame eval failed: {error}"))?;
383395

384396
wait_for_next_frame(&page).await;
385397

@@ -398,7 +410,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
398410
"#,
399411
frame
400412
);
401-
page.evaluate(script).await.unwrap();
413+
page.evaluate(script)
414+
.await
415+
.map_err(|error| format!("worker {worker_id}: waitCanvasFrame eval failed: {error}"))?;
402416

403417
wait_for_images_ready(&page).await;
404418
wait_for_webgl_frame(&page, frame).await;
@@ -411,9 +425,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
411425
.build(),
412426
)
413427
.await
414-
.unwrap();
428+
.map_err(|error| format!("worker {worker_id}: screenshot failed: {error}"))?;
415429

416-
writer.write_png_frame(&bytes).await.unwrap();
430+
writer
431+
.write_png_frame(&bytes)
432+
.await
433+
.map_err(|error| format!("worker {worker_id}: ffmpeg write failed: {error}"))?;
417434

418435
completed_clone.fetch_add(1, Ordering::Relaxed);
419436

@@ -422,18 +439,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
422439
}
423440
}
424441

425-
writer.finish().await.unwrap();
442+
writer
443+
.finish()
444+
.await
445+
.map_err(|error| format!("worker {worker_id}: ffmpeg finalize failed: {error}"))?;
446+
447+
browser
448+
.close()
449+
.await
450+
.map_err(|error| format!("worker {worker_id}: browser close failed: {error}"))?;
426451

427-
browser.close().await.unwrap();
452+
Ok::<(), String>(())
428453
}));
429454
}
430455

431-
while let Some(_) = tasks.next().await {}
456+
while let Some(task_result) = tasks.next().await {
457+
match task_result {
458+
Ok(Ok(())) => {}
459+
Ok(Err(worker_error)) => return Err(Box::<dyn std::error::Error>::from(worker_error)),
460+
Err(join_error) => {
461+
return Err(Box::<dyn std::error::Error>::from(format!(
462+
"render worker panicked: {join_error}"
463+
)));
464+
}
465+
}
466+
}
432467

433468
let mut segs = Vec::new();
434469

435-
for worker_id in 0..worker_count + if remainder > 0 { 1 } else { 0 } {
436-
let path = PathBuf::from(format!("{}/segment-{worker_id:03}.mp4", DIRECTORY));
470+
for worker_id in launched_worker_ids {
471+
let path = PathBuf::from(format!("{}/segment-{worker_id:03}.mp4", FRAME_DIRECTORY));
437472
if tokio::fs::metadata(&path).await.is_ok() {
438473
segs.push(path);
439474
}

0 commit comments

Comments
 (0)