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

Move Isolated EL operations to EL protocol #3070

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion IntegrationTests/tests_04_performance/Thresholds/5.10.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"encode_1000_ws_frames_new_buffer_with_space_with_mask": 5050,
"execute_hop_10000_tasks": 0,
"flat_schedule_10000_tasks": 130100,
"flat_schedule_assume_isolated_10000_tasks": 150100,
"flat_schedule_assume_isolated_10000_tasks": 110100,
"future_assume_isolated_lots_of_callbacks": 74050,
"future_erase_result": 4050,
"future_lots_of_callbacks": 74050,
Expand Down
2 changes: 1 addition & 1 deletion IntegrationTests/tests_04_performance/Thresholds/5.9.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"encode_1000_ws_frames_new_buffer_with_space_with_mask": 5050,
"execute_hop_10000_tasks": 0,
"flat_schedule_10000_tasks": 130100,
"flat_schedule_assume_isolated_10000_tasks": 150100,
"flat_schedule_assume_isolated_10000_tasks": 110100,
"future_assume_isolated_lots_of_callbacks": 74050,
"future_erase_result": 4050,
"future_lots_of_callbacks": 74050,
Expand Down
2 changes: 1 addition & 1 deletion IntegrationTests/tests_04_performance/Thresholds/6.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"encode_1000_ws_frames_new_buffer_with_space_with_mask": 5050,
"execute_hop_10000_tasks": 0,
"flat_schedule_10000_tasks": 130100,
"flat_schedule_assume_isolated_10000_tasks": 150100,
"flat_schedule_assume_isolated_10000_tasks": 110100,
"future_assume_isolated_lots_of_callbacks": 74050,
"future_erase_result": 4050,
"future_lots_of_callbacks": 74050,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"encode_1000_ws_frames_new_buffer_with_space_with_mask": 5050,
"execute_hop_10000_tasks": 0,
"flat_schedule_10000_tasks": 130100,
"flat_schedule_assume_isolated_10000_tasks": 150100,
"flat_schedule_assume_isolated_10000_tasks": 110100,
"future_assume_isolated_lots_of_callbacks": 74050,
"future_erase_result": 4050,
"future_lots_of_callbacks": 74050,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"encode_1000_ws_frames_new_buffer_with_space_with_mask": 5050,
"execute_hop_10000_tasks": 0,
"flat_schedule_10000_tasks": 130100,
"flat_schedule_assume_isolated_10000_tasks": 150100,
"flat_schedule_assume_isolated_10000_tasks": 110100,
"future_assume_isolated_lots_of_callbacks": 74050,
"future_erase_result": 4050,
"future_lots_of_callbacks": 74050,
Expand Down
111 changes: 111 additions & 0 deletions Sources/NIOCore/EventLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,69 @@ public protocol EventLoop: EventLoopGroup {
///
/// - NOTE: Event loops only need to implemented this if they provide a custom scheduled callback implementation.
func cancelScheduledCallback(_ scheduledCallback: NIOScheduledCallback)

/// Submit a given task to be executed by the ``EventLoop``, from a context where the caller
/// statically knows that the context is isolated.
///
/// This is an optional performance hook. ``EventLoop`` implementers are not required to implement
/// this witness, but may choose to do so to enable better performance of the isolated EL views. If
/// they do so, ``EventLoop/Isolated/execute`` will perform better.
func _executeIsolatedUnsafeUnchecked(_ task: @escaping () -> Void)

/// Submit a given task to be executed by the ``EventLoop```, from a context where the caller
/// statically knows that the context is isolated.
///
/// Once the execution is complete the returned ``EventLoopFuture`` is notified.
///
/// This is an optional performance hook. ``EventLoop`` implementers are not required to implement
/// this witness, but may choose to do so to enable better performance of the isolated EL views. If
/// they do so, ``EventLoop/Isolated/submit`` will perform better.
///
/// - Parameters:
/// - task: The closure that will be submitted to the ``EventLoop`` for execution.
/// - Returns: ``EventLoopFuture`` that is notified once the task was executed.
func _submitIsolatedUnsafeUnchecked<T>(_ task: @escaping () throws -> T) -> EventLoopFuture<T>

/// Schedule a `task` that is executed by this ``EventLoop`` at the given time, from a context where the caller
/// statically knows that the context is isolated.
///
/// This is an optional performance hook. ``EventLoop`` implementers are not required to implement
/// this witness, but may choose to do so to enable better performance of the isolated EL views. If
/// they do so, ``EventLoop/Isolated/scheduleTask(deadline:_:)`` will perform better.
///
/// - Parameters:
/// - deadline: The instant in time before which the task will not execute.
/// - task: The synchronous task to run. As with everything that runs on the ``EventLoop```, it must not block.
/// - Returns: A ``Scheduled``` object which may be used to cancel the task if it has not yet run, or to wait
/// on the completion of the task.
///
/// - Note: You can only cancel a task before it has started executing.
@discardableResult
func _scheduleTaskIsolatedUnsafeUnchecked<T>(
deadline: NIODeadline,
_ task: @escaping () throws -> T
) -> Scheduled<T>

/// Schedule a `task` that is executed by this ``EventLoop`` after the given amount of time, from a context where the caller
/// statically knows that the context is isolated.
///
/// This is an optional performance hook. ``EventLoop`` implementers are not required to implement
/// this witness, but may choose to do so to enable better performance of the isolated EL views. If
/// they do so, ``EventLoop/Isolated/scheduleTask(in:_:)`` will perform better.
///
/// - Parameters:
/// - in: The amount of time before which the task will not execute.
/// - task: The synchronous task to run. As with everything that runs on the ``EventLoop``, it must not block.
/// - Returns: A ``Scheduled`` object which may be used to cancel the task if it has not yet run, or to wait
/// on the completion of the task.
///
/// - Note: You can only cancel a task before it has started executing.
/// - Note: The `in` value is clamped to a maximum when running on a Darwin-kernel.
@discardableResult
func _scheduleTaskIsolatedUnsafeUnchecked<T>(
in: TimeAmount,
_ task: @escaping () throws -> T
) -> Scheduled<T>
}

