docs: add update scheduling policy and clarify action scheduling semantics#327
docs: add update scheduling policy and clarify action scheduling semantics#327
Conversation
…ntics Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
edwardalee
left a comment
There was a problem hiding this comment.
This is incredibly subtle. It feels like we need a lawyer to write the docs! I think I found a contradiction in the docs, however, so it still needs refinement.
| If a `<min_spacing>` has been declared, then it gives a minimum logical time | ||
| interval between the tags of two subsequently scheduled events. The first effect this | ||
| has is that events will have monotically increasing tags. The difference between the | ||
| times of two successive tags is at least `<min_spacing>`. If the |
There was a problem hiding this comment.
| times of two successive tags is at least `<min_spacing>`, with exceptions noted below. If the |
| @@ -202,9 +202,35 @@ | |||
| - `"defer"`: (**the default**) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is _t_prev_, then the tag of the new event simply becomes _t_prev_ + `<min_spacing>`. | |||
There was a problem hiding this comment.
Suggest adding:
A `min_spacing` of 0 with a `"defer"` policy is the same as having no `min_spacing` declaration at all.
| - `"defer"`: (**the default**) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is _t_prev_, then the tag of the new event simply becomes _t_prev_ + `<min_spacing>`. | ||
| - `"drop"`: The new event is dropped and `schedule()` returns without having modified the event queue. | ||
| - `"replace"`: The payload (if any) of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default `"defer"` policy is applied. | ||
| - `"update"`: When a new event _e'_ is scheduled at time _t'_, the scheduler checks two conditions: (1) whether the event queue contains an earlier pending event _e_ at time _t_ for the same action, and (2) whether _t'_ >= _t_ + `<min_spacing>`. If **both** conditions are true, the earlier event _e_ at _t_ is dropped and the new event _e'_ at _t'_ is kept. If **either** condition is false, the new event _e'_ at _t'_ is scheduled normally. |
There was a problem hiding this comment.
| - `"update"`: When a new event _e'_ is scheduled at time _t'_, the scheduler checks two conditions: (1) whether the event queue contains an earlier pending event _e_ at time _t_ for the same action, and (2) whether _t'_ >= _t_ + `<min_spacing>`. If **both** conditions are true, the earlier event _e_ at _t_ is dropped and the new event _e'_ at _t'_ is kept. If **either** condition is false, the new event _e'_ at _t'_ is scheduled normally. | |
| - `"update"`: When a new event _e'_ is scheduled at time _t'_, the scheduler checks two conditions: (1) whether the event queue contains an earlier pending event _e_ at some time _t_ for the same action, and (2) whether _t'_ >= _t_ + `<min_spacing>`. If **both** conditions are true, the earlier event _e_ at _t_ is dropped and the new event _e'_ at _t'_ is kept. If **either** condition is false, the new event _e'_ at _t'_ is scheduled normally, as if no `min_spacing` had been specified. |
|
|
||
| **Semantic distinction between policies.** The four policies fall into two categories: | ||
|
|
||
| - **"defer" and "drop"** govern how a _new_ event is handled relative to _all_ previously scheduled events for the same action, including events that have already been committed (i.e., already popped from the event queue and assigned to a tag). These policies decide whether the new event should be added to the queue or not. |
There was a problem hiding this comment.
| - **"defer" and "drop"** govern how a _new_ event is handled relative to _all_ previously scheduled events for the same action, including events that have already been committed (i.e., already popped from the event queue and assigned to a tag). These policies decide whether the new event should be added to the queue or not. | |
| - **"defer" and "drop"** govern how a _new_ event is handled relative to the most recent previously scheduled event for the same action, including events that have already been committed (i.e., already popped from the event queue and processed). These policies decide whether the new event should be added to the queue or not. |
| @@ -202,9 +202,35 @@ | |||
| - `"defer"`: (**the default**) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is _t_prev_, then the tag of the new event simply becomes _t_prev_ + `<min_spacing>`. | |||
| - `"drop"`: The new event is dropped and `schedule()` returns without having modified the event queue. | |||
| - `"replace"`: The payload (if any) of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default `"defer"` policy is applied. | |||
There was a problem hiding this comment.
The last sentence here implies that "replace" does guarantee that min_spacing is respected. Is this what the current implementation actually does? In any case, this sentence contradicts what is said below.
| - `"update"` keeps the _newer_ event's time and drops the earlier pending event. | ||
|
|
||
| :::caution | ||
| The `"replace"` and `"update"` policies do **not** guarantee that `<min_spacing>` is always respected. If the earlier event has already been popped from the event queue (i.e., the runtime has already committed to executing at that tag), then the policy cannot find it to replace or update. In that case, the new event is scheduled normally, which may result in two events closer together than `<min_spacing>`. |
There was a problem hiding this comment.
This contradicts the description of "replace" above.
| logical action election_timeout_reached(0 sec, forever, "update") | ||
| ``` | ||
|
|
||
| Here, the `forever` min_spacing ensures that at most one event for this action is on the event queue at any time (since no two events can ever satisfy a spacing of `forever`). Each time a heartbeat is received, the reactor schedules a new `election_timeout_reached` event in the future. Because the policy is `"update"`, the new event replaces the old pending timeout event, effectively resetting the watchdog. If no heartbeat arrives before the timeout, the pending event fires and triggers an election. |
There was a problem hiding this comment.
| Here, the `forever` min_spacing ensures that at most one event for this action is on the event queue at any time (since no two events can ever satisfy a spacing of `forever`). Each time a heartbeat is received, the reactor schedules a new `election_timeout_reached` event in the future. Because the policy is `"update"`, the new event replaces the old pending timeout event, effectively resetting the watchdog. If no heartbeat arrives before the timeout, the pending event fires and triggers an election. | |
| Here, the `forever` min_spacing ensures that at most one event for this action is on the event queue at any time (since no two events can ever satisfy a spacing of `forever`). Each time a heartbeat is received, the reactor schedules a new `election_timeout_reached` event in the future. Because the policy is `"update"`, the new event replaces the old pending timeout event, effectively resetting the watchdog. If no heartbeat arrives before the timeout, the pending event fires and triggers a reaction, which then handles the timeout, for example by triggering an election. |
Summary
logical action election_timeout_reached(0 sec, forever, "update")for Raft consensustest/C/src/LastTimeUpdate.lfandtest/C/src/concurrent/AsyncCallbackUpdate.lfContext: lf-lang/reactor-uc#334
Changes
docs/writing-reactors/actions.mdx(+31/-1 lines)