Shikhar
Shikhar

Reputation: 377

Throw exception from Guava EventBus subscriber

I am using Guava EventBus in sync. How can I rollback the complete transaction if any of the subscribers throw an Exception? How can I throw an Exception which will not be caught by the EventBus Subscriber?

Upvotes: 1

Views: 1965

Answers (3)

Shikhar
Shikhar

Reputation: 377

I solved it using java.lang.ThreadLocal variable across the publisher and the subscriber.

Publisher needs to be wrapped in a class which reads thread local exception and throws it

public void publish(Event event) {
  eventBus.post(event);
  if(threadLocalException != null) {
    Store threadLocalException in some variable say e
    Clear threadLocalException variable
    throw e;
  }
}

Subscriber needs to be wrapped in a class to set the exception in thread local variable

public abstract class EventSubscriber<T extends Event> {

  @Subscribe
  public void invoke(T event) {
    try {
      handle(event);
    } catch (Exception e) {
      Set thread local variable to e
    }
  }

  protected abstract void handle(T event);

}

Upvotes: 0

TimYi
TimYi

Reputation: 461

Inherit EventBus and make your own eventBus which will throw exception. The package must be com.google.common.eventbus for handleSubscriberException is an internal method.

package com.google.common.eventbus;

import com.google.common.util.concurrent.MoreExecutors;

/**
 * A eventbus wihch will throw exceptions during event handle process.
 * @author ytm
 *
 */
public class ErrorThrowEventBus extends EventBus {

    /**
     * Creates a new EventBus with the given {@code identifier}.
     *
     * @param identifier a brief name for this bus, for logging purposes. Should be a valid Java
     *     identifier.
     */
    public ErrorThrowEventBus(String identifier) {
        super(
            identifier,
            MoreExecutors.directExecutor(),
            Dispatcher.perThreadDispatchQueue(),
            LoggingHandler.INSTANCE);
    }

    /**
     * Creates a new EventBus with the given {@link SubscriberExceptionHandler}.
     *
     * @param exceptionHandler Handler for subscriber exceptions.
     * @since 16.0
     */
    public ErrorThrowEventBus(SubscriberExceptionHandler exceptionHandler) {
        super(
            "default",
            MoreExecutors.directExecutor(),
            Dispatcher.perThreadDispatchQueue(),
            exceptionHandler);
    }

    /**
     * Just throw a EventHandleException if there's any exception.
     * @param e
     * @param context
     * @throws EventHandleException 
     */
    @Override
    void handleSubscriberException(Throwable e, SubscriberExceptionContext context) throws EventHandleException {
        throw new EventHandleException(e);
    }
}

Upvotes: 0

Maciej Dobrowolski
Maciej Dobrowolski

Reputation: 12140

All you have to do is to look at the source code of Guava's EventBus class.

Let's start from the end:

How can I throw an Exception which will not be caught by the EventBus Subscriber?

Subscribers' methods are called in sequence, one after another, by com.google.common.eventbus.Dispatcher#dispatch method. To call methods of your Subscribers, EventBus use reflection's method Method#invoke which, in turn, throws InvocationTargetException if called method throws an exception.

As you can also see, InvocationTargetException (which will be wrapped around your Exception) is handled as follows:

} catch (InvocationTargetException e) {
  if (e.getCause() instanceof Error) {
    throw (Error) e.getCause();
  }
  throw e;
}

at the upper level, exception is handled like that:

try {
  invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
  bus.handleSubscriberException(e.getCause(), context(event));
}

TL;DR

So, the only way to omit EventBus exception handler is to throw not Exception, but Error in your subscribing method - what is certainly a bad practise.

How can I rollback the complete transaction if any of the subscribers throw an Exception?

EventBus exception handler handles exceptions by calling com.google.common.eventbus.EventBus#handleSubscriberException method. It looks like this:

try {
  exceptionHandler.handleException(e, context);
} catch (Throwable e2) {
  // logging
}

So, any exceptions thrown from exception handler will not help. You have two choices:

  1. Either throw Error from your subscriber method (it's sooo bad)
  2. Or manually set transaction as rollback-only from any place in this flow. I think that the best place for such things is obviously EventBus exception handler.

Upvotes: 1

Related Questions