ralscha / sse-eventbus

EventBus library for sending events from a Spring appliction to the web browser with SSE
Apache License 2.0
82 stars 28 forks source link

Impossible to trace the events really published #13

Closed ajnfde closed 4 years ago

ajnfde commented 4 years ago

When we create an emitter with the method createSseEmitter(String clientId, String... events) of SseEventBus class, we cannot log the messages really published.

In our case, we want to know if it our back app which doesn't publish some events or if it is our front app (angular 8) which has a trouble in the message reception.

ralscha commented 4 years ago

When you use the publisher approach you can add an event listener method into your code. This method then receives every message you send in your application.

@Service
public class MyService {
    private final ApplicationEventPublisher eventPublisher;
    public void broadcastEvent() {
        this.eventPublisher.publishEvent(SseEvent.ofData("some useful data"));
    }

    @EventListener
    public void handleEvent(SseEvent event) {
            // do something here
        }

}

When you use the other approach there is currently no way to log the messages.

@Service
public class DataEmitterService {
    private final SseEventBus eventBus;
    public void broadcastEvent() {
        this.eventBus.handleEvent(SseEvent.ofData("some useful data"));
    }

}

We could solve this by either adding log statements to the library or adding some lifecycle hooks. Hooks could be interesting not only for logging but also for statistic tracking.

ralscha commented 4 years ago

With the new version 1.1.8 you can now install a lifecycle listener

public class MyListener implements SseEventBusListener {

  /** An event has been added to the internal queue */
  @Override
  public void afterEventQueued(ClientEvent clientEvent, boolean firstAttempt) {
  }

  /** An event has been sent to the client. 
          exception -> null: event has been sent successfully
          exception -> not null: an error has occurred during sending
  */
  @Override
  public void afterEventSent(ClientEvent clientEvent, Exception exception) {
  }

  /** Library has removed one or multiple stale clients */
  @Override
  public void afterClientsUnregistered(Set<String> clientIds) {
  }
}
@SpringBootApplication
@EnableSseEventBus
public class MyConfiguration implements SseEventBusConfigurer {
  @Override
  public SseEventBusListener listener() {
    return new MyListener();
  }

This should give an application a bit more insight into the internal workings of the libraries and I hope helps for tracking errors.

ajnfde commented 4 years ago

When an error has occurred during sending the event, is the event discarded or saved in the queue?

ralscha commented 4 years ago

The library stores failed messages into a queue and tries to send them again.
By default the library tries to send failed messages every 500 milliseconds and it does that 40 times. After 40 failed send tries it discards the message. You can configure these parameters with the configurer.

https://github.com/ralscha/sse-eventbus/blob/master/src/main/java/ch/rasc/sse/eventbus/config/SseEventBusConfigurer.java

ralscha commented 4 years ago

The afterEventQueued hook should be called each time a message is added because of a failure.

ajnfde commented 4 years ago

Thank you @ralscha for your answer. In production, it seems to us, in certain conditions, many messages stay in the the sendQueue. We want to understand if there was a problem which causes this sendQueue state or if it is large memory used by the queue which causes the JVM error.

ralscha commented 4 years ago

You find the code for the event loop here: https://github.com/ralscha/sse-eventbus/blob/master/src/main/java/ch/rasc/sse/eventbus/SseEventBus.java#L264-L292

In my productive app I use the defaults of 40 retries and 500ms between retries but I shortened the client expiration parameters.

@Configuration
public class SseEventBusConfiguration implements SseEventBusConfigurer {

    @Override
    public Duration clientExpiration() {
        return Duration.ofMinutes(30); // default 1 day
    }

    @Override
    public Duration clientExpirationJobDelay() {
        return Duration.ofMinutes(5);  // default 1 da
    }
}
ajnfde commented 4 years ago

I suggest you some improvements:

ralscha commented 4 years ago

I wonder what exceptions could occur in these methods. In reScheduleFailedEvents only two method calls throw exceptions: .drainTo and .put
These two calls could throw: InterruptedException, UnsupportedOperationException, ClassCastException, NullPointerException and IllegalArgumentException.

Unsupported, class cast and null pointer are never possible I guess. Interrupted exception is handled. The only question is the illegal argument exception. Not sure in what situation this exception can occur.

ajnfde commented 4 years ago

@ralscha I agree with you about the risk to get an exception. But we got a outOfMemory in our back application. A snapshot of the JVM showed us the sendQueue took more than 200 MB. We don't have any explanation of that.

ralscha commented 4 years ago
  • Set Client class public
  • Log the errors in the eventLoop method. It is the same thing for the reScheduleFailedEvents method

Released 1.1.9 with these changes.