private static async Task ProcessEvents(ThreadParam param, DelayedClient client, ResolvedEvent[] events) { // A fake message that will travel through the pipeline in order to bulk process messages from the context bag var bulkMarker = new BulkMessage().Serialize(param.JsonSettings).AsByteArray(); var delayed = events.Select(x => x.Event.Data.Deserialize <DelayedMessage>(param.JsonSettings)).ToArray(); Logger.Write(LogLevel.Info, () => $"Processing {delayed.Count()} bulk events on stream {events.First().Event.EventStreamId}"); var contextBag = new ContextBag(); // Hack to get all the delayed messages to bulk invoker without NSB deserializing and processing each one contextBag.Set(Defaults.BulkHeader, delayed); // Run bulk process on this thread using (var tokenSource = new CancellationTokenSource()) { var success = false; var retry = 0; do { var transportTransaction = new TransportTransaction(); // Need to supply EnclosedMessageTypes to trick NSB pipeline into processing our fake message var messageId = Guid.NewGuid().ToString(); var headers = new Dictionary <string, string>() { [Headers.EnclosedMessageTypes] = typeof(BulkMessage).AssemblyQualifiedName, [Headers.MessageIntent] = MessageIntentEnum.Send.ToString(), [Headers.MessageId] = messageId, [Defaults.BulkHeader] = delayed.Count().ToString(), }; try { // If canceled, this will throw the number of time immediate retry requires to send the message to the error queue param.Token.ThrowIfCancellationRequested(); // Don't re-use the event id for the message id var messageContext = new NServiceBus.Transport.MessageContext(messageId, headers, bulkMarker, transportTransaction, tokenSource, contextBag); await Bus.OnMessage(messageContext).ConfigureAwait(false);//param.Token); Logger.Write(LogLevel.Debug, () => $"Scheduling acknowledge of {delayed.Count()} bulk events"); DelayedCount.Increment(delayed.Count()); client.Acknowledge(events); success = true; } catch (ObjectDisposedException) { // NSB transport has been disconnected break; } catch (Exception e) { DelayedErrors.Mark($"{e.GetType().Name} {e.Message}"); } retry++; } while (!success && retry <= param.MaxRetry); if (!success) { // Dont run through NSB's error handler, it expects a single serialized message which // is not compatible here. Just tell EventStore to retry it // Todo: ES will park messages that fail - develop something to monitor parked messages client.Nack(events); } } }
public Task Subscribe(CancellationToken cancelToken) { _cancelation = CancellationTokenSource.CreateLinkedTokenSource(cancelToken); var stream = $"$ce-{_endpoint}.{StreamTypes.Delayed}"; var group = $"{_endpoint}.{Assembly.GetEntryAssembly().GetName().Version}.{StreamTypes.Delayed}.ROUND"; Task.Run(async() => { while (Bus.OnMessage == null || Bus.OnError == null) { Logger.Warn($"Could not find NSBs onMessage handler yet - if this persists there is a problem."); await Task.Delay(1000, cancelToken).ConfigureAwait(false); } var settings = PersistentSubscriptionSettings.Create() .StartFromBeginning() .WithMaxRetriesOf(10) .WithReadBatchOf(_readsize) .WithLiveBufferSizeOf(_readsize * _readsize) //.WithMessageTimeoutOf(TimeSpan.FromMilliseconds(int.MaxValue)) .WithMessageTimeoutOf(TimeSpan.FromMinutes(1)) .CheckPointAfter(TimeSpan.FromSeconds(30)) .MaximumCheckPointCountOf(_readsize * _readsize) .ResolveLinkTos() .WithNamedConsumerStrategy(SystemConsumerStrategies.Pinned); if (_extraStats) { settings.WithExtraStatistics(); } var clients = new DelayedClient[_clients.Count()]; for (var i = 0; i < _clients.Count(); i++) { var client = _clients.ElementAt(i); var clientCancelSource = CancellationTokenSource.CreateLinkedTokenSource(_cancelation.Token); client.Closed += (object s, ClientClosedEventArgs args) => { Logger.Info($"Eventstore disconnected - shutting down delayed subscription"); clientCancelSource.Cancel(); }; try { await client.CreatePersistentSubscriptionAsync(stream, group, settings, client.Settings.DefaultUserCredentials).ConfigureAwait(false); Logger.Info($"Created ROUND ROBIN persistent subscription to stream [{stream}]"); } catch (InvalidOperationException) { } clients[i] = new DelayedClient(client, stream, group, _maxDelayed, clientCancelSource.Token); } _delayedThread = new Thread(Threaded) { IsBackground = true, Name = $"Delayed Event Thread" }; _delayedThread.Start(new ThreadParam { Token = cancelToken, Clients = clients, JsonSettings = _settings, MaxRetry = _maxRetry }); }); return(Task.CompletedTask); }