Skip to content

Conversation

@scottmarchant
Copy link
Collaborator

@scottmarchant scottmarchant commented Dec 2, 2025

Summary

Implement AsyncEventLoopExecutor that forms the basis of operations for NIOAsyncRuntime

Details

  • d7f5ff7 feat: Add AsyncEventLoopExecutor that forms the foundation for creating AsyncEventLoop.

Usage Examples

  • Spin up an executor and drive scheduled work:
let executor = AsyncEventLoopExecutor(loopID: UUID())
executor.enqueue { print("run on loop") }
let jobID = executor.schedule(after: .milliseconds(10), job: { print("delayed") }, failFn: { _ in })
await executor.run()
executor.cancelScheduledJob(withID: jobID)

Testing Performed

Tests coverage provided in tests ported from NIOPosix: #6.

The changes have also been tested in real-world application by running GraphManager and QuantumProjectManager in the browser to migrate database schemas, connect with our server, and pull data from a real user account.

PR Dependencies

#3

Overview of all changes

All planned changes can be viewed together in #2

@scottmarchant scottmarchant changed the title feat: Implement AsyncEventLoopExecutor that forms the basis of operations for NIOAsyncRuntime feat: Implement AsyncEventLoopExecutor Dec 2, 2025
@scottmarchant scottmarchant changed the title feat: Implement AsyncEventLoopExecutor feat: Implement AsyncEventLoopExecutor to lay foundation Dec 2, 2025
@scottmarchant scottmarchant changed the base branch from main to build/setUpInitialPackageAndCI December 2, 2025 18:06
failFn: @Sendable @escaping (Error) -> Void
) -> UUID {
let id = UUID()
Task { @_AsyncEventLoopExecutor._IsolatingSerialEntryActor [delay, job, weak self] in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you pick something besides the @mainactor now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@CrownedPhoenix Yep. That is what I'm doing here. @_AsyncEventLoopExecutor._IsolatingSerialEntryActor is equivalent to @MainActor, except that it forms it's own task pool separate from but similar to @MainActor.

Comment on lines +269 to +273
await run()
await awaitPendingEntryOperations()
if let existingTask = currentlyRunningExecutorTask {
_ = await existingTask.value
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is reentrancy a problem here?

@available(macOS 13, *)
enum _CurrentEventLoopKey { @TaskLocal static var id: UUID? }

/// This is an actor designed to execute provided tasks in the order then enter the actor.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
/// This is an actor designed to execute provided tasks in the order then enter the actor.
/// This is an actor designed to execute provided tasks in the order they enter the actor.

) -> UUID {
let id = UUID()
Task { @_AsyncEventLoopExecutor._IsolatingSerialEntryActor [delay, job, weak self] in
// ^----- Ensures FIFO entry from nonisolated contexts
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
// ^----- Ensures FIFO entry from nonisolated contexts
// ^----- Ensures first-in entry from nonisolated contexts

}
}

/// We use this actor to make serialized FIFO entry
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
/// We use this actor to make serialized FIFO entry
/// We use this actor to make serialized first-in entry


nonisolated func enqueue(_ job: @Sendable @escaping () -> Void) {
Task { @_AsyncEventLoopExecutor._IsolatingSerialEntryActor [job, weak self] in
// ^----- Ensures FIFO entry from nonisolated contexts
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
// ^----- Ensures FIFO entry from nonisolated contexts
// ^----- Ensures first-in entry from nonisolated contexts


nonisolated func cancelScheduledJob(withID id: UUID) {
Task { @_AsyncEventLoopExecutor._IsolatingSerialEntryActor [id, weak self] in
// ^----- Ensures FIFO entry from nonisolated contexts
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
// ^----- Ensures FIFO entry from nonisolated contexts
// ^----- Ensures first-in entry from nonisolated contexts

) -> UUID {
let id = UUID()
Task { @_AsyncEventLoopExecutor._IsolatingSerialEntryActor [job, weak self] in
// ^----- Ensures FIFO entry from nonisolated contexts
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
// ^----- Ensures FIFO entry from nonisolated contexts
// ^----- Ensures first-in entry from nonisolated contexts

@CrownedPhoenix
Copy link
Collaborator

Did this start as copy-pasta from a NIO implementation and then get pruned down to rip out unavailable dependencies?

/// enqueing themselves.
private func awaitPendingEntryOperations() async {
await Task { @_IsolatingSerialEntryActor [] in
// ^----- Ensures FIFO entry from nonisolated contexts
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
// ^----- Ensures FIFO entry from nonisolated contexts
// ^----- Ensures first-in entry from nonisolated contexts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants