Added watchdog to STKAutoRecoveringHTTPDataSource to catch TCP timeouts which otherwise would not raise an error since we aren't do any writes to the socket

This commit is contained in:
Thong Nguyen 2014-02-13 17:33:45 +00:00
parent 511b756694
commit 569764d869
7 changed files with 146 additions and 16 deletions

View File

@ -42,7 +42,7 @@
#import "libkern/OSAtomic.h"
#import <float.h>
#pragma mark Defines%
#pragma mark Defines
#define kOutputBus 0
#define kInputBus 1
@ -2728,15 +2728,8 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
NSArray* retval;
NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithCapacity:upcomingQueue.count + bufferingQueue.count];
for (STKQueueEntry* entry in upcomingQueue)
{
[mutableArray addObject:[entry queueItemId]];
}
for (STKQueueEntry* entry in bufferingQueue)
{
[mutableArray addObject:[entry queueItemId]];
}
[mutableArray skipQueueWithQueue:upcomingQueue];
[mutableArray skipQueueWithQueue:bufferingQueue];
retval = [NSArray arrayWithArray:mutableArray];
@ -2762,7 +2755,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
if (upcomingQueue.count > 0)
{
NSObject* retval = [[upcomingQueue objectAtIndex:0] queueItemId];
NSObject* retval = [upcomingQueue objectAtIndex:0];
pthread_mutex_unlock(&playerMutex);
@ -2771,7 +2764,7 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
if (bufferingQueue.count > 0)
{
NSObject* retval = [[bufferingQueue objectAtIndex:0] queueItemId];
NSObject* retval = [bufferingQueue objectAtIndex:0];
pthread_mutex_unlock(&playerMutex);

View File

@ -36,6 +36,13 @@
#import "STKHTTPDataSource.h"
#import "STKDataSourceWrapper.h"
typedef struct
{
int watchdogPeriodSeconds;
int inactivePeriodBeforeReconnectSeconds;
}
STKAutoRecoveringHTTPDataSourceOptions;
@interface STKAutoRecoveringHTTPDataSource : STKDataSourceWrapper
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSource;

View File

@ -38,18 +38,38 @@
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#import "mach/mach_time.h"
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import "STKAutoRecoveringHTTPDataSource.h"
#define MAX_IMMEDIATE_RECONNECT_ATTEMPTS (2)
#define DEFAULT_WATCHDOG_PERIOD_SECONDS (5)
#define DEFAULT_INACTIVE_PERIOD_BEFORE_RECONNECT_SECONDS (5)
static uint64_t GetTickCount(void)
{
static mach_timebase_info_data_t sTimebaseInfo;
uint64_t machTime = mach_absolute_time();
if (sTimebaseInfo.denom == 0 )
{
(void) mach_timebase_info(&sTimebaseInfo);
}
uint64_t millis = ((machTime / 1000000) * sTimebaseInfo.numer) / sTimebaseInfo.denom;
return millis;
}
@interface STKAutoRecoveringHTTPDataSource()
{
int serial;
int waitSeconds;
NSTimer* timeoutTimer;
BOOL waitingForNetwork;
uint64_t ticksWhenLastDataReceived;
SCNetworkReachabilityRef reachabilityRef;
STKAutoRecoveringHTTPDataSourceOptions options;
}
-(void) reachabilityChanged;
@ -66,6 +86,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
}
static void PopulateOptionsWithDefault(STKAutoRecoveringHTTPDataSourceOptions* options)
{
if (options->watchdogPeriodSeconds == 0)
{
options->watchdogPeriodSeconds = DEFAULT_WATCHDOG_PERIOD_SECONDS;
}
if (options->inactivePeriodBeforeReconnectSeconds == 0)
{
options->inactivePeriodBeforeReconnectSeconds = DEFAULT_INACTIVE_PERIOD_BEFORE_RECONNECT_SECONDS;
}
}
@implementation STKAutoRecoveringHTTPDataSource
-(STKHTTPDataSource*) innerHTTPDataSource
@ -79,6 +112,11 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn
{
return [self initWithHTTPDataSource:innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions){}];
}
-(id) initWithHTTPDataSource:(STKHTTPDataSource*)innerDataSourceIn andOptions:(STKAutoRecoveringHTTPDataSourceOptions)optionsIn
{
if (self = [super initWithDataSource:innerDataSourceIn])
{
@ -90,6 +128,10 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
PopulateOptionsWithDefault(&optionsIn);
self->options = optionsIn;
reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress);
}
@ -117,14 +159,68 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
[super registerForEvents:runLoop];
[self startNotifierOnRunLoop:runLoop];
if (timeoutTimer)
{
[timeoutTimer invalidate];
timeoutTimer = nil;
}
[self createTimeoutTimer];
return YES;
}
-(void) unregisterForEvents
{
[super unregisterForEvents];
[self stopNotifier];
[self destroyTimeoutTimer];
}
-(void) timeoutTimerTick:(NSTimer*)timer
{
if (![self hasBytesAvailable])
{
if ([self hasGotNetworkConnection])
{
uint64_t currentTicks = GetTickCount();
if (((currentTicks - ticksWhenLastDataReceived) / 1000) >= options.inactivePeriodBeforeReconnectSeconds)
{
serial++;
NSLog(@"timeoutTimerTick %lld/%lld", self.position, self.length);
[self attemptReconnectWithSerial:@(serial)];
}
}
}
}
-(void) createTimeoutTimer
{
[self destroyTimeoutTimer];
NSRunLoop* runLoop = self.innerDataSource.eventsRunLoop;
if (runLoop == nil)
{
return;
}
timeoutTimer = [NSTimer timerWithTimeInterval:options.watchdogPeriodSeconds target:self selector:@selector(timeoutTimerTick:) userInfo:@(serial) repeats:YES];
[runLoop addTimer:timeoutTimer forMode:NSRunLoopCommonModes];
}
-(void) destroyTimeoutTimer
{
if (timeoutTimer)
{
[timeoutTimer invalidate];
timeoutTimer = nil;
}
}
-(void) stopNotifier
@ -148,6 +244,12 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
return NO;
}
-(void) close
{
[self destroyTimeoutTimer];
[super close];
}
-(void) dealloc
{
NSLog(@"STKAutoRecoveringHTTPDataSource dealloc");
@ -155,6 +257,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
self.innerDataSource.delegate = nil;
[self stopNotifier];
[self destroyTimeoutTimer];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
@ -170,6 +273,10 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
{
waitingForNetwork = NO;
NSLog(@"reachabilityChanged %lld/%lld", self.position, self.length);
serial++;
[self attemptReconnectWithSerial:@(serial)];
}
}
@ -178,7 +285,8 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
{
serial++;
waitSeconds = 1;
ticksWhenLastDataReceived = GetTickCount();
[super dataSourceDataAvailable:dataSource];
}
@ -191,7 +299,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
NSLog(@"attemptReconnect %lld/%lld", self.position, self.length);
[self seekToOffset:self.position];
[self.innerDataSource reconnect];
}
-(void) attemptReconnectWithTimer:(NSTimer*)timer
@ -220,6 +328,8 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
else
{
serial++;
NSTimer* timer = [NSTimer timerWithTimeInterval:waitSeconds target:self selector:@selector(attemptReconnectWithTimer:) userInfo:@(serial) repeats:NO];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];

