Skip to content

Commit 4bf6ba8

Browse files
authored
Adopt span in Data IO implementation (#1576)
* Adopt span in Data IO implementation * Fix test failure * Fix Linux build failure * Fix Windows build * Fix FOUNDATION_FRAMEWORK build * Fix data reading for dataless files
1 parent de6ca57 commit 4bf6ba8

File tree

4 files changed

+76
-88
lines changed

4 files changed

+76
-88
lines changed

Sources/FoundationEssentials/Data/Data+Reading.swift

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -193,33 +193,33 @@ struct ReadBytesResult {
193193
}
194194

195195
#if os(Windows)
196+
@lifetime(pBuffer: copy pBuffer)
196197
private func read(from hFile: HANDLE, at path: PathOrURL,
197-
into pBuffer: UnsafeMutableRawPointer, length dwLength: Int,
198+
into pBuffer: inout OutputRawSpan,
198199
chunkSize dwChunk: Int = 4096, progress bProgress: Bool)
199-
throws -> Int {
200-
var pBuffer = pBuffer
201-
let progress = bProgress && Progress.current() != nil ? Progress(totalUnitCount: Int64(dwLength)) : nil
200+
throws {
201+
let progress = bProgress && Progress.current() != nil ? Progress(totalUnitCount: Int64(pBuffer.freeCapacity)) : nil
202202

203-
var dwBytesRemaining: DWORD = DWORD(dwLength)
204-
while dwBytesRemaining > 0 {
203+
while !pBuffer.isFull {
205204
if let progress, progress.isCancelled {
206205
throw CocoaError(.userCancelled)
207206
}
208207

209208
let dwBytesToRead: DWORD =
210-
DWORD(clamping: DWORD(min(DWORD(dwChunk), dwBytesRemaining)))
209+
DWORD(clamping: min(dwChunk, pBuffer.freeCapacity))
210+
211211
var dwBytesRead: DWORD = 0
212-
if !ReadFile(hFile, pBuffer, dwBytesToRead, &dwBytesRead, nil) {
213-
throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true)
212+
try pBuffer.withUnsafeMutableBytes { bytes, initializedCount in
213+
if !ReadFile(hFile, bytes.baseAddress!.advanced(by: initializedCount), dwBytesToRead, &dwBytesRead, nil) {
214+
throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true)
215+
}
216+
initializedCount += Int(dwBytesRead)
214217
}
215-
dwBytesRemaining -= DWORD(clamping: dwBytesRead)
216-
progress?.completedUnitCount = Int64(dwLength - Int(dwBytesRemaining))
218+
progress?.completedUnitCount += Int64(dwBytesRead)
217219
if dwBytesRead < dwBytesToRead {
218220
break
219221
}
220-
pBuffer = pBuffer.advanced(by: Int(dwBytesRead))
221222
}
222-
return dwLength - Int(dwBytesRemaining)
223223
}
224224
#endif
225225

@@ -283,18 +283,20 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
283283
}
284284
}))
285285
} else {
286-
guard let pBuffer: UnsafeMutableRawPointer = malloc(Int(szFileSize)) else {
286+
guard let ptr: UnsafeMutableRawPointer = malloc(Int(szFileSize)) else {
287287
throw CocoaError.errorWithFilePath(inPath, errno: ENOMEM, reading: true)
288288
}
289+
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: Int(szFileSize))
290+
var outputSpan = OutputRawSpan(buffer: buffer, initializedCount: 0)
289291

290-
localProgress?.becomeCurrent(withPendingUnitCount: Int64(szFileSize))
292+
localProgress?.becomeCurrent(withPendingUnitCount: Int64(outputSpan.freeCapacity))
291293
do {
292-
let dwLength = try read(from: hFile, at: inPath, into: pBuffer, length: Int(szFileSize), progress: reportProgress)
294+
try read(from: hFile, at: inPath, into: &outputSpan, progress: reportProgress)
293295
localProgress?.resignCurrent()
294-
return ReadBytesResult(bytes: pBuffer, length: dwLength, deallocator: .free)
296+
return ReadBytesResult(bytes: ptr, length: outputSpan.finalize(for: buffer), deallocator: .free)
295297
} catch {
296298
localProgress?.resignCurrent()
297-
free(pBuffer)
299+
free(ptr)
298300
throw error
299301
}
300302
}
@@ -367,18 +369,21 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
367369
#if os(Linux) || os(Android)
368370
// Linux has some files that may report a size of 0 but actually have contents
369371
let chunkSize = 1024 * 4
370-
var buffer = malloc(chunkSize)!
372+
var ptr = malloc(chunkSize)!
371373
var totalRead = 0
372374
while true {
373-
let length = try readBytesFromFileDescriptor(fd, path: inPath, buffer: buffer.advanced(by: totalRead), length: chunkSize, readUntilLength: false, reportProgress: false)
375+
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: totalRead + chunkSize)
376+
var outputSpan = OutputRawSpan(buffer: buffer, initializedCount: totalRead)
377+
try readBytesFromFileDescriptor(fd, path: inPath, buffer: &outputSpan, readUntilLength: false, reportProgress: false)
374378

