Skip to content

runc: share one implementation between sync and async - #490

Open
mxpv wants to merge 2 commits into
mainfrom
async
Open

runc: share one implementation between sync and async#490
mxpv wants to merge 2 commits into
mainfrom
async

Conversation

@mxpv

@mxpv mxpv commented Aug 27, 2026

Copy link
Copy Markdown
Member

The blocking and async clients were two near-copies of the same ~980 lines, and they had drifted. Async ps folded stderr into the JSON it then parsed; async run/start returned Result<()> where the blocking client returned Result<Response>; create reported an IO setup failure as two different error variants depending on the flavor; only one side had events() or honored THP_DISABLED. Async temp-file cleanup was a hand-rolled tc! macro that leaked the spec file whenever a future was dropped.

Write the client once instead. runc_impl! is invoked with the async keyword or with nothing, and maybe_await! expands to .await or to nothing, so the whole of impl Runc -- all sixteen commands plus the launch path -- lives in lib.rs with no cfg and nothing boxed. The macro interpolates the keyword rather than parsing signatures, so generics, where-clauses and lifetimes pass through untouched. Only Spawner, Io and Pipe remain flavor-specific, since their signatures genuinely differ.

Alongside that:

  • Fold command() into launch_io, so assembling argv, wiring up the Io driver and spawning are one function. Restoring THP_DISABLED moves there too, which means it now applies to every Spawner rather than only DefaultExecutor -- ShimExecutor, the one the shim actually installs, had silently stopped restoring it.

  • Read THP_DISABLED in the parent and hand the pre_exec closure only a bool. The hook previously called env::var and log::debug! post-fork, neither of which is async-signal-safe: a fork racing another thread's env::set_var would deadlock the child. Register the hook only when the knob is set, so the common path keeps the posix_spawn fast path.

  • Replace the combined_output bool with an Output enum, and fix list, state and stats, which deserialized JSON out of stdout+stderr.

  • Give both builds one RAII temp-file helper, dropping the tc! macro, the async write_value_to_temp_file and the uuid dependency.

  • Merge the two test modules into one covering the union of what each had, so the blocking client gains start and the async client gains exec.

Async run and start now return Result<Response>, matching the blocking client; the crate version should be bumped before the next publish.

The blocking and async clients were two near-copies of the same ~980 lines,
and they had drifted. Async `ps` folded stderr into the JSON it then parsed;
async `run`/`start` returned `Result<()>` where the blocking client returned
`Result<Response>`; `create` reported an IO setup failure as two different
error variants depending on the flavor; only one side had `events()` or
honored `THP_DISABLED`. Async temp-file cleanup was a hand-rolled `tc!` macro
that leaked the spec file whenever a future was dropped.

Write the client once instead. `runc_impl!` is invoked with the `async`
keyword or with nothing, and `maybe_await!` expands to `.await` or to nothing,
so the whole of `impl Runc` -- all sixteen commands plus the launch path --
lives in lib.rs with no `cfg` and nothing boxed. The macro interpolates the
keyword rather than parsing signatures, so generics, where-clauses and
lifetimes pass through untouched. Only `Spawner`, `Io` and `Pipe` remain
flavor-specific, since their signatures genuinely differ.

Alongside that:

- Fold `command()` into `launch_io`, so assembling argv, wiring up the Io
  driver and spawning are one function. Restoring `THP_DISABLED` moves there
  too, which means it now applies to every `Spawner` rather than only
  `DefaultExecutor` -- `ShimExecutor`, the one the shim actually installs, had
  silently stopped restoring it.

- Read `THP_DISABLED` in the parent and hand the `pre_exec` closure only a
  `bool`. The hook previously called `env::var` and `log::debug!` post-fork,
  neither of which is async-signal-safe: a fork racing another thread's
  `env::set_var` would deadlock the child. Register the hook only when the
  knob is set, so the common path keeps the `posix_spawn` fast path.

- Replace the `combined_output` bool with an `Output` enum, and fix `list`,
  `state` and `stats`, which deserialized JSON out of stdout+stderr.

- Give both builds one RAII temp-file helper, dropping the `tc!` macro, the
  async `write_value_to_temp_file` and the `uuid` dependency.

- Merge the two test modules into one covering the union of what each had, so
  the blocking client gains `start` and the async client gains `exec`.

Async `run` and `start` now return `Result<Response>`, matching the blocking
client; the crate version should be bumped before the next publish.

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
@mxpv
mxpv requested a review from a team August 27, 2026 19:28
@github-actions github-actions Bot added the C-runc runc helper label Aug 27, 2026
@mxpv
mxpv requested a lite review from Copilot August 27, 2026 19:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Refactors the runc crate to share a single Runc client implementation between the synchronous and asynchronous builds, reducing drift and aligning behavior across both flavors.

Changes:

  • Centralizes the Runc command execution path in lib.rs via runc_impl! + maybe_await!, including consistent stdout/stderr handling through an Output enum.
  • Unifies spec/resource temp-file creation into a single RAII helper and removes the uuid dependency.
  • Consolidates sync/async tests into one shared test module expanded with the appropriate test attribute.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/runc/src/utils.rs Switches temp-file creation to RAII NamedTempFile for both builds and adds a THP_DISABLED restore hook builder.
crates/runc/src/lib.rs Introduces the shared sync/async Runc implementation, common launch path, output mode enum, and unified tests.
crates/runc/src/synchronous/runc.rs Removes the duplicate sync Runc implementation, leaving only sync Spawner code.
crates/runc/src/asynchronous/runc.rs Removes the duplicate async Runc implementation and temp-file cleanup macro, leaving only async Spawner code.
crates/runc/Cargo.toml Drops the uuid dependency now that temp-file naming uses tempfile’s randomness.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/runc/src/utils.rs
Co-authored-by: mxpv <865334+mxpv@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-runc runc helper

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants