From 141d3e3c21ba385e81a15946bc4125d9e9aae9fb Mon Sep 17 00:00:00 2001 From: Piotr Wegrzynek Date: Thu, 17 Aug 2017 12:06:17 +0200 Subject: [PATCH] Introduce traceable stanza send/receive events in XMPPStream --- Core/XMPPInternal.h | 1 + Core/XMPPStream.h | 115 ++++++++- Core/XMPPStream.m | 398 ++++++++++++++++++++----------- Utilities/GCDMulticastDelegate.m | 3 +- 4 files changed, 381 insertions(+), 136 deletions(-) diff --git a/Core/XMPPInternal.h b/Core/XMPPInternal.h index 1a1bc50c14..f4dba9754d 100644 --- a/Core/XMPPInternal.h +++ b/Core/XMPPInternal.h @@ -99,6 +99,7 @@ extern NSString *const XMPPStreamDidChangeMyJIDNotification; * This is an advanced technique, but makes for some interesting possibilities. **/ - (void)injectElement:(NSXMLElement *)element; +- (void)injectElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID; /** * The XMPP standard only supports , and stanzas (excluding session setup stuff). diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index e73e068f16..70dbea6212 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @class XMPPModule; @class XMPPElement; @class XMPPElementReceipt; +@class XMPPElementEvent; @protocol XMPPStreamDelegate; #if TARGET_OS_IPHONE @@ -615,8 +616,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; - (void)sendElement:(NSXMLElement *)element; /** - * Just like the sendElement: method above, - * but allows you to receive a receipt that can later be used to verify the element has been sent. + * Just like the sendElement: method above, but allows you to: + * - Receive a receipt that can later be used to verify the element has been sent. + * - Provide an event ID that can later be used to trace the element in delegate callbacks. * * If you later want to check to see if the element has been sent: * @@ -644,6 +646,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * Even if you close the xmpp stream after this point, the OS will still do everything it can to send the data. **/ - (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; +- (void)sendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; /** * Fetches and resends the myPresence element (if available) in a single atomic operation. @@ -733,6 +736,19 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; **/ - (void)enumerateModulesOfClass:(Class)aClass withBlock:(void (^)(XMPPModule *module, NSUInteger idx, BOOL *stop))block; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Element Event Context +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the stream metadata corresponding to the currently processed XMPP stanza. + * + * Event information is only available in the context of @c didSendXXX/didFailToSendXXX/didReceiveXXX delegate callbacks. + * This method returns nil if called outside of those callbacks. + * For more details, please refer to @c XMPPElementEvent documentation. + */ +- (nullable XMPPElementEvent *)currentElementEvent; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -785,6 +801,79 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; @end +/** + * A handle that allows identifying elements sent or received in the stream across different delegates + * and tracking their processing progress. + * + * While the core XMPP specification does not require stanzas to be uniquely identifiable, you may still want to + * identify them internally across different modules or trace the sent ones to the respective send result delegate callbacks. + * + * An instance of this class is provided in the context of execution of any of the @c didSendXXX/didFailToSendXXX/didReceiveXXX + * stream delegate methods. It is retrieved by calling the @c currentElementEvent method on the calling stream. + * The delegates can then use it to: + * - identify the corresponding XMPP stanzas. + * - be notified of asynchronous processing completion for a given XMPP stanza. + * + * Using @c XMPPElementEvent handles is a more robust approach than relying on pointer equality of @c XMPPElement instances. + */ +@interface XMPPElementEvent : NSObject + +/// The universally unique identifier of the event that provides the internal identity of the corresponding XMPP stanza. +@property (nonatomic, copy, readonly) NSString *uniqueID; + +/// The value of the stream's @c myJID property at the time when the event occured. +@property (nonatomic, strong, readonly, nullable) XMPPJID *myJID; + +/// The local device time when the event occured. +@property (nonatomic, strong, readonly) NSDate *timestamp; + +/** + * A flag indicating whether all delegates are done processing the given event. + * + * Supports Key-Value Observing. Change notifications are emitted on the stream queue. + * + * @see beginDelayedProcessing + * @see endDelayedProcessingWithToken + */ +@property (nonatomic, assign, readonly, getter=isProcessingCompleted) BOOL processingCompleted; + +// Instances are created by the stream only. +- (instancetype)init NS_UNAVAILABLE; + +/** + * Marks the event as being asynchronously processed by a delegate and returns a completion token. + * + * Event processing is completed after every @c beginDelayedProcessing call has been followed + * by @c endDelayedProcessingWithToken: with a matching completion token. + * + * Unpaired invocations may lead to undefined behavior or stalled events. + * + * Events that are not marked for asynchronous processing by any of the delegates complete immediately + * after control returns from all callbacks. + * + * @see endDelayedProcessingWithToken: + * @see processingCompleted + */ +- (id)beginDelayedProcessing; + +/** + * Marks an end of the previously initiated asynchronous delegate processing. + * + * Event processing is completed after every @c beginDelayedProcessing call has been followed + * by @c endDelayedProcessingWithToken: with a matching completion token. + * + * Unpaired invocations may lead to undefined behavior or stalled events. + * + * Events that are not marked for asynchronous processing by any of the delegates complete immediately + * after control returns from all callbacks. + * + * @see beginDelayedProcessing + * @see processingCompleted + */ +- (void)endDelayedProcessingWithToken:(id)delayedProcessingToken; + +@end + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -987,6 +1076,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * As documented in NSXML / KissXML, elements are read-access thread-safe, but write-access thread-unsafe. * If you have need to modify an element for any reason, * you should copy the element first, and then modify and use the copy. + * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. **/ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq; - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message; @@ -1032,6 +1124,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * These methods are called after their respective XML elements are sent over the stream. * These methods may be used to listen for certain events (such as an unavailable presence having been sent), * or for general logging purposes. (E.g. a central history logging mechanism). + * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. **/ - (void)xmppStream:(XMPPStream *)sender didSendIQ:(XMPPIQ *)iq; - (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message; @@ -1040,11 +1135,24 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; /** * These methods are called after failing to send the respective XML elements over the stream. * This occurs when the stream gets disconnected before the element can get sent out. + * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. + * Note that if these methods are called, the event context is incomplete, e.g. the stream might have not been connected + * and the actual myJID value is not determined. For more details, please refer to @c XMPPElementEvent documentation. **/ - (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSError *)error; - (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error; - (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error; +/** + * This method is called after all delegates are done processing the given event. + * + * For more details, please refer to @c XMPPElementEvent documentation. + */ +- (void)xmppStream:(XMPPStream *)sender didFinishProcessingElementEvent:(XMPPElementEvent *)event +NS_SWIFT_NAME(xmppStream(_:didFinishProcessing:)); + /** * This method is called if the XMPP Stream's jid changes. **/ @@ -1136,6 +1244,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * If you're using custom elements, you must register the custom element name(s). * Otherwise the xmppStream will treat non-XMPP elements as errors (xmppStream:didReceiveError:). * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. + * * @see registerCustomElementNames (in XMPPInternal.h) **/ - (void)xmppStream:(XMPPStream *)sender didSendCustomElement:(NSXMLElement *)element; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 8a738719ae..d5f8011e66 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -153,6 +153,19 @@ - (void)signalFailure; @end +@interface XMPPElementEvent () + +@property (nonatomic, unsafe_unretained, readonly) XMPPStream *xmppStream; +@property (nonatomic, assign, readwrite, getter=isProcessingCompleted) BOOL processingCompleted; + +@end + +@interface XMPPElementEvent (PrivateAPI) + +- (instancetype)initWithStream:(XMPPStream *)xmppStream uniqueID:(NSString *)uniqueID myJID:(XMPPJID *)myJID timestamp:(NSDate *)timestamp; + +@end + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2253,7 +2266,7 @@ - (float)serverXmppStreamVersionNumber } } -- (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag +- (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2268,7 +2281,7 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag // None of the delegates implement the method. // Use a shortcut. - [self continueSendIQ:iq withTag:tag]; + [self continueSendIQ:iq withTag:tag registeredEvent:[self generateElementEventWithID:eventID]]; } else { @@ -2314,10 +2327,12 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag { dispatch_async(xmppQueue, ^{ @autoreleasepool { + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if (state == STATE_XMPP_CONNECTED) { - [self continueSendIQ:modifiedIQ withTag:tag]; + [self continueSendIQ:modifiedIQ withTag:tag registeredEvent:event]; } else { - [self failToSendIQ:modifiedIQ]; + [self failToSendIQ:modifiedIQ withRegisteredEvent:event]; } }}); } @@ -2325,7 +2340,7 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag } } -- (void)sendMessage:(XMPPMessage *)message withTag:(long)tag +- (void)sendMessage:(XMPPMessage *)message withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2340,7 +2355,7 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag // None of the delegates implement the method. // Use a shortcut. - [self continueSendMessage:message withTag:tag]; + [self continueSendMessage:message withTag:tag registeredEvent:[self generateElementEventWithID:eventID]]; } else { @@ -2386,11 +2401,13 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag { dispatch_async(xmppQueue, ^{ @autoreleasepool { + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if (state == STATE_XMPP_CONNECTED) { - [self continueSendMessage:modifiedMessage withTag:tag]; + [self continueSendMessage:modifiedMessage withTag:tag registeredEvent:event]; } else { - [self failToSendMessage:modifiedMessage]; + [self failToSendMessage:modifiedMessage withRegisteredEvent:event]; } }}); } @@ -2398,7 +2415,7 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag } } -- (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag +- (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2413,7 +2430,7 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag // None of the delegates implement the method. // Use a shortcut. - [self continueSendPresence:presence withTag:tag]; + [self continueSendPresence:presence withTag:tag registeredEvent:[self generateElementEventWithID:eventID]]; } else { @@ -2459,10 +2476,12 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag { dispatch_async(xmppQueue, ^{ @autoreleasepool { + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if (state == STATE_XMPP_CONNECTED) { - [self continueSendPresence:modifiedPresence withTag:tag]; + [self continueSendPresence:modifiedPresence withTag:tag registeredEvent:event]; } else { - [self failToSendPresence:modifiedPresence]; + [self failToSendPresence:modifiedPresence withRegisteredEvent:event]; } }}); } @@ -2470,7 +2489,7 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag } } -- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag +- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag registeredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2485,10 +2504,12 @@ - (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag withTimeout:TIMEOUT_XMPP_WRITE tag:tag]; - [multicastDelegate xmppStream:self didSendIQ:iq]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendIQ:iq]; + }]; } -- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag +- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag registeredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2503,10 +2524,12 @@ - (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag withTimeout:TIMEOUT_XMPP_WRITE tag:tag]; - [multicastDelegate xmppStream:self didSendMessage:message]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendMessage:message]; + }]; } -- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag +- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag registeredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2536,10 +2559,12 @@ - (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag } } - [multicastDelegate xmppStream:self didSendPresence:presence]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendPresence:presence]; + }]; } -- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag +- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2556,7 +2581,11 @@ - (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag if ([customElementNames countForObject:[element name]]) { - [multicastDelegate xmppStream:self didSendCustomElement:element]; + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendCustomElement:element]; + }]; } } @@ -2564,22 +2593,22 @@ - (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag * Private method. * Presencts a common method for the various public sendElement methods. **/ -- (void)sendElement:(NSXMLElement *)element withTag:(long)tag +- (void)sendElement:(NSXMLElement *)element withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); if ([element isKindOfClass:[XMPPIQ class]]) { - [self sendIQ:(XMPPIQ *)element withTag:tag]; + [self sendIQ:(XMPPIQ *)element withTag:tag registeringEventWithID:eventID]; } else if ([element isKindOfClass:[XMPPMessage class]]) { - [self sendMessage:(XMPPMessage *)element withTag:tag]; + [self sendMessage:(XMPPMessage *)element withTag:tag registeringEventWithID:eventID]; } else if ([element isKindOfClass:[XMPPPresence class]]) { - [self sendPresence:(XMPPPresence *)element withTag:tag]; + [self sendPresence:(XMPPPresence *)element withTag:tag registeringEventWithID:eventID]; } else { @@ -2587,19 +2616,19 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag if ([elementName isEqualToString:@"iq"]) { - [self sendIQ:[XMPPIQ iqFromElement:element] withTag:tag]; + [self sendIQ:[XMPPIQ iqFromElement:element] withTag:tag registeringEventWithID:eventID]; } else if ([elementName isEqualToString:@"message"]) { - [self sendMessage:[XMPPMessage messageFromElement:element] withTag:tag]; + [self sendMessage:[XMPPMessage messageFromElement:element] withTag:tag registeringEventWithID:eventID]; } else if ([elementName isEqualToString:@"presence"]) { - [self sendPresence:[XMPPPresence presenceFromElement:element] withTag:tag]; + [self sendPresence:[XMPPPresence presenceFromElement:element] withTag:tag registeringEventWithID:eventID]; } else { - [self continueSendElement:element withTag:tag]; + [self continueSendElement:element withTag:tag registeringEventWithID:eventID]; } } } @@ -2610,24 +2639,12 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag **/ - (void)sendElement:(NSXMLElement *)element { - if (element == nil) return; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (state == STATE_XMPP_CONNECTED) - { - [self sendElement:element withTag:TAG_XMPP_WRITE_STREAM]; - } - else - { - [self failToSendElement:element]; - } - }}; - - if (dispatch_get_specific(xmppQueueTag)) - block(); - else - dispatch_async(xmppQueue, block); + [self sendElement:element registeringEventWithID:[self generateUUID] andGetReceipt:nil]; +} + +- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr +{ + [self sendElement:element registeringEventWithID:[self generateUUID] andGetReceipt:receiptPtr]; } /** @@ -2637,57 +2654,62 @@ - (void)sendElement:(NSXMLElement *)element * After the element has been successfully sent, * the xmppStream:didSendElementWithTag: delegate method is called. **/ -- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr +- (void)sendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID andGetReceipt:(XMPPElementReceipt **)receiptPtr { if (element == nil) return; - if (receiptPtr == nil) - { - [self sendElement:element]; - } - else - { - __block XMPPElementReceipt *receipt = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (state == STATE_XMPP_CONNECTED) - { - receipt = [[XMPPElementReceipt alloc] init]; - [receipts addObject:receipt]; - - [self sendElement:element withTag:TAG_XMPP_WRITE_RECEIPT]; - } + BOOL isReceiptRequested = receiptPtr != nil; + __block XMPPElementReceipt *receipt; + + dispatch_block_t block = ^{ @autoreleasepool { + if (state == STATE_XMPP_CONNECTED) + { + if (isReceiptRequested) + { + receipt = [[XMPPElementReceipt alloc] init]; + [receipts addObject:receipt]; + + [self sendElement:element withTag:TAG_XMPP_WRITE_RECEIPT registeringEventWithID:eventID]; + } else { - [self failToSendElement:element]; + [self sendElement:element withTag:TAG_XMPP_WRITE_STREAM registeringEventWithID:eventID]; } - }}; - - if (dispatch_get_specific(xmppQueueTag)) - block(); - else - dispatch_sync(xmppQueue, block); - + } + else + { + [self failToSendElement:element registeringEventWithID:eventID]; + } + }}; + + if (dispatch_get_specific(xmppQueueTag)) + block(); + else if (receiptPtr) + dispatch_sync(xmppQueue, block); + else + dispatch_async(xmppQueue, block); + + if (receiptPtr) *receiptPtr = receipt; - } } -- (void)failToSendElement:(NSXMLElement *)element +- (void)failToSendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if ([element isKindOfClass:[XMPPIQ class]]) { - [self failToSendIQ:(XMPPIQ *)element]; + [self failToSendIQ:(XMPPIQ *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPMessage class]]) { - [self failToSendMessage:(XMPPMessage *)element]; + [self failToSendMessage:(XMPPMessage *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPPresence class]]) { - [self failToSendPresence:(XMPPPresence *)element]; + [self failToSendPresence:(XMPPPresence *)element withRegisteredEvent:event]; } else { @@ -2695,20 +2717,20 @@ - (void)failToSendElement:(NSXMLElement *)element if ([elementName isEqualToString:@"iq"]) { - [self failToSendIQ:[XMPPIQ iqFromElement:element]]; + [self failToSendIQ:[XMPPIQ iqFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"message"]) { - [self failToSendMessage:[XMPPMessage messageFromElement:element]]; + [self failToSendMessage:[XMPPMessage messageFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"presence"]) { - [self failToSendPresence:[XMPPPresence presenceFromElement:element]]; + [self failToSendPresence:[XMPPPresence presenceFromElement:element] withRegisteredEvent:event]; } } } -- (void)failToSendIQ:(XMPPIQ *)iq +- (void)failToSendIQ:(XMPPIQ *)iq withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); @@ -2716,10 +2738,12 @@ - (void)failToSendIQ:(XMPPIQ *)iq code:XMPPStreamInvalidState userInfo:nil]; - [multicastDelegate xmppStream:self didFailToSendIQ:iq error:error]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didFailToSendIQ:iq error:error]; + }]; } -- (void)failToSendMessage:(XMPPMessage *)message +- (void)failToSendMessage:(XMPPMessage *)message withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); @@ -2727,10 +2751,12 @@ - (void)failToSendMessage:(XMPPMessage *)message code:XMPPStreamInvalidState userInfo:nil]; - [multicastDelegate xmppStream:self didFailToSendMessage:message error:error]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didFailToSendMessage:message error:error]; + }]; } -- (void)failToSendPresence:(XMPPPresence *)presence +- (void)failToSendPresence:(XMPPPresence *)presence withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); @@ -2738,7 +2764,9 @@ - (void)failToSendPresence:(XMPPPresence *)presence code:XMPPStreamInvalidState userInfo:nil]; - [multicastDelegate xmppStream:self didFailToSendPresence:presence error:error]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didFailToSendPresence:presence error:error]; + }]; } /** @@ -2831,7 +2859,7 @@ - (void)sendBindElement:(NSXMLElement *)element dispatch_async(xmppQueue, block); } -- (void)receiveIQ:(XMPPIQ *)iq +- (void)receiveIQ:(XMPPIQ *)iq withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2853,14 +2881,14 @@ - (void)receiveIQ:(XMPPIQ *)iq dispatch_async(willReceiveStanzaQueue, ^{ dispatch_async(xmppQueue, ^{ @autoreleasepool { if (state == STATE_XMPP_CONNECTED) { - [self continueReceiveIQ:iq]; + [self continueReceiveIQ:iq withRegisteredEvent:event]; } }}); }); } else { - [self continueReceiveIQ:iq]; + [self continueReceiveIQ:iq withRegisteredEvent:event]; } } else @@ -2896,7 +2924,7 @@ - (void)receiveIQ:(XMPPIQ *)iq if (state == STATE_XMPP_CONNECTED) { if (modifiedIQ) - [self continueReceiveIQ:modifiedIQ]; + [self continueReceiveIQ:modifiedIQ withRegisteredEvent:event]; else [multicastDelegate xmppStreamDidFilterStanza:self]; } @@ -2905,7 +2933,7 @@ - (void)receiveIQ:(XMPPIQ *)iq } } -- (void)receiveMessage:(XMPPMessage *)message +- (void)receiveMessage:(XMPPMessage *)message withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2928,14 +2956,14 @@ - (void)receiveMessage:(XMPPMessage *)message dispatch_async(xmppQueue, ^{ @autoreleasepool { if (state == STATE_XMPP_CONNECTED) { - [self continueReceiveMessage:message]; + [self continueReceiveMessage:message withRegisteredEvent:event]; } }}); }); } else { - [self continueReceiveMessage:message]; + [self continueReceiveMessage:message withRegisteredEvent:event]; } } else @@ -2971,7 +2999,7 @@ - (void)receiveMessage:(XMPPMessage *)message if (state == STATE_XMPP_CONNECTED) { if (modifiedMessage) - [self continueReceiveMessage:modifiedMessage]; + [self continueReceiveMessage:modifiedMessage withRegisteredEvent:event]; else [multicastDelegate xmppStreamDidFilterStanza:self]; } @@ -2980,7 +3008,7 @@ - (void)receiveMessage:(XMPPMessage *)message } } -- (void)receivePresence:(XMPPPresence *)presence +- (void)receivePresence:(XMPPPresence *)presence withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -3003,14 +3031,14 @@ - (void)receivePresence:(XMPPPresence *)presence dispatch_async(xmppQueue, ^{ @autoreleasepool { if (state == STATE_XMPP_CONNECTED) { - [self continueReceivePresence:presence]; + [self continueReceivePresence:presence withRegisteredEvent:event]; } }}); }); } else { - [self continueReceivePresence:presence]; + [self continueReceivePresence:presence withRegisteredEvent:event]; } } else @@ -3046,7 +3074,7 @@ - (void)receivePresence:(XMPPPresence *)presence if (state == STATE_XMPP_CONNECTED) { if (modifiedPresence) - [self continueReceivePresence:presence]; + [self continueReceivePresence:presence withRegisteredEvent:event]; else [multicastDelegate xmppStreamDidFilterStanza:self]; } @@ -3055,7 +3083,7 @@ - (void)receivePresence:(XMPPPresence *)presence } } -- (void)continueReceiveIQ:(XMPPIQ *)iq +- (void)continueReceiveIQ:(XMPPIQ *)iq withRegisteredEvent:(XMPPElementEvent *)event { if ([iq requiresResponse]) { @@ -3066,27 +3094,32 @@ - (void)continueReceiveIQ:(XMPPIQ *)iq // So we notifiy all interested delegates and modules about the received IQ, // keeping track of whether or not any of them have handled it. - GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator]; - - id del; - dispatch_queue_t dq; - - SEL selector = @selector(xmppStream:didReceiveIQ:); - dispatch_semaphore_t delSemaphore = dispatch_semaphore_create(0); dispatch_group_t delGroup = dispatch_group_create(); - while ([delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector]) - { - dispatch_group_async(delGroup, dq, ^{ @autoreleasepool { - - if ([del xmppStream:self didReceiveIQ:iq]) - { - dispatch_semaphore_signal(delSemaphore); - } - - }}); - } + dispatch_group_enter(delGroup); + id didReceiveIqProcessingToken = [event beginDelayedProcessing]; + + [self performDelegateActionWithElementEvent:event block:^{ + + GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator]; + id delegate; + dispatch_queue_t delegateQueue; + + while ([delegateEnumerator getNextDelegate:&delegate delegateQueue:&delegateQueue forSelector:@selector(xmppStream:didReceiveIQ:)]) + { + dispatch_group_async(delGroup, delegateQueue, ^{ @autoreleasepool { + + if ([delegate xmppStream:self didReceiveIQ:iq]) + { + dispatch_semaphore_signal(delSemaphore); + } + + }}); + } + + dispatch_group_leave(delGroup); + }]; dispatch_async(didReceiveIqQueue, ^{ @autoreleasepool { @@ -3144,6 +3177,7 @@ - (void)continueReceiveIQ:(XMPPIQ *)iq dispatch_release(delGroup); #endif + [event endDelayedProcessingWithToken:didReceiveIqProcessingToken]; }}); } else @@ -3151,18 +3185,24 @@ - (void)continueReceiveIQ:(XMPPIQ *)iq // The IQ doesn't require a response. // So we can just fire the delegate method and ignore the responses. - [multicastDelegate xmppStream:self didReceiveIQ:iq]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveIQ:iq]; + }]; } } -- (void)continueReceiveMessage:(XMPPMessage *)message +- (void)continueReceiveMessage:(XMPPMessage *)message withRegisteredEvent:(XMPPElementEvent *)event { - [multicastDelegate xmppStream:self didReceiveMessage:message]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveMessage:message]; + }]; } -- (void)continueReceivePresence:(XMPPPresence *)presence +- (void)continueReceivePresence:(XMPPPresence *)presence withRegisteredEvent:(XMPPElementEvent *)event { - [multicastDelegate xmppStream:self didReceivePresence:presence]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceivePresence:presence]; + }]; } /** @@ -3170,6 +3210,11 @@ - (void)continueReceivePresence:(XMPPPresence *)presence * This is an advanced technique, but makes for some interesting possibilities. **/ - (void)injectElement:(NSXMLElement *)element +{ + [self injectElement:element registeringEventWithID:[self generateUUID]]; +} + +- (void)injectElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID { if (element == nil) return; @@ -3179,18 +3224,20 @@ - (void)injectElement:(NSXMLElement *)element { return_from_block; } + + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; if ([element isKindOfClass:[XMPPIQ class]]) { - [self receiveIQ:(XMPPIQ *)element]; + [self receiveIQ:(XMPPIQ *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPMessage class]]) { - [self receiveMessage:(XMPPMessage *)element]; + [self receiveMessage:(XMPPMessage *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPPresence class]]) { - [self receivePresence:(XMPPPresence *)element]; + [self receivePresence:(XMPPPresence *)element withRegisteredEvent:event]; } else { @@ -3198,19 +3245,21 @@ - (void)injectElement:(NSXMLElement *)element if ([elementName isEqualToString:@"iq"]) { - [self receiveIQ:[XMPPIQ iqFromElement:element]]; + [self receiveIQ:[XMPPIQ iqFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"message"]) { - [self receiveMessage:[XMPPMessage messageFromElement:element]]; + [self receiveMessage:[XMPPMessage messageFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"presence"]) { - [self receivePresence:[XMPPPresence presenceFromElement:element]]; + [self receivePresence:[XMPPPresence presenceFromElement:element] withRegisteredEvent:event]; } else if ([customElementNames countForObject:elementName]) { - [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + }]; } else { @@ -4630,17 +4679,19 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element } else { + XMPPElementEvent *event = [self generateElementEventWithID:[self generateUUID]]; + if ([elementName isEqualToString:@"iq"]) { - [self receiveIQ:[XMPPIQ iqFromElement:element]]; + [self receiveIQ:[XMPPIQ iqFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"message"]) { - [self receiveMessage:[XMPPMessage messageFromElement:element]]; + [self receiveMessage:[XMPPMessage messageFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"presence"]) { - [self receivePresence:[XMPPPresence presenceFromElement:element]]; + [self receivePresence:[XMPPPresence presenceFromElement:element] withRegisteredEvent:event]; } else if ([self isP2P] && ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"])) @@ -4649,7 +4700,9 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element } else if ([customElementNames countForObject:elementName]) { - [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + }]; } else { @@ -5059,6 +5112,16 @@ - (void)enumerateModulesOfClass:(Class)aClass withBlock:(void (^)(XMPPModule *mo }]; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Element Event Context +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (XMPPElementEvent *)currentElementEvent +{ + XMPPElementEvent *event = [GCDMulticastDelegateInvocationContext currentContext].value; + return event.xmppStream == self ? event : nil; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -5080,6 +5143,23 @@ - (GCDAsyncSocket*) newSocket { return socket; } +- (XMPPElementEvent *)generateElementEventWithID:(NSString *)eventID +{ + return [[XMPPElementEvent alloc] initWithStream:self uniqueID:eventID myJID:self.myJID timestamp:[NSDate date]]; +} + +- (void)performDelegateActionWithElementEvent:(XMPPElementEvent *)event block:(dispatch_block_t)block +{ + GCDMulticastDelegateInvocationContext *eventProcessingDelegateInvocationContext = [[GCDMulticastDelegateInvocationContext alloc] initWithValue:event]; + + [eventProcessingDelegateInvocationContext becomeCurrentOnQueue:self.xmppQueue forActionWithBlock:block]; + + dispatch_group_notify(eventProcessingDelegateInvocationContext.continuityGroup, self.xmppQueue, ^{ + event.processingCompleted = YES; + [multicastDelegate xmppStream:self didFinishProcessingElementEvent:event]; + }); +} + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -5165,3 +5245,55 @@ - (void)dealloc } @end + +@implementation XMPPElementEvent + +- (instancetype)initWithStream:(XMPPStream *)xmppStream uniqueID:(NSString *)uniqueID myJID:(XMPPJID *)myJID timestamp:(NSDate *)timestamp +{ + self = [super init]; + if (self) { + _xmppStream = xmppStream; + _uniqueID = [uniqueID copy]; + _myJID = myJID; + _timestamp = timestamp; + } + return self; +} + +- (BOOL)isProcessingCompleted +{ + __block BOOL result; + + dispatch_block_t block = ^{ + result = _processingCompleted; + }; + + if (dispatch_get_specific(self.xmppStream.xmppQueueTag)) + block(); + else + dispatch_sync(self.xmppStream.xmppQueue, block); + + return result; +} + +- (id)beginDelayedProcessing +{ + GCDMulticastDelegateInvocationContext *currentContext = [GCDMulticastDelegateInvocationContext currentContext]; + NSAssert(currentContext.value == self, @"Delayed processing can only be initiated in the context matching the current event"); + + dispatch_group_enter(currentContext.continuityGroup); + + return currentContext; +} + +- (void)endDelayedProcessingWithToken:(id)delayedProcessingToken +{ + NSAssert([delayedProcessingToken isKindOfClass:[GCDMulticastDelegateInvocationContext class]], @"Invalid delayed processing token"); + + GCDMulticastDelegateInvocationContext *originalContext = delayedProcessingToken; + NSAssert(originalContext.value == self, @"Delayed processing token mismatch"); + + dispatch_group_leave(originalContext.continuityGroup); +} + +@end diff --git a/Utilities/GCDMulticastDelegate.m b/Utilities/GCDMulticastDelegate.m index 4d9f5f2ae0..5f6f3ca6b4 100644 --- a/Utilities/GCDMulticastDelegate.m +++ b/Utilities/GCDMulticastDelegate.m @@ -708,7 +708,8 @@ - (dispatch_queue_t)transferContextToTargetQueue:(dispatch_queue_t)targetQueue { dispatch_group_enter(self.continuityGroup); - dispatch_queue_t contextTransferQueue = dispatch_queue_create_with_target("GCDMulticastDelegateInvocationContext.contextTransferQueue", nil, targetQueue); + dispatch_queue_t contextTransferQueue = dispatch_queue_create("GCDMulticastDelegateInvocationContext.contextTransferQueue", NULL); + dispatch_set_target_queue(contextTransferQueue, targetQueue); dispatch_queue_set_specific(contextTransferQueue, GCDMulticastDelegateInvocationContextKey, (void *)CFBridgingRetain(self),