-
Notifications
You must be signed in to change notification settings - Fork 23
AsyncBufferSequence.Buffer Improvements #48
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
AsyncBufferSequence.Buffer Improvements #48
Conversation
36e0e21
to
f261182
Compare
f261182
to
75b804c
Compare
@@ -267,7 +267,7 @@ public func run<Result, Error: OutputProtocol>( | |||
environment: Environment = .inherit, | |||
workingDirectory: FilePath? = nil, | |||
platformOptions: PlatformOptions = PlatformOptions(), | |||
error: Error, | |||
error: Error = .discarded, |
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.
Is this part of the same change?
Sources/Subprocess/Buffer.swift
Outdated
let span = RawSpan(_unsafeElements: ptr) | ||
return _overrideLifetime(of: span, to: self) | ||
case .array(let array): | ||
let span = array.span.bytes |
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.
Swift 6.2 on Windows: error: value of type '[UInt8]' has no member 'span'
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.
Hmm that's strange... should be available on 6.2
. Let me check
In stead of directly expose |
public func run<Result, Input: InputProtocol, Error: OutputProtocol>( | ||
_ executable: Executable, | ||
arguments: Arguments = [], | ||
environment: Environment = .inherit, | ||
workingDirectory: FilePath? = nil, | ||
platformOptions: PlatformOptions = PlatformOptions(), | ||
input: Input = .none, | ||
error: Error, | ||
error: Error = .discarded, |
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 accidentally left out from the ~Copyable
PR. I discovered it as I was testing closure based runs
return data | ||
let createdBuffers = Buffer.createFrom(data) | ||
// Most (all?) cases there should be only one buffer | ||
// because DispatchData are motsly contiguous |
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.
// because DispatchData are motsly contiguous | |
// because DispatchData are mostly contiguous |
internal var startIndex: Int { self.bytes.startIndex } | ||
internal var endIndex: Int { self.bytes.endIndex } | ||
|
||
internal init(bytes: UnsafeBufferPointer<UInt8>) { |
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.
Is this missing a startIndex argument? without that,
internal var startIndex: Int { self.bytes.startIndex }
This will always return 0 since UnsafeBufferPointer.startIndex
is always 0.
And without that it would not match the semantics of the other Slice-like types, where the index of the containing collection is preserved in the subsequence (like ArraySlice
and Array
) right?
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.
Here we are intentionally keeping a 1 to 1 Buffer
to Slice
mapping. We actually don't really care the relative position of the Slices within the source DispatchData
, as long as the slices are contiguous. This is the same conceptual design with the original Buffer
to DispatchData
mapping (as in we only care about individual chunk read). We only switched to slice instead of DispatchData
because DispatchData
is not contiguous and we need a contiguous storage in order to vend the bytes
property.
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.
Bikeshed: As we talked previously, since this is not really representing a sub-sequence like a Substring
or ArraySlice
, can we not call it Slice
? It sounds like we're really interested in ensuring that it's contiguous, so perhaps ContiguousRegion
or something like that?
…rom AsyncBufferSequence.Buffer
…closure based run()
be5121b
to
8fa05f4
Compare
8fa05f4
to
00bcd99
Compare
|
||
public func lines<Encoding: _UnicodeEncoding>( | ||
encoding: Encoding.Type = UTF8.self, | ||
bufferingPolicy: LineSequence<Encoding>.BufferingPolicy = .unbounded |
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.
Maybe we should label all 'new' API since 0.0.1 (including the ones I worked on) with some kind of comment so we know which ones we need to have further discussion about in review. Here I would like to talk about the tradeoff when defaulting to an unbounded buffer, with no arguments...
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.
Currently I track them by tagging the PRs with API Change
label. I'll add some comments and when we are ready for 0.0.1
I'll list out all the changes.
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 chose .unbounded
as the default argument because that's what AsyncStream
do today.
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 we decide to throw an error for #45
can we please have an issue tracking updating this to the same behavior?
Addressed Tony's comments. Most noticeably: throw an error when |
Resolves #15 |
b192b17
to
ddeb6d0
Compare
if self.buffer.isEmpty { | ||
return nil | ||
} | ||
return String(decoding: self.buffer[0 ..< endIndex], as: Encoding.self) |
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'm still a little unclear on why we need to copy this into an Array, and then copy it into a String. Why can't String read from the buffer data directly?
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 have to keep an [UInt8]
buffer in order to scan through and find new line characters. We can't use String
directly as the buffer type because every time when we receive a chunk of data, we can't directly convert it to String since the chunk might end in between byte encodings.
@@ -61,7 +59,15 @@ public struct AsyncBufferSequence: AsyncSequence, Sendable { | |||
#endif | |||
return nil | |||
} | |||
return data | |||
let createdBuffers = Buffer.createFrom(data) |
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.
Style nitpick, but this is just init
.
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.
Or, in this case it sort of goes the other way since it vends buffers from data... I guess we can clean it up later.
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.
And also it creates multiple. I didn't use initializer because it's DispatchData -> [Buffer]
.
internal var startIndex: Int { self.bytes.startIndex } | ||
internal var endIndex: Int { self.bytes.endIndex } | ||
|
||
internal init(bytes: UnsafeBufferPointer<UInt8>) { |
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.
Bikeshed: As we talked previously, since this is not really representing a sub-sequence like a Substring
or ArraySlice
, can we not call it Slice
? It sounds like we're really interested in ensuring that it's contiguous, so perhaps ContiguousRegion
or something like that?
Sources/Subprocess/Buffer.swift
Outdated
#if canImport(Darwin) || canImport(Glibc) || canImport(Android) || canImport(Musl) | ||
extension DispatchData { | ||
/// Unfortunately `DispatchData.Region` is not available on Linux, hence our own wrapper | ||
internal struct Slice: @unchecked Sendable, RandomAccessCollection { |
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 is this @unchecked Sendable
?
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.
Because it holds a let bytes: UnsafeBufferPointer<UInt8>
property. DispatchData.Region
's backing storage is also @unchecked Sendable
, I suspect for the same reason.
How about |
…turning partial (and potentially incorrect) data
ddeb6d0
to
195fcee
Compare
This PR enhances the usability of
AsyncBufferSequence.Buffer
and the closure-basedrun()
in several ways:AsyncBufferSequence.LineSequence
, which lets developers stream standard output and error line by line asString
. This is the recommended approach for convertingAsyncBufferSequence.Buffer
toString
because converting each buffer individually might not always work due to potential breaks in the String.AsyncBufferSequence.Buffer.bytes
by basing it onDispatchData.Slice
instead ofDispatchData
. This change ensures thatAsyncBufferSequence.Buffer
holds a contiguous chunk of bytes, which is necessary for the.bytes
API.run()
.