|
|
|
@ -18,15 +18,20 @@ using Squidex.Infrastructure.Tasks; |
|
|
|
|
|
|
|
namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
{ |
|
|
|
public class EventConsumerGrain : GrainV2<EventConsumerGrainState>, IEventSubscriber, IEventConsumerGrain |
|
|
|
public class EventConsumerGrain : GrainV2<EventConsumerGrainState>, IEventConsumerGrain |
|
|
|
{ |
|
|
|
private readonly EventDataFormatter eventFormatter; |
|
|
|
private readonly EventConsumerFactory eventConsumerFactory; |
|
|
|
private readonly IEventStore eventStore; |
|
|
|
private readonly ISemanticLog log; |
|
|
|
private IEventSubscription currentSubscription; |
|
|
|
private TaskScheduler scheduler; |
|
|
|
private IEventConsumer eventConsumer; |
|
|
|
private SingleThreadedDispatcher dispatcher; |
|
|
|
private IEventSubscription eventSubscription; |
|
|
|
|
|
|
|
protected IEventStore EventStore |
|
|
|
{ |
|
|
|
get { return eventStore; } |
|
|
|
} |
|
|
|
|
|
|
|
public EventConsumerGrain( |
|
|
|
EventDataFormatter eventFormatter, |
|
|
|
@ -62,7 +67,7 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
|
|
|
|
public override Task OnActivateAsync() |
|
|
|
{ |
|
|
|
dispatcher = new SingleThreadedDispatcher(1, TaskScheduler.Current); |
|
|
|
scheduler = TaskScheduler.Current; |
|
|
|
|
|
|
|
eventConsumer = eventConsumerFactory(this.GetPrimaryKeyString()); |
|
|
|
|
|
|
|
@ -71,53 +76,32 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
|
|
|
|
public Task ActivateAsync() |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => |
|
|
|
{ |
|
|
|
if (!State.IsStopped) |
|
|
|
{ |
|
|
|
Subscribe(State.Position); |
|
|
|
} |
|
|
|
|
|
|
|
return TaskHelper.Done; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|
|
|
{ |
|
|
|
if (subscription != currentSubscription) |
|
|
|
if (!State.IsStopped) |
|
|
|
{ |
|
|
|
return TaskHelper.Done; |
|
|
|
Subscribe(State.Position); |
|
|
|
} |
|
|
|
|
|
|
|
return DoAndUpdateStateAsync(async () => |
|
|
|
{ |
|
|
|
var @event = ParseKnownEvent(storedEvent); |
|
|
|
|
|
|
|
if (@event != null) |
|
|
|
{ |
|
|
|
await DispatchConsumerAsync(@event); |
|
|
|
} |
|
|
|
|
|
|
|
State = EventConsumerGrainState.Handled(storedEvent.EventPosition); |
|
|
|
}); |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
|
|
|
|
private Task HandleClosedAsync(IEventSubscription subscription) |
|
|
|
public Task StartAsync() |
|
|
|
{ |
|
|
|
if (subscription != currentSubscription) |
|
|
|
if (!State.IsStopped) |
|
|
|
{ |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
|
|
|
|
return DoAndUpdateStateAsync(() => |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
Subscribe(State.Position); |
|
|
|
|
|
|
|
State = State.Started(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private Task HandleErrorAsync(IEventSubscription subscription, Exception exception) |
|
|
|
public Task StopAsync() |
|
|
|
{ |
|
|
|
if (subscription != currentSubscription) |
|
|
|
if (State.IsStopped) |
|
|
|
{ |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
@ -126,76 +110,70 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
|
|
|
|
State = State.Failed(exception); |
|
|
|
State = State.Stopped(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public Task StartAsync() |
|
|
|
public Task ResetAsync() |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => |
|
|
|
return DoAndUpdateStateAsync(async () => |
|
|
|
{ |
|
|
|
if (!State.IsStopped) |
|
|
|
{ |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
Unsubscribe(); |
|
|
|
|
|
|
|
return DoAndUpdateStateAsync(() => |
|
|
|
{ |
|
|
|
Subscribe(State.Position); |
|
|
|
await ClearAsync(); |
|
|
|
|
|
|
|
Subscribe(null); |
|
|
|
|
|
|
|
State = State.Started(); |
|
|
|
}); |
|
|
|
State = EventConsumerGrainState.Initial(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public Task StopAsync() |
|
|
|
public Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent) |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => |
|
|
|
if (subscription.Value != eventSubscription) |
|
|
|
{ |
|
|
|
if (State.IsStopped) |
|
|
|
{ |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
|
|
|
|
return DoAndUpdateStateAsync(() => |
|
|
|
return DoAndUpdateStateAsync(async () => |
|
|
|
{ |
|
|
|
var @event = ParseKnownEvent(storedEvent.Value); |
|
|
|
|
|
|
|
if (@event != null) |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
await DispatchConsumerAsync(@event); |
|
|
|
} |
|
|
|
|
|
|
|
State = State.Stopped(); |
|
|
|
}); |
|
|
|
State = EventConsumerGrainState.Handled(storedEvent.Value.EventPosition); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public Task ResetAsync() |
|
|
|
public Task OnErrorAsync(Immutable<IEventSubscription> subscription, Immutable<Exception> exception) |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => |
|
|
|
if (subscription.Value != eventSubscription) |
|
|
|
{ |
|
|
|
return DoAndUpdateStateAsync(async () => |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
|
|
|
|
await ClearAsync(); |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
|
|
|
|
Subscribe(null); |
|
|
|
return DoAndUpdateStateAsync(() => |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
|
|
|
|
State = EventConsumerGrainState.Initial(); |
|
|
|
}); |
|
|
|
State = State.Failed(exception.Value); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
Task IEventSubscriber.OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent) |
|
|
|
public Task OnClosedAsync(Immutable<IEventSubscription> subscription) |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => HandleEventAsync(subscription, storedEvent)); |
|
|
|
} |
|
|
|
|
|
|
|
Task IEventSubscriber.OnErrorAsync(IEventSubscription subscription, Exception exception) |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => HandleErrorAsync(subscription, exception)); |
|
|
|
} |
|
|
|
if (subscription.Value != eventSubscription) |
|
|
|
{ |
|
|
|
return TaskHelper.Done; |
|
|
|
} |
|
|
|
|
|
|
|
Task IEventSubscriber.OnClosedAsync(IEventSubscription subscription) |
|
|
|
{ |
|
|
|
return dispatcher.DispatchAndUnwrapAsync(() => HandleClosedAsync(subscription)); |
|
|
|
return DoAndUpdateStateAsync(() => |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
public Task<Immutable<EventConsumerInfo>> GetStateAsync() |
|
|
|
@ -203,39 +181,6 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
return Task.FromResult(new Immutable<EventConsumerInfo>(State.ToInfo(this.GetPrimaryKeyString()))); |
|
|
|
} |
|
|
|
|
|
|
|
private Task DoAndUpdateStateAsync(Action action) |
|
|
|
{ |
|
|
|
return DoAndUpdateStateAsync(() => { action(); return TaskHelper.Done; }); |
|
|
|
} |
|
|
|
|
|
|
|
private async Task DoAndUpdateStateAsync(Func<Task> action) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
await action(); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
} |
|
|
|
catch (Exception unsubscribeException) |
|
|
|
{ |
|
|
|
ex = new AggregateException(ex, unsubscribeException); |
|
|
|
} |
|
|
|
|
|
|
|
log.LogFatal(ex, w => w |
|
|
|
.WriteProperty("action", "HandleEvent") |
|
|
|
.WriteProperty("state", "Failed") |
|
|
|
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|
|
|
|
|
|
|
State = State.Failed(ex); |
|
|
|
} |
|
|
|
|
|
|
|
await WriteStateAsync(); |
|
|
|
} |
|
|
|
|
|
|
|
private async Task ClearAsync() |
|
|
|
{ |
|
|
|
var actionId = Guid.NewGuid().ToString(); |
|
|
|
@ -283,19 +228,19 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
|
|
|
|
private void Unsubscribe() |
|
|
|
{ |
|
|
|
if (currentSubscription != null) |
|
|
|
if (eventSubscription != null) |
|
|
|
{ |
|
|
|
currentSubscription.StopAsync().Forget(); |
|
|
|
currentSubscription = null; |
|
|
|
eventSubscription.StopAsync().Forget(); |
|
|
|
eventSubscription = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void Subscribe(string position) |
|
|
|
{ |
|
|
|
if (currentSubscription == null) |
|
|
|
if (eventSubscription == null) |
|
|
|
{ |
|
|
|
currentSubscription?.StopAsync().Forget(); |
|
|
|
currentSubscription = CreateSubscription(eventStore, eventConsumer.EventsFilter, position); |
|
|
|
eventSubscription?.StopAsync().Forget(); |
|
|
|
eventSubscription = CreateSubscription(eventConsumer.EventsFilter, position); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -318,9 +263,52 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual IEventSubscription CreateSubscription(IEventStore eventStore, string streamFilter, string position) |
|
|
|
protected virtual IEventConsumerGrain GetSelf() |
|
|
|
{ |
|
|
|
return this.AsReference<IEventConsumerGrain>(); |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position) |
|
|
|
{ |
|
|
|
return new RetrySubscription(EventStore, subscriber, streamFilter, position); |
|
|
|
} |
|
|
|
|
|
|
|
private IEventSubscription CreateSubscription(string streamFilter, string position) |
|
|
|
{ |
|
|
|
return CreateSubscription(new WrapperSubscription(GetSelf(), scheduler), streamFilter, position); |
|
|
|
} |
|
|
|
|
|
|
|
private Task DoAndUpdateStateAsync(Action action) |
|
|
|
{ |
|
|
|
return DoAndUpdateStateAsync(() => { action(); return TaskHelper.Done; }); |
|
|
|
} |
|
|
|
|
|
|
|
private async Task DoAndUpdateStateAsync(Func<Task> action) |
|
|
|
{ |
|
|
|
return new RetrySubscription(eventStore, this, streamFilter, position); |
|
|
|
try |
|
|
|
{ |
|
|
|
await action(); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
Unsubscribe(); |
|
|
|
} |
|
|
|
catch (Exception unsubscribeException) |
|
|
|
{ |
|
|
|
ex = new AggregateException(ex, unsubscribeException); |
|
|
|
} |
|
|
|
|
|
|
|
log.LogFatal(ex, w => w |
|
|
|
.WriteProperty("action", "HandleEvent") |
|
|
|
.WriteProperty("state", "Failed") |
|
|
|
.WriteProperty("eventConsumer", eventConsumer.Name)); |
|
|
|
|
|
|
|
State = State.Failed(ex); |
|
|
|
} |
|
|
|
|
|
|
|
await WriteStateAsync(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |