Skip to content

Commit

Permalink
Use direct read/writes in separate queues for debugserver wiring
Browse files Browse the repository at this point in the history
Summary:
For whatever reason, it appears that using an `FBFileReader` on the socket fd does not result in bytes coming through in a timely manner. I suspect this is due to some underlying buffering that I don't quite understand in the bowels of `dispatch_io`.

However, it's actually very easy to have a read and write loop operate in two background queues. In this case we're using `-[NSFIleHandler availableData]` which does produce all data, as little as a single byte, whenever it is ready. This means that `lldb` will no longer hang due to receiving partial data over the protocol

Reviewed By: RuijieC-dev

Differential Revision: D33477041

fbshipit-source-id: 7d35e06dc7c6f63e56b713064fb73075bc878eca
  • Loading branch information
lawrencelomax authored and facebook-github-bot committed Jan 7, 2022
1 parent 5bc2d6e commit 7762446
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 49 deletions.
12 changes: 12 additions & 0 deletions FBDeviceControl/Management/FBAMDServiceConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,25 @@ NS_ASSUME_NONNULL_BEGIN

/**
Synchronously receive bytes from the connection.
This call will block until 'size' is met.
If a read fails before the 'size' is met, this call will fail.
@param size the number of bytes to read.
@param error an error out for any error that occurs.
@return the data.
*/
- (NSData *)receive:(size_t)size error:(NSError **)error;

/**
Synchronously receive up to 'size' bytes in the connection
This call will return an empty NSData when end of file is reached.
@param size the number of bytes to read up to.
@param error an error out for any error that occurs.
@return the data.
*/
- (NSData *)receiveUpTo:(size_t)size error:(NSError **)error;

