Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Execute tasks scheduled at the same time in order on EmbeddedEventLoop #1540

Merged
merged 1 commit into from
Jun 17, 2020

Conversation

glbrntt
Copy link
Contributor

@glbrntt glbrntt commented Jun 2, 2020

Motivation:

EmbeddedEventLoop executes scheduled tasks in their run-time order.
If multiple tasks are scheduled for the same time then the order in
which they are eventually executed is undefined.

Since execute defers to scheduling a task for "now" it is possible for
tasks to be executed out of order if run() is not called between each
execute.

In "real" event loops this isn't an issue because the system time must
advance between each call to execute.

Modifications:

  • Add a 'insertionOrder' to EmbeddedScheduledTask; use it to break
    ties when comparing two tasks with the same run time
  • Add a task counter to EmbeddedEventLoop which is used to provide an
    insertion order when scheduling tasks.

Result:

  • EmbeddedEventLoop will execute tasks in order

@glbrntt glbrntt requested review from weissi and Lukasa June 2, 2020 12:54
@Lukasa
Copy link
Contributor

Lukasa commented Jun 2, 2020

In "real" event loops this isn't an issue because the system time must
advance between each call to execute.

I mean...this is not actually true. Ultimately we delegate to DispatchTime.now, which on macOS delegates to mach_absolute_time and on Linux delegates to clock_gettime with CLOCK_MONOTONIC. Leaving aside the Mach case, while CLOCK_MONOTONIC is a monotonically incrementing clock, it does not say how long it may stay at the same value. For example, CLOCK_MONOTONIC quite famously does not count forward for time spent while the system is suspended. Additionally, system clocks do have a minimum resolution, and it's completely plausible for that minimum resolution to be substantially larger than the amount of time in which processors execute instructions.

This is not inherently a reason to refuse to make this change, but it's worth bearing in mind that there is absolutely no promise that tasks execute in any particular order.

As a related note, this patch actually affects all scheduled tasks. This is a strict change from the behaviour of SelectableEventLoop, where if two tasks are enqueued that have the same deadline they will be run in a random order. Again, this divergence is not critical as we don't actually make any guarantees here, but it's worth noting.

@weissi and @normanmaurer, where do you land here? Is it valuable to have EmbeddedEventLoop reflect the same ordering uncertainty that SelectableEventLoop has, even though on EmbeddedEventLoop vastly more tasks are enqueued at the exact same time?

@weissi
Copy link
Member

weissi commented Jun 2, 2020

@Lukasa what you're saying is definitely correct. We could hit this issue in the real world too. I'm happy with merging this and filing an issue that we should also add an insertion counter atomic and use that as a tie breaker if we have two tasks with equal ready times.

@Lukasa
Copy link
Contributor

Lukasa commented Jun 2, 2020

Currently with our Big Dumb Lock it doesn't even need to be atomic: we take a look to enqueue tasks irregardless, so we can always just use that lock to guard the counter.

@glbrntt
Copy link
Contributor Author

glbrntt commented Jun 2, 2020

In "real" event loops this isn't an issue because the system time must
advance between each call to execute.

I mean...this is not actually true. Ultimately we delegate to DispatchTime.now, which on macOS delegates to mach_absolute_time and on Linux delegates to clock_gettime with CLOCK_MONOTONIC. Leaving aside the Mach case, while CLOCK_MONOTONIC is a monotonically incrementing clock, it does not say how long it may stay at the same value. For example, CLOCK_MONOTONIC quite famously does not count forward for time spent while the system is suspended. Additionally, system clocks do have a minimum resolution, and it's completely plausible for that minimum resolution to be substantially larger than the amount of time in which processors execute instructions.

Interesting; thanks for the details.

This is not inherently a reason to refuse to make this change, but it's worth bearing in mind that there is absolutely no promise that tasks execute in any particular order.

If we don't accept this change then we should state this explicitly in the documentation.

@weissi
Copy link
Member

weissi commented Jun 2, 2020

@glbrntt I'd say: Let's take this & file a bug on NIO

@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Jun 2, 2020
@Lukasa Lukasa added this to the 2.18.0 milestone Jun 2, 2020
@Lukasa
Copy link
Contributor

Lukasa commented Jun 2, 2020

Works for me.

@weissi
Copy link
Member

weissi commented Jun 8, 2020

hit #1545

@weissi
Copy link
Member

weissi commented Jun 8, 2020

@swift-nio-bot test this please

Motivation:

`EmbeddedEventLoop` executes scheduled tasks in their run-time order.
If multiple tasks are scheduled for the same time then the order in
which they are eventually executed is undefined.

Since `execute` defers to scheduling a task for "now" it is possible for
tasks to be executed out of order if `run()` is not called between each
execute.

In "real" event loops this isn't an issue because the system time must
advance between each call to `execute`.

Modifications:

- Add a 'insertionOrder' to `EmbeddedScheduledTask`; use it to break
  ties when comparing two tasks with the same run time
- Add a task counter to `EmbeddedEventLoop` which is used to provide an
  insertion order when scheduling tasks.

Result:

- EmbeddedEventLoop will `execute` tasks in order
@glbrntt
Copy link
Contributor Author

glbrntt commented Jun 17, 2020

Ping @weissi for review

Copy link
Member

@weissi weissi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! LGTM, sorry for the delay, thought I had done this one already 😭

@glbrntt glbrntt merged commit 7d2e632 into apple:master Jun 17, 2020
@glbrntt glbrntt deleted the gb-embedded branch June 17, 2020 09:56
@Lukasa Lukasa modified the milestones: 2.18.0, 2.19.0 Jun 23, 2020
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants