From paul.miller at xanalys.com Thu Apr 24 10:59:01 2014 From: paul.miller at xanalys.com (Paul Miller) Date: Thu, 24 Apr 2014 11:59:01 +0100 Subject: [Rdnzl-devel] FW: Problems using RDNZL with AppDomains and IIS. In-Reply-To: References: Message-ID: <75b092dd-9d58-448e-8aa9-20ad61aceb07@xanalys.com> [Resending as plain text as previous HTML version was too big] Hello, I have been using RDNZL for a while without any problems, in a in desktop Lisp application (in LispWorks for Windows). However I have recently been trying to use RDNZL with a Lisp DLL called by a .Net WCF service deployed with IIS. No doubt this is a fairly unusual scenario but I thought I would check if anyone has seen any similar issues. So I have a Lisp DLL created with LispWorks which also uses RDNZL to call some 3rd party .Net libraries. The Lisp DLL is now being used by some .Net WCF web services via IIS 7.5. This gives an unusual setup with managed .Net calling into unmanaged Lisp which then calls into managed .Net. This setup seems to work fine in a simple console or desktop application but running under IIS introduces some complexity with .Net AppDomains. (As a further complication the Lisp code implements some MTA COM interfaces used by the .Net WCF services). It is not entirely deterministic but frequently I see the error "Cannot pass a GCHandle across AppDomains" and it seems to be occurring when the RDNZL VC++ library calls the DotNetReference destructor: // destructor DotNetReference::~DotNetReference() { if (ptr) { // give up pointer so garbage collector regains control static_cast((IntPtr)ptr).Free(); } } It seems as if .Net has moved the thread that created the DotNetReference into another AppDomain by the time we come to call the destructor. Or maybe the destructor is executing on a different thread in a different AppDomain. So far it seems that I can prevent the error by making the following changes to check the current AppDomain before calling GCHandle.Free: class DotNetReference { public: DotNetReference(); DotNetReference(Object ^o); ~DotNetReference(); Object ^getObject(); private: void *ptr; int appDomainID; // to record current AppDomain ID }; // normal constructor DotNetReference::DotNetReference(Object ^o) { // acquire pointer to object so it can't be reclaimed by the .NET // garbage collector until explicitely freed appDomainID = AppDomain::CurrentDomain->Id; ptr = ((IntPtr) GCHandle::Alloc(o)).ToPointer(); } // destructor DotNetReference::~DotNetReference() { if (ptr && (appDomainID == AppDomain::CurrentDomain->Id)) { // give up pointer so garbage collector regains control static_cast((IntPtr)ptr).Free(); } } Of course this means that we are leaking GCHandles (unless maybe the original AppDomain has been entirely unloaded by .Net). Even more worrying is that RDNZL uses GCHandle.Target to get the referenced object back and this will not work if it is called in the wrong AppDomain. So far though, I have not actually seen any errors from this. Object ^DotNetReference::getObject() { // return the object the instance was initialized with return ptr ? safe_cast(static_cast((IntPtr)ptr).Target) : nullptr; } The VC++ DotNetReference destructor is called by LispWorks as part of a special free action during garbage collection so it is not deterministic when it will be called. (The special free action is on a structure which wraps a pointer to a DotNetContainer object, which in turn has a couple of private members that contain RDNZL DotNetReference objects). --- ;; register MAYBE-FREE-CONTAINER-POINTER as a finalization ;; function - needed for LispWorks (hcl:add-special-free-action 'maybe-free-container-pointer) (defun flag-for-finalization (object &optional function) "Mark OBJECT such that FUNCTION is applied to OBJECT before OBJECT is removed by GC." ;; LispWorks can ignore FUNCTION because it was registered globally ;; above (declare (ignore function)) (hcl:flag-special-free-action object)) --- It could be that as long as I don't hold on to RDNZL objects any longer than needed to make a single function call (or a group of closely related function calls) then the ^DotNetReference::getObject will never encounter a problem with AppDomains. As I said earlier, I guess this is a fairly unusual scenario but if anyone else has had to deal with this kind of problem and has any experience or advice to share it would be much appreciated. Thanks, Paul