Skip to content

Improvement/attachments and requests #147

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 14 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions Backtrace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2692,7 +2692,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -2771,7 +2771,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace;
Expand Down Expand Up @@ -2848,7 +2848,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -2916,7 +2916,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-tvOSTests";
Expand Down Expand Up @@ -3003,7 +3003,7 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -3086,7 +3086,7 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace;
Expand Down Expand Up @@ -3166,7 +3166,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -3239,7 +3239,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-macOSTests";
Expand Down Expand Up @@ -3315,7 +3315,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -3389,7 +3389,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.tvos.example;
Expand All @@ -3413,7 +3413,7 @@
buildSettings = {
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MARKETING_VERSION = 2.0.7;
STRIP_STYLE = debugging;
STRIP_SWIFT_SYMBOLS = NO;
Expand All @@ -3427,7 +3427,7 @@
buildSettings = {
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MARKETING_VERSION = 2.0.7;
STRIP_STYLE = debugging;
STRIP_SWIFT_SYMBOLS = NO;
Expand Down Expand Up @@ -3507,7 +3507,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -3594,7 +3594,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace;
Expand Down Expand Up @@ -3679,7 +3679,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -3755,7 +3755,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-iOSTests";
Expand Down Expand Up @@ -3837,7 +3837,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -3915,7 +3915,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.ios.example;
Expand Down Expand Up @@ -3975,7 +3975,7 @@
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = JWKXD469L2;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
Expand All @@ -3999,7 +3999,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -4053,7 +4053,7 @@
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = JWKXD469L2;
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
Expand All @@ -4071,7 +4071,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.objc.ios.examples;
Expand Down Expand Up @@ -4150,7 +4150,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -4221,7 +4221,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 11.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.macos.example;
Expand Down
1 change: 0 additions & 1 deletion Examples/Example-iOS-ObjC/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,4 @@ - (IBAction) crashAction: (id) sender {
array[1];
}


@end
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let package = Package(
name: "Backtrace",
platforms: [
.iOS(.v12),
.macOS(.v10_13),
.macOS(.v11),
Copy link
Collaborator

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?

Copy link
Collaborator Author

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)

Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted

.tvOS(.v12)
],
products: [
Expand Down
4 changes: 2 additions & 2 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PODS:
- Backtrace (2.0.6):
- Backtrace (2.0.7):
- PLCrashReporter (= 1.11.1)
- Nimble (10.0.0)
- PLCrashReporter (1.11.1)
Expand All @@ -22,7 +22,7 @@ EXTERNAL SOURCES:
:path: "./Backtrace.podspec"

SPEC CHECKSUMS:
Backtrace: 72bea7c55570b42ab18dd7218ce41ceef3edca14
Backtrace: a686b3bfb9a7a176080e259f7f8dd7a5f521d957
Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84
PLCrashReporter: 5d2d3967afe0efad61b3048d617e2199a5d1b787
Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179
Expand Down
64 changes: 41 additions & 23 deletions Sources/Features/Network/MultipartRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,41 +48,59 @@ extension MultipartRequest {
var multipartRequest = urlRequest
let boundary = generateBoundaryString()
multipartRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

let boundaryPrefix = "--\(boundary)\r\n"
let body = NSMutableData()

// temporary file to stream data
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
Copy link
Collaborator

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?

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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)

FileManager.default.createFile(atPath: tempURL.path, contents: nil, attributes: nil)
let fileHandle = try FileHandle(forWritingTo: tempURL)

defer { fileHandle.closeFile() }

// attributes
for attribute in report.attributes {
body.appendString(boundaryPrefix)
body.appendString("Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n")
body.appendString("\(attribute.value)\r\n")
writeToFile(fileHandle, "--\(boundary)\r\n")
writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n")
writeToFile(fileHandle, "\(attribute.value)\r\n")
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Copy link
Collaborator

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

}
// report file
body.appendString(boundaryPrefix)
body.appendString("Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n")
body.appendString("Content-Type: application/octet-stream\r\n\r\n")
body.append(report.reportData)
body.appendString("\r\n")


// report
writeToFile(fileHandle, "--\(boundary)\r\n")
writeToFile(fileHandle, "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n")
writeToFile(fileHandle, "Content-Type: application/octet-stream\r\n\r\n")
fileHandle.write(report.reportData)
writeToFile(fileHandle, "\r\n")

// attachments
for attachment in Set(report.attachmentPaths).compactMap(Attachment.init(filePath:)) {
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)
Copy link
Collaborator

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?

Copy link
Collaborator Author

@melekr melekr Jan 10, 2025

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

Copy link
Collaborator Author

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

body.appendString("\r\n")
writeToFile(fileHandle, "--\(boundary)\r\n")
writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n")
writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n")
fileHandle.write(attachment.data)
writeToFile(fileHandle, "\r\n")
}
body.appendString("\(boundaryPrefix)--")
multipartRequest.httpBody = body as Data

multipartRequest.setValue("\(body.length)", forHTTPHeaderField: "Content-Length")


// Final boundary
writeToFile(fileHandle, "--\(boundary)--\r\n")

// Set Content-Length
let fileSize = try FileManager.default.attributesOfItem(atPath: tempURL.path)[.size] as? Int ?? 0
multipartRequest.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length")

// Attach file stream to HTTP body
multipartRequest.httpBodyStream = InputStream(url: tempURL)

return multipartRequest
}

private static func generateBoundaryString() -> String {
return "Boundary-\(NSUUID().uuidString)"
}

private static func writeToFile(_ fileHandle: FileHandle, _ string: String) {
if let data = string.data(using: .utf8) {
fileHandle.write(data)
}
}
}

private extension NSMutableData {
Expand Down
Loading