Hot questions for Using EventBus in exception

Question:

I have a verticle that creates an event bus consumer as follows:

    public void start() {
        vertx.eventBus().consumer(ADDRESS_REQUEST, this::handleRequestMessage);

    }

    private void handleRequestMessage(Message<VWApiConversation> msg) {

       VWApiConversation conversation = msg.body();

    }

But when sending a message to this address :

 vertx.eventBus().send(VehicleStateCoordinatorVerticle.ADDRESS_REQUEST, conversation, deliveryOptions, res -> {

...

I get the error :

java.lang.IllegalArgumentException: No message codec for type: class com.vulog.vwgateway.model.VWApiConversation

Am I missing something?


Answer:

Vert.x supports serializing JVM primitives, Buffers, and JsonObjects by default. for other custom types you'll need to write your own MessageCodec.

here's some documentation that might be of help:

  • the official docs has a few notes about this. the section titled "Types of messages" will be of particular interest to you.
  • here is a sample MessageCodec implementation. (not shown in this snippet is registration of the codec via EventBus.registerCodec().)

for my taste i've always used JsonObject as the messaging medium (as my setups have enabled me to). seems like a hassle to write custom (de)serializers for every domain type.

Question:

So, my app exchanges messages from/to my MainActivity to/from a background Service and I used EventBus to handle that. I'm registering both components with

EventBus.getDefault().register(this);

on their onCreates. And I'm sending/receiving an event with:

EventBus.getDefault().post(new MyMessagePojo("message"));

and

public void onEvent(MyMessagePojo event) { ... }

Everything works well when I run the project from my AndroidStudio right to my test phone. However, when I generated the signed APK I installed the app and got a crash with the following exception:

Subscriber class my.package.MainActivity has no public methods called onEvent

Where it clearly has. I tried changing it from onEvent to onEventMainThread in my MainActivity but with no success. It's pretty frustrating since I was about to publish the app and now I can't fix this problem.

I've been through this and this but they were of no help.

Any ideas?


Answer:

This is is addressed in the EventBus HOWTO docs. https://github.com/greenrobot/EventBus/blob/master/HOWTO.md

Use the below code to keep only the onEvent functions.

-keepclassmembers class ** {
    public void onEvent*(**);
}

Question:

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?


Answer:

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.

Question:

Time to time I get this exception.

I just use green-robot the standard way, between views, fragments, activities, services and the app, with the default instance, and time to time some stickyEvents.

I did't find any other post that are related to this exception. Any idea, or hint to start my investigation ?

The event bus is working nicely (~ 20 events, 10 subscribers), everything is user triggered so no big workload in the field.

the complete stack-trace is here :

de.greenrobot.event.EventBusException: Invoking subscriber failed
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at de.greenrobot.event.EventBus.handleSubscriberException(EventBus.java:518)
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at de.greenrobot.event.EventBus.invokeSubscriber(EventBus.java:500)
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at de.greenrobot.event.EventBus.postToSubscription(EventBus.java:429)
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at de.greenrobot.event.EventBus.postSingleEventForEventType(EventBus.java:410)
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at de.greenrobot.event.EventBus.postSingleEvent(EventBus.java:383)
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at de.greenrobot.event.EventBus.post(EventBus.java:263)
10-27 15:37:00.522 25414-25414/fr.tech.app..u E/AndroidRuntime:     at fr.u.app.u.Dialog.TastingNavigationDialog$1.onSelection(TastingNavigationDialog.java:42)

error is triggered from a MaterialDialog instance :

  dialogBuilder = new MaterialDialog.Builder(context)
            .title(R.string.dialogTastingNavigationTripTitle)
            .negativeText(R.string.buttonCancel)
            .cancelable(false)
            .adapter(listAdapter, new MaterialDialog.ListCallback() {
                @Override
                public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) {
                    EventBus.getDefault().post(new TastingPageToEvent(listAdapter.list.get(which), which));
                    dialog.dismiss();
                }
            });

Edit

I found one thing that trigger the exception, is posting one stickyEvent from one Fragment. It is intended that a appearing soon fragment will be able to get back that sticky event.

When going to Eventbus source, it stops at :

   void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

Edit

Here is the scenario.

public class TopEvent {
    String name;
    public TopEvent(String name){
        this.name = name;
    }
}

public class MyEvent extends TopEvent {
    String extraInfo;
    public MyEvent(String name, String extra){
        super(name);
        this.extraInfo = extra; 
    }
}

Here is one Fragment Tx :

...
EventBus.getDefault().postStickyEvent(new MyEvent("Stack","Overflow");
...

Here is a second Fragment Rx

...
String extra = EventBus.getDefault().getStickyEvent(MyEvent.class).extraInfo;
...

Here is a service (the one that got a Strange behavior)

public class MyService extends ServiceĀ {

    ...

    EventBus.getDefault().registerSticky(this)

    onEvent(TopEvent event){
       ...
       ...
    }

}

At end of the onEvent, the exception is thrown. As if they were some hidden behavior with sticky extended Event (from super) posted that triggers non-sticky event of super event.


Answer:

This is was due to EventBus configuration :

EventBus.builder().throwSubscriberException(BuildConfig.DEBUG).installDefaultEventBus();

However I have difficulties to understand why this option is interesting. In fact we only (almost) see the exception of the subscriber that failed, because there were another exception of a subscriber. But it is often hidden. So for why is this interesting ?