Skip to content

Commit

Permalink
Re-applied "Rewrote AEMainThreadEndpoint to use dispatch semaphore, r…
Browse files Browse the repository at this point in the history
…ather than polling"

We have the final word on the realtime-thread safety of Mach semaphores, and they *are* fine. The internal implementation has diverged from the open-source one, and includes protections against priority inversion.
  • Loading branch information
michaeltyson committed Jun 14, 2016
1 parent d77c2fb commit 8cfec37
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 40 deletions.
12 changes: 6 additions & 6 deletions Tests/AECrossThreadMessagingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ - (void)testMainThreadEndpointMessaging {
}];

AEMainThreadEndpointSend(endpoint, NULL, 0);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:endpoint.pollInterval]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

XCTAssertEqualObjects(messages, (@[[NSData dataWithBytes:NULL length:0]]));
[messages removeAllObjects];
Expand All @@ -43,13 +43,13 @@ - (void)testMainThreadEndpointMessaging {
AEMainThreadEndpointSend(endpoint, &value1, sizeof(value1));
AEMainThreadEndpointSend(endpoint, &value2, sizeof(value2));
AEMainThreadEndpointSend(endpoint, &value3, sizeof(value3));
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:endpoint.pollInterval]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

XCTAssertEqualObjects(messages, (@[[NSData dataWithBytes:&value1 length:sizeof(value1)], [NSData dataWithBytes:&value2 length:sizeof(value2)], [NSData dataWithBytes:&value3 length:sizeof(value3)]]));
[messages removeAllObjects];

AEMainThreadEndpointSend(endpoint, &value1, sizeof(value1));
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:endpoint.pollInterval]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

XCTAssertEqualObjects(messages, (@[[NSData dataWithBytes:&value1 length:sizeof(value1)]]));
[messages removeAllObjects];
Expand Down Expand Up @@ -130,7 +130,7 @@ - (void)testMessageQueueAudioThreadMessaging {
XCTAssertNotNil(weakObject);

AEMessageQueuePoll(queue);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:queue.pollInterval]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

XCTAssertTrue(hitBlock);
XCTAssertTrue(hitCompletionBlock);
Expand All @@ -147,7 +147,7 @@ - (void)testMessageQueueMainThreadMessaging {
AEMessageQueue * queue = [AEMessageQueue new];

[self sendMainThreadMessage:queue];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:queue.pollInterval]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

XCTAssertEqual(self.mainThreadMessageValue1, 1);
XCTAssertEqual(self.mainThreadMessageValue2, self);
Expand All @@ -159,7 +159,7 @@ - (void)testMessageQueueMainThreadMessaging {

AEMessageQueuePerformSelectorOnMainThread(queue, self, @selector(mainThreadMessageTestWithNoArguments), AEArgumentNone);

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:queue.pollInterval]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];

XCTAssertEqual(self.mainThreadMessageValue1, 3);
}
Expand Down
4 changes: 0 additions & 4 deletions TheAmazingAudioEngine/Utilities/AEMainThreadEndpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,6 @@ void * _Nullable AEMainThreadEndpointCreateMessage(__unsafe_unretained AEMainThr
*/
void AEMainThreadEndpointDispatchMessage(__unsafe_unretained AEMainThreadEndpoint * _Nonnull endpoint);


//! The poll interval (default is 10ms)
@property (nonatomic) AESeconds pollInterval;

@end

#ifdef __cplusplus
Expand Down
64 changes: 44 additions & 20 deletions TheAmazingAudioEngine/Utilities/AEMainThreadEndpoint.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,21 @@

#import "AEMainThreadEndpoint.h"
#import "TPCircularBuffer.h"
#import "AEWeakRetainingProxy.h"
#import <dispatch/semaphore.h>

@class AEMainThreadEndpointThread;

@interface AEMainThreadEndpoint () {
TPCircularBuffer _buffer;
BOOL _hasPendingMainThreadMessages;
}
@property (nonatomic, copy) AEMainThreadEndpointHandler handler;
@property (nonatomic, strong) NSTimer * timer;
@property (nonatomic) dispatch_semaphore_t semaphore;
@property (nonatomic, strong) AEMainThreadEndpointThread * thread;
@end

@interface AEMainThreadEndpointThread : NSThread
@property (nonatomic, unsafe_unretained) AEMainThreadEndpoint * endpoint;
@end

@implementation AEMainThreadEndpoint
Expand All @@ -44,31 +52,29 @@ - (instancetype)initWithHandler:(AEMainThreadEndpointHandler)handler {
- (instancetype)initWithHandler:(AEMainThreadEndpointHandler)handler bufferCapacity:(size_t)bufferCapacity {
if ( !(self = [super init]) ) return nil;

_pollInterval = 0.01;
self.handler = handler;

if ( !TPCircularBufferInit(&_buffer, (int32_t)bufferCapacity) ) {
return nil;
}

[self startTimer];
self.semaphore = dispatch_semaphore_create(0);

self.thread = [AEMainThreadEndpointThread new];
self.thread.endpoint = self;
[self.thread start];

return self;
}

- (void)dealloc {
[self.timer invalidate];
@synchronized ( self.thread ) {
[self.thread cancel];
dispatch_semaphore_signal(_semaphore);
}
TPCircularBufferCleanup(&_buffer);
}

- (void)setPollInterval:(AESeconds)pollInterval {
_pollInterval = pollInterval;

// Restart timer
[self.timer invalidate];
[self startTimer];
}

BOOL AEMainThreadEndpointSend(__unsafe_unretained AEMainThreadEndpoint * THIS, const void * data, size_t length) {

// Prepare message
Expand Down Expand Up @@ -114,14 +120,10 @@ void AEMainThreadEndpointDispatchMessage(__unsafe_unretained AEMainThreadEndpoin

// Mark as ready to read
TPCircularBufferProduce(&THIS->_buffer, (int32_t)size);
dispatch_semaphore_signal(THIS->_semaphore);
}

- (void)startTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.pollInterval target:[AEWeakRetainingProxy proxyWithTarget:self]
selector:@selector(poll) userInfo:nil repeats:YES];
}

- (void)poll {
- (void)serviceMessages {
while ( 1 ) {
// Get pointer to readable bytes
int32_t availableBytes;
Expand All @@ -133,11 +135,33 @@ - (void)poll {
void * data = length > 0 ? (tail + sizeof(size_t)) : NULL;

// Run handler
self.handler(data, length);
dispatch_sync(dispatch_get_main_queue(), ^{
self.handler(data, length);
});

// Mark as read
TPCircularBufferConsume(&_buffer, (int32_t)(sizeof(size_t) + length));
}
}

@end

@implementation AEMainThreadEndpointThread

- (void)main {
dispatch_semaphore_t semaphore = self.endpoint.semaphore;

while ( 1 ) {
@synchronized ( self ) {
if ( self.cancelled ) {
break;
}
@autoreleasepool {
[self.endpoint serviceMessages];
}
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}

@end
3 changes: 0 additions & 3 deletions TheAmazingAudioEngine/Utilities/AEMessageQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,6 @@ BOOL AEMessageQueuePerformSelectorOnMainThread(__unsafe_unretained AEMessageQueu
*/
void AEMessageQueuePoll(__unsafe_unretained AEMessageQueue * _Nonnull THIS);

//! The poll interval (default is 10ms)
@property (nonatomic) AESeconds pollInterval;

@end

#ifdef __cplusplus
Expand Down
7 changes: 0 additions & 7 deletions TheAmazingAudioEngine/Utilities/AEMessageQueue.m
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ - (instancetype)initWithBufferCapacity:(size_t)bufferCapacity {
AEMainThreadEndpointSend(mainThread, data, length);
} bufferCapacity:bufferCapacity];

_pollInterval = self.mainThreadEndpoint.pollInterval;

return self;
}

Expand Down Expand Up @@ -233,9 +231,4 @@ void AEMessageQueuePoll(__unsafe_unretained AEMessageQueue * _Nonnull THIS) {
AEAudioThreadEndpointPoll(THIS->_audioThreadEndpoint);
}

- (void)setPollInterval:(AESeconds)pollInterval {
_pollInterval = pollInterval;
self.mainThreadEndpoint.pollInterval = pollInterval;
}

@end

0 comments on commit 8cfec37

Please sign in to comment.