/**
Synchronously receive bytes from the connection, writing to a file handle.
Expand Down
20 changes: 20 additions & 0 deletions FBDeviceControl/Management/FBAMDServiceConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,26 @@ - (BOOL)receive:(void *)destination ofSize:(size_t)size error:(NSError **)error
return YES;
}

- (NSData *)receiveUpTo:(size_t)size error:(NSError **)error
{
// Create a buffer that contains the data
void *buffer = alloca(size);
// Read the underlying bytes.
ssize_t result = [self receive:buffer size:size];
// End of file.
if (result == 0) {
return NSData.data;
}
// A negative return indicates an error
if (result == -1) {
return [[FBDeviceControlError
describeFormat:@"Failure in receive of up to %zu bytes: %s", size, strerror(errno)]
fail:error];
}
size_t readBytes = (size_t) result;
return [[NSData alloc] initWithBytes:buffer length:readBytes];
}

- (BOOL)receiveUnsignedInt32:(uint32_t *)valueOut error:(NSError **)error
{
return [self receive:valueOut ofSize:sizeof(uint32_t) error:error];
Expand Down
125 changes: 76 additions & 49 deletions FBDeviceControl/Management/FBDeviceDebugServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,61 +13,87 @@

@interface FBDeviceDebugServer_TwistedPairFiles : NSObject

@property (nonatomic, assign, readonly) int source;
@property (nonatomic, strong, readonly) FBAMDServiceConnection *sink;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, strong, readonly) dispatch_queue_t sinkWriteQueue;
@property (nonatomic, strong, readonly) dispatch_queue_t sinkReadQueue;

@property (nonatomic, strong, nullable, readwrite) id<FBDataConsumer> sourceWriter;
@property (nonatomic, strong, nullable, readwrite) id<FBFileReader> sourceReader;
@property (nonatomic, strong, nullable, readwrite) id<FBDataConsumer> sinkWriter;
@property (nonatomic, strong, nullable, readwrite) id<FBFileReader> sinkReader;
@property (nonatomic, assign, readonly) int socket;
@property (nonatomic, strong, readonly) FBAMDServiceConnection *connection;
@property (nonatomic, strong, readonly) id<FBControlCoreLogger> logger;
@property (nonatomic, strong, readonly) dispatch_queue_t socketToConnectionQueue;
@property (nonatomic, strong, readonly) dispatch_queue_t connectionToSocketQueue;

@end

@implementation FBDeviceDebugServer_TwistedPairFiles

- (instancetype)initWithSource:(int)source sink:(FBAMDServiceConnection *)sink queue:(dispatch_queue_t)queue
- (instancetype)initWithSocket:(int)socket connection:(FBAMDServiceConnection *)connection logger:(id<FBControlCoreLogger>)logger
{
self = [super init];
if (!self) {
return nil;
}

_source = source;
_sink = sink;
_queue = queue;
_sinkWriteQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver_sink_write", DISPATCH_QUEUE_SERIAL);
_sinkReadQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver_sink_read", DISPATCH_QUEUE_SERIAL);
_socket = socket;
_connection = connection;
_logger = logger;
_socketToConnectionQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver.socket_to_connection", DISPATCH_QUEUE_SERIAL);
_connectionToSocketQueue = dispatch_queue_create("com.facebook.fbdevicecontrol.debugserver.connection_to_socket", DISPATCH_QUEUE_SERIAL);

return self;
}

- (FBFuture<FBFuture<NSNull *> *> *)start
static size_t const ConnectionReadSizeLimit = 1024;

- (FBFuture<NSNull *> *)startWithError:(NSError **)error
{
NSError *error = nil;
id<FBDataConsumer, FBDataConsumerLifecycle> sourceWriter = [FBFileWriter asyncWriterWithFileDescriptor:self.source closeOnEndOfFile:NO error:&error];
if (!sourceWriter) {
return [FBFuture futureWithError:error];
if (@available(macOS 10.15, *)) {
id<FBControlCoreLogger> logger = self.logger;
int socket = self.socket;
NSFileHandle *socketReadHandle = [[NSFileHandle alloc] initWithFileDescriptor:socket closeOnDealloc:NO];
NSFileHandle *socketWriteHandle = [[NSFileHandle alloc] initWithFileDescriptor:socket closeOnDealloc:NO];
FBAMDServiceConnection *connection = self.connection;
FBMutableFuture<NSNull *> *socketReadCompleted = FBMutableFuture.future;
FBMutableFuture<NSNull *> *connectionReadCompleted = FBMutableFuture.future;
dispatch_async(self.socketToConnectionQueue, ^{
while (socketReadCompleted.state == FBFutureStateRunning && connectionReadCompleted.state == FBFutureStateRunning) {
NSError *innerError = nil;
NSData *data = [socketReadHandle availableData];
if (data.length == 0) {
[logger log:@"Socket read reached end of file"];
break;
}
if (![connection send:data error:&innerError]) {
[logger logFormat:@"Sending data to remote debugserver failed: %@", innerError];
break;
}
}
[logger logFormat:@"Exiting socket %d read loop", socket];
[socketReadCompleted resolveWithResult:NSNull.null];
});
dispatch_async(self.connectionToSocketQueue, ^{
while (socketReadCompleted.state == FBFutureStateRunning && connectionReadCompleted.state == FBFutureStateRunning) {
NSError *innerError = nil;
NSData *data = [connection receiveUpTo:ConnectionReadSizeLimit error:&innerError];
if (data.length == 0) {
[logger logFormat:@"debugserver read ended: %@", innerError];
break;
}
if (![socketWriteHandle writeData:data error:&innerError]) {
[logger logFormat:@"Socket write failed: %@", innerError];
break;
}
}
[logger logFormat:@"Exiting connection %@ read loop", connection];
[connectionReadCompleted resolveWithResult:NSNull.null];
});
return [[FBFuture
futureWithFutures:@[
socketReadCompleted,
connectionReadCompleted,
]]
onQueue:self.connectionToSocketQueue notifyOfCompletion:^(id _) {
[logger logFormat:@"Closing socket file descriptor %d", socket];
close(socket);
}];
}
self.sourceWriter = sourceWriter;
self.sinkWriter = [self.sink writeWithConsumerWritingOnQueue:self.sinkWriteQueue];
self.sourceReader = [FBFileReader readerWithFileDescriptor:self.source closeOnEndOfFile:NO consumer:self.sinkWriter logger:nil];
self.sinkReader = [self.sink readFromConnectionWritingToConsumer:self.sourceWriter onQueue:self.sinkReadQueue];
return [[FBFuture
futureWithFutures:@[
[self.sourceReader startReading],
[self.sinkReader startReading],
]]
onQueue:self.queue map:^(id _) {
return [[FBFuture
race:@[
self.sourceReader.finishedReading,
self.sinkReader.finishedReading,
]]
mapReplace:NSNull.null];
}];
return nil;
}

@end
Expand Down Expand Up @@ -131,17 +157,18 @@ - (void)socketServer:(FBSocketServer *)server clientConnected:(struct in6_addr)a
return;
}
[self.logger log:@"Client connected, connecting all file handles"];
self.twistedPair = [[FBDeviceDebugServer_TwistedPairFiles alloc] initWithSource:fileDescriptor sink:self.serviceConnection queue:self.queue];
[[[self.twistedPair
start]
onQueue:self.queue fmap:^(FBFuture<NSNull *> *finished) {
[self.logger log:@"File handles connected"];
return finished;
}]
onQueue:self.queue notifyOfCompletion:^(id _) {
[self.logger log:@"Client Disconnected"];
self.twistedPair = nil;
}];
FBDeviceDebugServer_TwistedPairFiles *twistedPair = [[FBDeviceDebugServer_TwistedPairFiles alloc] initWithSocket:fileDescriptor connection:self.serviceConnection logger:self.logger];
NSError *error = nil;
FBFuture<NSNull *> *completed = [twistedPair startWithError:&error];
if (!completed) {
[self.logger logFormat:@"Failed to start connection %@", error];
return;
}
[completed onQueue:self.queue notifyOfCompletion:^(id _) {
[self.logger log:@"Client Disconnected"];
self.twistedPair = nil;
}];
self.twistedPair = twistedPair;
}

#pragma mark FBiOSTargetOperation
Expand Down

0 comments on commit 7762446

Please sign in to comment.