Исключение WCF CommunicationException, связанное с ServiceKnownType после запуска в течение некоторого времени (или повторного использования пула приложений IIS?)

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

Все это, кажется, работает нормально, однако иногда мы видим, что все запросы к этим службам завершаются с ошибкой CommunicationException; "Произошла ошибка при попытке сериализации параметра http://tempuri.org/:arg . Сообщение InnerException было "Тип" MyNamespace.SomeInterfaceImplementation" с именем контракта данных "SomeInterfaceImplementation:http://schemas.datacontract.org/2004/07/MyNamespace" не ожидается. Добавляйте в список известных типов любые неизвестные статически типы, например, используя атрибут KnownTypeAttribute или добавляя их в список известных типов, передаваемых в DataContractSerializer.'. Дополнительные сведения см. в разделе InnerException."

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

Я могу надежно воспроизвести исключение, исключив 'MyNamespace.MyType' из поставщика 'ServiceKnownTypes', однако это на самом деле не говорит мне, почему это происходит с перерывами.

В следующем фрагменте показано объявление службы. Это общая служба для различных типов реализации . Обратите внимание, что именно аргумент операции 'arg' создает исключение CommunicationException.

[ServiceKnownType("GetKnownTypes", typeof(KnownTypesCache))]
[ServiceContract]
public interface IMyService<T>
    where T : class, IMyServiceTypeInterface
{
    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    MyReturnType HandleRequest(T element, ISomeInterfaceArgumentType arg);
}

Теперь реализация KnownTypesCache, предоставляющая известные типы для ISomeInterfaceArgumentType, выглядит так;

public static class KnownTypesCache
{
    private static readonly List<Assembly> queriedAssemblies = new List<Assembly>();
    private static readonly List<Type> knownTypes = new List<Type>();


    static KnownTypesCache()
    {
        // get all available assemblies at this time
        List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

        // find all available known types publishers
        IEnumerable<Type> knownTypesPublisherTypes = assemblies
            .Where(a => !queriedAssemblies.Contains(a)) // exclude already queried assemblies to speed things up
            .SelectMany(s => s.GetTypes())
            .Where(p => typeof(IKnownTypesPublisher).IsAssignableFrom(p) && p.HasAttribute(typeof(KnownTypesPublisherAttribute)));

        // add all known types
        foreach (Type type in knownTypesPublisherTypes)
        {
            IKnownTypesPublisher publisher = (IKnownTypesPublisher)Activator.CreateInstance(type);

                AddRange(publisher.GetKnownTypes());
            }
        }


        // record the assemblies we've already loaded to avoid relookup
        queriedAssemblies.AddRange(assemblies);
    }

    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
    {
        return knownTypes;
    }
}

По сути, этот глобальный статический кэш запрашивает все загруженные сборки в AppDomain для разработчиков IKnownTypesPublisher, которые, в свою очередь, предоставляют доступные типы сериализации для каждой сборки. Таким образом, на каждую сборку приходится один IKnownTypesPublisher, который отвечает за идентификацию типов только в этой сборке, а KnownTypesCache просто объединяет все это и возвращает в DataContractSerializer во время выполнения.

Как я уже упоминал, этот подход работает нормально в 99% случаев. А затем по неизвестной мне причине он перестает работать и может быть разрешен только вызовом iisreset.

Сейчас я в замешательстве, я пробовал всевозможные решения, но поскольку я могу надежно воспроизвести эту ошибку, только дождавшись понедельника, это немного сложно!

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


person RJ Lohan    schedule 23.07.2012    source источник


Ответы (1)


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

С этим вы, вероятно, уже на пути к ответу на свой вопрос.

AppDomain.GetAssemblies() возвращает только те сборки, которые уже были загружены в контекст выполнения AppDomain. После любого перезапуска рабочего процесса IIS, который создает новый AppDomain, у вас может возникнуть состояние гонки между первым использованием типа KnownTypesCache и загрузкой любой сборки, содержащей один из ваших типов IKnownTypesPublisher. Периодические, трудно воспроизводимые ошибки часто могут быть вызваны условиями гонки.

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

person Chris Dickson    schedule 23.07.2012