MarshalByRefObject becoming "disconnected at server" even while sponsored
Asked Answered
I

2

1

I am writing a game which supports mods, for safety I am sandboxing the mods into a separate AppDomain from the game engine (so I can restrict the capabilities of mods separately from the engine). However, objects in the scripting domain which the engine keeps a reference to keep getting garbage collected too early and I get an exception like this:

Object '/30b08873_4929_48a5_989c_e8e5cebc601f/lhejbssq8d8qsgvuulhbkqbo_615.rem' has been disconnected or does not exist at the server.

On the server side I am creating objects like this (using 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);
}

The idea is that Reference<T> is the sponsor for the object and as long as it stays alive the object in the remote domain will also stay alive. This is where the problem lies, I am accessing objects through their reference and yet I still get the "disconnected at server" exception. Here is my implementation of Reference<T>:

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;
        }
    }
}

What am I doing wrong to cause my objects to die even whilst there is a live Reference<T> to them?

Incautious answered 24/3, 2014 at 18:16 Comment(6)
Is there a reason, you don't use the built in ClientSponsor? Could also be your AppDomain is dying if the InitializeLifetimeService() is not being called on it which would start wreaking havoc on your instances.Joinville
Only because I didn't know the ClientSponsor existed! I assume AppDomainToolkit handles calling InitializeLifetimeService for me, I'll check that out.Incautious
Actually, I know the AppDomain is definitely not dying, because some remote objects are still working perfectly fine whilst others are becoming disconnected.Incautious
I would attempt to use that then to test since it should be fairly simple to implement and is what most people use; myself included.Joinville
So far using ClientSponsor seems to be working. Obviously it's kinda hard to tell given the nature of the problem and the GC kinda doing what it wants when it wants!Incautious
Well I'm happy to say this is fixed. I'm curious about what I was doing wrong, but if you want to put down ClientSponsor as an answer I'll happily accept it :)Incautious
J
2

Use the ClientSponsor class that is built into .NET. It is very stable and simplistic.

It's what most people turn to when working across AppDomains. Your implementation looks fine, but could be as simple as your SponsorTime() not returning the correct value from the config.

Joinville answered 24/3, 2014 at 21:6 Comment(0)
I
1

I found something that seems to be a much simpler solution in this StackOverflow post.

It consists of telling that object has an infinite life time, doing that :

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

This solution is not unanimous but the post brings us a lot more valuable answers.

Ison answered 15/3, 2018 at 10:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.