From cb45e3681576cf7fd487482520c9db44d43317a0 Mon Sep 17 00:00:00 2001 From: wood1986 <5212215+wood1986@users.noreply.github.com> Date: Thu, 30 Jun 2022 05:13:17 -0700 Subject: [PATCH] fix: fix the race condition when calling readAsDataURL after new Blob(blobs) (#34096) Summary: ```js async () => { let blobs = []; for (let i = 0; i < 4; i++) { const res = await fetch(); blobs = [...blobs, await res.blob()] } const blob = new Blob(blobs); // <<<<<<<<<<<<<<< a return await new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = async () => { await RNFS.writeFile(destPath, (fileReader.result as string).split(',')[1], 'base64'); resolve(destPath); }; fileReader.onabort = () => { reject(''); }; fileReader.onerror = (event) => { reject(''); }; fileReader.readAsDataURL(blob); // <<<<<<<<<<<<<<< b }); } ``` Sometime `fileReader.readAsDataURL` is unable to get blob from the dictionary after `new Blob(blobs)` and then reject with `Unable to resolve data for blob: blobId` in iOS or `The specified blob is invalid` in android. Because line `a` and `b` can be run in different thread. `new Blob([])` is in progress and `fileReader.readAsDataURL` accesses the blob dictionary ahead of the blob creation. The expected behaviour is it should finish new Blob([]) first and then readAsDataURL(blob) To fix that, there should be a lock inside the method `createFromParts`. For iOS, It needs to be a recursive_mutex to allow same thread to acquire lock ## Changelog [iOS] [Fixed] - fix the race condition when calling readAsDataURL after new Blob(blobs) Pull Request resolved: https://github.com/facebook/react-native/pull/34096 Reviewed By: cipolleschi Differential Revision: D37514981 Pulled By: javache fbshipit-source-id: 4bf84ece99871276ecaa5aa1849b9145ff44dbf4 (cherry picked from commit 112d67865b2a186d27c75e322397f67d06792807) --- Libraries/Blob/RCTFileReaderModule.mm | 58 ++++++++++++++------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Libraries/Blob/RCTFileReaderModule.mm b/Libraries/Blob/RCTFileReaderModule.mm index e834c61cc7970f..f52a68aba98c19 100644 --- a/Libraries/Blob/RCTFileReaderModule.mm +++ b/Libraries/Blob/RCTFileReaderModule.mm @@ -31,24 +31,26 @@ @implementation RCTFileReaderModule reject:(RCTPromiseRejectBlock)reject) { RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"]; - NSData *data = [blobManager resolve:blob]; + dispatch_async(blobManager.methodQueue, ^{ + NSData *data = [blobManager resolve:blob]; - if (data == nil) { - reject(RCTErrorUnspecified, - [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); - } else { - NSStringEncoding stringEncoding; - - if (encoding == nil) { - stringEncoding = NSUTF8StringEncoding; + if (data == nil) { + reject(RCTErrorUnspecified, + [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); } else { - stringEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef) encoding)); - } + NSStringEncoding stringEncoding; - NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding]; + if (encoding == nil) { + stringEncoding = NSUTF8StringEncoding; + } else { + stringEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef) encoding)); + } - resolve(text); - } + NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding]; + + resolve(text); + } + }); } RCT_EXPORT_METHOD(readAsDataURL:(NSDictionary *)blob @@ -56,19 +58,21 @@ @implementation RCTFileReaderModule reject:(RCTPromiseRejectBlock)reject) { RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"]; - NSData *data = [blobManager resolve:blob]; - - if (data == nil) { - reject(RCTErrorUnspecified, - [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); - } else { - NSString *type = [RCTConvert NSString:blob[@"type"]]; - NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@", - type != nil && [type length] > 0 ? type : @"application/octet-stream", - [data base64EncodedStringWithOptions:0]]; - - resolve(text); - } + dispatch_async(blobManager.methodQueue, ^{ + NSData *data = [blobManager resolve:blob]; + + if (data == nil) { + reject(RCTErrorUnspecified, + [NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]], nil); + } else { + NSString *type = [RCTConvert NSString:blob[@"type"]]; + NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@", + type != nil && [type length] > 0 ? type : @"application/octet-stream", + [data base64EncodedStringWithOptions:0]]; + + resolve(text); + } + }); } - (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params