Skip to content

Refactoring URLSession for multiple native protocols #1216

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

Closed
wants to merge 11 commits into from
Closed
48 changes: 29 additions & 19 deletions Foundation.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Foundation/URLSession/HTTPBodySource.swift - URLSession & libcurl
// Foundation/URLSession/BodySource.swift - URLSession & libcurl
//
// This source file is part of the Swift.org open source project
//
Expand All @@ -20,35 +20,35 @@ import CoreFoundation
import Dispatch


/// Turn `NSData` into `dispatch_data_t`
/// Turn `Data` into `DispatchData`
internal func createDispatchData(_ data: Data) -> DispatchData {
//TODO: Avoid copying data
let buffer = UnsafeRawBufferPointer(start: data._backing.bytes,
count: data.count)
return DispatchData(bytes: buffer)
}

/// Copy data from `dispatch_data_t` into memory pointed to by an `UnsafeMutableBufferPointer`.
/// Copy data from `DispatchData` into memory pointed to by an `UnsafeMutableBufferPointer`.
internal func copyDispatchData<T>(_ data: DispatchData, infoBuffer buffer: UnsafeMutableBufferPointer<T>) {
precondition(data.count <= (buffer.count * MemoryLayout<T>.size))
_ = data.copyBytes(to: buffer)
}

/// Split `dispatch_data_t` into `(head, tail)` pair.
/// Split `DispatchData` into `(head, tail)` pair.
internal func splitData(dispatchData data: DispatchData, atPosition position: Int) -> (DispatchData,DispatchData) {
return (data.subdata(in: 0..<position), data.subdata(in: position..<data.count))
}

/// A (non-blocking) source for HTTP body data.
internal protocol _HTTPBodySource: class {
/// A (non-blocking) source for body data.
internal protocol _BodySource: class {
/// Get the next chunck of data.
///
/// - Returns: `.data` until the source is exhausted, at which point it will
/// return `.done`. Since this is non-blocking, it will return `.retryLater`
/// if no data is available at this point, but will be available later.
func getNextChunk(withLength length: Int) -> _HTTPBodySourceDataChunk
func getNextChunk(withLength length: Int) -> _BodySourceDataChunk
}
internal enum _HTTPBodySourceDataChunk {
internal enum _BodySourceDataChunk {
case data(DispatchData)
/// The source is depleted.
case done
Expand All @@ -57,26 +57,26 @@ internal enum _HTTPBodySourceDataChunk {
case error
}

/// A HTTP body data source backed by `dispatch_data_t`.
internal final class _HTTPBodyDataSource {
var data: DispatchData!
/// A body data source backed by `DispatchData`.
internal final class _BodyDataSource {
var data: DispatchData!
init(data: DispatchData) {
self.data = data
}
}

extension _HTTPBodyDataSource : _HTTPBodySource {
extension _BodyDataSource : _BodySource {
enum _Error : Error {
case unableToRewindData
}

func getNextChunk(withLength length: Int) -> _HTTPBodySourceDataChunk {
func getNextChunk(withLength length: Int) -> _BodySourceDataChunk {
let remaining = data.count
if remaining == 0 {
return .done
} else if remaining <= length {
let r: DispatchData! = data
data = DispatchData.empty
data = DispatchData.empty
return .data(r)
} else {
let (chunk, remainder) = splitData(dispatchData: data, atPosition: length)
Expand All @@ -87,21 +87,21 @@ extension _HTTPBodyDataSource : _HTTPBodySource {
}


/// A HTTP body data source backed by a file.
/// A body data source backed by a file.
///
/// This allows non-blocking streaming of file data to the remote server.
///
/// The source reads data using a `dispatch_io_t` channel, and hence reading
/// The source reads data using a `DispatchIO` channel, and hence reading
/// file data is non-blocking. It has a local buffer that it fills as calls
/// to `getNextChunk(withLength:)` drain it.
///
/// - Note: Calls to `getNextChunk(withLength:)` and callbacks from libdispatch
/// should all happen on the same (serial) queue, and hence this code doesn't
/// have to be thread safe.
internal final class _HTTPBodyFileSource {
internal final class _BodyFileSource {
fileprivate let fileURL: URL
fileprivate let channel: DispatchIO
fileprivate let workQueue: DispatchQueue
fileprivate let channel: DispatchIO
fileprivate let workQueue: DispatchQueue
fileprivate let dataAvailableHandler: () -> Void
fileprivate var hasActiveReadHandler = false
fileprivate var availableChunk: _Chunk = .empty
Expand Down Expand Up @@ -146,7 +146,7 @@ internal final class _HTTPBodyFileSource {
}
}

fileprivate extension _HTTPBodyFileSource {
fileprivate extension _BodyFileSource {
fileprivate var desiredBufferLength: Int { return 3 * CFURLSessionMaxWriteSize }
/// Enqueue a dispatch I/O read to fill the buffer.
///
Expand All @@ -158,7 +158,7 @@ fileprivate extension _HTTPBodyFileSource {
guard availableByteCount < desiredBufferLength else { return }
guard !hasActiveReadHandler else { return } // We're already reading
hasActiveReadHandler = true

let lengthToRead = desiredBufferLength - availableByteCount
channel.read(offset: 0, length: lengthToRead, queue: workQueue) { (done: Bool, data: DispatchData?, errno: Int32) in
let wasEmpty = self.availableByteCount == 0
Expand All @@ -176,7 +176,7 @@ fileprivate extension _HTTPBodyFileSource {
default:
fatalError("Invalid arguments to read(3) callback.")
}

if wasEmpty && (0 < self.availableByteCount) {
self.dataAvailableHandler()
}
Expand Down Expand Up @@ -208,8 +208,8 @@ fileprivate extension _HTTPBodyFileSource {
}
}

extension _HTTPBodyFileSource : _HTTPBodySource {
func getNextChunk(withLength length: Int) -> _HTTPBodySourceDataChunk {
extension _BodyFileSource : _BodySource {
func getNextChunk(withLength length: Int) -> _BodySourceDataChunk {
switch availableChunk {
case .empty:
readNextChunk()
Expand All @@ -222,7 +222,7 @@ extension _HTTPBodyFileSource : _HTTPBodySource {

availableChunk = tail.isEmpty ? .empty : .data(tail)
readNextChunk()

if head.isEmpty {
return .retryLater
} else {
Expand Down
Loading