Клиент WCF приводит к зависанию сервера до сбоя подключения

Приведенный ниже текст является попыткой расширить и добавить цвет к этому вопросу:

Как я могу предотвратить ненадлежащее поведение клиента, отказавшегося от всей услуги?

По сути, у меня есть такой сценарий: служба WCF запущена и работает с обратным вызовом клиента, имеющим прямое, простое одностороннее соединение, не очень отличающееся от этого:

public interface IMyClientContract
{
  [OperationContract(IsOneWay = true)]
  void SomethingChanged(simpleObject myObj);
}

Я вызываю этот метод потенциально тысячи раз в секунду из службы в то, что в конечном итоге будет около 50 одновременно подключенных клиентов, с минимально возможной задержкой (<15 мс было бы неплохо). Это работает нормально до тех пор, пока я не установлю точку останова на одном из клиентских приложений, подключенных к серверу, а затем все зависает, возможно, через 2-5 секунд служба зависает, и ни один из других клиентов не получает никаких данных в течение примерно 30 секунд или около того, пока служба не будет регистрирует событие сбоя соединения и отключает клиента После этого все остальные клиенты продолжают веселиться, получая сообщения.

Я провел исследование serviceThrottling, настройки параллелизма, установки минимальных потоков потоков, секретных соусов WCF и целых 9 ярдов, но в конце этой статьи MSDN - основы WCF, односторонние вызовы, обратные вызовы и события описывают в точности проблема, которую я имею без действительно рекомендации.

Третье решение, которое позволяет службе безопасно перезванивать клиенту, заключается в том, чтобы контрактные операции обратного вызова были настроены как односторонние операции. Это позволяет службе перезванивать, даже если для параллелизма задан однопоточный режим, поскольку не будет никакого ответного сообщения для борьбы за блокировку.

но ранее в статье описывается проблема, которую я вижу, только с точки зрения клиента

Когда односторонние вызовы достигают службы, они могут отправляться не все сразу и могут быть поставлены в очередь на стороне службы для отправки по одному, все в соответствии с настроенным для службы режимом параллелизма и режимом сеанса. Сколько сообщений (односторонних или запросов-ответов) служба готова поставить в очередь, является продуктом настроенного канала и режима надежности. Если количество сообщений в очереди превысило емкость очереди, клиент заблокирует даже при выполнении одностороннего вызова

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

Какой правильный способ справиться с этим? Должен ли я найти способ проверить, сколько сообщений находится в очереди на служебном коммуникационном уровне для каждого клиента, и прервать их соединения после достижения определенного предела?

Похоже, что если сама служба WCF блокирует заполнение очереди, то все стратегии async / oneway / fire-and-Forgot, которые я когда-либо мог реализовать внутри службы, будут по-прежнему блокироваться при заполнении очереди одного клиента.

2 ответа

Решение

Обновить:

Я реализовал настройку Fire-and- Forgot для вызова канала обратного вызова клиента, и сервер больше не блокируется, когда буфер заполняется клиенту.

MyEvent - это событие с делегатом, которое соответствует одному из методов, определенных в клиентском контракте WCF, когда они подключаются, я по существу добавляю обратный вызов к событию

MyEvent += OperationContext.Current.GetCallbackChannel<IFancyClientContract>().SomethingChanged

и т. д... и затем, чтобы отправить эти данные всем клиентам, я делаю следующее

//serialize using protobuff
using (var ms = new MemoryStream())
{
    ProtoBuf.Serializer.Serialize(ms, new SpecialDataTransferObject(inputData));
    byte[] data = ms.GetBuffer();
    Parallel.ForEach(MyEvent.GetInvocationList(), p => ThreadUtil.FireAndForget(p, data));
}

в классе ThreadUtil я сделал по существу следующее изменение в коде, определенном в статье fire-and-foget

static void InvokeWrappedDelegate(Delegate d, object[] args)
{
    try
    {
        d.DynamicInvoke(args);
    }
    catch (Exception ex)
    {
        //THIS will eventually throw once the client's WCF callback channel has filled up and timed out, and it will throw once for every single time you ever tried sending them a payload, so do some smarter logging here!!
        Console.WriteLine("Error calling client, attempting to disconnect.");
        try
        {
            MyService.SingletonServiceController.TerminateClientChannelByHashcode(d.Target.GetHashCode());//this is an IContextChannel object, kept in a dictionary of active connections, cross referenced by hashcode just for this exact occasion
        }
        catch (Exception ex2)
        {
            Console.WriteLine("Attempt to disconnect client failed: " + ex2.ToString());
        }
    }
}

У меня нет хороших идей, как пойти и убить все ожидающие пакеты, которые сервер все еще ждет, чтобы увидеть, будут ли они доставлены. Как только я получу первое исключение, я теоретически должен быть в состоянии пойти и завершить все другие запросы в какой-то очереди, но эта настройка является функциональной и соответствует целям.

Не знаю много о клиентских обратных вызовах, но это похоже на общие проблемы блокировки кода wcf. Я часто решаю эти проблемы, вызывая BackgroundWorker и выполняя клиентский вызов в потоке. В течение этого времени основной поток подсчитывает, сколько времени занимает дочерний поток. Если дочерний процесс не завершил работу в течение нескольких миллисекунд, основной поток просто перемещается и покидает поток (в конечном итоге он умирает сам по себе, поэтому утечка памяти отсутствует). Это в основном то, что г-н Грейвс предлагает с помощью фразы "запусти и забудь".

Другие вопросы по тегам