-
Notifications
You must be signed in to change notification settings - Fork 18
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
Improvement/attachments and requests #147
base: master
Are you sure you want to change the base?
Conversation
- Replace NSMutableData operation with streaming Data to a temporary file - Use httpBodyStream to stream file content directly into the request body - Use FileHandle to manage file writing - Calculate file size after writing to ensure accurate HTTP header - defer file handling
- Migrate to modern UniformTypeIdentifiers API, keep legacy support - Cleanup
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 pull request description doesn't tell me why exactly we need it. Can you please share more thoughts on this?
Package.swift
Outdated
@@ -6,7 +6,7 @@ let package = Package( | |||
name: "Backtrace", | |||
platforms: [ | |||
.iOS(.v12), | |||
.macOS(.v10_13), | |||
.macOS(.v11), |
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 do we want to bump the macos target?
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.
macos 11 is way past its end of life, this was a perfect opportunity to let it go.
Next bump should be :
.iOS(.v13),
.macOS(.v12),
.tvOS(.v13)
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.
should it this be a part of this pull request? Later if we will try to find where have we changed the macos version, it would be hard to understand why we did that here
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.
reverted
@@ -50,8 +50,13 @@ extension BacktraceCrashReporter: CrashReporting { | |||
func generateLiveReport(exception: NSException? = nil, | |||
attributes: Attributes, | |||
attachmentPaths: [String] = []) throws -> BacktraceReport { | |||
let reportData: 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.
why - generateLiveReport allows to pass nullable exception - why it matters?
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.
for testing, will remove.
let body = NSMutableData() | ||
|
||
// temporary file to stream data | ||
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) |
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 happen to the file if the app is restarted while file is being created?
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.
file is created in Temp directory managed by OS, if the app is restarted there is no guarantee the file is persisted but :
- OOM : MultipartRequest will be retried/reconstructed from the already generated and persisted live report.
- BacktraceReport/Resource : MultipartRequest will be retried/reconstructed from repository
- Watcher: MultipartRequest will be retried/reconstructed from repository
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.
unlike NSMutableData it's even possible to recover multipart request file! (not sure if necessary but can be a good excuse to consider for // TODO: T16698 - Add retry logic
)
writeToFile(fileHandle, "--\(boundary)\r\n") | ||
writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n") | ||
writeToFile(fileHandle, "\(attribute.value)\r\n") |
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.
shouldn't we do one write operation instead of multiple? I know opening writing and closing cost resources and time
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.
My intention was multiple small write operations with batched writes to reduce system overhead, i'll look into it.
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 i think it would be faster if we create a string with attributes in memory and then do one write operation. There is no chance we will oom the app via string with attributes so we should be good to go
body.appendString(boundaryPrefix) | ||
body.appendString("Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") | ||
body.appendString("Content-Type: \(attachment.mimeType)\r\n\r\n") | ||
body.append(attachment.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.
can we stream data instead of reading it to memory?
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 use a Stream.
Benefits:
- no longer holds attachment or report data in memory before writing since data is written directly to file handle as it's being read
- Data is never loaded entirely into memory, great for multiple and/or large attachments
Caviats:
- Memory and buffer management
- Error handling (incomplete streams, stream cannot be opened
- Small buffer chunks
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.
comment is on original not the updated code
Holding data in memory for attributes or attachments changes the purpose of writing into files. |
case .fileCreationFailed(let url): | ||
return "File Error occurred: \(url)." | ||
case .fileWriteFailed: | ||
return "File Write Error occurred." |
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.
Suggestion:
I think file is to generic and might be weird from the developer perspective that the file means a temporary file during HTTP submission. My recommendation is to make it more explicit by saying "Temporary submission file" instead.
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.
yes, it looks out of place
also need to properly handle an 'attachmentError'.
do { | ||
let fileCreated = FileManager.default.createFile(atPath: tempURL.path, contents: nil, attributes: nil) | ||
if !fileCreated { | ||
throw HttpError.fileCreationFailed(tempURL) |
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.
it won't be captured by the L:111 - which means we will return a generic error rather this this one?
try writeToFile(fileHandle, attributesString) | ||
|
||
// report | ||
var reportString = "--\(boundary)\r\n" |
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.
It would be better if we start with report rather than with attributes - if the application stops during submission part, we can still submit a valid report.
try writeToFile(fileHandle, "--\(boundary)\r\n") | ||
try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") | ||
try writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n") |
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 should do it with a single write
try writeToFile(fileHandle, "--\(boundary)\r\n") | ||
try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") | ||
try writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n") | ||
fileHandle.write(attachment.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.
you're making an attachment copy in the temporary file. WE cannot stream a file directly from the file available somewhere?
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.
possible but requires creating an InputStream for each attachments.
If we decide to keep files, i'll make the necessary improvements.
|
case .malformedUrl(let url): return "Provided URL cannot be parsed: \(url)." | ||
case .unknownError: return "Unknown error occurred." | ||
case .malformedUrl(let url): | ||
return "Provided URL cannot be parsed: \(url)." |
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.
small nit: can we either get rid of trailing dots or put the URL in quotes?
this goes for every description that has some data displayed in the string
multipartRequest.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length") | ||
|
||
// Attach file stream to HTTP body | ||
multipartRequest.httpBodyStream = InputStream(url: tempURL) |
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.
Instead of creating the file and setting it as httpBodyStream
, couldn't you write directly to the request?
I'm not proficient in Swift, but I found this:
https://developer.apple.com/documentation/foundation/url_loading_system/uploading_streams_of_data
You can use bound streams to achieve that, it seems.
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, this is the the approach I was referencing:
Holding data in memory for attributes or attachments changes the purpose of writing into files. A hybrid approach would be buffering data in chunks, using a buffered stream.
try writeToStream(outputStream, reportString, writeLock: writeLock) | ||
try writeLock.sync { | ||
let data = report.reportData | ||
let bytesWritten = data.withUnsafeBytes { |
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 do we need to execute this part of the code in the sync context? You won't submit two identical reports.
|
||
// Data from Output Stream | ||
guard let data = outputStream.property(forKey: .dataWrittenToMemoryStreamKey) as? Data else { |
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.
do we read data from the output stream here?
This PR refactors the MultipartRequest and Attachments implementation to transition from in-memory multipart body construction to disk-based streaming using a temporary file and improve filenameExtension detection.
Why?
Changes
ref: BT-5311