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

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():

cpp
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:

text
run before this point in time

A timeout is an execution duration:

text
execution should not take longer than this duration

For example:

text
deadline
submitted at 10:00:00
deadline     10:00:01

        absolute point


timeout
task starts

allowed execution duration = 1 second

Queue waiting consumes the available deadline window.

Queue waiting does not consume a task execution timeout.

See Timeouts for timeout behavior.

Deadline clock

Deadline uses:

cpp
std::chrono::steady_clock

through:

cpp
vix::threadpool::Deadline::clock

The corresponding time-point type is:

cpp
vix::threadpool::Deadline::time_point

std::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:

cpp
vix::threadpool::Deadline deadline;

It reports:

text
enabled()         false
disabled_value()  true
expired()         false
remaining()       0

A disabled deadline never expires.

You can also create one explicitly:

cpp
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:

cpp
auto deadline = vix::threadpool::Deadline::after(
        std::chrono::milliseconds{500}
);

The stored time point is calculated when after() is called:

text
steady_clock::now()
        +
500 ms

absolute deadline

The duration can use any std::chrono::duration type.

For example:

cpp
auto deadline = vix::threadpool::Deadline::after(
        std::chrono::seconds{2}
);

or:

cpp
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:

cpp
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:

text
enabled()  true
expired()  true

A 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:

cpp
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:

cpp
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.

cpp
auto timeout = vix::threadpool::Timeout::milliseconds(500);

auto deadline =
        vix::threadpool::Deadline::from_timeout(timeout);

Conceptually:

text
Timeout = 500 ms

from_timeout()

steady_clock::now() + 500 ms

Deadline

The 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:

cpp
auto deadline = vix::threadpool::Deadline::from_timeout(
        vix::threadpool::Timeout::disabled()
);

The result reports:

text
enabled()  false
expired()  false

This preserves the meaning of a disabled timing constraint.

Attach a deadline to a task

Use TaskOptions::with_deadline():

cpp
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:

cpp
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:

cpp
if (options.has_deadline())
{
  // An absolute deadline is configured.
}

Check whether a deadline is enabled

Use:

cpp
if (deadline.enabled())
{
  // Deadline contains an absolute time point.
}

The opposite check is:

cpp
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:

cpp
if (deadline.expired())
{
  // Deadline has been reached.
}

An enabled deadline expires when:

text
current time >= deadline time

The comparison includes equality.

Conceptually:

text
now < deadline

not expired


now >= deadline

expired

A disabled deadline always returns false.

Check expiration at a specific time

Use expired_at() when the comparison time is already available:

cpp
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:

text
enabled && now >= deadline.time()

Read the deadline time

Use:

cpp
const auto time = deadline.time();

The returned value is meaningful when:

cpp
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:

cpp
const auto remaining = deadline.remaining();

For an active future deadline:

text
remaining = deadline time - current time

For an expired deadline:

text
remaining = 0

For a disabled deadline:

text
remaining = 0

A zero remaining duration can therefore mean either:

text
deadline disabled
        or
deadline expired

Use enabled() and expired() when the distinction matters.

Remaining milliseconds

For convenience:

cpp
const auto remaining = deadline.remaining_ms();

returns a:

cpp
std::chrono::milliseconds

value.

For example:

cpp
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:

cpp
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:

text
ready()   true
status()  timed_out
result()  timeout
error()   timeout

Calling:

cpp
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:

text
submit at t0

task enters queue

deadline = t0 + 100 ms

task waits 150 ms

worker reaches task

deadline expired

user callable skipped

This 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:

cpp
#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:

text
submit()

deadline already expired?

   ├── yes → Future timeout

   └── no  → continue

Then immediately before invoking the user callable:

text
worker reaches task

deadline expired?
   ┌───────┴───────┐
  yes              no
   │                │
Future timeout     invoke callable

This 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:

text
deadline valid

callable starts

deadline expires

callable continues

callable returns value

Future can complete successfully

The 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:

text
latest acceptable start time

rather than a forced completion boundary.

TaskHandle deadlines

handle() uses the same deadline observation path as submit().

