Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active March 21, 2017 16:00
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save steipete/b22babbf3014e29c19f0 to your computer and use it in GitHub Desktop.
Save steipete/b22babbf3014e29c19f0 to your computer and use it in GitHub Desktop.
Haven't done much with dispatch_io yet so I'd appreciate a few more eyeballs on this. Am I closing things correctly in all error conditions? Are there other knobs I could change to make things even faster? (I know I've been lazy on the NSError's)
static NSData *PSPDFCalculateSHA256FromFileURL(NSURL *fileURL, CC_LONG dataLength, NSError **error) {
NSCParameterAssert(fileURL);
dispatch_queue_t shaQueue = dispatch_queue_create("com.pspdfkit.sha256-queue", DISPATCH_QUEUE_SERIAL);
__block dispatch_io_t readChannel;
void (^processIntError)(int intError) = ^(int intError) {
if (intError != 0) {
PSPDFLogWarning(@"Stream error: %d", intError);
if (error) *error = [NSError errorWithDomain:@"SHA256Error" code:100 userInfo:@{NSLocalizedDescriptionKey: @"failed to open file for calculating SHA256."}];
}
dispatch_async(shaQueue, ^{
dispatch_io_close(readChannel, DISPATCH_IO_STOP);
});
};
// Open the IO channel
readChannel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, fileURL.path.UTF8String, O_RDONLY, 0, shaQueue, processIntError);
if (!readChannel) {
if (error) *error = [NSError errorWithDomain:@"SHA256Error" code:100 userInfo:@{NSLocalizedDescriptionKey: @"failed to open file for calculating SHA256."}];
return nil;
}
// Prepare SHA hash
__block CC_SHA256_CTX ctx;
CC_SHA256_Init(&ctx);
// Start read and block until we are done.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int lastIntError = 0;
dispatch_io_set_high_water(readChannel, 512*1024);
dispatch_io_read(readChannel, 0, SIZE_MAX, shaQueue, ^(bool done, dispatch_data_t data, int intError) {
if (intError != 0) {
lastIntError = intError;
processIntError(intError);
dispatch_semaphore_signal(semaphore);
return;
}
dispatch_data_apply(data, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
CC_SHA256_Update(&ctx, (const void *)buffer, (CC_LONG)size);
return true;
});
if (done) {
dispatch_semaphore_signal(semaphore);
}
});
// Wait for the signal, then wake up.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_io_close(readChannel, DISPATCH_IO_STOP);
// Finalize SHA256 calculation unless there's an error.
if (lastIntError == 0) {
unsigned char sha256[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(sha256, &ctx);
NSData *shaData = [NSData dataWithBytes:sha256 length:CC_SHA256_DIGEST_LENGTH];
return shaData;
}
return nil;
}
@steipete
Copy link
Author

Follow conversation back on some interesting points related to priority donation: https://twitter.com/catfish_man/status/621442751138172928

@vittoriom
Copy link

As mentioned on Twitter, looking at line 36. Maybe you could remove the return and wrap the dispatch_data_apply in an if condition checking if data is dispatch_data_empty (from Apple doc:

"If an unrecoverable error occurs while performing the I/O
operation, the handler block will be submitted with the done flag set and the appriate POSIX error code
in the error parameter."

), so that when you get an error, you still enter the if (done) check and signal the semaphore. But honestly I never did dispatch io before and probably I'm just missing something obvious :)

@Vyazovoy
Copy link

What for you pass dataLength in this function. I can't find where it used.

@steipete
Copy link
Author

@Vyazovoy It's the file size so you can hash over a part if you choose.

@Vyazovoy
Copy link

@steipete in line 36 you return, but in Apple docs I see: If an unrecoverable error occurs on the channel’s file descriptor, the done parameter is set to YES and an appropriate error value is reported in the handler’s error parameter. So you return but semaphore doesn't signaled and thread not woken in line 50

@Vyazovoy
Copy link

@steipete , what for you use serial queue? From docs: Your block need not be reentrant. The system guarantees that only one instance of this block will be executed at any given time.

@steipete
Copy link
Author

@Vyazovoy I fixed the error handling part (note: also I'm not actually shipping that code, ended up with a simpler/faster way)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment