Example #1
0
    public async Task <IHttpActionResult> Post(TaskRequest request)
    {
        Logger.InfoFormat("Received task {taskJson}", request.TaskJson);

        TaskBase taskObject = request.TaskJson.FromJson <TaskBase>();

        // check error rate
        string cacheKey = $"error-rate:{taskObject.GetType().Name}";

        if (CurrentHttpContext.Instance().Cache.Get(cacheKey) is not RateLimit cacheItem)
        {
            // allow 1 error per hour
            cacheItem = RateLimit.Create(cacheKey, 1, 1, 3600);
            CurrentHttpContext.Instance().Cache.Insert(
                cacheKey,
                cacheItem,
                null,
                Cache.NoAbsoluteExpiration,
                Cache.NoSlidingExpiration,
                CacheItemPriority.Default,
                (key, item, reason) =>
                Logger.InfoFormat(
                    "cache item '{key}' removed due to '{reason}'",
                    key,
                    reason));
        }

        _ = cacheItem.UpdateAllowance(DateTime.Now);
        if (cacheItem.Allowance < 1)
        {
            Logger.InfoFormat(
                "throttling {cacheKey} due to allowance = {allowance}",
                cacheKey,
                cacheItem.Allowance.ToString("N2"));
            return(Ok(
                       $"throttled due to unhandled exception (allowance = {cacheItem.Allowance:N2})"));
        }

        IDisposable scope = NLog.NestedDiagnosticsLogicalContext.Push(taskObject.BusinessKey);

        try
        {
            Logger.Info("begin");
            IHttpActionResult result = await Transact <IHttpActionResult>(async databases =>
            {
                // check for published task
                PublishedTask?publishedTask =
                    await databases.Snittlistan.PublishedTasks.SingleOrDefaultAsync(
                        x => x.MessageId == request.MessageId);
                if (publishedTask is null)
                {
                    return(BadRequest($"no published task found with message id {request.MessageId}"));
                }

                if (publishedTask.HandledDate.HasValue)
                {
                    return(Ok($"task with message id {publishedTask.MessageId} already handled"));
                }

                bool handled = await HandleMessage(
                    taskObject,
                    request.CorrelationId ?? default,
                    request.MessageId ?? default,
                    databases);
                if (handled)
                {
                    publishedTask.MarkHandled(DateTime.Now);
                }

                return(Ok());
            });

            return(result);
        }
        catch (Exception ex)
        {
            Logger.WarnFormat(
                ex,
                "decreasing allowance for {cacheKey}",
                cacheKey);
            _ = cacheItem.DecreaseAllowance();
            throw;
        }
        finally
        {
            Logger.Info("end");
            scope.Dispose();
        }
    }
Example #2
0
    public async Task Handle(HandlerContext <TCommand> context)
    {
        (string key, int rate, int perSeconds) = GetRate(context);

        // check cache
        if (CurrentHttpContext.Instance().Cache.Get(key) is not RateLimit cacheItem)
        {
            cacheItem = RateLimit.Create(key, 1, rate, perSeconds);
            Logger.InfoFormat(
                "add {key} to cache: {@cacheItem}",
                key,
                cacheItem);
            CurrentHttpContext.Instance().Cache.Insert(
                key,
                cacheItem,
                null,
                Cache.NoAbsoluteExpiration,
                Cache.NoSlidingExpiration,
                CacheItemPriority.Default,
                (key, item, reason) =>
                Logger.InfoFormat(
                    "cache item '{key}' removed due to '{reason}'",
                    key,
                    reason));
        }

        // check database
        KeyValueProperty?rateLimitProperty =
            await CompositionRoot.Databases.Snittlistan.KeyValueProperties
            .SingleOrDefaultAsync(x => x.Key == key);

        if (rateLimitProperty == null)
        {
            KeyValueProperty keyValueProperty = new(
                context.Tenant.TenantId,
                key,
                RateLimit.Create(key, 1, rate, perSeconds));
            rateLimitProperty =
                CompositionRoot.Databases.Snittlistan.KeyValueProperties.Add(keyValueProperty);
        }
        else
        {
            rateLimitProperty.ModifyValue <RateLimit>(
                r => r.SetRate(rate).SetPerSeconds(perSeconds),
                x => Logger.InfoFormat("before: {@x}", x),
                x => Logger.InfoFormat("after: {@x}", x));
        }

        DateTime now = DateTime.Now;

        rateLimitProperty.ModifyValue <RateLimit>(
            x => x.UpdateAllowance(now),
            x => Logger.InfoFormat("before: {@x}", x),
            x => Logger.InfoFormat("after: {@x}", x));
        _ = cacheItem.UpdateAllowance(now);
        double allowance = rateLimitProperty.GetValue <RateLimit, double>(x => x.Allowance);

        if (allowance < 1 || cacheItem.Allowance < 1)
        {
            string message =
                $"(db) allowance = {allowance:N2}, (cache) allowance = {cacheItem.Allowance:N2}, wait to reach 1";
            throw new HandledException(message);
        }

        TEmail email = await CreateEmail(context);

        EmailState state = email.State;

        _ = CompositionRoot.Databases.Snittlistan.SentEmails.Add(new(
                                                                     email.From,
                                                                     email.To,
                                                                     email.Bcc,
                                                                     email.Subject,
                                                                     state));
        Logger.InfoFormat("sending email {@email}", email);
        await CompositionRoot.EmailService.SendAsync(email);

        rateLimitProperty.ModifyValue <RateLimit>(
            x => x.DecreaseAllowance(),
            x => Logger.InfoFormat("before: {@x}", x),
            x => Logger.InfoFormat("after: {@x}", x));
        _ = cacheItem.DecreaseAllowance();
    }