Proposal to Deprecate IMultithreaded Interfaces
Background Information
Let me start out by saying this proposal doesn't propose that support be dropped for IMultithreaded interfaces. This proposal does propose that they should be marked as deprecated and as appropriate IMultithreaded channels should be refactored as Channels or Portlets. Eventually there might come a time when most current IMultithreadedChannel implementations have dropped IMultithreaded and we can simplify and improve the performance of uPortal by dropping support for this interface, but there are no near term plans to drop that support. Also, we need to make it clear that IMultithreaded* channels shouldn't be perceived as a better, faster more optimized way of developing a channel.
Advantages of Implementing the IMultithreaded* Interfaces
Reduces calls to class.instanceOf()
IMultithreaded* allows the running portal to minimize the number of calls to class.newInstance(), because this call happens once per channel class name and then, for IMultithreadeds, the instantiated class instance is stored in a static Map so that from then on it can be looked up by String class name. Map lookup, wrapper instantiation, and channel state object instantation do not add up to quite as much performance cost as does the class.instanceOf() excecution. However, the performance difference is negligible, may be outweighed by the additional runtime costs of IMultihreaded in channel state map synchronization and access costs, and all of this will be drowned out by the other costly operations involved in running the portal (executing transforms, database access, serializing the output, doing whatever valuable thing your channel does, etc.)
Drawbacks of Implementing the IMultithreaded* Interfaces
Complexity
Channels implemented as IMultithreaded* are much more complex than normal IChannels with the same features. This is a serious problem in an open source project. It is a serious barrier to collaboration, and this reason alone is enough to warrant deprecation.
IMultithreaded re-invents Statics
An object keyed off of a classname, such that only one such object for a classname can exist in a given Classloader, is a static field. This is what a static field is - an object keyed off of a classname.
IMulithreaded re-invents this. IMultithreaded asks the portal to guarantee that all boxes using this channel will be backed by the same IMultithreaded instance, such that the instance fields in that channel instance are shared across all usages of the channel. This is completely equivalent to writing an IChannel and placing those shared objects in static fields. It's an increasing best practice to avoid the use of static fields, which are well understood - it should be even more a best practice to avoid the use of an equivalent facility that is much less well understood.
No Session Replication
The static scoped state map won't be serialized and replicated so the channel will reset or display with an error once serialized and reloaded.
Potential Memory Leaks
They require the channel programmer to code the channel to remove objects from the state map when a user's session is removed. This easily results in a channel that leaks memory if implemented incorrectly. It also moves into custom code garbage collection that would otherwise be performed by the Java runtime environment's garbage collector. That is, with IChannels, when a user's session goes out of scope, the IChannel instances used to support that session also go out of scope and become available for garbage collection. IMultithreadeds never go out of scope (are stored in a ChannelFactory static map) and so custom code is required by the channel developer to remove from an internal Map the ChannelState instance representing a particular usage of the IMultithreaded. There was a bug for some time in uPortal wherein the appropriate session ending events were not propogated in certain exceptional conditions, leading IMultithreadeds reliant upon this event propogation to accumulate state in their maps ("leaking memory"). The framework bug blocking event propogation has since been resolved, but the experience highlights the unnecessary dependency of IMultithreadeds on event propogation to accomplish something IChannels accomplish more naturally.
Less Diagnostic Information
IMultithreaded* channels also can't expose much useful information in their toString() methods to let us know what is going on when a
channel fails to render. IChannels can provide all the relevant information needed to diagnose a rendering failure including which user
the problem occurred with and what the publish/subscribe id of the channel is. The IMultithreaded* interfaces would need to be modified to have a method toString(uid) that would take the user's session/uid as a parameter in order to print out the same useful information, and then code using this method would need to be aware that it was using an IMultithreaded and the appropriate key. This highlights that "IMultithreadeds are weird, not behaving like normal Java objects."
More, not less, object creation
Use of IMultithreaded* channels leads to more, not less, object creation in a running uPortal instance. This is because ChannelRenderer knows only how to render IChannels. All IMultithreadedChannels must be wrapped in IChannels in order to be rendered. So, for each box in uPortal represented by an IChannel, we have one IChannel instance. And for each box in uPortal represented by an IMultithreadedChannel, we have an IChannel instance (the wrapper) and the IMultithreadedChannel instance. IMultithreadedChannels typically include a Map of ChannelState instances. For each box in uPortal represented by an IMultithreadedChannel, we have one such ChannelState instance.
The result is more, not less, object creation. Creating objects isn't actually that expensive and this factor-of-two additional object creation almost certainly doesn't matter, but it is clear that IMultithreaded increases, rather than reduces, object creation overhead.
Additional locking and unlocking, synchronization costs
With normal IChannels, each box a user sees in a portal is backed by an IChannel object instance which includes the state for that one box. With IMultithreadeds, the IChannel for each box is just a wrapper for a single IMultithreaded instance shared across all users. That IMultithreaded includes a state Map with uid keys into the Map to find the appropriate state to service a particular box (uid corresponds one to one with wrapper IChannel reference).
This creates a synchronization problem inside the IMultithreaded implementation. Access to the Map must be synchronized such that one channel usage doesn't add to or delete from the Map concurrently with another's accessing the Map. So synhronization is required, which adds locking and unlocking costs. Implementing the same functionality with an IChannel avoids this locking and unlocking, since there is no shared Map of channel states.
Sharing the Map also creates a slim theoretical potential for insteresting bugs, wherein one user's channel instance incorrectly accesses the backing state map and uses another user's channel state information. Such a bug is less feasible to introduce to traditional IChannels, since they do not share state across usages.
What IMultithreaded does to ChannelFactory
Honoring the IMultithreaded gaurantees forces ChannelFactory to store a Map from channel class name to channel instance. It also requires ChannelFactory to lock on channel instantiation while it detects whether the requested channel is IMultithreaded, whether it is in the Map if it is, and while it instantiates it if it either isn't IMultithreaded or is IMultithreaded but is not yet instantiated. This results in ChannelFactory serializing (with lock acquisition and release) instantiation of all channels, not just IMultithreadeds. Were ChannelFactory not supporting IMultithreaded, this locking would not be required and channel instantation could be serviced by multiple threads concurrently.
Supporing IMultithreaded also complicates ChannelFactory, with its logic tree and instanceof checks on the channel being instantiated to see whether and what IChannel-implementing-wrapper to use around the channel.
Since IMutlithreaded* is a published and commonly used interface, uPortal cannot in the near term realize the efficiencies of dropping support for this interface.
IMultithreaded makes it hard to talk about uPortal
I don't mean this in jest. Writing this document is hard. Where I'd like to talk about "channel instances", I can't, and instead have to invent "channel usages", because IMultithreaded uses a single object instance with a keyed internal map to represent many boxes across many users' layouts. IChannel uses one object instance to represent one box in one user's layout. This is more articulable, easier to understand. Simplicity is worth something.
Alternatives to using IMultithreaded
IMultithreaded re-implements statics
IMultithreaded provides a way to share state across the objects backing boxes rendered in users layouts (the usages of the channel). Using IMultithreaded is entirely equivalent to using IChannel with static fields for state desired to be shared across all instances of the channel. Any given IMultithreaded* channel can be refactored as an IChannel with its instance fields become static fields.
Share services, not channel instance state
Including a Map with everyone else's ChannelState objects doesn't help IMultithreaded service my request. Everyone else's state is irrelevant to my request and it's just junk to be carefully ignored in a threadsafe, synchronized way.
However, it is entirely plausible that there are some things that can be shared across channel instances. An efficiency might be to lookup a portal.properties property once rather than once per channel instance. Or an Announcements channel implementation should be backed by an Announcements service rather than stuffing your institution's entire Announcements infrastructure into the channel implementation.
And these services can be shared. They can be grabbed from Factories using static initializers if they need to be shared statically. They can be grabbed from Spring if you'd like to declaratively configure them. They could even be grabbed from JNDI. Writing traditional IChannels, rather than IMultithreadeds, does not by any means mean that your channels cannot share the state and services they ought to share. Avoiding IMultithreaded does, however, help channels to avoid sharing state they don't need to share, state that just gets in the way.
Summary
There are no important advantages of IMultithreaded* to be had, and there are real costs of implementing this interface. It is clear that we should discourage implementation of channels as IMultithreaded*, and I think deprecating the interfaces is the clearest way to communicate this without ambiguity.
Proposal Implementation
- Apply patch attached to UP-1362: Allow multiple threads to concurrently instantiate channels
- Mark all IMultithreaded* interfaces in uPortal 2.5-patches and HEAD as deprecated.
- Discourage use of these deprecated APIs
- Refactor the core channels to implement IChannel* .
- These framework channels implement one of the IMultithreaded* interfaces:
- CGroupsManager (Patch attached to UP-1371)
- CPortletAdapter (Patch attached to UP-1300)
- CWebProxy (Patch attached to UP-1302)
- CApplet (Patch attached to UP-1369)
- CGenericXSLT (Patch attached to UP-1303)
- CImage (Patch attached to UP-1367)
- CInlineFrame (Patch attached to UP-1368)
- CPersonAttributes (Patch attached to UP-1370)
- These framework channels implement one of the IMultithreaded* interfaces:
- Adopt a policy that no new channels implementing IMultithreaded*will be accepted into the framework (uPortal source tree)
- Propose a presentation for JA-SIG Vancouver conference "Why IMultithreaded is Deprecated and How to Refactor Your Channels away from IMultithreaded"