示例#1
0
文件: SmsJob.cs 项目: lulzzz/tellma
        protected override async Task ExecuteAsync(CancellationToken cancellation)
        {
            while (!cancellation.IsCancellationRequested)
            {
                try // To make sure the background service keeps running
                {
                    // When the queue is empty, this goes to sleep until an item is enqueued
                    var(sms, scheduledAt) = await _queue.DequeueAsync(cancellation);

                    // This SMS spent too long in the queue it is considered stale, SmsPollingJob will
                    // (and might have already) pick it up and send it again, so ignore it
                    if (IsStale(scheduledAt))
                    {
                        _logger.LogWarning($"Stale SMS remained in the {nameof(SmsQueue)} for {(DateTimeOffset.Now - scheduledAt).TotalSeconds} seconds. TenantId = {sms.TenantId}, MessageId = {sms.MessageId}.");
                        continue;
                    }

                    // Begin serializable transaction
                    using var trx = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = IsolationLevel.Serializable }, TransactionScopeAsyncFlowOption.Enabled);

                    // Update the state first (since this action can be rolled back)
                    await _repo.Notifications_SmsMessages__UpdateState(sms.TenantId, sms.MessageId, SmsState.Dispatched, DateTimeOffset.Now); // actions that modify state should not use cancellationToken

                    try
                    {
                        // Send the SMS after you update the state in the DB, since sending SMS
                        // is non-transactional and therefore cannot be rolled back
                        await _smsSender.SendAsync(sms);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogWarning(ex, $"Failed to Dispatch SMS. TenantId = {sms.TenantId}, MessageId = {sms.MessageId}.");

                        // If sending the SMS fails, update the state to DispatchFailed together with the error message
                        await _repo.Notifications_SmsMessages__UpdateState(sms.TenantId, sms.MessageId, SmsState.DispatchFailed, DateTimeOffset.Now, ex.Message);
                    }

                    trx.Complete();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"Error in {nameof(SmsJob)}.");
                }
            }
        }
示例#2
0
        public async Task HandleCallback(SmsEventNotification smsEvent, CancellationToken cancellation)
        {
            // Nothing to do
            if (smsEvent == null || smsEvent.TenantId == null || smsEvent.TenantId == 0) // Right now we do not handle null tenant Ids, those were probably sent from identity or admin servers
            {
                return;
            }

            // Map the event to the database representation
            var state = smsEvent.Event switch
            {
                SmsEvent.Sent => SmsState.Sent,
                SmsEvent.Failed => SmsState.SendingFailed,
                SmsEvent.Delivered => SmsState.Delivered,
                SmsEvent.Undelivered => SmsState.DeliveryFailed,
                _ => throw new InvalidOperationException($"[Bug] Unknown {nameof(SmsEvent)} = {smsEvent.Event}"), // Future proofing
            };

            // Update the state in the database (should we make it serializable?)
            await _repo.Notifications_SmsMessages__UpdateState(smsEvent.TenantId.Value, smsEvent.MessageId, state, smsEvent.Timestamp, smsEvent.Error, cancellation);
        }