Dan Haywood created ISIS-948:
--------------------------------

             Summary: 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.


[1] http://isis.markmail.org/thread/o5zbm54cpb4tizqt



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

Reply via email to