Example #1
0
        protected override async Task Terminate(IInvokeHandlerContext context)
        {
            var channel = context.Builder.Build <IDelayedChannel>();

            var msgType = context.MessageBeingHandled.GetType();

            if (!msgType.IsInterface)
            {
                msgType = _mapper.GetMappedTypeFor(msgType) ?? msgType;
            }

            context.Extensions.Set(new State
            {
                ScopeWasPresent = Transaction.Current != null
            });

            var messageHandler = context.MessageHandler;
            var channelKey     = $"{messageHandler.HandlerType.FullName}:{msgType.FullName}";

            bool contains = false;

            lock (Lock) contains = IsNotDelayed.Contains(channelKey);

            var contextChannelKey = "";

            // Special case for when we are bulk processing messages from DelayedSubscriber, simply process it and return dont check for more bulk
            if (channel == null || contains || (context.Extensions.TryGet(Defaults.ChannelKey, out contextChannelKey) && contextChannelKey == channelKey))
            {
                Logger.Write(LogLevel.Debug, () => $"Invoking handle for message {msgType.FullName} on handler {messageHandler.HandlerType.FullName}");
                await messageHandler.Invoke(context.MessageBeingHandled, context).ConfigureAwait(false);

                return;
            }
            // If we are bulk processing and the above check fails it means the current message handler shouldn't be called
            if (!string.IsNullOrEmpty(contextChannelKey))
            {
                return;
            }

            if (IsDelayed.ContainsKey(channelKey))
            {
                DelayedAttribute delayed;
                IsDelayed.TryGetValue(channelKey, out delayed);

                var msgPkg = new DelayedMessage
                {
                    MessageId  = context.MessageId,
                    Headers    = context.Headers,
                    Message    = context.MessageBeingHandled,
                    Received   = DateTime.UtcNow,
                    ChannelKey = channelKey
                };

                InvokesDelayed.Mark();

                var specificKey = "";
                if (delayed.KeyPropertyFunc != null)
                {
                    try
                    {
                        specificKey = delayed.KeyPropertyFunc(context.MessageBeingHandled);
                    }
                    catch
                    {
                        Logger.Warn($"Failed to get key properties from message {msgType.FullName}");
                    }
                }

                await channel.AddToQueue(channelKey, msgPkg, key : specificKey).ConfigureAwait(false);

                bool bulkInvoked;
                if (context.Extensions.TryGet <bool>("BulkInvoked", out bulkInvoked) && bulkInvoked)
                {
                    // Prevents a single message from triggering a dozen different bulk invokes
                    Logger.Write(LogLevel.Info, () => $"Limiting bulk processing for a single message to a single invoke");
                    return;
                }

                lock (RecentLock)
                {
                    if (RecentlyInvoked.ContainsKey($"{channelKey}.{specificKey}"))
                    {
                        Logger.Write(LogLevel.Info, () => $"Channel [{channel}] specific [{specificKey}] was checked by this instance recently - leaving it alone");
                        return;
                    }
                    RecentlyInvoked.Add($"{channelKey}.{specificKey}", DateTime.UtcNow.AddSeconds(10));
                }


                int?     size = null;
                TimeSpan?age  = null;

                if (delayed.Delay.HasValue)
                {
                    age = await channel.Age(channelKey, key : specificKey).ConfigureAwait(false);
                }
                if (delayed.Count.HasValue)
                {
                    size = await channel.Size(channelKey, key : specificKey).ConfigureAwait(false);
                }


                if (!ShouldExecute(delayed, size, age))
                {
                    Logger.Write(LogLevel.Info, () => $"Threshold Count [{delayed.Count}] DelayMs [{delayed.Delay}] Size [{size}] Age [{age?.TotalMilliseconds}] - delaying processing channel [{channelKey}]");

                    return;
                }

                context.Extensions.Set <bool>("BulkInvoked", true);

                Logger.Write(LogLevel.Info, () => $"Threshold Count [{delayed.Count}] DelayMs [{delayed.Delay}] Size [{size}] Age [{age?.TotalMilliseconds}] - bulk processing channel [{channelKey}]");

                await InvokeDelayedChannel(channel, channelKey, specificKey, delayed, messageHandler, context).ConfigureAwait(false);

                return;
            }

            var attrs =
                Attribute.GetCustomAttributes(messageHandler.HandlerType, typeof(DelayedAttribute))
                .Cast <DelayedAttribute>();
            var single = attrs.SingleOrDefault(x => x.Type == msgType);

            if (single == null)
            {
                lock (Lock) IsNotDelayed.Add(channelKey);
            }
            else
            {
                Logger.Write(LogLevel.Debug,
                             () => $"Found delayed handler {messageHandler.HandlerType.FullName} for message {msgType.FullName}");
                IsDelayed.TryAdd(channelKey, single);
            }
            await Terminate(context).ConfigureAwait(false);
        }
        protected override async Task Terminate(IInvokeHandlerContext context)
        {
            var channel = _builder.Build <IDelayedChannel>();

            var msgType = context.MessageBeingHandled.GetType();

            if (!msgType.IsInterface)
            {
                msgType = _mapper.GetMappedTypeFor(msgType) ?? msgType;
            }

            context.Extensions.Set(new State
            {
                ScopeWasPresent = Transaction.Current != null
            });

            var messageHandler = context.MessageHandler;


            var key = $"{messageHandler.HandlerType.FullName}:{msgType.FullName}";

            if (IsNotDelayed.Contains(key))
            {
                Logger.Write(LogLevel.Debug, () => $"Invoking handle for message {msgType.FullName} on handler {messageHandler.HandlerType.FullName}");
                await messageHandler.Invoke(context.MessageBeingHandled, context).ConfigureAwait(false);

                return;
            }
            if (IsDelayed.ContainsKey(key))
            {
                DelayedAttribute delayed;
                IsDelayed.TryGetValue(key, out delayed);

                var msgPkg = new DelayedMessage
                {
                    MessageId = context.MessageId,
                    Headers   = context.Headers,
                    Message   = context.MessageBeingHandled,
                    Received  = DateTime.UtcNow,
                };

                InvokesDelayed.Mark();

                if (delayed.KeyPropertyFunc != null)
                {
                    try
                    {
                        key = $"{key}:{delayed.KeyPropertyFunc(context.MessageBeingHandled)}";
                    }
                    catch
                    {
                        Logger.Warn($"Failed to get property key {delayed.KeyProperty} on message {msgType.FullName}");
                    }
                }
                Logger.Write(LogLevel.Debug, () => $"Delaying message {msgType.FullName} delivery");
                var size = await channel.AddToQueue(key, msgPkg).ConfigureAwait(false);

                if (_bulkedMessageId == context.MessageId)
                {
                    // Prevents a single message from triggering a dozen different bulk invokes
                    Logger.Write(LogLevel.Debug, () => $"Limiting bulk processing for a single message to a single invoke");
                    return;
                }


                TimeSpan?age = null;

                if (delayed.Delay.HasValue)
                {
                    age = await channel.Age(key).ConfigureAwait(false);
                }

                if ((delayed.Count.HasValue && size < delayed.Count.Value) || (delayed.Delay.HasValue && age < TimeSpan.FromMilliseconds(delayed.Delay.Value)))
                {
                    Logger.Write(LogLevel.Debug, () => $"Threshold Count [{delayed.Count}] DelayMs [{delayed.Delay}] not hit Size [{size}] Age [{age?.TotalMilliseconds}] - delaying processing {msgType.FullName}");
                    return;
                }

                _bulkedMessageId = context.MessageId;
                Logger.Write(LogLevel.Debug, () => $"Threshold Count [{delayed.Count}] DelayMs [{delayed.Delay}] hit Size [{size}] Age [{age?.TotalMilliseconds}] - bulk processing {msgType.FullName}");
                var msgs = await channel.Pull(key).ConfigureAwait(false);

                Invokes.Mark();
                Logger.Write(LogLevel.Debug, () => $"Invoking handle {msgs.Count()} times for message {msgType.FullName} on handler {messageHandler.HandlerType.FullName}");
                foreach (var msg in msgs.Cast <DelayedMessage>())
                {
                    await messageHandler.Invoke(msg.Message, context).ConfigureAwait(false);
                }

                return;
            }

            var attrs =
                Attribute.GetCustomAttributes(messageHandler.HandlerType, typeof(DelayedAttribute))
                .Cast <DelayedAttribute>();
            var single = attrs.SingleOrDefault(x => x.Type == msgType);

            if (single == null)
            {
                IsNotDelayed.Add(key);
            }
            else
            {
                Logger.Write(LogLevel.Debug,
                             () => $"Found delayed handler {messageHandler.HandlerType.FullName} for message {msgType.FullName}");
                IsDelayed.TryAdd(key, single);
            }
            await Terminate(context).ConfigureAwait(false);
        }
