Issue Details (XML | Word | Printable)

Key: MULE-966
Type: Improvement Improvement
Status: Closed Closed
Resolution: Fixed
Priority: Critical Critical
Assignee: Holger Hoffstaette
Reporter: Holger Hoffstaette
Votes: 0
Watchers: 2
Operations

If you were logged in you would be able to see more operations.
Mule

Improve receiver/dispatcher state handling for better thread safety

Created: 10/Aug/06 10:19 PM   Updated: 14/Aug/07 06:30 AM
Component/s: Core: Concurrency / Threading
Affects Version/s: 1.3-rc4
Fix Version/s: 1.4.1

Time Tracking:
Not Specified

Issue Links:
Block
 
Related

Labels:


 Description  « Hide
Receivers and dispatchers are per-endpoint singletons. that has the unfortunate effect that a single receiver instance is used from multiple threads; often that means repeated work for per-instance state management. I'm sure the whole affair can be improved greatly by pooling/recycling/creating receivers per endpoint for each request with relatively little effort; that way they would be inherently threadsafe and a lot of tedious per-provider custom pooling/ThreadLocal wiggling can be cut out.

 All   Comments   Work Log   Change History   Transitions   FishEye      Sort Order: Ascending order - Click to sort in descending order
Holger Hoffstaette added a comment - 31/Aug/06 01:18 PM
The same point was raised in MULE-237 and closed as fixed..apparently too early.

Holger Hoffstaette added a comment - 06/Sep/06 01:57 PM
adding some more thoughts -
the problem is in AbstractConnector.getDispatcher(UMOImmutableEndpoint) and similarly getReceiver(UMOComponent, UMOEndpoint). A simple "fix" would be to have a KeyedObjectPool that manages things by endpoint key and only hands out a new dispatcher/receiver when the old one has been returned. I do not know (suspect not) if this is a good idea since it means that only one request or dispatch for a given connector's endpoint can run at the same time.
One other alternative approach would be to keep the currently used
ConcurrentMap + synchronized retrieval on endpoint (which is an appropriate design, scales nicely and has minimal overhead) and simply put
a pool into the map, so that each endpoint key maps to several receivers/dispatchers "standing in line". The existing Dispatcher/Receiver factory could be wrapped as PoolFactory which would take care of lifecycle control like handout/return/creation/cleanup.

Holger Hoffstaette added a comment - 08/Sep/06 09:19 AM
I gave some more thought to this problem and its wider implications. The one thing that I'm not entirely clear about is whether multiple receivers/dispatchers can or must not exist for the same endpoint? My hunch is that this depends on the concrete type of connector. If only one "connection" per endpoint is possible (like a single socket to a specific port) then the answer would be no. This might be just a specific case that either the connector or the receiver/dispatcher factories can handle (generally allowing for multiple handlers (trick question: how many? or only one as special case).

Holger Hoffstaette added a comment - 02/Feb/07 08:49 AM
Status update on this since the comments are mostly outdated so far. The management of dispatchers has been put under the control of a KeyedObjectPool that maps each endpoint to a pool of dispatchers. These are lifecycle-managed by a UMOMessageDispatcherFactory subclass that creates/activates/validates/passivates/destroys them as approrpiate. All existing transports have been adapted to this new scheme and it greatly simplifies the control flow and state management inside a singel dispatcher, which is now guaranteed to be only used by a single thread at a time. More documentation will be available on the Wiki.

Holger Hoffstaette added a comment - 02/Feb/07 09:04 AM
Lajos asked about the process of inspecting existing transports for the new dispatcher handling so here's a small writeup. It is obviously incomplete since the Wiki docs are not yet ready but it should provide simple guidelines to follow:
  • look into dispatcher/reciever and see if they have instance variables
  • if they do not: good, that means doSend/Dispatch can work on their own. In that case simply check the code in the do* methods how much external state they require and where they get it from (e.g. JdbcConnector.getConnection()) and how any acquired resources are managed & especially returned/released in case of exceptions. If JdbcConnector.getConnection() would only ever return a single shared connection, that connection would have to be guarded properly. How to do that depends on what kind of object/resource is being acquired, and needs to be documented properly.
  • if they do: make sure the instance variables are either immutable and/or participate in the lifecycle of the dispatcher. I do not have the diagrams ready yet but the activate/validate/passivate lifecycle is nested inside the connect/disconnect, so you have proper state management hooks for all points of an invocation. Other than that the same as above applies.

Basically the less external state the do* methods require, the better. Since we now have multiple dispatchers, they actually can simply use instance variables more freely; however a dispatcher still needs to make sure that they are properly managed between do* invocations.

A dispatcher can dispose() itself (e.g. after an unrecoverable exception) which by default will make AbstractMessageDispatcher.validate() return false. Any other additional condition can be used to return true or false from validate, that depends on the dispatcher subclass. This might be useful for when a dispatcher is able to keep its own "connection" as instance variable, but the connection might only be usable n times, have a lifetime or whatever.
Also dispatchers can be created per request, which is a policy implemented by the dispatcher's factory (see AbstractMessageDispatcherFactory.validate()), but this should be rarely necessary. The value used to be configurable on the connector but that made little sense since it is not really a user-configurable setting.


Holger Hoffstaette added a comment - 02/Feb/07 09:18 AM
A nice example for this process can be seen in the reduction of code as done for MULE-1347. When both the XFire client was not threadsafe itself and only one dispatcher per endpoint existed, I had to add a special pool of XFire clients from which concurrent requests were served. In the meantime the XFire client was made threadsafe and we now have multiple dispatchers per endpoint, so the problem is solved 'twice', by both parties. Nevertheless the fix on our end would have made this work even with a non-threadsafe client.

Holger Hoffstaette added a comment - 12/Feb/07 10:29 AM
Progress: basic dispatcher pool configuration is now possible as of svn r5071 and should be configurable as
connector int property "maxDispatchersActive" (per endpoint). More maintenance (e.g. shrinking a pool to recover from peaks) will come later, I'd like to avoid the builtin pool reaper thread for that because the connector's scheduler is just more appropriate.

Ross Mason added a comment - 27/Feb/07 02:30 AM
I've added a separate issue (MULE-1436) for tracking which modules are being migrate to reduce the noise on this issue.

Andrew Perepelytsya added a comment - 24/Apr/07 12:42 PM
Descoping the 1.4.1, unset Fix Version for some issues.

Holger Hoffstaette added a comment - 14/Aug/07 06:30 AM
As the blocking JIRA has been closed this can follow as well, as the basic work has been completed for a long time now. Therefore setting fix-version to 1.4.1.