MarshalByRefObject становится "отключенным на сервере" даже при спонсорской поддержке

Я пишу игру, которая поддерживает моды, в целях безопасности я помещаю моды в изолированную программную среду в отдельный AppDomain от игрового движка (поэтому я могу ограничить возможности модов отдельно от движка). Тем не менее, объекты в домене сценариев, на который движок ссылается, чтобы собирать мусор слишком рано, получают исключение, подобное этому:

Объект '/30b08873_4929_48a5_989c_e8e5cebc601f/lhejbssq8d8qsgvuulhbkqbo_615.rem' был отключен или не существует на сервере.

На стороне сервера я создаю такие объекты (используя AppDomainToolkit:

// Take in a "script reference" which is basically just the name of a type in the scripting domain
public Reference<T> Dereference<T>(ScriptReference reference) where T : MarshalByRefObject
{
    // Create a remote instance of this type
    T instance = (T)RemoteFunc.Invoke<ScriptReference, object>(_pluginContext.Domain, reference, r =>
    {
        var t = Type.GetType(r.TypeName, true, false);
        return Activator.CreateInstance(t);
    });

    //Return a reference which keeps this object alive until disposed
    return new Reference<T>(instance, reference.TypeName, reference.Name);
}

Идея состоит в том, что Reference является спонсором для объекта, и пока он остается живым, объект в удаленном домене также будет оставаться живым. В этом и заключается проблема: я обращаюсь к объектам по их ссылке, и все же я получаю исключение "отключено на сервере". Вот моя реализация Reference:

public sealed class Reference<T>
    : IDisposable
    where T : MarshalByRefObject
{
    private InnerSponsor _sponsor;

    public T RemoteObject
    {
        get { return _sponsor.RemoteObject; }
    }

    public string TypeName { get; private set; }
    public string Name { get; private set; }

    public Reference(T obj, string typeName = null, string name = null)
    {
        TypeName = typeName;
        Name = name;

        _sponsor = new InnerSponsor(obj);
    }

    ~Reference()
    {
        if (_sponsor != null)
            _sponsor.Dispose();
        _sponsor = null;
    }

    public void Dispose()
    {
        _sponsor.Dispose();
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Inner sponsor is the actual sponsor (and is kept alive by the remoting system).
    /// If all references to Reference are lost, it will dispose the sponsor in its destructor
    /// </summary>
    private sealed class InnerSponsor
        : MarshalByRefObject, ISponsor, IDisposable
    {
        private readonly ILease _lease;
        private bool _isDisposed;
        public readonly T RemoteObject;

        private bool _isUnregistered = false;

        public InnerSponsor(T obj)
        {
            RemoteObject = obj;
            _lease = (ILease)obj.GetLifetimeService();
            _lease.Register(this, SponsorTime());
        }

        public TimeSpan Renewal(ILease lease)
        {
            if (lease == null)
                throw new ArgumentNullException("lease");

            if (_isDisposed)
            {
                if (!_isUnregistered)
                {
                    _lease.Unregister(this);
                    _isUnregistered = true;
                }
                return TimeSpan.Zero;
            }

            return SponsorTime();
        }

        private static TimeSpan SponsorTime()
        {
            return TimeSpan.FromMilliseconds(/*Some value fetched from configuration*/);
        }

        public void Dispose()
        {
            _isDisposed = true;
        }
    }
}

Что я делаю не так, чтобы мои объекты умирали, хотя на них есть живая ссылка?

2 ответа

Решение

Используйте класс ClientSponsor, встроенный в.NET. Это очень стабильно и упрощенно.

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

Я нашел кое-что, что кажется намного более простым решением в этом посте Stackru.

Он состоит в том, чтобы сказать, что объект имеет бесконечное время жизни, делая это:

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}

Это решение не является единодушным, но пост дает нам гораздо более ценные ответы.

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