Example #3
0
        protected override async Task Terminate(IInvokeHandlerContext context)
        {
            if (context.Extensions.TryGet(out ActiveSagaInstance saga) && saga.NotFound && ((SagaMetadata)saga.GetType().GetProperty("Metadata", BindingFlags.NonPublic).GetValue(saga)).SagaType == context.MessageHandler.Instance.GetType())
            {
                return;
            }

            IDelayedChannel channel = null;

            try
            {
                IContainer container;
                if (context.Extensions.TryGet <IContainer>(out container))
                {
                    channel = container.Resolve <IDelayedChannel>();
                }
            }
            catch (Exception)
            {
                // Catch in case IDelayedChannel isn't registered which shouldn't happen unless a user registered Consumer without GetEventStore
            }

            var msgType = context.MessageBeingHandled.GetType();

            if (!msgType.IsInterface)
            {
                msgType = _mapper.GetMappedTypeFor(msgType) ?? msgType;
            }

            context.Extensions.Set(new State
            {
                ScopeWasPresent = Transaction.Current != null
            });

            var messageHandler = context.MessageHandler;
            var channelKey     = $"{messageHandler.HandlerType.FullName}:{msgType.FullName}";

            bool contains = false;

            lock (Lock) contains = IsNotDelayed.Contains(channelKey);

            context.Extensions.TryGet(Defaults.ChannelKey, out string contextChannelKey);

            // Special case for when we are bulk processing messages from DelayedSubscriber or BulkMessage, simply process it and return dont check for more bulk
            if (channel == null || contains || contextChannelKey == channelKey || context.Headers.ContainsKey(Defaults.BulkHeader))
            {
                await messageHandler.Invoke(context.MessageBeingHandled, context).ConfigureAwait(false);

                return;
            }
            // If we are bulk processing and the above check fails it means the current message handler shouldn't be called
            // because if contextChannelKey is set we are using delayed bulk SendLocal
            // which should only execute the message on the handlers that were marked delayed, the other handlers were already invoked when the message
            // originally came in
            if (!string.IsNullOrEmpty(contextChannelKey))
            {
                return;
            }


            if (IsDelayed.ContainsKey(channelKey))
            {
                DelayedAttribute delayed;
                IsDelayed.TryGetValue(channelKey, out delayed);


                var specificKey = "";
                if (delayed.KeyPropertyFunc != null)
                {
                    try
                    {
                        specificKey = delayed.KeyPropertyFunc(context.MessageBeingHandled);
                    }
                    catch
                    {
                        Logger.Warn($"Failed to get key properties from message {msgType.FullName}");
                    }
                }

                var msgPkg = new DelayedMessage
                {
                    MessageId   = context.MessageId,
                    Headers     = context.Headers,
                    Message     = context.MessageBeingHandled as IMessage,
                    Received    = DateTime.UtcNow,
                    ChannelKey  = channelKey,
                    SpecificKey = specificKey
                };

                await channel.AddToQueue(channelKey, msgPkg, key : specificKey).ConfigureAwait(false);

                Logger.DebugEvent("QueueAdd", "Message added to delayed queue [{Channel:l}] key [{Key:l}]", channelKey, specificKey);

                bool bulkInvoked;
                if (context.Extensions.TryGet <bool>("BulkInvoked", out bulkInvoked) && bulkInvoked)
                {
                    // Prevents a single message from triggering a dozen different bulk invokes
                    return;
                }

                int?     size = null;
                TimeSpan?age  = null;

                if (delayed.Delay.HasValue)
                {
                    age = await channel.Age(channelKey, key : specificKey).ConfigureAwait(false);
                }
                if (delayed.Count.HasValue)
                {
                    size = await channel.Size(channelKey, key : specificKey).ConfigureAwait(false);
                }

                if (!ShouldExecute(delayed, size, age))
                {
                    return;
                }

                context.Extensions.Set <bool>("BulkInvoked", true);

                Logger.DebugEvent("Threshold", "Count [{Count}] DelayMs [{DelayMs}] bulk procesing Size [{Size}] Age [{Age}] - [{Channel:l}] key [{Key:l}]", delayed.Count, delayed.Delay, size, age?.TotalMilliseconds, channelKey, specificKey);

                await InvokeDelayedChannel(channel, channelKey, specificKey, delayed, messageHandler, context).ConfigureAwait(false);

                return;
            }

            var attrs =
                Attribute.GetCustomAttributes(messageHandler.HandlerType, typeof(DelayedAttribute))
                .Cast <DelayedAttribute>();
            var single = attrs.SingleOrDefault(x => x.Type == msgType);

            if (single == null)
            {
                lock (Lock) IsNotDelayed.Add(channelKey);
            }
            else
            {
                IsDelayed.TryAdd(channelKey, single);
            }

            await Terminate(context).ConfigureAwait(false);
        }
        public async Task Resolve <T>(T entity, IEnumerable <IFullEvent> uncommitted, Guid commitId, IDictionary <string, string> commitHeaders) where T : class, IEventSource
        {
            var sourced = (IEventSourced)entity;
            // Store conflicting events in memory
            // After 100 or so pile up pull the latest stream and attempt to write them again

            var streamName = _streamGen(typeof(T), StreamTypes.Domain, sourced.Stream.Bucket, sourced.Stream.StreamId, sourced.Stream.Parents);

            var message = new ConflictingEvents
            {
                Bucket     = sourced.Stream.Bucket,
                StreamId   = sourced.Stream.StreamId,
                EntityType = typeof(T).AssemblyQualifiedName,
                Parents    = BuildParentList(entity),
                Events     = uncommitted
            };

            var package = new DelayedMessage
            {
                MessageId  = Guid.NewGuid().ToString(),
                Headers    = new Dictionary <string, string>(),
                Message    = message,
                Received   = DateTime.UtcNow,
                ChannelKey = streamName
            };

            foreach (var header in commitHeaders)
            {
                package.Headers[$"Conflict.{header.Key}"] = header.Value;
            }

            await _delay.AddToQueue(ConcurrencyConflict.ResolveWeakly.ToString(), package, streamName)
            .ConfigureAwait(false);

            // Todo: make 30 seconds configurable
            var age = await _delay.Age(ConcurrencyConflict.ResolveWeakly.ToString(), streamName).ConfigureAwait(false);

            if (!age.HasValue || age < TimeSpan.FromSeconds(30))
            {
                return;
            }

            var stream = sourced.Stream;

            Logger.Write(LogLevel.Info,
                         () => $"Starting weak conflict resolve for stream [{stream.StreamId}] type [{typeof(T).FullName}] bucket [{stream.Bucket}]");
            try
            {
                await _store.Freeze <T>(stream).ConfigureAwait(false);

                var delayed = await _delay.Pull(streamName, max : 200).ConfigureAwait(false);

                // If someone else pulled while we were waiting
                if (!delayed.Any())
                {
                    return;
                }

                Logger.Write(LogLevel.Info,
                             () => $"Resolving {delayed.Count()} uncommitted events to stream [{stream.StreamId}] type [{typeof(T).FullName}] bucket [{stream.Bucket}]");

                var latestEvents =
                    await _eventstore.GetEvents(streamName, stream.CommitVersion + 1L)
                    .ConfigureAwait(false);

                Logger.Write(LogLevel.Info,
                             () => $"Stream [{stream.StreamId}] bucket [{stream.Bucket}] is {latestEvents.Count()} events behind store");

                sourced.Hydrate(latestEvents.Select(x => x.Event as IEvent));


                Logger.Write(LogLevel.Debug, () => $"Merging {delayed.Count()} conflicted events");
                try
                {
                    foreach (var u in delayed.Select(x => x.Message as ConflictingEvents).SelectMany(x => x.Events))
                    {
                        sourced.Conflict(u.Event as IEvent,
                                         metadata:
                                         new Dictionary <string, string>
                        {
                            { "ConflictResolution", ConcurrencyConflict.ResolveWeakly.ToString() }
                        });
                    }
                }
                catch (NoRouteException e)
                {
                    Logger.Write(LogLevel.Info, () => $"Failed to resolve conflict: {e.Message}");
                    throw new ConflictResolutionFailedException("Failed to resolve conflict", e);
                }

                Logger.Write(LogLevel.Info, () => "Successfully merged conflicted events");

                if (stream.StreamVersion != stream.CommitVersion && entity is ISnapshotting &&
                    ((ISnapshotting)entity).ShouldTakeSnapshot())
                {
                    Logger.Write(LogLevel.Debug,
                                 () =>
                                 $"Taking snapshot of [{typeof(T).FullName}] id [{entity.Id}] version {stream.StreamVersion}");
                    var memento = ((ISnapshotting)entity).TakeSnapshot();
                    stream.AddSnapshot(memento);
                }

                await _store.WriteStream <T>(commitId, stream, commitHeaders).ConfigureAwait(false);
            }
            finally
            {
                await _store.Unfreeze <T>(stream).ConfigureAwait(false);
            }
        }