I didn't have time to investigate the problem I had with RemotingServices.Disconnect. Instead I switched from client-side to server-side sponsorship. I didn't want to modify any of the classes being remoted so I had to find an external way of registering and unregistering class instances for sponsorship.
I created a class to expose the ITrackingHandler interface. The implementation of the MarshaledObject method registers for sponsorship the object being marshaled (for the types for which sponsorship is required). Details of the object are stored in a collection so at any time we know which objects have been sponsored.
Most of the relevant types implement IDisposable (to allow unmanaged resources to be released deterministically) so I use a call to Dispose to indicate that sponsorship should be unregistered. To detect calls to Dispose I implemented a channel sink which examines the message details to determine the method being called, etc. If the method is Dispose, the object has its sponsorship unregistered and its details removed from the collection.
Finally, for objects which Dispose is not called or which don't implement IDisposable, the collection is checked periodically and any members which have been there longer than a configured period of time have their sponsorship unregistered.
This solution has now been running live for two weeks and the leakage of objects on the server has not occurred again. I'm not sure if this is a good general purpose approach but for the constraints of the particular problem it has worked well.