- 
                Notifications
    
You must be signed in to change notification settings  - Fork 45
 
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
  
    | 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   | 
    
| 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.
| 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.Bufferand 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.BuffertoStringbecause converting each buffer individually might not always work due to potential breaks in the String.AsyncBufferSequence.Buffer.bytesby basing it onDispatchData.Sliceinstead ofDispatchData. This change ensures thatAsyncBufferSequence.Bufferholds a contiguous chunk of bytes, which is necessary for the.bytesAPI.run().