Main problem
I'm implementing bandwidth management for Socket Rocket.
To reduce amount of alteration in Socket Rocket I've decided to create subclass of NSOutputStream
which will wrap NSOutputStream
created for a socket. Concept is quite nice and should work like charm.
Encounter problem
In - (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len
method I'm queering from bandwidth manager if data can be send or not. If not bandwidth manager gives me required delay for next write operation.
So my code looks more or less like this:
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len
{
auto bandwithInfo = [_bandwidthManager bandwidthInfoForSize: len];
if (bandwithInfo.m_bOKtoSendNow)
{
auto actualWritten = [self.throttledStream write: buffer maxLength: len];
if (actualWritten<0)
{
// report failure of sending data
[_bandwidthManager reportNetworkBackPressure: len];
}
else
{
[_bandwidthManager ReportConsumedSendBandwidth: actualWritten];
int bytesNotSentCount = len - actualWritten;
if (bytesNotSentCount>0)
{
[_bandwidthManager ReportNetworkBackPressure: bytesNotSentCount];
}
[self enqueueEvent: NSStreamEventHasSpaceAvailable];
}
return actualWritten;
}
else
{
auto delay = bandwithInfo.m_nWaitTimeMilliseconds;
NSASSERT(delay>0);
[self scheduleNotifyReadyToWriteAfterMs: delay];
return 0;
}
}
- (void)scheduleNotifyReadyToWriteAfterMs: (int)miliseconds
{
if (!isReadyToWriteScheduled)
{
isReadyToWriteScheduled = YES;
static const NSTimeInterval kTimeIntervalMilisecond = 0.001;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: miliseconds * kTimeIntervalMilisecond
target: self
selector: @selector(notifyReadyToWrite:)
userInfo: nil
repeats: false];
NSLog(@"timer=%@\nRunLoop=%@", timer, [NSRunLoop currentRunLoop]);
// this alternative also doesn't work:
// [self performSelector: @selector(notifyReadyToWrite:)
// withObject: nil
// afterDelay: miliseconds * kTimeIntervalMilisecond];
}
}
- (void)notifyReadyToWrite: (NSTimer *)timer
{
[timer invalidate];
if (self.hasSpaceAvailable) {
isReadyToWriteScheduled = NO;
[self enqueueEvent: NSStreamEventHasSpaceAvailable];
}
}
In logs I can see, (there is a run loop and it contains created timer):
timer=<__NSCFTimer: 0x109a96180>
RunLoop=<CFRunLoop 0x109a962d0 [0x7fff7208f440]>{wakeup port = 0x4507, stopped = false, ignoreWakeUps = true,
current mode = (none),
common modes = <CFBasicHash 0x109a96390 [0x7fff7208f440]>{type = mutable set, count = 1,
entries =>
2 : <CFString 0x7fff71fa1940 [0x7fff7208f440]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x109a963d0 [0x7fff7208f440]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopMode 0x109a96410 [0x7fff7208f440]>{name = kCFRunLoopDefaultMode, port set = 0x440b, queue = 0x109a964e0, source = 0x109a96570 (not fired), timer port = 0x4b03,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = <CFArray 0x109a95b10 [0x7fff7208f440]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopTimer 0x109a96180 [0x7fff7208f440]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 485078914 (-0.00818103552 @ 3808422033916), callout = (NSTimer) [SRSendStreamWithThrottling notifyReadyToWrite:] (0x7fff87025d0d / 0x10264b100) (/Users/maru/Library/Developer/Xcode/DerivedData/AvayaCommonWorkspace-cnweundajqjciphewynfexnutumh/Build/Products/Debug/testrunner), context = <CFRunLoopTimer context 0x109a93370>}
)},
currently 485078914 (3808417241824) / soft deadline in: 0.00479207 sec (@ 3808422033916) / hard deadline in: 0.004791892 sec (@ 3808422033916)
},
}
}
Now I have a run loop my custom stream and wrapped stream are scheduled in this run loop, so socket rocket and my wrapping class are reciveing all notifications, so I've rule out all obvious mistakes.
Still for some mysterious reason notifyReadyToWrite:
is never called. Does anyone have a clue why?