Vix.cpp v2.7.0 is here Read the blog
Skip to content

Status and Wait

status() and wait() are the two main APIs used to observe a spawned child process. status() checks whether the process is still running. wait() waits for the process to exit and returns its final exit code.

These functions belong to the lower-level process lifecycle workflow. Use them after spawn() when the application needs a Child handle and wants to decide when to observe, wait for, or stop the child process.

Use the public process header:

cpp
#include <vix/process.hpp>

For examples that print output or diagnostics:

cpp
#include <vix/print.hpp>

Status

status() checks the current running state of a child process.

cpp
auto running = vix::process::status(child);

The return type is ProcessRunningResult, which is a Vix Result<bool>.

cpp
if (!running)
{
  vix::print("status failed", running.error().message());
  return 1;
}

vix::print("running", running.value());

A value of true means the process was observed as still running. A value of false means the process has already exited. This is a snapshot, not a permanent fact. A process that is running now can exit immediately after the check.

Wait

wait() blocks until the child process exits and returns the final exit code.

cpp
auto exit_code = vix::process::wait(child);

The return type is ProcessExitCodeResult, which is a Vix Result<int>.

cpp
if (!exit_code)
{
  vix::print("wait failed", exit_code.error().message());
  return 1;
}

vix::print("exit code", exit_code.value());

Use wait() when the final result matters. It is the function that turns a running child process into a completed process result.

Basic wait example

This example starts a process and waits for its exit code.

cpp
#include <vix/print.hpp>
#include <vix/process.hpp>

int main()
{
#if defined(_WIN32)
  vix::process::Command command("cmd");
  command.arg("/C");
  command.arg("exit 0");
#else
  vix::process::Command command("true");
#endif

  auto spawned = vix::process::spawn(command);

  if (!spawned)
  {
    vix::print("spawn failed", spawned.error().message());
    return 1;
  }

  auto waited = vix::process::wait(spawned.value());

  if (!waited)
  {
    vix::print("wait failed", waited.error().message());
    return 1;
  }

  vix::print("exit code", waited.value());

  return waited.value() == 0 ? 0 : 1;
}

The process module returns the exit code produced by the child process. A code of 0 usually means success, but the exact meaning belongs to the program that was launched.

Status before wait

A common lifecycle is to spawn a process, check whether it is running, and then wait for the final exit code.

cpp
#include <vix/print.hpp>
#include <vix/process.hpp>

int main()
{
#if defined(_WIN32)
  vix::process::Command command("cmd");
  command.arg("/C");
  command.arg("ping 127.0.0.1 -n 2 > nul");
#else
  vix::process::Command command("sleep");
  command.arg("1");
#endif

  command.stdin_mode(vix::process::PipeMode::Null);
  command.stdout_mode(vix::process::PipeMode::Null);
  command.stderr_mode(vix::process::PipeMode::Null);

  auto spawned = vix::process::spawn(command);

  if (!spawned)
  {
    vix::print("spawn failed", spawned.error().message());
    return 1;
  }

  vix::process::Child child = spawned.value();

  auto running = vix::process::status(child);

  if (!running)
  {
    vix::print("status failed", running.error().message());
    return 1;
  }

  vix::print("running before wait", running.value());

  auto waited = vix::process::wait(child);

  if (!waited)
  {
    vix::print("wait failed", waited.error().message());
    return 1;
  }

  vix::print("exit code", waited.value());

  return waited.value() == 0 ? 0 : 1;
}

This pattern is useful when the application wants to observe a child process without immediately blocking for the final result.

Status is a snapshot

status() should not be used as a final result. It only reports what the backend can observe at the time of the call.

cpp
auto running = vix::process::status(child);

if (running && running.value())
{
  vix::print("child is still running");
}

The process may exit after this check. When the application needs the final exit code, call wait().

cpp
auto exit_code = vix::process::wait(child);

This distinction matters in tools and services. status() is useful for polling and monitoring. wait() is useful for completion.

Non-zero exit codes

A non-zero exit code is still a successful process API operation when the module was able to launch and wait for the child.

cpp
#include <vix/print.hpp>
#include <vix/process.hpp>

int main()
{
#if defined(_WIN32)
  vix::process::Command command("cmd");
  command.arg("/C");
  command.arg("exit 7");
#else
  vix::process::Command command("sh");
  command.arg("-c");
  command.arg("exit 7");
#endif

  auto spawned = vix::process::spawn(command);

  if (!spawned)
  {
    vix::print("spawn failed", spawned.error().message());
    return 1;
  }

  auto waited = vix::process::wait(spawned.value());

  if (!waited)
  {
    vix::print("wait failed", waited.error().message());
    return 1;
  }

  vix::print("exit code", waited.value());

  return waited.value() == 7 ? 0 : 1;
}

The Vix result reports whether the process operation succeeded. The exit code reports what the child program decided. Keep those two meanings separate.

Invalid child handles

Both status() and wait() validate the child handle.

cpp
#include <vix/print.hpp>
#include <vix/process.hpp>

int main()
{
  vix::process::Child child;

  auto running = vix::process::status(child);

  if (!running)
  {
    vix::print("status error", running.error().message());
  }

  auto waited = vix::process::wait(child);

  if (!waited)
  {
    vix::print("wait error", waited.error().message());
  }

  return 0;
}

A default-constructed Child is invalid because it does not refer to a process. This is useful as an empty value, but it cannot be used for lifecycle operations.

Waiting after status reports false

A process can finish before or during a status() check. When status() returns false, the child is no longer running, but the application may still use wait() to collect the final exit code from the child handle.

cpp
auto running = vix::process::status(child);

if (running && !running.value())
{
  auto exit_code = vix::process::wait(child);

  if (exit_code)
  {
    vix::print("exit code", exit_code.value());
  }
}

This keeps the workflow clear: status() answers the running-state question, while wait() answers the completion-result question.

Polling with status

For synchronous code, polling can be built with status(). Keep the loop simple and include a sleep so the process is not checked continuously.

cpp
#include <chrono>
#include <thread>

#include <vix/print.hpp>
#include <vix/process.hpp>

int main()
{
#if defined(_WIN32)
  vix::process::Command command("cmd");
  command.arg("/C");
  command.arg("ping 127.0.0.1 -n 2 > nul");
#else
  vix::process::Command command("sleep");
  command.arg("1");
#endif

  command.stdin_mode(vix::process::PipeMode::Null);
  command.stdout_mode(vix::process::PipeMode::Null);
  command.stderr_mode(vix::process::PipeMode::Null);

  auto spawned = vix::process::spawn(command);

  if (!spawned)
  {
    vix::print("spawn failed", spawned.error().message());
    return 1;
  }

  vix::process::Child child = spawned.value();

  while (true)
  {
    auto running = vix::process::status(child);

    if (!running)
    {
      vix::print("status failed", running.error().message());
      return 1;
    }

    if (!running.value())
    {
      break;
    }

    vix::print("child is still running");

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  auto waited = vix::process::wait(child);

  if (!waited)
  {
    vix::print("wait failed", waited.error().message());
    return 1;
  }

  vix::print("exit code", waited.value());

  return waited.value() == 0 ? 0 : 1;
}

For coroutine-based code, prefer vix::process::async::wait(). It uses the async timer service and supports cancellation through cancel_token.

Async wait

The async API provides a coroutine-friendly wait function.

cpp
auto exit_code = co_await vix::process::async::wait(ctx, child);

The async wait function checks the child status periodically and suspends the coroutine between checks. It accepts a cancellation token and a polling interval.

cpp
auto exit_code = co_await vix::process::async::wait(
  ctx,
  child,
  cancel.token(),
  std::chrono::milliseconds(50)
);

This is better than writing a blocking wait in an async application, because it gives the io_context a chance to keep running other work.

Status, wait, and output

status() and wait() are used with a Child returned by spawn().

cpp
auto spawned = vix::process::spawn(command);

output() is different. It runs the full workflow internally and returns a completed ProcessOutput.

cpp
auto result = vix::process::output(command);

Use spawn() with status() and wait() when the application needs lifecycle control. Use output() when the application wants a finished result with captured stdout and stderr.

Common mistakes

Do not use status() as if it were a final process result. It only reports whether the child is running at the time of the call.

Do not treat a non-zero exit code as a Vix API error. The API can succeed while the child program exits with a failure code.

Do not wait on a default-constructed Child. It does not represent a process.

Do not spin in a tight loop around status(). Add a delay in synchronous code, or use the async wait API in coroutine-based code.

Do not use wait() when the current thread must remain responsive. In that case, use the async process API or move the blocking work to a controlled worker thread.

Next step

Continue with terminate and kill to understand how the module requests graceful termination and forceful process shutdown.

md
[Terminate and Kill](./terminate-and-kill.md)

Released under the MIT License.