extension EventLoop {
Expand Down Expand Up @@ -422,6 +485,54 @@ extension EventLoop {
{
nil
}

/// Default implementation: wraps the task in an UnsafeTransfer.
@inlinable
public func _executeIsolatedUnsafeUnchecked(_ task: @escaping () -> Void) {
self.assertInEventLoop()
let unsafeTransfer = UnsafeTransfer(task)
self.execute {
unsafeTransfer.wrappedValue()
}
}

/// Default implementation: wraps the task in an UnsafeTransfer.
@inlinable
public func _submitIsolatedUnsafeUnchecked<T>(_ task: @escaping () throws -> T) -> EventLoopFuture<T> {
self.assertInEventLoop()
let unsafeTransfer = UnsafeTransfer(task)
return self.submit {
try unsafeTransfer.wrappedValue()
}
}

/// Default implementation: wraps the task in an UnsafeTransfer.
@inlinable
@discardableResult
public func _scheduleTaskIsolatedUnsafeUnchecked<T>(
deadline: NIODeadline,
_ task: @escaping () throws -> T
) -> Scheduled<T> {
self.assertInEventLoop()
let unsafeTransfer = UnsafeTransfer(task)
return self.scheduleTask(deadline: deadline) {
try unsafeTransfer.wrappedValue()
}
}

/// Default implementation: wraps the task in an UnsafeTransfer.
@inlinable
@discardableResult
public func _scheduleTaskIsolatedUnsafeUnchecked<T>(
in delay: TimeAmount,
_ task: @escaping () throws -> T
) -> Scheduled<T> {
self.assertInEventLoop()
let unsafeTransfer = UnsafeTransfer(task)
return self.scheduleTask(in: delay) {
try unsafeTransfer.wrappedValue()
}
}
}

extension EventLoop {
Expand Down
29 changes: 9 additions & 20 deletions Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ public struct NIOIsolatedEventLoop {
@available(*, noasync)
@inlinable
public func execute(_ task: @escaping () -> Void) {
let unsafeTransfer = UnsafeTransfer(task)
self._wrapped.execute {
unsafeTransfer.wrappedValue()
}
self._wrapped._executeIsolatedUnsafeUnchecked(task)
}

/// Submit a given task to be executed by the `EventLoop`. Once the execution is complete the returned `EventLoopFuture` is notified.
Expand All @@ -48,10 +45,7 @@ public struct NIOIsolatedEventLoop {
@available(*, noasync)
@inlinable
public func submit<T>(_ task: @escaping () throws -> T) -> EventLoopFuture<T> {
let unsafeTransfer = UnsafeTransfer(task)
return self._wrapped.submit {
try unsafeTransfer.wrappedValue()
}
self._wrapped._submitIsolatedUnsafeUnchecked(task)
}

/// Schedule a `task` that is executed by this `EventLoop` at the given time.
Expand All @@ -70,10 +64,7 @@ public struct NIOIsolatedEventLoop {
deadline: NIODeadline,
_ task: @escaping () throws -> T
) -> Scheduled<T> {
let unsafeTransfer = UnsafeTransfer(task)
return self._wrapped.scheduleTask(deadline: deadline) {
try unsafeTransfer.wrappedValue()
}
self._wrapped._scheduleTaskIsolatedUnsafeUnchecked(deadline: deadline, task)
}

/// Schedule a `task` that is executed by this `EventLoop` after the given amount of time.
Expand All @@ -93,10 +84,7 @@ public struct NIOIsolatedEventLoop {
in delay: TimeAmount,
_ task: @escaping () throws -> T
) -> Scheduled<T> {
let unsafeTransfer = UnsafeTransfer(task)
return self._wrapped.scheduleTask(in: delay) {
try unsafeTransfer.wrappedValue()
}
self._wrapped._scheduleTaskIsolatedUnsafeUnchecked(in: delay, task)
}

/// Schedule a `task` that is executed by this `EventLoop` at the given time.
Expand All @@ -122,10 +110,11 @@ public struct NIOIsolatedEventLoop {
line: UInt = #line,
_ task: @escaping () throws -> EventLoopFuture<T>
) -> Scheduled<T> {
let unsafeTransfer = UnsafeTransfer(task)
return self._wrapped.flatScheduleTask(deadline: deadline, file: file, line: line) {
try unsafeTransfer.wrappedValue()
}
let promise: EventLoopPromise<T> = self._wrapped.makePromise(file: file, line: line)
let scheduled = self.scheduleTask(deadline: deadline, task)

scheduled.futureResult.flatMap { $0 }.cascade(to: promise)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we save an alloc here if we use scheduled.futureResult.whenComplete { ... }?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so: the flatMap unwraps an extra level of futureness.

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean like this (I appreciate it's more code than the flatMap + cascade):

scheduled.futureResult.whenComplete { result in
    switch result {
    case .success(let futureResult):
        promise.completeWith(futureResult)
    case .failure(let error):
        promise.fail(error)
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're probably right. However, all the flatScheduleTask implementations in swift-nio follow this spelling. I've filed #3071 to track making the improvement generically, and so for now would rather keep this in line with the others.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for filing that, makes sense to track them together.

return .init(promise: promise, cancellationTask: { scheduled.cancel() })
}

/// Returns the wrapped event loop.
Expand Down
Loading