Runtime executor
This page explains vix::executor::RuntimeExecutor.
Use it when you want to understand how Vix Core connects application work to the internal vix::runtime scheduler, workers, tasks, and metrics.
Public header
#include <vix.hpp>You can also include the executor header directly:
#include <vix/executor/RuntimeExecutor.hpp>What RuntimeExecutor provides
RuntimeExecutor is the executor adapter used by Vix Core.
It sits between the HTTP application layer and the internal runtime.
Vix Core
-> RuntimeExecutor
-> vix::runtime::Runtime
-> Scheduler
-> Workers
-> TasksIt provides:
- runtime startup
- runtime shutdown
- task submission
- HTTP fast-path submission
- executor metrics
- idle waiting
- rejected task tracking
- timeout tracking
- safe lifecycle management
Most applications do not create it manually.
They use:
vix::App app;The app creates and starts a default executor.
Why RuntimeExecutor exists
vix::runtime exposes a low-level task model.
Core needs a higher-level executor interface that can be used by the HTTP server, sessions, router, and future application work.
So RuntimeExecutor adapts this:
vix::runtime::TaskResult()into this:
executor.post([]()
{
// application work
});This keeps application-level code cleaner while still using the internal runtime engine.
Basic model
The model is:
RuntimeExecutor
-> owns Runtime
-> starts Runtime
-> accepts work
-> submits tasks
-> tracks metrics
-> stops RuntimeThe runtime then handles:
Runtime
-> Scheduler
-> Worker
-> RunQueue
-> TaskDefault executor in App
A default vix::App creates an executor automatically.
vix::App app;Internally, the app creates:
RuntimeExecutor
-> Runtime
-> worker count based on hardware concurrencyThen it starts the executor before the server begins handling requests.
Access the app executor
You can access the executor from an app.
#include <vix.hpp>
int main()
{
vix::App app;
auto &executor = app.executor();
(void)executor;
app.run(8080);
return 0;
}Most applications do not need direct executor access.
It is mainly useful for:
- advanced scheduling
- metrics
- diagnostics
- integrations
- internal tooling
- custom runtime behavior
Create an executor manually
Advanced code can create an executor explicitly.
#include <vix/executor/RuntimeExecutor.hpp>
int main()
{
vix::executor::RuntimeExecutor executor{4};
executor.start();
executor.post([]()
{
// work
});
executor.stop();
return 0;
}The integer constructor creates a runtime with the requested number of workers.
Use an external executor with App
You can create an executor and pass it to App.
#include <vix.hpp>
int main()
{
auto executor = std::make_shared<vix::executor::RuntimeExecutor>(4);
vix::App app{executor};
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("hello");
});
app.run(8080);
return 0;
}This is useful when the application wants to control the executor configuration.
Start the executor
Use start() to start the underlying runtime.
executor.start();start() is safe to call multiple times.
If the executor is already started, the call has no effect.
start
-> if already started, return
-> runtime.start
-> accepting = trueStop the executor
Use stop() to stop the runtime.
executor.stop();stop() is safe to call multiple times.
It stops accepting new work and stops the underlying runtime.
stop
-> accepting = false
-> runtime.stop
-> started = falseRuntime::stop() is blocking because it waits for runtime workers to stop.
Stop and wait
Use stop_and_wait() when you want current work to drain before stopping.
executor.stop_and_wait();The flow is:
stop accepting new work
-> wait until pending and active tasks reach zero
-> stop runtimeThis is useful for graceful shutdown.
Check started state
Use started().
if (executor.started())
{
vix::print("executor started");
}Check running state
Use running().
if (executor.running())
{
vix::print("runtime is running");
}running() reflects the underlying runtime state.
Check accepting state
Use accepting().
if (executor.accepting())
{
executor.post([]()
{
// work
});
}If the executor is no longer accepting work, new submissions are rejected.
Submit a runtime task function
You can submit a runtime task function directly.
executor.submit([]() -> vix::runtime::TaskResult
{
return vix::runtime::TaskResult::complete;
});A runtime task function returns:
vix::runtime::TaskResult::complete
vix::runtime::TaskResult::yield
vix::runtime::TaskResult::failedUse this when you want direct access to the low-level runtime task contract.
Submit a void task
Use post(...) for normal callable work.
executor.post([]()
{
vix::print("hello from runtime executor");
});post(...) adapts a void() callable into a runtime task.
Conceptually:
void function
-> wrapper
-> TaskResult::complete
-> Runtime::submitTask options
post(...) accepts task options.
vix::executor::TaskOptions opt;
opt.timeout = std::chrono::milliseconds(100);
opt.may_block = false;
executor.post([]()
{
// work
}, opt);TaskOptions contains:
| Field | Purpose |
|---|---|
priority | Priority value reserved for scheduling policy. |
timeout | Timeout measurement threshold. |
deadline | Deadline value reserved for scheduling policy. |
may_block | Marks whether the task may block. |
In the current model, timeout is used for metrics.
Timeout metrics
When a task is posted with a timeout, the executor measures how long the callable took.
vix::executor::TaskOptions opt;
opt.timeout = std::chrono::milliseconds(10);
executor.post([]()
{
// work
}, opt);If the task takes longer than the timeout, the timeout counter is incremented.
This does not automatically kill the running task.
It records that the task exceeded the configured timeout.
HTTP fast path
post_http_fast(...) is a lighter submission path for short HTTP work.
executor.post_http_fast([]()
{
// short HTTP work
});It is designed to reduce executor overhead for hot-path HTTP tasks.
Compared with post(...), it avoids extra active-task tracking and timeout measurement.
Use it for short work that does not need generic executor accounting.
HTTP fast path with TaskResult
executor.post_http_fast([]() -> vix::runtime::TaskResult
{
return vix::runtime::TaskResult::complete;
});This is useful when the caller already uses the runtime task contract.
Accepted and rejected work
When a task is submitted, the executor checks whether it is accepting work.
if accepting
-> submit to runtime
-> increment submitted count
if not accepting
-> reject
-> increment rejected countThis prevents new tasks from entering the runtime during shutdown.
Metrics
Use metrics() to read a snapshot.
const auto m = executor.metrics();
vix::print("pending:", m.pending);
vix::print("active:", m.active);
vix::print("timed out:", m.timed_out);Metrics contain:
| Field | Meaning |
|---|---|
pending | Number of pending runtime tasks. |
active | Number of tasks currently active through tracked post(...). |
timed_out | Number of tasks that exceeded their timeout threshold. |
Submitted task count
Use submitted_tasks().
const auto submitted = executor.submitted_tasks();This returns the number of tasks accepted by the executor.
Rejected task count
Use rejected_tasks().
const auto rejected = executor.rejected_tasks();This returns the number of rejected submissions.
A task can be rejected when:
- the executor is not accepting work
- the task function is invalid
- the runtime refuses the task
Fast HTTP submitted count
Use fast_http_submitted_tasks().
const auto fast = executor.fast_http_submitted_tasks();This returns the number of tasks accepted through post_http_fast(...).
Wait until idle
Use wait_idle() to block until no pending or active work remains.
executor.wait_idle();The loop checks:
pending == 0
active == 0This does not stop the executor.
It only waits for current tracked work to finish.
Executor lifecycle
The normal lifecycle is:
construct RuntimeExecutor
-> start
-> submit work
-> wait_idle if needed
-> stopExample:
vix::executor::RuntimeExecutor executor{4};
executor.start();
executor.post([]()
{
vix::print("work");
});
executor.wait_idle();
executor.stop();App lifecycle with executor
When using App, the lifecycle is handled for you.
App constructor
-> create RuntimeExecutor
-> start executor
-> create HTTPServer
App close
-> stop HTTP server
-> stop executor through destructor or lifecycleApplication code:
vix::App app;
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("hello");
});
app.run(8080);RuntimeExecutor and Runtime
RuntimeExecutor owns a vix::runtime::Runtime.
RuntimeExecutor
-> unique_ptr<Runtime>The runtime owns the lower-level scheduling system.
Runtime
-> Scheduler
-> Workers
-> RunQueue
-> TaskRuntimeExecutor provides a safer and simpler interface on top.
RuntimeExecutor and HTTPServer
HTTPServer receives a shared executor.
App
-> RuntimeExecutor
-> HTTPServerThe server passes the executor to sessions.
HTTPServer
-> Session
-> Router
-> handlersThis keeps Core connected to the runtime execution model.
RuntimeExecutor and Session
A session stores a shared executor.
Session
-> RuntimeExecutorThis makes it possible for session dispatch or handler execution paths to use runtime scheduling when needed.
The session still uses vix::async for network I/O.
RuntimeExecutor and vix::async
The executor is not the same as vix::async.
They serve different roles.
vix::async
-> I/O context
-> network reads and writes
-> timers
-> coroutine tasks
RuntimeExecutor
-> runtime tasks
-> worker scheduling
-> task metricsThe HTTP server uses both.
I/O work
-> vix::async
application or runtime work
-> RuntimeExecutorRuntimeExecutor and vix::runtime
The executor adapts higher-level callables into runtime tasks.
std::function<void()>
-> RuntimeExecutor::post
-> runtime task function
-> Runtime::submitFor direct runtime tasks:
TaskFn
-> RuntimeExecutor::submit
-> Runtime::submitThis gives Core a clean bridge to the lower-level scheduler.
Runtime task result
A direct runtime task returns a TaskResult.
executor.submit([]() -> vix::runtime::TaskResult
{
return vix::runtime::TaskResult::complete;
});Results:
| Result | Meaning |
|---|---|
complete | The task finished. |
yield | The task should be scheduled again. |
failed | The task failed. |
Yielding task
A low-level task can yield.
std::size_t step = 0;
executor.submit([step]() mutable -> vix::runtime::TaskResult
{
++step;
if (step < 10)
{
return vix::runtime::TaskResult::yield;
}
return vix::runtime::TaskResult::complete;
});When a task yields, the runtime can reschedule it.
This is useful for work that can be split into small steps.
Failed task
A direct task can report failure.
executor.submit([]() -> vix::runtime::TaskResult
{
return vix::runtime::TaskResult::failed;
});A task submitted through post(...) returns failed if the callable throws.
executor.post([]()
{
throw std::runtime_error("failed");
});The executor records failure internally.
Safe destruction
RuntimeExecutor stops the runtime in its destructor.
Conceptually:
~RuntimeExecutor
-> stop
-> ignore exceptionsThis makes cleanup safer during application shutdown.
Still, explicit lifecycle management is recommended in advanced code.
executor.stop();Complete manual example
#include <vix/executor/RuntimeExecutor.hpp>
#include <vix/print.hpp>
#include <chrono>
int main()
{
vix::executor::RuntimeExecutor executor{4};
executor.start();
vix::executor::TaskOptions options;
options.timeout = std::chrono::milliseconds(100);
executor.post([]()
{
vix::print("normal task");
}, options);
executor.submit([]() -> vix::runtime::TaskResult
{
vix::print("runtime task");
return vix::runtime::TaskResult::complete;
});
executor.post_http_fast([]()
{
vix::print("fast HTTP task");
});
executor.wait_idle();
const auto metrics = executor.metrics();
vix::print("pending:", metrics.pending);
vix::print("active:", metrics.active);
vix::print("timed out:", metrics.timed_out);
executor.stop();
return 0;
}Complete App example
#include <vix.hpp>
int main()
{
auto executor = std::make_shared<vix::executor::RuntimeExecutor>(4);
vix::App app{executor};
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("App using a custom RuntimeExecutor");
});
app.get("/metrics", [&app](vix::Request &req, vix::Response &res)
{
(void)req;
const auto m = app.executor().metrics();
res.json({
{"pending", m.pending},
{"active", m.active},
{"timed_out", m.timed_out}
});
});
app.run(8080);
return 0;
}API summary
| API | Purpose |
|---|---|
RuntimeExecutor(config) | Create an executor from runtime config. |
RuntimeExecutor(workers) | Create an executor with a worker count. |
RuntimeExecutor(runtime) | Create an executor from an existing runtime. |
start() | Start the underlying runtime. |
stop() | Stop the underlying runtime. |
stop_and_wait() | Wait for current work to drain, then stop. |
started() | Return whether the executor has started. |
running() | Return whether the runtime is running. |
accepting() | Return whether new work is accepted. |
submit(Task) | Submit a pre-built runtime task. |
submit(TaskFn, affinity) | Submit a low-level runtime task function. |
post(fn, options) | Submit a normal void callable. |
post_http_fast(fn, affinity) | Submit a short HTTP fast-path task. |
metrics() | Return executor metrics. |
wait_idle() | Wait until pending and active work reach zero. |
submitted_tasks() | Return accepted submission count. |
rejected_tasks() | Return rejected submission count. |
fast_http_submitted_tasks() | Return fast HTTP submission count. |
Metrics summary
| Metric | Meaning |
|---|---|
pending | Tasks waiting in the runtime. |
active | Tasks currently active through tracked post(...). |
timed_out | Tasks that exceeded their configured timeout threshold. |
submitted_tasks() | Total accepted submissions. |
rejected_tasks() | Total rejected submissions. |
fast_http_submitted_tasks() | Accepted fast-path HTTP submissions. |
Best practices
Use the app-managed executor for normal applications.
vix::App app;Use a custom executor only when you need explicit control.
auto executor = std::make_shared<vix::executor::RuntimeExecutor>(4);
vix::App app{executor};Use post(...) for normal work.
executor.post(task);Use post_http_fast(...) only for short hot-path HTTP work.
executor.post_http_fast(task);Use submit(...) when you need the low-level runtime task contract.
executor.submit(task_fn);Call wait_idle() before stopping if you need graceful draining.
executor.stop_and_wait();Do not submit new work after shutdown begins.
if (executor.accepting())
{
executor.post(task);
}Next steps
Read the next pages: