Appearance
Executor
Vix uses an executor abstraction to run work outside the networking code.
The executor is responsible for:
- accepting tasks (
post) - exposing a lightweight metrics snapshot (
metrics) - allowing idle synchronization (
wait_idle) - supporting a future-based API (
submit) on top ofpost
In the HTTP stack, the server and the App use an IExecutor to execute request handlers.
IExecutor
vix::executor::IExecutor is the generic task execution interface.
Core API
cpp
struct IExecutor
{
virtual ~IExecutor() = default;
virtual bool post(std::function<void()> fn, TaskOptions opt = {}) = 0;
virtual Metrics metrics() const = 0;
virtual void wait_idle() = 0;
template <class F, class... Args>
auto submit(F&& f, Args&&... args, TaskOptions opt = {})
-> std::future<std::invoke_result_t<F, Args...>>;
};post(fn, opt)
Posts a task for execution.
- Returns
trueif accepted by the executor. - Returns
falsewhen rejected (queue full, shutting down, etc).
Task options allow priority and time constraints.
metrics()
Returns an instantaneous snapshot of executor state:
pending: tasks waiting to runactive: tasks currently executingtimed_out: tasks that timed out (implementation-defined)
wait_idle()
Blocks the caller until the executor becomes idle.
This is useful for:
- clean shutdown sequences
- tests that need deterministic completion
- draining background work before exit
Futures with submit
submit is implemented in the interface as a helper built on top of post.
It wraps the callable into a std::packaged_task and returns a std::future.
cpp
auto fut = ex.submit([]{
return 42;
});
int x = fut.get();Rejection behavior
If post rejects the task, submit returns a future already set with an exception:
std::runtime_error("submit rejected by executor")
This ensures the API is safe: you always get a future, and failures are explicit.
TaskOptions
TaskOptions are execution hints associated with a task.
cpp
struct TaskOptions
{
int priority = 0;
std::chrono::milliseconds timeout{0};
std::chrono::milliseconds deadline{0};
bool may_block = false;
};Fields
priorityScheduling hint. Higher means more important (implementation-defined).timeoutMaximum allowed runtime or queue time (implementation-defined).deadlineAbsolute time constraint (implementation-defined).may_blockIndicates the task may block (I/O, locks, sleeps). Executors can route it differently.
Metrics
Metrics is a small, copyable snapshot struct:
cpp
struct Metrics
{
std::uint64_t pending{0};
std::uint64_t active{0};
std::uint64_t timed_out{0};
};This is intentionally minimal: it is safe to read frequently and cheap to copy.
Lightweight wrappers
Vix provides lightweight wrappers around an executor pointer for convenience.
Stand
Stand is a simple forwarding wrapper:
cpp
auto s = vix::executor::makeStand(ex);
s.post([]{
// work
});LimitedExecutor
limit(ex, n) builds a limited view over an executor.
cpp
auto limited = vix::executor::limit(ex, 64);
limited.post([]{
// work
});Note: the wrapper stores maxPending, but the real enforcement policy is owned by the executor implementation. The wrapper is meant to express intent and provide a consistent API surface.
How it fits in core
- The HTTP server accepts connections and parses requests.
- The router resolves a handler for the request.
- The handler is executed via the executor.
- This separation prevents networking threads from being blocked by user work.
Practical guidance
- Keep request handlers non-blocking when possible.
- Mark tasks that may block via
TaskOptions{ .may_block = true }. - Use
wait_idle()in tests and controlled shutdown paths. - Use
metrics()to expose basic runtime health in dev tooling.