Short answer: vTaskDelay() has no cancellation API, but the kernel does clean up after it. Deleting a delayed task removes its delay-list entry and the pending timeout with it. Suspending a delayed task does not freeze its timeout — the ticks keep counting. And changing configTICK_RATE_HZ at runtime is not a supported operation, so pending delays under a changed tick rate are simply undefined behavior, not a documented edge case.
Confirmed behavior (FreeRTOS v10.x, default tick implementation)
Task deleted during vTaskDelay
When vTaskDelete() runs against a task sitting in the delayed list, the kernel removes that task's list item from the delayed (or overflow-delayed) list as part of TCB teardown. There is no lingering timeout: the TCB is freed (by the idle task in the default configuration), the handle becomes invalid, and nothing will \"wake up\" later. If you recreate a task with the same priority/stack, it starts with a fresh TCB and no memory of the old delay. So the answer to \"does FreeRTOS clear the remaining delay\" is yes — implicitly, because the delay state lives in the TCB's list item, which is destroyed with the task.
Task suspended during vTaskDelay
vTaskSuspend() moves the task out of the delayed list into the suspended list, but the tick value stored in the task's list item is the absolute wake tick, not a countdown. When you later call vTaskResume(), the kernel re-evaluates that stored wake time against the current tick count. Two outcomes:
- If the original wake time has already passed while suspended, the task becomes ready immediately on resume — the delay is effectively consumed, not restarted.
- If time remains, the task is re-inserted into the delayed list with only the remainder left.
So the timeout does not resume \"from where it left off\" in the sense of pausing a stopwatch; it resumes against an absolute deadline that kept running. This is the likely explanation for the \"duplicate activations\" symptom in the question: a task resumed after its deadline fires immediately, which can look like a spurious extra wake-up if the code assumed the full delay would re-run.
Notifications aborting a delay
A task blocked in vTaskDelay() alone cannot be unblocked early by a notification — vTaskDelay() doesn't wait on a notification object. Early-unblock behavior only applies to the interruptible waits (xTaskNotifyWait(), ulTaskNotifyTake(), queue/semaphore receives with a timeout). If you're seeing a delay \"aborted\" by a notification, the task is almost certainly blocked on one of those primitives with a tick timeout, not in a bare vTaskDelay(). That distinction matters for diagnosis.
Runtime change of configTICK_RATE_HZ
configTICK_RATE_HZ is a compile-time constant used to derive the tick compare value programmed into the timer. FreeRTOS provides no API to change it at runtime. If you reprogram the timer frequency yourself mid-run, every absolute wake tick already stored in delayed list items is now measured against a different tick period — delays become proportionally too long or too short, and nothing in the kernel compensates. Treat this as unsupported; if you need a different tick rate, change it at build time and reboot.
Verification you can run
// Deletion: start a task delaying 5000 ticks, delete it after 100.\n// Expected: no further activity from that handle; idle task frees the TCB.\n\n// Suspension: task delays 1000 ticks; suspend at tick 200, resume at tick 1200.\n// Expected: task becomes ready immediately on resume (deadline already passed),\n// not 800 ticks after resume.
Instrument with xTaskGetTickCount() at block and wake points and compare against the expected absolute deadline.
Uncertainty and scope
This describes the default tick-driven implementation in v10.x. Tickless-idle ports and v11.x rework some delay-list handling, so verify against your exact kernel version and port. If your symptom involves early wake-ups, the one detail worth confirming is which blocking primitive the task actually uses — that changes the diagnosis entirely.