Fix IO Channel disposal in FBFileWriter

Summary: See the comments for this one

Reviewed By: c-ryan747

Differential Revision: D13692550

fbshipit-source-id: dedd7bcf17a659bd468c207902cea65fdb1b486b
This commit is contained in:
Lawrence Lomax 2019-01-16 10:19:14 -08:00 коммит произвёл Facebook Github Bot
Родитель 3e189fe83b
Коммит 572374a220
4 изменённых файлов: 65 добавлений и 4 удалений

Просмотреть файл

@ -234,13 +234,26 @@
NSParameterAssert(!self.io);
// If there is an error creating the IO Object, the errorCode will be delivered asynchronously.
NSFileHandle *fileHandle = self.fileHandle;
self.io = dispatch_io_create(DISPATCH_IO_STREAM, fileHandle.fileDescriptor, self.writeQueue, ^(int errorCode) {
[self ioChannelDidCloseWithError:errorCode];
// Having a self -> IO -> self cycle shouldn't be a problem in theory, since the cleanup handler should get when IO is done.
// However, it appears that having the cycle in place here means that the cleanup handler is *never* called in the following circumstance:
// 1) Pipe of FD14 is created.
// 2) A writer is created for this pipe
// 3) Data is written to this writer
// 4) `consumeEndOfFile` is called and subsequently dispatch_io_close.
// 5) The cleanup handler is called and subsequently the FD closed and IO channel disposed of via nil-ification.
// 6) Pipe FD14 is torn down.
// 7) A new Pipe resolving to FD14 is created.
// 8) Data is written to this writer
// 9) `consumeEndOfFile` is called and subsequently dispatch_io_close.
// 10) The cleanup handler is *never* called and the FD is therefore never closed.
// This isn't a problem in practice if different FDs are splayed, but repeating FDs representing different dispatch channels will cause this problem.
__weak typeof(self) weakSelf = self;
self.io = dispatch_io_create(DISPATCH_IO_STREAM, self.fileHandle.fileDescriptor, self.writeQueue, ^(int errorCode) {
[weakSelf ioChannelDidCloseWithError:errorCode];
});
if (!self.io) {
return [[FBControlCoreError
describeFormat:@"A IO Channel could not be created for fd %d", fileHandle.fileDescriptor]
describeFormat:@"A IO Channel could not be created for fd %d", self.fileHandle.fileDescriptor]
failBool:error];
}

Просмотреть файл

@ -53,6 +53,11 @@
*/
+ (NSString *)agentCrashPathWithCustomDeviceSet;
/**
All of the above, in a directory
*/
+ (NSString *)bundleResource;
@end
@interface XCTestCase (FBControlCoreFixtures)

Просмотреть файл

@ -48,6 +48,11 @@
return [[NSBundle bundleForClass:self] pathForResource:@"agent_custom_set" ofType:@"crash"];
}
+ (NSString *)bundleResource
{
return [NSBundle bundleForClass:self].resourcePath;
}
@end
@implementation XCTestCase (FBControlCoreFixtures)

Просмотреть файл

@ -324,4 +324,42 @@
XCTAssertEqual(task.completed.state, FBFutureStateDone);
}
- (void)testPipingInputToSuccessivelyRunTasksSucceeds
{
NSString *tarSource = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.tar.gz", NSUUID.UUID.UUIDString]];
NSString *tarDestination = [tarSource stringByAppendingString:@".destination"];
NSError *error = nil;
BOOL success = [NSFileManager.defaultManager createDirectoryAtPath:tarDestination withIntermediateDirectories:YES attributes:nil error:&error];
XCTAssertNil(error);
XCTAssertTrue(success);
success = [[[[[[[FBTaskBuilder
withLaunchPath:@"/usr/bin/tar"]
withArguments:@[@"-zcvf", tarSource, FBControlCoreFixtures.bundleResource]]
withStdOutToDevNull]
withStdErrToDevNull]
runUntilCompletion]
mapReplace:NSNull.null]
await:&error] != nil;
XCTAssertNil(error);
XCTAssertTrue(success);
NSData *tarData = [NSData dataWithContentsOfFile:tarSource];
for (NSUInteger count = 0; count < 10; count++) {
success = [[[[[[[[FBTaskBuilder
withLaunchPath:@"/usr/bin/tar"]
withArguments:@[@"-C", tarDestination, @"-zxpf", @"-"]]
withStdInFromData:tarData]
withStdOutToDevNull]
withStdErrToDevNull]
runUntilCompletion]
mapReplace:NSNull.null]
await:&error] != nil;
XCTAssertNil(error);
XCTAssertTrue(success);
}
}
@end