-
-
Notifications
You must be signed in to change notification settings - Fork 12
Create AsyncQueue #2
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
Conversation
02d3b9d
to
16e00d3
Compare
Sources/AsyncQueue/AsyncQueue.swift
Outdated
// MARK: Private | ||
|
||
private let streamTask: Task<Void, Never> | ||
private let taskStreamContinuation: AsyncStream<@Sendable () async -> Void>.Continuation! |
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.
Not ideal that we're force-unwrapping here. I originally made this type optional, but figured we really do want to fail LOUDLY if somehow we don't have a taskStreamContinuation
and are attempting to append tasks to the queue.
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.
Can we do the failing at the init method so we can initialize this with a non-optional?
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.
We can, but that would force every call-site to force unwrap, which seems pretty gnarly. Since taskStreamContinuation
should exist (and it not existing would mean that the world is very broken), I think it's reasonable to crash if it somehow does not exist.
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.
Oh, I meant failing in a very general case. Crashing is fine for something like this where the logic means we would never expect a nil
value.
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.
The point was to remove the exclamation mark from here (together with the changes in init
proposed).
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.
How's this look? 6e0519c
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.
This is silly, but I'm going to use a force-unwrap and a comment instead of a fatalError
. I want 100% coverage 😅
Codecov Report
@@ Coverage Diff @@
## main #2 +/- ##
=========================================
+ Coverage 0 97.50% +97.50%
=========================================
Files 0 1 +1
Lines 0 40 +40
=========================================
+ Hits 0 39 +39
- Misses 0 1 +1
|
@@ -174,6 +174,9 @@ for rawPlatform in rawPlatforms { | |||
if platform.shouldTest { | |||
xcodeBuildArguments.append("test") | |||
} | |||
xcodeBuildArguments.append("-test-iterations") | |||
xcodeBuildArguments.append("100") | |||
xcodeBuildArguments.append("-run-tests-until-failure") |
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.
This flag stops running tests once they fail. It respects the iterations limit above.
} | ||
} | ||
|
||
func test_await_canReturn() async { |
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.
This test and test_await_canThrow
below are a little silly but I found them helpful for ensuring that the capability exists as I iterated on the queue's API.
@@ -26,8 +26,149 @@ import XCTest | |||
|
|||
final class AsyncQueueTests: XCTestCase { |
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.
As you read through the below tests: please be thinking of cases I haven't thought of!
|
||
A queue that enables ordered sending of events from synchronous to asynchronous code. | ||
|
||
## Usage |
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.
This is pretty freaking bare-bones but gotta start somewhere.
Co-authored-by: Óscar Morales Vivó <38818781+Gabardone@users.noreply.github.com>
Co-authored-by: Óscar Morales Vivó <38818781+Gabardone@users.noreply.github.com>
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.
See comments but overall looks like a nice thing to have and hopefully we don't have to complicate it much beyond this (at least until such time as Swift or its libraries adds better support for these use cases).
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.
See last comment but it's good to merge either way.
Co-authored-by: Óscar Morales Vivó <38818781+Gabardone@users.noreply.github.com>
07e704a
to
0eb5698
Compare
0eb5698
to
c6f0b50
Compare
c6f0b50
to
02cf4e6
Compare
a458835
to
ec8838a
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.
I think we ought to merge this already and then keep working on the other PR?
Yup. Was giving more folk a chance to weigh in, but this repo is very beta (first published version will be Folk who come across this PR: please feel free to leave a post-merge review. I will respond, and will happily open up new PRs to address feedback. Given how early we are in the versioning, post-merge naming feedback is still appreciated! |
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.
Nice!
@@ -159,7 +159,8 @@ for rawPlatform in rawPlatforms { | |||
"-scheme", "swift-async-queue", | |||
"-sdk", platform.sdk, | |||
"-derivedDataPath", platform.derivedDataPath, | |||
"-PBXBuildsContinueAfterErrors=0" | |||
"-PBXBuildsContinueAfterErrors=0", | |||
"OTHER_SWIFT_FLAGS=-warnings-as-errors", |
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.
👍
@@ -1,5 +1,31 @@ | |||
# swift-async-queue | |||
A queue that enables ordered sending of events from synchronous to asynchronous code | |||
[](https://github.com/apple/swift-package-manager) |
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.
@dfed we seem to have an issue with some of the later badges. The screenshot is from the main page of the repo.
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.
Yup. I need to publish the pod before those badges will work. I'll remove them for now: 51e98a3
// Continuation will be captured during stream creation, so it is safe to force unwrap here. | ||
// If this force-unwrap fails, something is fundamentally broken in the Swift runtime. |
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.
👌
// If this force-unwrap fails, something is fundamentally broken in the Swift runtime. | ||
taskStreamContinuation = capturedTaskStreamContinuation! | ||
|
||
streamTask = Task.detached(priority: priority) { |
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.
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.
Exactly!
// MARK: Public | ||
|
||
/// Schedules an asynchronous task for execution and immediately returns. | ||
/// The schedueled task will not execute until all prior tasks have completed. |
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.
"schedueled" -> "scheduled"
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.
Same comment for other documentation comments in this file.
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.
Good catch! 2bd88a1
await systemUnderTest.await { /* Drain the queue */ } | ||
} | ||
|
||
func test_async_retainsReceiverUntilFlushed() async { |
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.
What is the receiver in this context?
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.
The receiver is the systemUnderTest
, i.e. the async queue.
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.
Ah got it. I understand now 👍
await waitForExpectations(timeout: 1.0) | ||
} | ||
|
||
func test_async_doesNotRetainTaskAfterExecution() async { |
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.
Nice test!
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.
This was a fun one to write 😄
guard iteration % 25 == 0 else { | ||
// Keep sending async events to the queue. | ||
continue | ||
} |
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 don't follow why this code exists in the test. Can you help me understand?
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 wanted some way to interleave multiple async
events with each await
event that was deterministic. We could await
after every async
, but because the await
method requires waiting for the work to finish I felt like the test wouldn't necessarily catch an issue where await
wouldn't wait for every enqueued event but would just wait for the first enqueued event...
Short version: this guard
could be removed and it'd probably be fine. But I kinda like emitting multiple async
events for every await
check.
private actor Counter { | ||
func incrementAndExpectCount(equals expectedCount: Int) { | ||
increment() | ||
XCTAssertEqual(expectedCount, count) |
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.
Nit: would be nice to pass through the file and line number so any XCTest failure is easier to debug.
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.
This PR introduces the
AsyncQueue
class, which makes it possible to send ordered work to an asynchronous context from a synchronous or non-isolated context.Any and all API feedback is welcome! Note that the name of the queue changes in #3, which builds upon this work.