Skip to content

fix: handle race condition in ensure_symlink for multi-process TP workers - #2981

Closed
johnnynunez wants to merge 1 commit into
flashinfer-ai:mainfrom
johnnynunez:fix/ensure-symlink-race-condition
Closed

johnnynunez wants to merge 1 commit into
flashinfer-ai:mainfrom
johnnynunez:fix/ensure-symlink-race-condition

Conversation

@johnnynunez

@johnnynunez johnnynunez commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix TOCTOU race condition in ensure_symlink() that causes FileExistsError when multiple tensor-parallel workers concurrently create the same symlink

Problem

With --tensor-parallel-size 2 (or higher), multiple worker processes call gen_trtllm_gen_fused_moe_sm100_module() concurrently during startup. Both workers:

  1. Check if the symlink exists → False
  2. Call link.symlink_to(target)
  3. The second worker crashes with FileExistsError: [Errno 17] File exists

This crashes the vLLM server during initialization:

FileExistsError: [Errno 17] File exists: '.../trtllmGen_bmm_export' -> '.../trtllm/batched_gemm/trtllmGen_bmm_export'

Fix

Wrap symlink_to() in a try/except FileExistsError. If another process already created the correct symlink between our check and create, silently succeed. If the symlink points to the wrong target, re-raise.

Test plan

  • Verified the fix matches the pattern used by other projects (e.g. huggingface_hub) for the same race condition
  • Run vLLM with --tensor-parallel-size 2 --moe-backend=flashinfer_trtllm on a model like Llama-4-Scout FP8

Fixes #2980

Summary by CodeRabbit

  • Chores
    • Improved concurrent-safety of JIT compilation symlink creation for multi-process scenarios, preventing race conditions when multiple processes attempt simultaneous operations.

…kers

When using tensor parallelism (e.g. --tensor-parallel-size 2), multiple
worker processes call `gen_trtllm_gen_fused_moe_sm100_module()` concurrently.
Both workers check if the symlink exists (it doesn't), both call
`symlink_to()`, and the second one crashes with `FileExistsError`.

This is a classic TOCTOU (time-of-check-time-of-use) race. The fix wraps
`symlink_to()` in a try/except that catches `FileExistsError` and verifies
the existing symlink points to the correct target. If it does, the race is
benign and we return successfully. If not, we re-raise.

Fixes flashinfer-ai#2980

Made-with: Cursor
@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a5d7226-18d9-45c8-b86c-b269b1fd6186

📥 Commits

Reviewing files that changed from the base of the PR and between 19329d8 and 55775d4.

📒 Files selected for processing (1)
  • flashinfer/jit/cubin_loader.py

📝 Walkthrough

Walkthrough

Modified ensure_symlink() in the CUBIN loader to handle concurrent symlink creation across multiple processes. The function now wraps symlink creation in a try/except block to catch FileExistsError, validating that any existing symlink points to the correct target before returning safely.

Changes

Cohort / File(s) Summary
Race Condition Fix
flashinfer/jit/cubin_loader.py
Added try/except FileExistsError handling around symlink creation with validation that existing symlinks point to the correct target, resolving TOCTOU race condition in concurrent multi-process scenarios.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A symlink race became quite the dare,
Multiple workers tripping on the stair,
Now a try/except saves the day—
Each process gets its symlink way! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: handling a race condition in ensure_symlink for multi-process tensor-parallel workers.
Description check ✅ Passed The description provides a clear summary, detailed problem explanation with error reproduction steps, the fix approach, and references the linked issue. While manual testing is not yet complete, the description is substantially complete.
Linked Issues check ✅ Passed The code changes directly address all technical objectives from issue #2980: implement try/except FileExistsError around symlink creation, verify the symlink target matches, and suppress errors only when appropriate.
Out of Scope Changes check ✅ Passed All code changes are narrowly focused on fixing the race condition in ensure_symlink() as specified in the linked issue. No unrelated modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies the ensure_symlink function in flashinfer/jit/cubin_loader.py to handle FileExistsError during symlink creation, aiming to support concurrent execution. However, the review feedback correctly identifies that the function is still not fully thread-safe because the preceding cleanup logic contains TOCTOU race conditions that could lead to FileNotFoundError when multiple processes attempt to remove the same file or directory.

Comment on lines +242 to +243
This function is safe to call concurrently from multiple processes (e.g.
tensor-parallel workers) that may race to create the same symlink.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The claim that this function is safe to call concurrently is not entirely accurate because the cleanup logic (lines 247-254) still contains TOCTOU (Time-of-Check to Time-of-Use) race conditions. If multiple processes attempt to remove a stale file or directory at the same time, one will likely fail with a FileNotFoundError when calling link.unlink() or shutil.rmtree(link). To make this truly safe for concurrent workers, these cleanup operations should be wrapped in a try-except FileNotFoundError block or use missing_ok=True for unlink (available in Python 3.8+).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the ensure_symlink function in flashinfer/jit/cubin_loader.py to support concurrent execution by handling FileExistsError during symlink creation. While the changes improve concurrency, the cleanup logic for stale links still contains a race condition where multiple processes might attempt to delete the same file, potentially leading to FileNotFoundError. Additionally, the verification logic for existing symlinks needs to be adjusted to correctly resolve relative paths against the symlink's parent directory rather than the current working directory.

Comment on lines +242 to +243
This function is safe to call concurrently from multiple processes (e.g.
tensor-parallel workers) that may race to create the same symlink.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The docstring states that this function is safe to call concurrently, but the cleanup logic for stale links (lines 251-254) still contains a race condition. If multiple processes attempt to unlink() or rmtree() the same stale path simultaneously, one will succeed while others may fail with a FileNotFoundError. To make this fully safe for concurrent workers, the cleanup phase should handle the case where the file has already been removed by another process (e.g., by catching FileNotFoundError or using missing_ok=True for unlink).

link.symlink_to(target)
except FileExistsError:
# Another process created the symlink between our check and create.
if link.is_symlink() and link.resolve() == target.resolve():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Comparing link.resolve() with target.resolve() can be unreliable if target is a relative path, as target.resolve() resolves against the current working directory instead of the symlink's parent directory. Resolving target relative to link.parent provides a more robust comparison that works correctly for both absolute and relative paths.

Suggested change
if link.is_symlink() and link.resolve() == target.resolve():
if link.is_symlink() and link.resolve() == (link.parent / target).resolve():

@johnnynunez

Copy link
Copy Markdown
Contributor Author

closing in favor #2979

@johnnynunez johnnynunez closed this Apr 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition in ensure_symlink causes FileExistsError with tensor parallelism

1 participant