[ 
https://issues.apache.org/jira/browse/ISIS-948?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Dan Haywood resolved ISIS-948.
------------------------------
    Resolution: Fixed

> Fix concurrent exception in EventBus, improve support for request-scoped 
> services
> ---------------------------------------------------------------------------------
>
>                 Key: ISIS-948
>                 URL: https://issues.apache.org/jira/browse/ISIS-948
>             Project: Isis
>          Issue Type: Improvement
>          Components: Core
>    Affects Versions: core-1.7.0
>            Reporter: Dan Haywood
>            Assignee: Dan Haywood
>             Fix For: core-1.8.0
>
>
> This ticket was prompted originally by [1], flagging up a concurrency issue.
> The background here is that EventBusService (a singleton) manages the 
> registered subscribers for the guava EventBus, but the EventBus itself is 
> stored internally by the framework on IsisSession, ie is in effect 
> request-scoped.  There are some special-case hooks to call open() and close() 
> on EventBusService when the xactn is started/completed so that the service 
> can call register() on the guava EventBus object for each interaction.
> The concurrency exception is (I think) because the set used in 
> EventBusService was not thread-safe; should've been ConcurrentHashSet or 
> similar.
> ~~~
> Thinking about this issue has led me to consider whether the guava EventBus 
> should have been singleton rather than request-scoped, in particular with 
> respect to whether we can accommodate request-scoped subscriber services as 
> well as singleton services.   Have come to the conclusion that it doesn't 
> actually matter either way; it would be safe to have a singleton event bus 
> having request-scoped services as subscribers because the proxies for those 
> services would always route the method call to the on(Event) to the 
> appropriate underlying service from its proxy's threadlocal.
> That said, have decided to keep the EventBus request-scoped.  But rather than 
> have all the special case logic, instead wrap the guava EventBus in a new 
> RequestScopedEventBus domain service (as its name suggests, is 
> @RequestScoped) and have this act as the container of the guava EventBus and 
> also keep track of the subscribers.
> For its part, the original EventBusService remains as a singleton whose job 
> it is to keep track of all the subscribers.  Some of these are singletons 
> that will register in the system bootstrapping (during their @PostConstruct 
> lifecycle).  Some of these might also be request-scoped services.  So far as 
> possible want the model to be the same... that they can register in their own 
> @PostConstruct lifecycle.  Now for these request-scoped services, the 
> @PostConstruct should actually be called after injection of the 
> EventBusService...  The one slight modification is that they should not 
> register themselves but instead their proxy.  They can get hold of their 
> proxy simply by having it be injected:
> eg.
> {code:java}
> @RequestScoped @DomainService
> public class MySubscribingService {
>     private EventBusService ebs;
>     public void injectEventBusService(EventBusService ebs) { this.ebs = ebs; }
>     private MySubscribingService proxy;
>     public void injectProxy(MySubscribingService proxy) { this.proxy = proxy; 
> }
>     @PostConstruct
>     public void startRequest() {
>        ebs.register(proxy);
>     }
>     @PreDestroy
>     public void endRequest() {
>        ebs.unregister(proxy);
>     }
> }
> {code}
> Note also that we now allow injection into request-scoped services (though 
> must use injectXxx rather than @Inject xxx).
> In the EventBusService, because request-scoped services might be continually 
> registering and unregistering, but also each will be registering their proxy, 
> then this needs to change to do reference counting.  Thus, if there are two 
> concurrent threads then the MySubscribingService (above) will be instantiated 
> twice, each held in the thread-local of the proxy.  This proxy will (in the 
> @PostConstruct) be registered with the EventBusService twice (for each 
> thread), but of course that proxy should only be registered with the guava 
> EventBus once.
> The RequestScopedEventBus is responsible for instantiating the guava 
> EventBus, but it does this lazily, when the first call to post(...) is made.  
> At this time it calls back to the EventBusService asking for the current 
> subscribers.  The EventBusService will return all the singletons (because 
> they will always have a reference count = 1) plus it will return the proxy of 
> all the subscribing request-scoped services (because there will always at 
> least one current thread that would've caused its proxy to have been 
> registered).
> As I say, an alternative design could probably have dispensed with the 
> reference counting and having the guava EventBus being request-scoped; this 
> simpler design would instead simply hold a set of subscribers in 
> EventBusService.   The singleton subscribing services would all be added 
> during bootstrap, while the (singleton) proxy for all the request-scoped 
> subscribing services would be added in the very first transaction.
> Might end up refactoring to this simpler design as and when we bring CDI into 
> the mix.
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> UPDATE: as the commits below perhaps indicate, in the end I've decided to go 
> the extra step and to refactor the design so that the guava EventBus is a 
> singleton, held by EventBusService (and new'd up by the 
> EventBusServiceDefault subclass).  This removes lots of complexity at the 
> expense of a no-longer fully symmetric design:
> - the event bus is new'd up only when needed, NOT in the @PostConstruct of 
> the EventBusService
> - request-scoped services get re-registered for each subsequent xactn, and 
> these additional re-registrations are simply ignored.  
> - request-scoped services must NOT unregister themselves.  In fact, the 
> EventBusService's unregister is now a no-op.
> Have also added in some checks to ensure that request-scoped services only 
> ever register their proxy.
> [1] http://isis.markmail.org/thread/o5zbm54cpb4tizqt



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to