Preloaded document packet queuing
This page explains why we queue packets for unloaded documents.
PacketListener notification is done in a separate thread from packet processing. What this means is that a document can have packets addressed to it immediately after the receipt of the the IQ containing that document.
Routing packets to existing documents is done in another unrelated thread. The following scenario illustrates the problem this causes:
/index.svg is requested.
/index.svg is returned with thread ID "abc". Document loading begins.
An IQ for the document with thread "abc" arrives before the document loading is completed. Because this document has not yet loaded, and is not available to the packet router, a 404 is returned, indicating to the server that this document does not exist, or has been unloaded.
GradientSVGCanvas.managerStarted() is called, and the document is registered with the packet router. However, because of (3), the server believes this document does not exist, and will no longer update the document.
The obvious solution to this would be to synchronize part of the document request & response processing on the PacketRouter's lock. Within this synchronized block, the document loaded notifies the packetRouter that the new document (with controller JID, thead & path) exists.
When the PacketRouter's lock is released, the "Smack Listener Processor" thread resumes (if blocked), and if it then calls PacketRouter.processPacket with packets, some of which may be meant for the new document - and now we already know that to keep these packets for when the document is ready for them.
This is an attractive solution due to its simplicity.
This is not a good solution however, because we lock the entire PacketRouter on code that depends on the correctness of the URL. Bad URLs will cause the PacketRouter lock to persist for the length of the timeout on the getIQResponse method, or permanently if no timeout is specified. This would prevent all documents using that router from receiving stanzas for an unspecified length of time.
A better solution is to notify the PacketListener when we are about to load a document from a given JID with a certain path. The PacketListener from that point on stores all received packets from that JID that may be routable to this document, i.e.
- do not have a path,
- have a path equal to that of the document to be loaded, or
- have an as-yet unrecognised thread ID.
It does this for each unique document. When the document is loaded, it goes through this list and asks the canvas to process them. For packets with previously unrecognised threads, only those matching the new thread are processed.
Having to do this makes me suspect that I have an architecture problem somewhere, but if I do, then it's too deep and too big to fix at this stage.
This solution was implemented as follows. Before the document request is sent, the document loader calls PacketRouter.getPacketQueuingHandler(controllerJID, path). This returns the PacketHandler implementation that captures packets that are or may be routable to this document using the above criteria.
The GradientDocumentLoader then attempts to load the document.
If successful, then setNextDocInfo is called. This method is actually an abstract method. GradientSVGCanvas creates an anonymous inner class extending GradientDocumentLoader when the private createDocumentLoader is called. In this way, the canvas has the information for the next document.
The GradientSVGCanvas keeps a reference to the PacketQueuingHandler, and when managerStarted() is called, and the canvas can process packets, then PacketQueuingHandler.switchWithLoadedDocument is called. This method is abstract in PacketQueuingHandler, and is implemented by PacketRouter when an extending anonymous inner class is created in getPacketQueuingHandler.
When switchWithLoadedDocument is called, any packets in the queue that either have a matching path or thread (or neither) are processed by the canvas, and the loaded document takes the place of the PacketQueuingHandler in the routing trees.
This is done inside a code block synchronized on the PacketRouter instance to avoid losing packets during switching. The time this synchronization takes is not dependent upon correctness of user input or response time of network hosts, as above.
Packets that cannot be processed by the canvas (packets with unmatching threads) may either be from other documents being preloaded, or from dead threads. If they do not match the dead thread list, and no other documents with that JID are in pre-loading, then they are assumed to be dead and an error is returned, else they are silently discarded.
Bugs in the code that implement this solution should be apparent in TestRPCService behaviour.
© 2006. Some rights reserved. Author: Ian Sollars.