/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, * 2018, 2019 * Jonathan Schleifer <js@heap.zone> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include <assert.h> #include <errno.h> #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFData.h" #import "OFDictionary.h" #ifdef OF_HAVE_SOCKETS # import "OFKernelEventObserver.h" # import "OFTCPSocket.h" # import "OFTCPSocket+Private.h" #endif #import "OFThread.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" # import "OFCondition.h" #endif #import "OFSortedList.h" #import "OFTimer.h" #import "OFTimer+Private.h" #import "OFDate.h" #import "OFObserveFailedException.h" #ifdef OF_HAVE_SOCKETS # import "OFConnectionFailedException.h" #endif of_run_loop_mode_t of_run_loop_mode_default = @"of_run_loop_mode_default"; static OFRunLoop *mainRunLoop = nil; @interface OFRunLoop () - (OFRunLoop_State *)of_stateForMode: (of_run_loop_mode_t)mode create: (bool)create; @end @interface OFRunLoop_State: OFObject #ifdef OF_HAVE_SOCKETS <OFKernelEventObserverDelegate> #endif { @public OFSortedList OF_GENERIC(OFTimer *) *_timersQueue; #ifdef OF_HAVE_THREADS OFMutex *_timersQueueMutex; #endif #if defined(OF_HAVE_SOCKETS) OFKernelEventObserver *_kernelEventObserver; OFMutableDictionary *_readQueues, *_writeQueues; #elif defined(OF_HAVE_THREADS) OFCondition *_condition; #endif } @end #ifdef OF_HAVE_SOCKETS @interface OFRunLoop_QueueItem: OFObject { @public id _delegate; } - (bool)handleObject: (id)object; @end @interface OFRunLoop_ReadQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_stream_async_read_block_t _block; # endif void *_buffer; size_t _length; } @end @interface OFRunLoop_ExactReadQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_stream_async_read_block_t _block; # endif void *_buffer; size_t _exactLength, _readLength; } @end @interface OFRunLoop_ReadLineQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_stream_async_read_line_block_t _block; # endif of_string_encoding_t _encoding; } @end @interface OFRunLoop_WriteDataQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_stream_async_write_data_block_t _block; # endif OFData *_data; size_t _writtenLength; } @end @interface OFRunLoop_WriteStringQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_stream_async_write_string_block_t _block; # endif OFString *_string; of_string_encoding_t _encoding; size_t _writtenLength; } @end # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) @interface OFRunLoop_ConnectQueueItem: OFRunLoop_QueueItem @end # endif @interface OFRunLoop_AcceptQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_tcp_socket_async_accept_block_t _block; # endif } @end @interface OFRunLoop_UDPReceiveQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_udp_socket_async_receive_block_t _block; # endif void *_buffer; size_t _length; } @end @interface OFRunLoop_UDPSendQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS of_udp_socket_async_send_data_block_t _block; # endif OFData *_data; of_socket_address_t _receiver; } @end #endif @implementation OFRunLoop_State - (instancetype)init { self = [super init]; @try { _timersQueue = [[OFSortedList alloc] init]; #if defined(OF_HAVE_SOCKETS) _kernelEventObserver = [[OFKernelEventObserver alloc] init]; [_kernelEventObserver setDelegate: self]; _readQueues = [[OFMutableDictionary alloc] init]; _writeQueues = [[OFMutableDictionary alloc] init]; #elif defined(OF_HAVE_THREADS) _condition = [[OFCondition alloc] init]; #endif } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_timersQueue release]; #if defined(OF_HAVE_SOCKETS) [_kernelEventObserver release]; [_readQueues release]; [_writeQueues release]; #elif defined(OF_HAVE_THREADS) [_condition release]; #endif [super dealloc]; } #ifdef OF_HAVE_SOCKETS - (void)objectIsReadyForReading: (id)object { /* * Retain the queue so that it doesn't disappear from us because the * handler called -[cancelAsyncRequests]. */ OFList OF_GENERIC(OF_KINDOF(OFRunLoop_ReadQueueItem *)) *queue = [[_readQueues objectForKey: object] retain]; assert(queue != nil); @try { if (![[queue firstObject] handleObject: object]) { of_list_object_t *listObject = [queue firstListObject]; /* * The handler might have called -[cancelAsyncRequests] * so that our queue is now empty, in which case we * should do nothing. */ if (listObject != NULL) { /* * Make sure we keep the target until after we * are done removing the object. The reason for * this is that the target might call * -[cancelAsyncRequests] in its dealloc. */ [[listObject->object retain] autorelease]; [queue removeListObject: listObject]; if ([queue count] == 0) { [_kernelEventObserver removeObjectForReading: object]; [_readQueues removeObjectForKey: object]; } } } } @finally { [queue release]; } } - (void)objectIsReadyForWriting: (id)object { /* * Retain the queue so that it doesn't disappear from us because the * handler called -[cancelAsyncRequests]. */ OFList *queue = [[_writeQueues objectForKey: object] retain]; assert(queue != nil); @try { if (![[queue firstObject] handleObject: object]) { of_list_object_t *listObject = [queue firstListObject]; /* * The handler might have called -[cancelAsyncRequests] * so that our queue is now empty, in which case we * should do nothing. */ if (listObject != NULL) { /* * Make sure we keep the target until after we * are done removing the object. The reason for * this is that the target might call * -[cancelAsyncRequests] in its dealloc. */ [[listObject->object retain] autorelease]; [queue removeListObject: listObject]; if ([queue count] == 0) { [_kernelEventObserver removeObjectForWriting: object]; [_writeQueues removeObjectForKey: object]; } } } } @finally { [queue release]; } } #endif @end #ifdef OF_HAVE_SOCKETS @implementation OFRunLoop_QueueItem - (bool)handleObject: (id)object { OF_UNRECOGNIZED_SELECTOR } - (void)dealloc { [_delegate release]; [super dealloc]; } @end @implementation OFRunLoop_ReadQueueItem - (bool)handleObject: (id)object { size_t length; id exception = nil; @try { length = [object readIntoBuffer: _buffer length: _length]; } @catch (id e) { length = 0; exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) return _block(object, _buffer, length, exception); else { # endif if (![_delegate respondsToSelector: @selector(stream:didReadIntoBuffer:length:exception:)]) return false; return [_delegate stream: object didReadIntoBuffer: _buffer length: length exception: exception]; # ifdef OF_HAVE_BLOCKS } # endif } # ifdef OF_HAVE_BLOCKS - (void)dealloc { [_block release]; [super dealloc]; } # endif @end @implementation OFRunLoop_ExactReadQueueItem - (bool)handleObject: (id)object { size_t length; id exception = nil; @try { length = [object readIntoBuffer: (char *)_buffer + _readLength length: _exactLength - _readLength]; } @catch (id e) { length = 0; exception = e; } _readLength += length; if (_readLength != _exactLength && ![object isAtEndOfStream] && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) { if (!_block(object, _buffer, _readLength, exception)) return false; _readLength = 0; return true; } else { # endif if (![_delegate respondsToSelector: @selector(stream:didReadIntoBuffer:length:exception:)]) return false; if (![_delegate stream: object didReadIntoBuffer: _buffer length: _readLength exception: exception]) return false; _readLength = 0; return true; # ifdef OF_HAVE_BLOCKS } # endif } # ifdef OF_HAVE_BLOCKS - (void)dealloc { [_block release]; [super dealloc]; } # endif @end @implementation OFRunLoop_ReadLineQueueItem - (bool)handleObject: (id)object { OFString *line; id exception = nil; @try { line = [object tryReadLineWithEncoding: _encoding]; } @catch (id e) { line = nil; exception = e; } if (line == nil && ![object isAtEndOfStream] && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) return _block(object, line, exception); else { # endif if (![_delegate respondsToSelector: @selector(stream:didReadLine:exception:)]) return false; return [_delegate stream: object didReadLine: line exception: exception]; # ifdef OF_HAVE_BLOCKS } # endif } # ifdef OF_HAVE_BLOCKS - (void)dealloc { [_block release]; [super dealloc]; } # endif @end @implementation OFRunLoop_WriteDataQueueItem - (bool)handleObject: (id)object { size_t length; id exception = nil; size_t dataLength = [_data count] * [_data itemSize]; OFData *newData, *oldData; @try { const char *dataItems = [_data items]; length = [object writeBuffer: dataItems + _writtenLength length: dataLength - _writtenLength]; } @catch (id e) { length = 0; exception = e; } _writtenLength += length; if (_writtenLength != dataLength && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) { newData = _block(object, _data, _writtenLength, exception); if (newData == nil) return false; oldData = _data; _data = [newData copy]; [oldData release]; _writtenLength = 0; return true; } else { # endif if (![_delegate respondsToSelector: @selector(stream:didWriteData:bytesWritten:exception:)]) return false; newData = [_delegate stream: object didWriteData: _data bytesWritten: _writtenLength exception: exception]; if (newData == nil) return false; oldData = _data; _data = [newData copy]; [oldData release]; _writtenLength = 0; return true; # ifdef OF_HAVE_BLOCKS } # endif } - (void)dealloc { [_data release]; # ifdef OF_HAVE_BLOCKS [_block release]; # endif [super dealloc]; } @end @implementation OFRunLoop_WriteStringQueueItem - (bool)handleObject: (id)object { size_t length; id exception = nil; size_t cStringLength = [_string cStringLengthWithEncoding: _encoding]; OFString *newString, *oldString; @try { const char *cString = [_string cStringWithEncoding: _encoding]; length = [object writeBuffer: cString + _writtenLength length: cStringLength - _writtenLength]; } @catch (id e) { length = 0; exception = e; } _writtenLength += length; if (_writtenLength != cStringLength && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) { newString = _block(object, _string, _encoding, _writtenLength, exception); if (newString == nil) return false; oldString = _string; _string = [newString copy]; [oldString release]; _writtenLength = 0; return true; } else { # endif if (![_delegate respondsToSelector: @selector(stream: didWriteString:encoding:bytesWritten:exception:)]) return false; newString = [_delegate stream: object didWriteString: _string encoding: _encoding bytesWritten: _writtenLength exception: exception]; if (newString == nil) return false; oldString = _string; _string = [newString copy]; [oldString release]; _writtenLength = 0; return true; # ifdef OF_HAVE_BLOCKS } # endif } - (void)dealloc { [_string release]; # ifdef OF_HAVE_BLOCKS [_block release]; # endif [super dealloc]; } @end # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) @implementation OFRunLoop_ConnectQueueItem - (bool)handleObject: (id)object { id exception = nil; int errNo; if ((errNo = [object of_socketError]) != 0) exception = [OFConnectionFailedException exceptionWithHost: nil port: 0 socket: object errNo: errNo]; if ([_delegate respondsToSelector: @selector(of_socketDidConnect:exception:)]) [_delegate of_socketDidConnect: object exception: exception]; return false; } @end # endif @implementation OFRunLoop_AcceptQueueItem - (bool)handleObject: (id)object { OFTCPSocket *acceptedSocket; id exception = nil; @try { acceptedSocket = [object accept]; } @catch (id e) { acceptedSocket = nil; exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) return _block(object, acceptedSocket, exception); else { # endif if (![_delegate respondsToSelector: @selector(socket:didAcceptSocket:exception:)]) return false; return [_delegate socket: object didAcceptSocket: acceptedSocket exception: exception]; # ifdef OF_HAVE_BLOCKS } # endif } # ifdef OF_HAVE_BLOCKS - (void)dealloc { [_block release]; [super dealloc]; } # endif @end @implementation OFRunLoop_UDPReceiveQueueItem - (bool)handleObject: (id)object { size_t length; of_socket_address_t address; id exception = nil; @try { length = [object receiveIntoBuffer: _buffer length: _length sender: &address]; } @catch (id e) { length = 0; exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) return _block(object, _buffer, length, &address, exception); else { # endif if (![_delegate respondsToSelector: @selector( socket:didReceiveIntoBuffer:length:sender:exception:)]) return false; return [_delegate socket: object didReceiveIntoBuffer: _buffer length: length sender: &address exception: exception]; # ifdef OF_HAVE_BLOCKS } # endif } # ifdef OF_HAVE_BLOCKS - (void)dealloc { [_block release]; [super dealloc]; } # endif @end @implementation OFRunLoop_UDPSendQueueItem - (bool)handleObject: (id)object { id exception = nil; OFData *newData, *oldData; @try { [object sendBuffer: [_data items] length: [_data count] * [_data itemSize] receiver: &_receiver]; } @catch (id e) { exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) { newData = _block(object, _data, &_receiver, exception); if (newData == nil) return false; oldData = _data; _data = [newData copy]; [oldData release]; return true; } else { # endif if (![_delegate respondsToSelector: @selector(socket:didSendData:receiver:exception:)]) return false; newData = [_delegate socket: object didSendData: _data receiver: &_receiver exception: exception]; if (newData == nil) return false; oldData = _data; _data = [newData copy]; [oldData release]; return true; # ifdef OF_HAVE_BLOCKS } # endif } - (void)dealloc { [_data release]; # ifdef OF_HAVE_BLOCKS [_block release]; # endif [super dealloc]; } @end #endif @implementation OFRunLoop @synthesize currentMode = _currentMode; + (OFRunLoop *)mainRunLoop { return mainRunLoop; } + (OFRunLoop *)currentRunLoop { #ifdef OF_HAVE_THREADS return [[OFThread currentThread] runLoop]; #else return [self mainRunLoop]; #endif } + (void)of_setMainRunLoop: (OFRunLoop *)runLoop { mainRunLoop = [runLoop retain]; } #ifdef OF_HAVE_SOCKETS # define NEW_READ(type, object, mode) \ void *pool = objc_autoreleasePoolPush(); \ OFRunLoop *runLoop = [self currentRunLoop]; \ OFRunLoop_State *state = [runLoop of_stateForMode: mode \ create: true]; \ OFList *queue = [state->_readQueues objectForKey: object]; \ type *queueItem; \ \ if (queue == nil) { \ queue = [OFList list]; \ [state->_readQueues setObject: queue \ forKey: object]; \ } \ \ if ([queue count] == 0) \ [state->_kernelEventObserver \ addObjectForReading: object]; \ \ queueItem = [[[type alloc] init] autorelease]; # define NEW_WRITE(type, object, mode) \ void *pool = objc_autoreleasePoolPush(); \ OFRunLoop *runLoop = [self currentRunLoop]; \ OFRunLoop_State *state = [runLoop of_stateForMode: mode \ create: true]; \ OFList *queue = [state->_writeQueues objectForKey: object]; \ type *queueItem; \ \ if (queue == nil) { \ queue = [OFList list]; \ [state->_writeQueues setObject: queue \ forKey: object]; \ } \ \ if ([queue count] == 0) \ [state->_kernelEventObserver \ addObjectForWriting: object]; \ \ queueItem = [[[type alloc] init] autorelease]; #define QUEUE_ITEM \ [queue appendObject: queueItem]; \ \ objc_autoreleasePoolPop(pool); + (void)of_addAsyncReadForStream: (OFStream <OFReadyForReadingObserving> *) stream buffer: (void *)buffer length: (size_t)length mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_stream_async_read_block_t)block # endif delegate: (id <OFStreamDelegate>)delegate { NEW_READ(OFRunLoop_ReadQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_buffer = buffer; queueItem->_length = length; QUEUE_ITEM } + (void)of_addAsyncReadForStream: (OFStream <OFReadyForReadingObserving> *) stream buffer: (void *)buffer exactLength: (size_t)exactLength mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_stream_async_read_block_t)block # endif delegate: (id <OFStreamDelegate>)delegate { NEW_READ(OFRunLoop_ExactReadQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_buffer = buffer; queueItem->_exactLength = exactLength; QUEUE_ITEM } + (void)of_addAsyncReadLineForStream: (OFStream <OFReadyForReadingObserving> *) stream encoding: (of_string_encoding_t)encoding mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_stream_async_read_line_block_t)block # endif delegate: (id <OFStreamDelegate>)delegate { NEW_READ(OFRunLoop_ReadLineQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_encoding = encoding; QUEUE_ITEM } + (void)of_addAsyncWriteForStream: (OFStream <OFReadyForWritingObserving> *) stream data: (OFData *)data mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_stream_async_write_data_block_t)block # endif delegate: (id <OFStreamDelegate>)delegate { NEW_WRITE(OFRunLoop_WriteDataQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_data = [data copy]; QUEUE_ITEM } + (void)of_addAsyncWriteForStream: (OFStream <OFReadyForWritingObserving> *) stream string: (OFString *)string encoding: (of_string_encoding_t)encoding mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_stream_async_write_string_block_t)block # endif delegate: (id <OFStreamDelegate>)delegate { NEW_WRITE(OFRunLoop_WriteStringQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_string = [string copy]; queueItem->_encoding = encoding; QUEUE_ITEM } # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) + (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)stream mode: (of_run_loop_mode_t)mode delegate: (id <OFTCPSocketDelegate_Private>) delegate { NEW_WRITE(OFRunLoop_ConnectQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; QUEUE_ITEM } # endif + (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)stream mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_tcp_socket_async_accept_block_t)block # endif delegate: (id <OFTCPSocketDelegate>)delegate { NEW_READ(OFRunLoop_AcceptQueueItem, stream, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif QUEUE_ITEM } + (void)of_addAsyncReceiveForUDPSocket: (OFUDPSocket *)sock buffer: (void *)buffer length: (size_t)length mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_udp_socket_async_receive_block_t) block # endif delegate: (id <OFUDPSocketDelegate>)delegate { NEW_READ(OFRunLoop_UDPReceiveQueueItem, sock, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_buffer = buffer; queueItem->_length = length; QUEUE_ITEM } + (void)of_addAsyncSendForUDPSocket: (OFUDPSocket *)sock data: (OFData *)data receiver: (const of_socket_address_t *)receiver mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS block: (of_udp_socket_async_send_data_block_t) block # endif delegate: (id <OFUDPSocketDelegate>)delegate { NEW_WRITE(OFRunLoop_UDPSendQueueItem, sock, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_data = [data copy]; queueItem->_receiver = *receiver; QUEUE_ITEM } # undef NEW_READ # undef NEW_WRITE # undef QUEUE_ITEM + (void)of_cancelAsyncRequestsForObject: (id)object mode: (of_run_loop_mode_t)mode { void *pool = objc_autoreleasePoolPush(); OFRunLoop *runLoop = [self currentRunLoop]; OFRunLoop_State *state = [runLoop of_stateForMode: mode create: false]; OFList *queue; if (state == nil) return; if ((queue = [state->_writeQueues objectForKey: object]) != nil) { assert([queue count] > 0); /* * Clear the queue now, in case this has been called from a * handler, as otherwise, we'd do the cleanups below twice. */ [queue removeAllObjects]; [state->_kernelEventObserver removeObjectForWriting: object]; [state->_writeQueues removeObjectForKey: object]; } if ((queue = [state->_readQueues objectForKey: object]) != nil) { assert([queue count] > 0); /* * Clear the queue now, in case this has been called from a * handler, as otherwise, we'd do the cleanups below twice. */ [queue removeAllObjects]; [state->_kernelEventObserver removeObjectForReading: object]; [state->_readQueues removeObjectForKey: object]; } objc_autoreleasePoolPop(pool); } #endif - (instancetype)init { self = [super init]; @try { OFRunLoop_State *state; _states = [[OFMutableDictionary alloc] init]; state = [[OFRunLoop_State alloc] init]; @try { [_states setObject: state forKey: of_run_loop_mode_default]; } @finally { [state release]; } #ifdef OF_HAVE_THREADS _statesMutex = [[OFMutex alloc] init]; #endif } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_states release]; #ifdef OF_HAVE_THREADS [_statesMutex release]; #endif [super dealloc]; } - (OFRunLoop_State *)of_stateForMode: (of_run_loop_mode_t)mode create: (bool)create { OFRunLoop_State *state; #ifdef OF_HAVE_THREADS [_statesMutex lock]; @try { #endif state = [_states objectForKey: mode]; if (create && state == nil) { state = [[OFRunLoop_State alloc] init]; @try { [_states setObject: state forKey: mode]; } @finally { [state release]; } } #ifdef OF_HAVE_THREADS } @finally { [_statesMutex unlock]; } #endif return state; } - (void)addTimer: (OFTimer *)timer { [self addTimer: timer forMode: of_run_loop_mode_default]; } - (void)addTimer: (OFTimer *)timer forMode: (of_run_loop_mode_t)mode { OFRunLoop_State *state = [self of_stateForMode: mode create: true]; #ifdef OF_HAVE_THREADS [state->_timersQueueMutex lock]; @try { #endif [state->_timersQueue insertObject: timer]; #ifdef OF_HAVE_THREADS } @finally { [state->_timersQueueMutex unlock]; } #endif [timer of_setInRunLoop: self mode: mode]; #if defined(OF_HAVE_SOCKETS) [state->_kernelEventObserver cancel]; #elif defined(OF_HAVE_THREADS) [state->_condition signal]; #endif } - (void)of_removeTimer: (OFTimer *)timer forMode: (of_run_loop_mode_t)mode { OFRunLoop_State *state = [self of_stateForMode: mode create: false]; if (state == nil) return; #ifdef OF_HAVE_THREADS [state->_timersQueueMutex lock]; @try { #endif of_list_object_t *iter; for (iter = [state->_timersQueue firstListObject]; iter != NULL; iter = iter->next) { if ([iter->object isEqual: timer]) { [state->_timersQueue removeListObject: iter]; break; } } #ifdef OF_HAVE_THREADS } @finally { [state->_timersQueueMutex unlock]; } #endif } - (void)run { [self runUntilDate: nil]; } - (void)runUntilDate: (OFDate *)deadline { _stop = false; while (!_stop && (deadline == nil || [deadline timeIntervalSinceNow] >= 0)) [self runMode: of_run_loop_mode_default beforeDate: deadline]; } - (void)runMode: (of_run_loop_mode_t)mode beforeDate: (OFDate *)deadline { void *pool = objc_autoreleasePoolPush(); of_run_loop_mode_t previousMode = _currentMode; OFRunLoop_State *state = [self of_stateForMode: mode create: false]; if (state == nil) return; _currentMode = mode; @try { OFDate *nextTimer; for (;;) { OFTimer *timer; #ifdef OF_HAVE_THREADS [state->_timersQueueMutex lock]; @try { #endif of_list_object_t *listObject = [state->_timersQueue firstListObject]; if (listObject != NULL && [[listObject->object fireDate] timeIntervalSinceNow] <= 0) { timer = [[listObject->object retain] autorelease]; [state->_timersQueue removeListObject: listObject]; [timer of_setInRunLoop: nil mode: nil]; } else break; #ifdef OF_HAVE_THREADS } @finally { [state->_timersQueueMutex unlock]; } #endif if ([timer isValid]) { [timer fire]; return; } } #ifdef OF_HAVE_THREADS [state->_timersQueueMutex lock]; @try { #endif nextTimer = [[state->_timersQueue firstObject] fireDate]; #ifdef OF_HAVE_THREADS } @finally { [state->_timersQueueMutex unlock]; } #endif /* Watch for I/O events until the next timer is due */ if (nextTimer != nil || deadline != nil) { of_time_interval_t timeout; if (nextTimer != nil && deadline == nil) timeout = [nextTimer timeIntervalSinceNow]; else if (nextTimer == nil && deadline != nil) timeout = [deadline timeIntervalSinceNow]; else timeout = [[nextTimer earlierDate: deadline] timeIntervalSinceNow]; if (timeout < 0) timeout = 0; #if defined(OF_HAVE_SOCKETS) @try { [state->_kernelEventObserver observeForTimeInterval: timeout]; } @catch (OFObserveFailedException *e) { if ([e errNo] != EINTR) @throw e; } #elif defined(OF_HAVE_THREADS) [state->_condition lock]; [state->_condition waitForTimeInterval: timeout]; [state->_condition unlock]; #else [OFThread sleepForTimeInterval: timeout]; #endif } else { /* * No more timers and no deadline: Just watch for I/O * until we get an event. If a timer is added by * another thread, it cancels the observe. */ #if defined(OF_HAVE_SOCKETS) @try { [state->_kernelEventObserver observe]; } @catch (OFObserveFailedException *e) { if ([e errNo] != EINTR) @throw e; } #elif defined(OF_HAVE_THREADS) [state->_condition lock]; [state->_condition wait]; [state->_condition unlock]; #else [OFThread sleepForTimeInterval: 86400]; #endif } objc_autoreleasePoolPop(pool); } @finally { _currentMode = previousMode; } } - (void)stop { OFRunLoop_State *state = [self of_stateForMode: of_run_loop_mode_default create: false]; _stop = true; if (state == nil) return; #if defined(OF_HAVE_SOCKETS) [state->_kernelEventObserver cancel]; #elif defined(OF_HAVE_THREADS) [state->_condition signal]; #endif } @end