-
Notifications
You must be signed in to change notification settings - Fork 634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Call the cancellationTask
of Scheduled
directly
#2011
Call the cancellationTask
of Scheduled
directly
#2011
Conversation
9d369f7
to
7a185d4
Compare
@swift-nio-bot test perf please |
performance reportbuild id: 91 timestamp: Wed Dec 15 08:39:45 UTC 2021 results
comparison
significant differences found |
7a185d4
to
7a9b590
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great modulo a couple of nits.
Sources/NIOCore/EventLoop.swift
Outdated
@@ -21,18 +21,13 @@ import Dispatch | |||
/// will be notified once the execution is complete. | |||
public struct Scheduled<T> { | |||
/* private but usableFromInline */ @usableFromInline let _promise: EventLoopPromise<T> | |||
|
|||
@usableFromInline let cancellationTask: (() -> Void) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably this would be private
if it wasn't @usableFromInline
. By convention in these cases we prefix the name with a _
so this should be _cancellationTask
.
Sources/NIOEmbedded/Embedded.swift
Outdated
while self.scheduledTasks.peek() != nil { | ||
self.scheduledTasks.pop()?.fail(EventLoopError.shutdown) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not while let task = self.scheduledTasks.pop() { ... }
?
7a9b590
to
7883758
Compare
while self.scheduledTasks.pop() != nil {} | ||
while let task = self.scheduledTasks.pop() { | ||
task.fail(EventLoopError.shutdown) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this is a behavioural change. Can you write a test that encodes this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa So there is a test that is executing this testFinishWithRecursivelyScheduledTasks
and without this change the test is actually crashing because the future is leaking. Which I am still not 100% sure why it did work before. Should I write an additional test that checks if the error is actually the EventLoopError.shutdown
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a different test that was already testing the EmbeddedEventLoop
specifically when draining the remaining tasks. I just added an assertion that makes sure the last task is failed with the proper error. Is that fine with you?
7883758
to
6a935b8
Compare
f04f2ab
to
fbd9eae
Compare
### Motivation: In my previous PR apple#2010, I was able to decrease the allocations for both `scheduleTask` and `execute` by 1 already. Gladly, there are no more allocations left to remove from `execute` now; however, `scheduleTask` still provides a couple of allocations that we can try to get rid of. ### Modifications: This PR removes two allocations inside `Scheduled` where we were using the passed in `EventLoopPromise` to call the `cancellationTask` once the `EventLoopFuture` of the promise fails. This requires two allocations inside `whenFailure` and inside `_whenComplete`. However, since we are passing the `cancellationTask` to `Scheduled` anyhow and `Scheduled` is also the one that is failing the promise from the `cancel()` method. We can just go ahead and store the `cancellationTask` inside `Scheduled` and call it from the `cancel()` method directly instead of going through the future. Importantly, here is that the `cancellationTask` is not allowed to retain the `ScheduledTask.task` otherwise we would change the semantics and retain the `ScheduledTask.task` longer than necessary. My previous PR apple#2010, already implemented the work to get rid of the retain from the `cancellationTask` closure. So we are good to go ahead and store the `cancellationTask` inside `Scheduled` now ### Result: `scheduleTask` requires two fewer allocations
fbd9eae
to
7fcc0bb
Compare
Motivation: When a task is scheduled using a `DispatchTimerSource` on a `NIOTSEventLoop` the source is retain via a callback on the promise associated with the scheduled task. This stops the source from deinit'd before the task is run. However, the callback being added is an implementation details of `Scheduled` and the retain cycle is incidental. If that detail chnaged (such as in apple/swift-nio#2011) then tasks may not run and the promise could be leaked. Modifications: - Explicitly add a retain cycle between the future and the timer source which is broken by the promise being completed and callbacks run. Result: The timer source is kept alive until the event fires.
Motivation: When a task is scheduled using a `DispatchTimerSource` on a `NIOTSEventLoop` the source is retain via a callback on the promise associated with the scheduled task. This stops the source from deinit'd before the task is run. However, the callback being added is an implementation details of `Scheduled` and the retain cycle is incidental. If that detail chnaged (such as in apple/swift-nio#2011) then tasks may not run and the promise could be leaked. Modifications: - Explicitly add a retain cycle between the future and the timer source which is broken by the promise being completed and callbacks run. Result: The timer source is kept alive until the event fires.
Motivation:
In my previous PR #2010, I was able to decrease the allocations for both
scheduleTask
andexecute
by 1 already. Gladly, there are no more allocations left to remove fromexecute
now; however,scheduleTask
still provides a couple of allocations that we can try to get rid of.Modifications:
This PR removes two allocations inside
Scheduled
where we were using the passed inEventLoopPromise
to call thecancellationTask
once theEventLoopFuture
of the promise fails. This requires two allocations insidewhenFailure
and inside_whenComplete
. However, since we are passing thecancellationTask
toScheduled
anyhow andScheduled
is also the one that is failing the promise from thecancel()
method. We can just go ahead and store thecancellationTask
insideScheduled
and call it from thecancel()
method directly instead of going through the future.Importantly, here is that the
cancellationTask
is not allowed to retain theScheduledTask.task
otherwise we would change the semantics and retain theScheduledTask.task
longer than necessary. My previous PR #2010, already implemented the work to get rid of the retain from thecancellationTask
closure. So we are good to go ahead and store thecancellationTask
insideScheduled
nowResult:
scheduleTask
requires two fewer allocations