View File

@ -54,6 +54,7 @@
-(BOOL) reregisterForEvents;
-(void) open;
-(void) openCompleted;
-(void) dataAvailable;
-(void) eof;
-(void) errorOccured;

View File

@ -49,6 +49,9 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
case kCFStreamEventHasBytesAvailable:
[datasource dataAvailable];
break;
case kCFStreamEventOpenCompleted:
[datasource openCompleted];
break;
default:
break;
}
@ -191,4 +194,8 @@ static void ReadStreamCallbackProc(CFReadStreamRef stream, CFStreamEventType eve
return 0;
}
-(void) openCompleted
{
}
@end

View File

@ -50,5 +50,6 @@ typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek,
-(id) initWithURLProvider:(STKURLProvider)urlProvider;
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider;
-(NSRunLoop*) eventsRunLoop;
-(void) reconnect;
@end

View File

@ -214,6 +214,17 @@
return fileLength >= 0 ? fileLength : 0;
}
-(void) reconnect
{
NSRunLoop* savedEventsRunLoop = eventsRunLoop;
[self close];
eventsRunLoop = savedEventsRunLoop;
[self seekToOffset:self.position];
}
-(void) seekToOffset:(long long)offset
{
NSRunLoop* savedEventsRunLoop = eventsRunLoop;