For example:

cpp
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:

text
before scheduling
        +
immediately before the callable

If it expires before the callable begins:

text
status = timed_out
result = timeout
error  = timeout

If 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:

text
deadline expired?

yes

do not invoke callable

timed_out

If the deadline has not expired, the callable runs.

After the callable returns, Task::run() checks the deadline again:

text
callable returns

finish time >= deadline?

yes

timed_out

Therefore, 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.

cpp
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:

text
before callable
      +
after callable

If 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:

cpp
const bool accepted = pool.post([](){
  perform_work();
}, options);

answers:

text
Was the work accepted by the execution runtime?

It does not answer:

text
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:

text
post()

deadline expired?

  ├── yes → record timed_out, callable skipped

  └── no  → execute callable

After the callable returns, it checks the deadline again.

If the deadline expired during execution:

text
callable runs to completion

deadline expired

timed_out metric recorded

The callable is never forcibly interrupted.

Deadlines do not stop running code

A deadline is not a thread-interruption mechanism.

This:

text
deadline reached

terminate callable immediately

does not happen.

The actual behavior is based on observation points:

text
check deadline

decide whether to start

or

run callable

check deadline afterward

record timing outcome

depending 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:

cpp
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:

cpp
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.

cpp
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:

text
                 shared deadline

        ┌─────────────┴─────────────┐
        ▼                           ▼
     Task A                       Task B
submitted at t0                submitted at t0
        │                           │
        └──── must start before ────┘
                    t0 + 1s

This makes deadlines useful for groups of work that become stale together.

from_timeout() and shared deadlines

Creating a new deadline separately for every task:

cpp
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:

cpp
Deadline::clock::now()

observation.

If several tasks must share exactly the same expiration point, create one deadline and reuse it:

cpp
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:

cpp
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:

text
cancellation

explicit program request


deadline

absolute time condition

When 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:

text
cancellation requested
        +
deadline expired

the current pre-submission result mapping checks cancellation first.

The Future therefore receives:

text
ThreadPoolErrc::cancelled

rather than:

text
ThreadPoolErrc::timeout

This ordering belongs to the current result-producing submission path.

Deadline and timeout together

A task can have both:

cpp
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:

text
deadline

absolute expiration point


timeout

execution-duration observation

For example, a task can wait 800 ms in a queue and then execute for 50 ms:

text
queue wait       800 ms
execution         50 ms
total             850 ms

With:

text
deadline = submission + 1 second
timeout  = 100 ms

both 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:

cpp
config.default_timeout;

but it does not provide a default deadline field.

Deadlines are configured per task:

cpp
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:

cpp
if (first == second)
{
  // Same enabled state and same stored time point.
}

Equality requires both:

text
same enabled state
        +
same time point

Inequality is available through:

cpp
first != second

Two 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:

text
request arrives

deadline established

task enters queue

worker becomes available

still before deadline?
   ┌───────┴───────┐
  yes              no
   │                │
execute            skip

This 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:

text
Deadline

   ├── disabled
   │      ↓
   │   never expires

   └── enabled

     absolute steady_clock
          time point

     now >= time?
       ┌────┴────┐
      yes        no
       │          │
    expired     valid

For submit() and handle():

text
submission

deadline expired?

    ├── yes → timeout result

    └── no

       queue

worker reaches task

deadline expired?
    ┌────┴────┐
   yes        no
    │          │
timeout      callable starts

          callable runs normally

For low-level Task, post(), and InlineExecutor, the deadline can also be observed after callable execution and recorded as a timeout.

The important properties are:

  • Deadline represents an absolute std::chrono::steady_clock time 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() and remaining_ms() return zero for both disabled and expired deadlines.
  • submit() and handle() check deadlines before scheduling and again before the user callable begins.
  • The current submit() and handle() paths do not convert expiration during the callable into a timeout result.
  • Low-level Task, post(), and InlineExecutor also 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.
  • ThreadPoolConfig has no pool-level default deadline.

Continue with Timeouts for execution-duration observation or Scopes for structured groups of work.

Released under the MIT License.