Too many asynchronous setup steps needed for basic usage
Submitted by Olli Salli
Assigned to Telepathy bugs list
Telepathy-Qt4 has a model to avoid making synchronous D-Bus calls when creating proxy objects where no state download over D-Bus is initially done, but only started when the application calls becomeReady() on the object with the desired "features" (optional functionality items, each one causing additional D-Bus state download round-trips and potentially more wakeups on state changes). The state download getting finished is signaled using a Qt signal finished() on the PendingOperation subclass returned from the becomeReady() call.
While this approach guarantees no synchronous D-Bus calls (potentially causing deadlocks and reduced responsibility in general) are made, and allows synchronous cached access to the downloaded data afterwards, the initial setup code in applications becomes very complex. Real-world use cases require setting up at least (in order) AccountManager, Account and Connection objects, with each stage requiring an asynchronous wait for the object to become ready before requesting the next level of object. In addition, there is more waiting for signals required:
- A ready Account signals haveConnectionChanged() when it can form a Connection object corresponding to the connection to that account
- Waiting the Connection to become Connected once you have it
- Building Contact objects and/or upgrading them to have more features requires an async step
- "Upgrading" objects to have more features requires yet another becomeReady() and finished() wait just like Contacts
It should be kept in mind that any operation really requiring additional info to be fetched from the CM does and always will require asynchronous logic. This is simply the nature of using objects with data and methods with return values over a message-passing system like D-Bus. However, we could reduce the amount of async setup steps by doing the likely next step in advance, for example:
- allow specifying which Account features should be ready in any Account an AccountManager gives you, and make AccountManager make them ready before signaling accountCreated() and signaling itself being ready in the initial accounts download on AM::becomeReady() -> no need to do Account::becomeReady() on accounts from the AM
- allow specifying which Connection features (on which Connection subclass) should be made ready before signaling Account::haveConnectionChanged(true) -> no need to do Connection::becomeReady() on whatever Account::connection() gives you
- allow specifying which Contact features should always be ready for whatever is in ContactManager::allKnownContacts()
- allow specifying which Contact features should always be ready for a contact signaled as having joined a Channel (this should be documented as dangerous if used with too many features - if the contact in question leaves the channel before we finish introspecting every feature, the operation may fail and we won't necessarily even know who it was)
- allow specifying which Channel features are always interesting for any given channel class, so that Channels given to you when you're a Client or when you request one in general are always ready with the desired features (requires extending ChannelFactory to accept dynamically overriding which Channel subtypes to construct, and which features to begin making ready for a given set of immutable properties making up the channel class)
I'll file the corresponding sub-bugs as dependencies when I have time to do so. Suggestions, especially turning in more superfluous setup step suspects kindly welcome. The suggestions above can be implemented in a backwards-compatible fashion, but we should identify beneficial candidates for an API/ABI break too.
The basic assumption above is that each application has a basic set of features they're always interested in, and will want for every object. Stuff that an application is only potentially interested in (menu option to show eg. avatars for chat participants, the user opening a "more gory details on your friend" expander widget, and so on) can always be "upgraded" afterwards on top of the base set in the current fashion (becomeReady() with more features, upgradeContacts()). Also, in more sophisticated applications, whatever can be lazily updated to the view, should be lazily updated after an upgrade operation finishes, and should be documented as the recommended action in tp-qt4 docs.
One final point is - whatever we do, this shouldn't counteract trying to avoid extra D-Bus state download and signal connections to uninteresting information (forcing Features on etc). Also, we shouldn't hamper subclassability. Even if Account constructs Connection objects for you you should be able to specify the Connection subclass and desired Connection features, etc. This could be accomplished in general using a factory pattern, where you specify a factory function returning the desired subclass and the features to always make ready in constructed objects.
To keep the factories simple, they should only begin the process of making the desired Features ready (and do the other misc manipulation on the constructed object). The constructed object should be returned immediately. The object (eg. AM) which requested constructing the object (the Account) should then wait for the object to get ready, with no further interaction with the factory for that object.
Version: git master