bennidi / mbassador

Powerful event-bus optimized for high throughput in multi-threaded applications. Features: Sync and Async event publication, weak/strong references, event filtering, annotation driven
MIT License
955 stars 146 forks source link

Lock on handler (or Listener) #152

Closed ndr-brt closed 7 years ago

ndr-brt commented 7 years ago

Hi, how can I lock a single handler (or entire listener) to avoid concurrent event handling in asyncronous mode?

ndr-brt commented 7 years ago

Ok, I handle lock on single Listener with the "Synchronized" annotation, but there's a way to obtain lock on the entire listener?

bennidi commented 7 years ago

Individual synchronization scenarios are not handled within mbassador. Handle synchronization within your client code (using java.concurrent.lock.* classes).

2017-06-26 13:40 GMT+02:00 Andrea Bertagnolli notifications@github.com:

Ok, I handle lock on single Listener with the "Synchronized" annotation, but there's a way to obtain lock on the entire listener?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/bennidi/mbassador/issues/152#issuecomment-311035502, or mute the thread https://github.com/notifications/unsubscribe-auth/ABwc9HB6Abs0pFL3bAviAC7ZPgid9mD1ks5sH5irgaJpZM4OFMPg .

ndr-brt commented 7 years ago

You think this could be a future feature? It will be intersting for my case...

In this example, I need that when I launch two events in this order: EntityCreatedEvent EntityUpdatedEvent

The listener will wait the handle of the EntityCreatedEvent before handling the EntityUpdatedEvent.

public class Listener {
    private Repository repository;

    @Handler
    public void consume(EntityCreatedEvent event) {
        repository.insert(readModelFrom(event));
    }

    @Handler
    public void consume(EntityUpdatedEvent event) {
        ReadModel model = repository.get(event.getId());
        model.updateWith(event);
        repository.update(model);
    }
}
techyourchance commented 7 years ago

How about this:

public class Listener {

    private final Object LOCK = new Object();
    private Repository repository;

    @Handler
    public void consume(EntityCreatedEvent event) {
        synchronized(LOCK) {
             repository.insert(readModelFrom(event));
        }
    }

    @Handler
    public void consume(EntityUpdatedEvent event) {
        synchronized(LOCK) {
            ReadModel model = repository.get(event.getId());
            model.updateWith(event);
            repository.update(model);
        }
    }
}

This is the simplest approach that will synchronize everything. If you want to perform synchronization only if events come in a specific order you can use something like Semaphore.

bennidi commented 7 years ago

That is one way to do it but the lock scope is really broad. You are limiting write concurrency to one at a time. I assume you want to prevent a model instance from being updated before its initial creation has completed, so the lock monitor should be an object associated with a particular model instance (by its uuid for instance).

Another option is to handle the case in the update handler and not use a lock at all. If the read method does not return a model then it is not yet created. You could put all model updates in queue and have workers consume updates from there. Whenever a model does not exist the update is put back on the queue.

2017-06-27 9:26 GMT+02:00 techyourchance notifications@github.com:

How about this:

public class Listener {

private final Object LOCK = new Object();
private Repository repository;

@Handler
public void consume(EntityCreatedEvent event) {
    synchronized(LOCK) {
         repository.insert(readModelFrom(event));
    }
}

@Handler
public void consume(EntityUpdatedEvent event) {
    synchronized(LOCK) {
        ReadModel model = repository.get(event.getId());
        model.updateWith(event);
        repository.update(model);
    }
}

}

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/bennidi/mbassador/issues/152#issuecomment-311275982, or mute the thread https://github.com/notifications/unsubscribe-auth/ABwc9H3SqJ-3pH576tpvr0tgEOIzBPwHks5sIK64gaJpZM4OFMPg .

ndr-brt commented 7 years ago

@bennidi yeah, the second solution will the best in this case, but there could be a failing scenario:

EntityCreatedEvent -> takes some time to be fully consumed EntityUpdatedEvent n1 -> try to get, but there's no read model, so i put it in queue (meanwhile read model is created) EntityUpdatedEvent n2 -> gets consumed, before the n1

I think that this problem could be handled by mbassador, giving opportunity to serialize all the events in a single listener. One futhcer enanchement could be to serialize them by a value like uuid or entity id (present in the event).

bennidi commented 7 years ago

The ordering problem you describe can be circumvented with use of (a comination) of appropriate data structure (an ordered multi-map for instance). I think that you are describing a problem that is not meant to be solved by the core of mbassador but as part of the embedding system.

The requirements of event ordering vary with application and it would be hard to bring features into the core of mbassador to support those different flavors while maintaining its high-performance aspects.

In your case you are not only requesting the events to be delivered in order - which is possible without a problem, given you are not using asynchronous handler invocation. You want the framework to handle an individual case of logical processing dependencies between different types of events. This custom dependencies need to be accounted for outside the core of mbassador. Have a look at the spring extension to see how a transactional event processing can be implemented within a managed environment.

2017-07-03 13:12 GMT+02:00 Andrea Bertagnolli notifications@github.com:

@bennidi https://github.com/bennidi yeah, the second solution will the best in this case, the problem is, in this case:

EntityCreatedEvent -> takes some time to be fully consumed EntityUpdatedEvent #1 https://github.com/bennidi/mbassador/issues/1 -> try to get, but there's no read model, so i put it in queue (meanwhile read model is created) EntityUpdatedEvent #2 https://github.com/bennidi/mbassador/issues/2 -> gets consumed, before the #1 https://github.com/bennidi/mbassador/issues/1

I think that this problem could be handled by mbassador, giving opportunity to serialize all the events in a single listener. One futhcer enanchement could be to serialize them by a value like uuid or entity id (present in the event).

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/bennidi/mbassador/issues/152#issuecomment-312618497, or mute the thread https://github.com/notifications/unsubscribe-auth/ABwc9M5QmYFSjZpupPIRbcNtGlVu-oaBks5sKMyPgaJpZM4OFMPg .