379+
let length = outputSpan.finalize(for: buffer)
375380
totalRead += length
376381
if length != chunkSize {
377382
break
378383
}
379-
buffer = realloc(buffer, totalRead + chunkSize)
384+
ptr = realloc(ptr, totalRead + chunkSize)
380385
}
381-
result = ReadBytesResult(bytes: buffer, length: totalRead, deallocator: .free)
386+
result = ReadBytesResult(bytes: ptr, length: totalRead, deallocator: .free)
382387
#else
383388
result = ReadBytesResult(bytes: nil, length: 0, deallocator: nil)
384389
#endif
@@ -415,13 +420,15 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
415420
guard let bytes = malloc(Int(fileSize)) else {
416421
throw CocoaError.errorWithFilePath(inPath, errno: ENOMEM, reading: true)
417422
}
423+
let buffer = UnsafeMutableRawBufferPointer(start: bytes, count: Int(fileSize))
424+
var outputSpan = OutputRawSpan(buffer: buffer, initializedCount: 0)
418425

419426
localProgress?.becomeCurrent(withPendingUnitCount: Int64(fileSize))
420427
do {
421-
let length = try readBytesFromFileDescriptor(fd, path: inPath, buffer: bytes, length: fileSize, reportProgress: reportProgress)
428+
try readBytesFromFileDescriptor(fd, path: inPath, buffer: &outputSpan, reportProgress: reportProgress)
422429
localProgress?.resignCurrent()
423430

424-
result = ReadBytesResult(bytes: bytes, length: length, deallocator: .free)
431+
result = ReadBytesResult(bytes: bytes, length: outputSpan.finalize(for: buffer), deallocator: .free)
425432
} catch {
426433
localProgress?.resignCurrent()
427434
free(bytes)
@@ -440,25 +447,24 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
440447
/// Read data from a file descriptor.
441448
/// Takes an `Int` size and returns an `Int` to match `Data`'s count. If we are going to read more than Int.max, throws - because we won't be able to store it in `Data`.
442449
/// If `readUntilLength` is `false`, then we will end the read if we receive less than `length` bytes. This can be used to read from something like a socket, where the `length` simply represents the maximum size you can read at once.
443-
private func readBytesFromFileDescriptor(_ fd: Int32, path: PathOrURL, buffer inBuffer: UnsafeMutableRawPointer, length: Int, readUntilLength: Bool = true, reportProgress: Bool) throws -> Int {
444-
var buffer = inBuffer
450+
private func readBytesFromFileDescriptor(_ fd: Int32, path: PathOrURL, buffer inBuffer: inout OutputRawSpan, readUntilLength: Bool = true, reportProgress: Bool) throws {
445451
// If chunkSize (8-byte value) is more than blksize_t.max (4 byte value), then use the 4 byte max and chunk
446452

447453
let preferredChunkSize: size_t
448454
let localProgress: Progress?
455+
let length = inBuffer.freeCapacity
449456

450457
if Progress.current() != nil && reportProgress {
451-
localProgress = Progress(totalUnitCount: Int64(length))
458+
localProgress = Progress(totalUnitCount: Int64(inBuffer.freeCapacity))
452459
// To report progress, we have to try reading in smaller chunks than the whole file. Aim for about 1% increments.
453-
preferredChunkSize = max(length / 100, 1024 * 4)
460+
preferredChunkSize = max(inBuffer.freeCapacity / 100, 1024 * 4)
454461
} else {
455462
localProgress = nil
456463
// Get it all in one go, if possible
457-
preferredChunkSize = length
464+
preferredChunkSize = inBuffer.freeCapacity
458465
}
459466

460-
var numBytesRemaining = length
461-
while numBytesRemaining > 0 {
467+
while !inBuffer.isFull {
462468
if let localProgress, localProgress.isCancelled {
463469
throw CocoaError(.userCancelled)
464470
}
@@ -467,22 +473,27 @@ private func readBytesFromFileDescriptor(_ fd: Int32, path: PathOrURL, buffer in
467473
var numBytesRequested = CUnsignedInt(clamping: min(preferredChunkSize, Int(CInt.max)))
468474

469475
// Furthermore, don't request more than the number of bytes remaining
470-
if numBytesRequested > numBytesRemaining {
471-
numBytesRequested = CUnsignedInt(clamping: min(numBytesRemaining, Int(CInt.max)))
476+
if numBytesRequested > inBuffer.freeCapacity {
477+
numBytesRequested = CUnsignedInt(clamping: min(inBuffer.freeCapacity, Int(CInt.max)))
472478
}
473479

474-
var numBytesRead: CInt
480+
var numBytesRead: CInt = 0
475481
repeat {
476482
if let localProgress, localProgress.isCancelled {
477483
throw CocoaError(.userCancelled)
478484
}
479485

480486
// read takes an Int-sized argument, which will always be at least the size of Int32.
487+
inBuffer.withUnsafeMutableBytes { buffer, initializedCount in
481488
#if os(Windows)
482-
numBytesRead = _read(fd, buffer, numBytesRequested)
489+
numBytesRead = _read(fd, buffer.baseAddress!.advanced(by: initializedCount), numBytesRequested)
483490
#else
484-
numBytesRead = CInt(read(fd, buffer, Int(numBytesRequested)))
491+
numBytesRead = CInt(read(fd, buffer.baseAddress!.advanced(by: initializedCount), Int(numBytesRequested)))
485492
#endif
493+
if numBytesRead >= 0 {
494+
initializedCount += Int(clamping: numBytesRead)
495+
}
496+
}
486497
} while numBytesRead < 0 && errno == EINTR
487498

488499
if numBytesRead < 0 {
@@ -495,23 +506,14 @@ private func readBytesFromFileDescriptor(_ fd: Int32, path: PathOrURL, buffer in
495506
break
496507
} else {
497508
// Partial read
498-
numBytesRemaining -= Int(clamping: numBytesRead)
499-
if numBytesRemaining < 0 {
500-
// Just in case; we do not want to have a negative amount of bytes remaining. We will just assume that is the end.
501-
numBytesRemaining = 0
502-
}
503-
localProgress?.completedUnitCount = Int64(length - numBytesRemaining)
509+
localProgress?.completedUnitCount = Int64(length - inBuffer.freeCapacity)
504510

505511
// The `readUntilLength` argument controls if we should end early when `read` returns less than the amount requested, or if we should continue to loop until we have reached `length` bytes.
506512
if !readUntilLength && numBytesRead < numBytesRequested {
507513
break
508514
}
509-
510-
buffer = buffer.advanced(by: numericCast(numBytesRead))
511515
}
512516
}
513-
514-
return length - numBytesRemaining
515517
}
516518

517519
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)

0 commit comments

Comments
 (0)