Skip to content

Commit cdd295f

Browse files
author
Sebastian Roth
authored
Resolve various iOS crashes relating to bad access from multiple threads (#129)
1 parent 9dd202a commit cdd295f

File tree

8 files changed

+90
-7
lines changed

8 files changed

+90
-7
lines changed

example/integration_test/flutter_uploader_test.dart

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ void main() {
6565
expect(res.status, UploadTaskStatus.complete);
6666
});
6767

68+
testWidgets('multiple uploads stresstest', (WidgetTester tester) async {
69+
final taskIds = <String>[];
70+
for (var i = 0; i < 10; i++) {
71+
taskIds.add(await uploader.enqueue(
72+
MultipartFormDataUpload(url: url.toString(), files: [
73+
FileItem(path: await _tmpFile(), field: 'file'),
74+
]),
75+
));
76+
}
77+
78+
final res = await Future.wait(
79+
taskIds.map(
80+
(taskId) => uploader.result.firstWhere(isCompleted(taskId)),
81+
),
82+
);
83+
84+
for (var i = 0; i < res.length; i++) {
85+
expect(res[i].taskId, taskIds[i]);
86+
}
87+
});
88+
6889
testWidgets('can submit custom data', (tester) async {
6990
var fileItem = FileItem(path: await _tmpFile(), field: 'file');
7091

@@ -85,7 +106,6 @@ void main() {
85106

86107
final res = await uploader.result.firstWhere(isCompleted(taskId));
87108
final json = jsonDecode(res.response);
88-
print(json);
89109

90110
expect(json['request']['fields']['simpleKey'], 'simpleValue');
91111
expect(jsonDecode(json['request']['fields']['listOf']),
@@ -192,6 +212,25 @@ void main() {
192212
expect(res.status, UploadTaskStatus.complete);
193213
});
194214

215+
testWidgets('multiple uploads stresstest', (WidgetTester tester) async {
216+
final taskIds = <String>[];
217+
for (var i = 0; i < 10; i++) {
218+
taskIds.add(await uploader.enqueue(
219+
RawUpload(url: url.toString(), path: await _tmpFile()),
220+
));
221+
}
222+
223+
final res = await Future.wait(
224+
taskIds.map(
225+
(taskId) => uploader.result.firstWhere(isCompleted(taskId)),
226+
),
227+
);
228+
229+
for (var i = 0; i < res.length; i++) {
230+
expect(res[i].taskId, taskIds[i]);
231+
}
232+
});
233+
195234
testWidgets("can overwrite 'Accept' header", (WidgetTester tester) async {
196235
final taskId = await uploader.enqueue(RawUpload(
197236
url: url.toString(),

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,11 @@
293293
"${PODS_ROOT}/../Flutter/Flutter.framework",
294294
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
295295
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
296-
"${BUILT_PRODUCTS_DIR}/e2e/e2e.framework",
297296
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
298297
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
299298
"${BUILT_PRODUCTS_DIR}/flutter_uploader/flutter_uploader.framework",
300299
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
300+
"${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework",
301301
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
302302
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework",
303303
);
@@ -309,11 +309,11 @@
309309
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
310310
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
311311
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
312-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/e2e.framework",
313312
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
314313
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
315314
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_uploader.framework",
316315
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
316+
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework",
317317
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
318318
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework",
319319
);

example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
</BuildableProductRunnable>
6262
</LaunchAction>
6363
<ProfileAction
64-
buildConfiguration = "Profile"
64+
buildConfiguration = "Debug"
6565
shouldUseLaunchSchemeArgsEnv = "YES"
6666
savedToolIdentifier = ""
6767
useCustomWorkingDirectory = "NO"

example/ios/Runner/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<key>FUMaximumConnectionsPerHost</key>
3535
<integer>5</integer>
3636
<key>FUMaximumUploadOperation</key>
37-
<integer>1</integer>
37+
<integer>3</integer>
3838
<key>LSRequiresIPhoneOS</key>
3939
<true/>
4040
<key>NSCameraUsageDescription</key>

example/lib/upload_screen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class _UploadScreenState extends State<UploadScreen> {
181181
allowCompression: false,
182182
allowMultiple: true,
183183
);
184-
if (files.count > 0) {
184+
if (files != null && files.count > 0) {
185185
if (binary) {
186186
for (var file in files.files) {
187187
_handleFileUpload([file.path]);

ios/Classes/CachingStreamHandler.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,31 @@ class CachingStreamHandler<T>: NSObject, FlutterStreamHandler {
1111
var cache: [String:T] = [:]
1212

1313
var eventSink: FlutterEventSink?
14+
15+
private let cacheSemaphore = DispatchSemaphore(value: 1)
1416

1517
func add(_ id: String, _ value: T) {
18+
cacheSemaphore.wait()
1619
cache[id] = value
17-
20+
cacheSemaphore.signal()
21+
1822
if let sink = eventSink {
1923
sink(value)
2024
}
2125
}
2226

2327
func clear() {
28+
cacheSemaphore.wait()
2429
cache.removeAll()
30+
cacheSemaphore.signal()
2531
}
2632

2733
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
34+
cacheSemaphore.wait()
2835
for cacheEntry in cache {
2936
events(cacheEntry.value)
3037
}
38+
cacheSemaphore.signal()
3139

3240
self.eventSink = events
3341

ios/Classes/EngineManager.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ import Foundation
1010
class EngineManager {
1111
private var headlessRunner: FlutterEngine?
1212
public var registerPlugins: FlutterPluginRegistrantCallback?
13+
14+
private let semaphore = DispatchSemaphore(value: 1)
1315

1416
private func startEngineIfNeeded() {
17+
semaphore.wait()
18+
19+
defer {
20+
semaphore.signal()
21+
}
22+
1523
guard let callbackHandle = UploaderDefaults.shared.callbackHandle else {
1624
if let runner = headlessRunner {
1725
runner.destroyContext()

ios/Classes/URLSessionUploader.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class URLSessionUploader: NSObject {
1818

1919
var session: URLSession?
2020
let queue = OperationQueue()
21+
22+
// Accessing uploadedData & runningTaskById will require exclusive access
23+
private let semaphore = DispatchSemaphore(value: 1)
2124

2225
// Reference for uploaded data.
2326
var uploadedData = [String: Data]()
@@ -51,7 +54,10 @@ class URLSessionUploader: NSObject {
5154
delegates.uploadEnqueued(taskId: taskId)
5255

5356
uploadTask.resume()
57+
58+
semaphore.wait()
5459
self.runningTaskById[taskId] = UploadTask(taskId: taskId, status: .enqueue, progress: 0)
60+
semaphore.signal()
5561

5662
return uploadTask
5763
}
@@ -153,6 +159,11 @@ extension URLSessionUploader: URLSessionDelegate, URLSessionDataDelegate, URLSes
153159
}
154160

155161
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
162+
semaphore.wait()
163+
defer {
164+
semaphore.signal()
165+
}
166+
156167
NSLog("URLSessionDidReceiveData:")
157168

158169
guard let uploadTask = dataTask as? URLSessionUploadTask else {
@@ -179,6 +190,11 @@ extension URLSessionUploader: URLSessionDelegate, URLSessionDataDelegate, URLSes
179190
}
180191

181192
public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
193+
semaphore.wait()
194+
defer {
195+
semaphore.signal()
196+
}
197+
182198
if totalBytesExpectedToSend == NSURLSessionTransferSizeUnknown {
183199
NSLog("Unknown transfer size")
184200
} else {
@@ -191,6 +207,7 @@ extension URLSessionUploader: URLSessionDelegate, URLSessionDataDelegate, URLSes
191207
let bytesExpectedToSend = Double(integerLiteral: totalBytesExpectedToSend)
192208
let tBytesSent = Double(integerLiteral: totalBytesSent)
193209
let progress = round(Double(tBytesSent / bytesExpectedToSend * 100))
210+
194211
let runningTask = self.runningTaskById[taskId]
195212
NSLog("URLSessionDidSendBodyData: taskId: \(taskId), byteSent: \(bytesSent), totalBytesSent: \(totalBytesSent), totalBytesExpectedToSend: \(totalBytesExpectedToSend), progress:\(progress)")
196213

@@ -211,7 +228,13 @@ extension URLSessionUploader: URLSessionDelegate, URLSessionDataDelegate, URLSes
211228

212229
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
213230
NSLog("URLSessionDidFinishEvents:")
231+
214232
session.getTasksWithCompletionHandler { (_, uploadTasks, _) in
233+
self.semaphore.wait()
234+
defer {
235+
self.semaphore.signal()
236+
}
237+
215238
if uploadTasks.isEmpty {
216239
NSLog("all upload tasks have been completed")
217240

@@ -222,6 +245,11 @@ extension URLSessionUploader: URLSessionDelegate, URLSessionDataDelegate, URLSes
222245
}
223246

224247
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
248+
semaphore.wait()
249+
defer {
250+
semaphore.signal()
251+
}
252+
225253
guard let uploadTask = task as? URLSessionUploadTask else {
226254
NSLog("URLSessionDidCompleteWithError: not an uplaod task")
227255
return

0 commit comments

Comments
 (0)