Лямбда обнаружила UnobservedTaskException - Невозможно получить доступ к удаленному объекту

Я заметил это исключение в журналах CloudWatch для AWS Lambda.

Кажется, все обрабатывается, поэтому я думаю, что это исключение в коде AWS (в отличие от того, что я написал), которое создается после того, как Lambda закончила работу.

Так как функционально это работает, я игнорирую это, но я обеспокоен тем, что могут быть проблемы, которые я не заметил.


Lambda обнаружила исключение UnobservedTaskException с помощью события TaskScheduler.UnobservedTaskException:

{
    "errorType": "AggregateException",
    "errorMessage": "A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Cannot access a disposed object.\nObject name: 'System.Net.Sockets.UdpClient'.)",
    "cause": {
        "errorType": "ObjectDisposedException",
        "errorMessage": "Cannot access a disposed object.\nObject name: 'System.Net.Sockets.UdpClient'.",
        "stackTrace": [
            "at System.Net.Sockets.UdpClient.EndReceive(IAsyncResult asyncResult, IPEndPoint& remoteEP)",
            "at System.Net.Sockets.UdpClient.<>c.<ReceiveAsync>b__56_1(IAsyncResult asyncResult)",
            "at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)"
        ]
    },
    "causes": [ {
        "errorType": "ObjectDisposedException",
        "errorMessage": "Cannot access a disposed object.\nObject name: 'System.Net.Sockets.UdpClient'.",
        "stackTrace": [
            "at System.Net.Sockets.UdpClient.EndReceive(IAsyncResult asyncResult, IPEndPoint& remoteEP)",
            "at System.Net.Sockets.UdpClient.<>c.<ReceiveAsync>b__56_1(IAsyncResult asyncResult)",
            "at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)"
        ]
    }]
}

Лямбда-код довольно прост: он добавляет эти SNS messages в базу данных SQL с помощью Dapper,

Я думаю, что могут быть некоторые проблемы с тем, как я делаю async в обработчике Fucntion. Есть идеи?

public class Function
{
    private static string _connectionString;

    public async Task<IEnumerable<InsertSnsResult>> FunctionHandler(SNSEvent @event, ILambdaContext context)
    {
        try
        {
            context.Logger.LogLine("Adding SNS Messages");
            _connectionString = _connectionString ?? await DecryptHelper.DecryptEnvironmentVariableAsync("ConnectionString").ConfigureAwait(false);
            var handler = new AddSnsMessageHandler(new SnsMessagesRepository(_connectionString, context.Logger));
            return await handler.AddSnsEvents(@event).ConfigureAwait(false);
        }
        catch (Exception e)
        {
            context.Logger.LogLine(e.Message);
            throw;
        }
        finally
        {
            context.Logger.LogLine("Finished SNS Adding Messages");
        }
    }
}

[Редактировать]

Просто поймите, что это исключение не попадает в блок try/catch. Если бы это было так, это не было бы UnobservedTaskException, Вот почему у меня проблемы с получением корня проблемы.

И это код хранилища

public async Task<List<InsertSnsResult>> InsertSnsMessages(IEnumerable<SnsEvent> records)
{
    using (var connection = new SqlConnection(_connectionString))
    {
        await connection.OpenAsync().ConfigureAwait(false);

        var results = new List<InsertSnsResult>();
        foreach (var record in records)
        {
            try
            {
                await connection.ExecuteAsync(InsertEventCommand, record).ConfigureAwait(false);
                results.Add(new InsertSnsResult(record.CorrelationId, true));
            }
            catch (Exception ex)
            {
                _logger.LogLine($"InsertSns failed for {record.Id}. {ex.Message}");
                results.Add(new InsertSnsResult(record.CorrelationId, false));
            }
        }

        return results;
    }            
}

1 ответ

Сообщения журнала просты и объясняют, что происходит:

  • У вас есть асинхронная задача
  • Эта асинхронная задача обращается к объекту, который уже был удален, возможно, потому, что в вашем рабочем процессе имеется некоторое состояние гонки, в результате которого объект в асинхронном рабочем процессе выходит из строя с другой частью рабочего процесса, которая в этом нуждается. Это означает, что что-то серьезно нарушено в этом рабочем процессе.
  • Асинхронная задача никогда не ожидается, либо асинхронно с await, либо (не делайте этого!) Синхронно с Result или Wait. Это означает, что исключительный путь продолжения никогда не берется, и задача замечает это, когда он собирается. Опять же, возможно, что-то серьезно нарушено в вашем рабочем процессе, если у вас есть задача, которую вы никогда не дождетесь результата. Объедините этот факт с фактом из предыдущего пункта: теперь у нас есть два доказательства, которые подтверждают друг друга, что в этом рабочем процессе что-то серьезно нарушено и что оно включает в себя задачу, которая не ожидается, когда это необходимо для обеспечения ограничения порядка,
  • И, следовательно, вы получите исключение в вашей ветке финализатора, что действительно плохо.

Так как функционально это работает, я игнорирую это

Однажды я слышал, что когда завод загорается и горит на земле, в среднем было семь разных систем безопасности, которые люди игнорировали или отключали. Избавьтесь от этой привычки думать, что это работает, так что это должно быть безопасно. Может быть, это ничего, но я бы посчитал эти сообщения показательными для серьезной проблемы, пока у меня не будет доказательств обратного.

Я тоже столкнулся с местом, где у меня есть сторонняя библиотека, вызывающая ошибки. Я хотел зарегистрировать это вне CloudWatch. Чтобы Lambda не регистрировала их, я смог сделать некоторую злобную рефлексию, чтобы сбросить обработчик событий.

Вот код, чтобы сделать это самостоятельно. Обратите внимание, что это злой код. Он хрупкий и сломается, когда код будет изменен в CLR или даже если компилятор выполнит оптимизацию (что недавно произошло). Однако это был единственный способ отказаться от этой функции, предоставляемой Lambda.

private void ReplaceLambdaDefaultUnobservedTaskException()
{
    try
    {
        var bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
        Type type = typeof(TaskScheduler);

        var field = type.GetField("_unobservedTaskException", bindingFlags);

        if (field == null)
        {
            field = type.GetField("UnobservedTaskException", bindingFlags);
        }

        var handler = new EventHandler<UnobservedTaskExceptionEventArgs>(TaskSchedulerOnUnobservedTaskException);
        field.SetValue(null, handler);
    }
    catch (Exception ex)
    {
        logger.Warning(ex, "Unable to do evil reflection.");
    }

    TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
}

private void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    e.SetObserved();
    logger.Error(e.Exception, "Lambda threw an UnobservedTaskException");
}
Другие вопросы по тегам