Deadlines
A Deadline represents an absolute point in time after which work is considered expired.
Create a deadline relative to the current time with Deadline::after():
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{500})
);
auto future = pool.submit([](){
return 42;
}, options);For result-producing ThreadPool submissions, a deadline primarily prevents stale work from starting after its allowed time point.
Deadline vs timeout
A deadline and a timeout describe different timing constraints.
A deadline is an absolute time point:
run before this point in timeA timeout is an execution duration:
execution should not take longer than this durationFor example:
deadline
submitted at 10:00:00
deadline 10:00:01
↑
absolute point
timeout
task starts
↓
allowed execution duration = 1 secondQueue waiting consumes the available deadline window.
Queue waiting does not consume a task execution timeout.
See Timeouts for timeout behavior.
Deadline clock
Deadline uses:
std::chrono::steady_clockthrough:
vix::threadpool::Deadline::clockThe corresponding time-point type is:
vix::threadpool::Deadline::time_pointstd::chrono::steady_clock is monotonic and is appropriate for execution timing because it is not affected by wall-clock changes.
Disabled deadline
A default-constructed deadline is disabled:
vix::threadpool::Deadline deadline;It reports:
enabled() false
disabled_value() true
expired() false
remaining() 0A disabled deadline never expires.
You can also create one explicitly:
auto deadline = vix::threadpool::Deadline::disabled();This is the default deadline stored in TaskOptions.
Create a deadline after a duration
The most common construction method is:
auto deadline = vix::threadpool::Deadline::after(
std::chrono::milliseconds{500}
);The stored time point is calculated when after() is called:
steady_clock::now()
+
500 ms
↓
absolute deadlineThe duration can use any std::chrono::duration type.
For example:
auto deadline = vix::threadpool::Deadline::after(
std::chrono::seconds{2}
);or:
auto deadline = vix::threadpool::Deadline::after(
std::chrono::microseconds{500}
);The duration is converted to the duration type used by steady_clock.
Non-positive durations
Deadline::after() does not disable non-positive durations.
For example:
auto deadline = vix::threadpool::Deadline::after(
std::chrono::milliseconds{-1}
);creates an enabled deadline whose time point is already in the past.
It therefore reports:
enabled() true
expired() trueA zero-duration deadline also represents the current time and becomes expired immediately.
This differs from Timeout, where a non-positive duration disables timeout observation.
Construct from an absolute time point
A deadline can be created directly from a steady_clock time point:
const auto time =
vix::threadpool::Deadline::clock::now() +
std::chrono::seconds{1};
vix::threadpool::Deadline deadline(time);This is useful when several tasks should share the same absolute expiration point.
For example:
const auto time =
vix::threadpool::Deadline::clock::now() +
std::chrono::seconds{1};
vix::threadpool::Deadline deadline(time);
vix::threadpool::TaskOptions options =
vix::threadpool::TaskOptions::with_deadline(deadline);Every task receiving the same Deadline observes the same absolute time point.
Create a deadline from a Timeout
Deadline::from_timeout() converts a relative timeout value into an absolute deadline starting at the moment of conversion.
auto timeout = vix::threadpool::Timeout::milliseconds(500);
auto deadline =
vix::threadpool::Deadline::from_timeout(timeout);Conceptually:
Timeout = 500 ms
↓
from_timeout()
↓
steady_clock::now() + 500 ms
↓
DeadlineThe important distinction is that the result is now absolute.
If the task waits in a queue for 400 ms, only approximately 100 ms remain before that deadline expires.
Disabled Timeout produces disabled Deadline
A disabled timeout produces a disabled deadline:
auto deadline = vix::threadpool::Deadline::from_timeout(
vix::threadpool::Timeout::disabled()
);The result reports:
enabled() false
expired() falseThis preserves the meaning of a disabled timing constraint.
Attach a deadline to a task
Use TaskOptions::with_deadline():
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{500})
);
auto future = pool.submit([](){
return 42;
}, options);Or use the setter:
vix::threadpool::TaskOptions options;
options.set_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{500})
);
auto future = pool.submit([](){
return 42;
}, options);Check whether options contain an enabled deadline with:
if (options.has_deadline())
{
// An absolute deadline is configured.
}Check whether a deadline is enabled
Use:
if (deadline.enabled())
{
// Deadline contains an absolute time point.
}The opposite check is:
if (deadline.disabled_value())
{
// Deadline is disabled.
}These methods describe whether the deadline is active.
They do not say whether an enabled deadline has already expired.
Check expiration
Use:
if (deadline.expired())
{
// Deadline has been reached.
}An enabled deadline expires when:
current time >= deadline timeThe comparison includes equality.
Conceptually:
now < deadline
↓
not expired
now >= deadline
↓
expiredA disabled deadline always returns false.
Check expiration at a specific time
Use expired_at() when the comparison time is already available:
const auto now = vix::threadpool::Deadline::clock::now();
if (deadline.expired_at(now))
{
// Expired at this time point.
}This avoids obtaining another clock value and is useful when several timing decisions should use the same observation time.
The rule remains:
enabled && now >= deadline.time()Read the deadline time
Use:
const auto time = deadline.time();The returned value is meaningful when:
deadline.enabled()is true.
For a disabled deadline, time() returns the default steady_clock::time_point{} stored internally.
Check enabled() before interpreting the value as an active deadline.
Remaining time
Use:
const auto remaining = deadline.remaining();For an active future deadline:
remaining = deadline time - current timeFor an expired deadline:
remaining = 0For a disabled deadline:
remaining = 0A zero remaining duration can therefore mean either:
deadline disabled
or
deadline expiredUse enabled() and expired() when the distinction matters.
Remaining milliseconds
For convenience:
const auto remaining = deadline.remaining_ms();returns a:
std::chrono::millisecondsvalue.
For example:
auto deadline = vix::threadpool::Deadline::after(
std::chrono::seconds{1}
);
const auto remaining = deadline.remaining_ms();The exact value depends on how much time elapsed between construction and observation.
Do not rely on it being exactly 1000.
Deadline before submission
If a submit() deadline is already expired when the pool processes the submission:
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{-1})
);
auto future = pool.submit([](){
return 42;
}, options);the callable is not scheduled for normal execution.
The Future becomes:
ready() true
status() timed_out
result() timeout
error() timeoutCalling:
future.get();throws std::system_error.
Deadline while waiting in a queue
A deadline is checked again when a result-producing worker task reaches the user callable.
This means queue delay consumes the deadline window.
Conceptually:
submit at t0
↓
task enters queue
↓
deadline = t0 + 100 ms
↓
task waits 150 ms
↓
worker reaches task
↓
deadline expired
↓
user callable skippedThis is one of the main uses of deadlines.
They can prevent work from starting after that work has become stale.
Queue waiting example
A single-worker pool can make the behavior visible:
#include <chrono>
#include <thread>
#include <vix/threadpool/all.hpp>
int main()
{
vix::threadpool::ThreadPool pool(1);
auto blocker = pool.submit([](){
std::this_thread::sleep_for(
std::chrono::milliseconds{100}
);
});
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{10})
);
auto future = pool.submit([](){
return 42;
}, options);
blocker.get();
return future.status() ==
vix::threadpool::TaskStatus::timed_out
? 0
: 1;
}The second callable waits behind the first task.
Its deadline expires before the worker can begin it, so the result-producing wrapper reports a timeout instead of invoking the callable.
submit() deadline checks
The current ThreadPool::submit() path checks the deadline at two points before the user callable starts.
First:
submit()
↓
deadline already expired?
│
├── yes → Future timeout
│
└── no → continueThen immediately before invoking the user callable:
worker reaches task
↓
deadline expired?
┌───────┴───────┐
yes no
│ │
Future timeout invoke callableThis second check covers time spent waiting in the worker queue.
submit() deadline after execution begins
The current result-producing submit() path does not check its deadline again after the user callable starts.
Once execution begins:
deadline valid
↓
callable starts
↓
deadline expires
↓
callable continues
↓
callable returns value
↓
Future can complete successfullyThe deadline does not forcibly interrupt the callable, and the current submit() wrapper does not convert a value returned after the deadline into a timeout result.
For result-producing ThreadPool work, the deadline should therefore be understood primarily as:
latest acceptable start timerather than a forced completion boundary.
TaskHandle deadlines
handle() uses the same deadline observation path as submit().
For example:
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{500})
);
auto handle = pool.handle([](){
return 42;
}, options);The deadline is checked:
before scheduling
+
immediately before the callableIf it expires before the callable begins:
status = timed_out
result = timeout
error = timeoutIf it expires only after the callable starts, the current handle result path can still complete successfully.
Low-level Task deadlines
The low-level Task::run() path observes deadlines differently.
A Task checks its deadline before the callable:
deadline expired?
↓
yes
↓
do not invoke callable
↓
timed_outIf the deadline has not expired, the callable runs.
After the callable returns, Task::run() checks the deadline again:
callable returns
↓
finish time >= deadline?
↓
yes
↓
timed_outTherefore, a directly configured low-level Task can report a timeout when its callable finishes after the deadline.
post() deadline behavior
ThreadPool::post() keeps the deadline attached to the low-level Task.
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{10})
);
const bool accepted = pool.post([](){
perform_work();
}, options);The low-level task checks the deadline:
before callable
+
after callableIf the deadline is already expired when execution reaches the task, the callable is skipped.
If it expires while the callable is running, the callable still runs to completion, then the low-level task is recorded as timed out.
Because post() has no Future, this result is observed through runtime metrics, statistics, or application-managed state.
post() acceptance and deadline outcome are different
The boolean returned by:
const bool accepted = pool.post([](){
perform_work();
}, options);answers:
Was the work accepted by the execution runtime?It does not answer:
Did the work complete before its deadline?A posted task can therefore be accepted and later be recorded as timed out.
InlineExecutor deadlines
InlineExecutor checks deadlines before running the callable:
post()
↓
deadline expired?
│
├── yes → record timed_out, callable skipped
│
└── no → execute callableAfter the callable returns, it checks the deadline again.
If the deadline expired during execution:
callable runs to completion
↓
deadline expired
↓
timed_out metric recordedThe callable is never forcibly interrupted.
Deadlines do not stop running code
A deadline is not a thread-interruption mechanism.
This:
deadline reached
↓
terminate callable immediatelydoes not happen.
The actual behavior is based on observation points:
check deadline
↓
decide whether to start
or
run callable
↓
check deadline afterward
↓
record timing outcomedepending on the execution API being used.
Arbitrary C++ code already executing continues until it returns or throws.
Long-running work
If running work must react while it is executing, use a mechanism that the callable itself can observe.
For example, cancellation can be checked inside a loop:
auto token = source.token();
auto future = pool.submit([token](){
while (has_more_work())
{
if (token.stop_requested())
{
return false;
}
process_next_item();
}
return true;
}, options);A deadline can also be captured explicitly when application logic needs to stop itself at the absolute time point:
auto deadline = vix::threadpool::Deadline::after(
std::chrono::seconds{1}
);
auto future = pool.submit([deadline](){
while (has_more_work())
{
if (deadline.expired())
{
return false;
}
process_next_item();
}
return true;
});In this pattern, the callable itself defines the safe interruption points.
Shared deadline across tasks
Because a deadline stores an absolute time point, the same value can be shared by several submissions.
auto deadline = vix::threadpool::Deadline::after(
std::chrono::seconds{1}
);
vix::threadpool::TaskOptions options =
vix::threadpool::TaskOptions::with_deadline(deadline);
auto first = pool.submit([](){
return 20;
}, options);
auto second = pool.submit([](){
return 22;
}, options);Both tasks have the same expiration point.
They do not each receive a new one-second interval when they begin execution.
Conceptually:
shared deadline
│
┌─────────────┴─────────────┐
▼ ▼
Task A Task B
submitted at t0 submitted at t0
│ │
└──── must start before ────┘
t0 + 1sThis makes deadlines useful for groups of work that become stale together.
from_timeout() and shared deadlines
Creating a new deadline separately for every task:
auto firstDeadline = vix::threadpool::Deadline::from_timeout(timeout);
auto secondDeadline = vix::threadpool::Deadline::from_timeout(timeout);can produce slightly different absolute time points because each call uses a new:
Deadline::clock::now()observation.
If several tasks must share exactly the same expiration point, create one deadline and reuse it:
auto deadline =
vix::threadpool::Deadline::from_timeout(timeout);
vix::threadpool::TaskOptions options =
vix::threadpool::TaskOptions::with_deadline(deadline);Deadline and cancellation
Deadlines and cancellation can be combined:
vix::threadpool::CancellationSource source;
vix::threadpool::TaskOptions options;
options
.set_deadline(
vix::threadpool::Deadline::after(std::chrono::seconds{1})
)
.set_cancellation(source.token());Both can prevent the callable from starting.
The difference is:
cancellation
↓
explicit program request
deadline
↓
absolute time conditionWhen TaskOptions::should_skip_before_run() is evaluated, cancellation is tested together with deadline expiration.
See Cancellation.
Cancellation takes precedence in pre-run result mapping
For submit() and handle(), if both conditions are already true:
cancellation requested
+
deadline expiredthe current pre-submission result mapping checks cancellation first.
The Future therefore receives:
ThreadPoolErrc::cancelledrather than:
ThreadPoolErrc::timeoutThis ordering belongs to the current result-producing submission path.
Deadline and timeout together
A task can have both:
vix::threadpool::TaskOptions options;
options
.set_deadline(
vix::threadpool::Deadline::after(std::chrono::seconds{1})
)
.set_timeout(
vix::threadpool::Timeout::milliseconds(100)
);They describe different constraints:
deadline
↓
absolute expiration point
timeout
↓
execution-duration observationFor example, a task can wait 800 ms in a queue and then execute for 50 ms:
queue wait 800 ms
execution 50 ms
total 850 msWith:
deadline = submission + 1 second
timeout = 100 msboth conditions remain within their limits.
If queue waiting reaches 1 second before the callable starts, the deadline can prevent execution even though the execution timeout has never started.
No pool-level default deadline
ThreadPoolConfig provides:
config.default_timeout;but it does not provide a default deadline field.
Deadlines are configured per task:
vix::threadpool::TaskOptions options = vix::threadpool::TaskOptions::with_deadline(
vix::threadpool::Deadline::after(std::chrono::milliseconds{500})
);This is appropriate because a deadline represents a concrete absolute expiration point usually tied to one operation or group of related operations.
Equality
Two deadlines can be compared:
if (first == second)
{
// Same enabled state and same stored time point.
}Equality requires both:
same enabled state
+
same time pointInequality is available through:
first != secondTwo separately created calls to Deadline::after() should not generally be expected to compare equal because they can capture different current time points.
Use deadlines for stale work
Deadlines are especially useful when work loses value after a specific time.
Conceptually:
request arrives
↓
deadline established
↓
task enters queue
↓
worker becomes available
↓
still before deadline?
┌───────┴───────┐
yes no
│ │
execute skipThis avoids beginning work that is already too late to be useful.
For result-producing submit() and handle(), this is the clearest interpretation of the current deadline behavior.
Deadline model summary
The core type behaves as:
Deadline
│
├── disabled
│ ↓
│ never expires
│
└── enabled
↓
absolute steady_clock
time point
↓
now >= time?
┌────┴────┐
yes no
│ │
expired validFor submit() and handle():
submission
↓
deadline expired?
│
├── yes → timeout result
│
└── no
↓
queue
↓
worker reaches task
↓
deadline expired?
┌────┴────┐
yes no
│ │
timeout callable starts
↓
callable runs normallyFor low-level Task, post(), and InlineExecutor, the deadline can also be observed after callable execution and recorded as a timeout.
The important properties are:
Deadlinerepresents an absolutestd::chrono::steady_clocktime point.- A default deadline is disabled and never expires.
Deadline::after()creates an absolute time point relative to now.- Non-positive durations create enabled deadlines that are already expired.
Deadline::from_timeout()converts a timeout into an absolute deadline.- Queue waiting consumes the available deadline window.
remaining()andremaining_ms()return zero for both disabled and expired deadlines.submit()andhandle()check deadlines before scheduling and again before the user callable begins.- The current
submit()andhandle()paths do not convert expiration during the callable into a timeout result. - Low-level
Task,post(), andInlineExecutoralso check the deadline after callable execution. - Deadlines never forcibly interrupt arbitrary running C++ code.
- Capture and inspect the deadline inside long-running code when the callable itself must stop at that time point.
ThreadPoolConfighas no pool-level default deadline.
Continue with Timeouts for execution-duration observation or Scopes for structured groups of work.