Dependency Injection File Locking

Posts   
 
    
matdone
User
Posts: 12
Joined: 02-Oct-2007
# Posted on: 23-Nov-2007 00:55:07   

Hello, I seem to have an issue with the dependency injection and file locking of the DLL that contains the Auditor Class. When the application is running a File Lock on the assembly is created for the Auditor. This lock prevents me from uploading a new version of my website because I get "This file is been used by another process". This is a major problem for me as I don't have the ability to reset the web server every time I want to make a change.

Can you tell me if this is a common trait of dependency injection or whether I am doing something wrong.

My auditor code is the same as it is in the example. Nothing different here. My Web config code is.



<section name="dependencyInjectionInformation" type="SD.LLBLGen.Pro.ORMSupportClasses.DependencyInjectionSectionHandler, 
                 SD.LLBLGen.Pro.ORMSupportClasses.NET20, Version=2.5.0.0, Culture=neutral, PublicKeyToken=ca73b74ba4e3ff27"/>

<dependencyInjectionInformation>
    <additionalAssemblies>
        <assembly filename="MBL.CAG.FOD.RACS.Helper.dll"/>
    </additionalAssemblies>
</dependencyInjectionInformation>

  <appSettings>
      <add key="autoDependencyInjectionDiscovery" value="true"/>
  </appSettings>

If have tried with autoDependencyInjectionDiscovery set to false and set to true. This doesn't change anything.

I tried to replicate the issue using your Auditing example and it happens in that as well. Steps to Reproduce. 1. Setup IIS virtual Directory that points to you app. 2. Make a copy of the website files and store them somewhere else. 3. Compile and run the website (eg

http://localhost/LLBLGenAuditing/Default.aspx
  1. Login
  2. While the website is running copy the files over the top of the web site files.
  3. You will get "Cannot copy database Auditor. The file is in use by another process"

I thought ASP.net creates shadow copies of the DLL is it using for the exact purpose of been able to override Bin Directory files (therefore no locking)

It appears that LLBGen is not using the shadow copied DLLs and referencing the Bin directory directly.

Anybody with any advice or tips as to how I can solve this issue would be appreciated.

Regards, Mattt.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 23-Nov-2007 10:10:35   

LLBLGen Pro loads the assembly into memory with Assembly.Load() and doesn't use a shadowcopy.

It's not really necessary either, as you have to restart the website's appdomain to make it re-load the new dll anyway. So if you will be able to copy over the dll into the bin folder it will have no effect, the site will have to be restarted anyway.

But, restarting a site can be done in many ways, and it might be that the restart you now have to do (stop the application pool or stop the webapp, copy the file, restart the app/application pool) is too cumbersome or not possible and you want to restart it by altering the web.config file for example to trigger a restart. Please let us know if this is the case so we can look into using shadow copy for this (it might not be possible in this case, we've to research that)

Frans Bouma | Lead developer LLBLGen Pro
matdone
User
Posts: 12
Joined: 02-Oct-2007
# Posted on: 26-Nov-2007 00:11:07   

Thanks for the reply but I don't believe the statement you made is entirely correct:

It's not really necessary either, as you have to restart the website's appdomain to make it re-load the new dll anyway. So if you will be able to copy over the dll into the bin folder it will have no effect, the site will have to be restarted anyway.

The explanation as provided by http://www.odetocode.com/Articles/305.aspx is the way I understood shadow coping to work.

Once an assembly is loaded into an AppDomain, there is no way to remove the assembly from the AppDomain. It is possible, however, to remove an AppDomain from a process.

If you copy an updated dll into an application’s bin subdirectory, the ASP.NET runtime recognizes there is new code to execute. Since ASP.NET cannot swap the dll into the existing AppDomain , it starts a new AppDomain. The old application domain is “drain stopped”, that is, existing requests are allowed to finish executing, and once they are all finished the AppDomain can unload. The new AppDomain starts with the new code and begins taking all new requests.

Typically, when a dll loads into a process, the process locks the dll and you cannot overwrite the file on disk. However, AppDomains have a feature known as Shadow Copy that allows assemblies to remain unlocked and replaceable on disk.

The runtime initializes ASP.NET with Shadow Copy enabled for the bin directory. The AppDomain will copy any dll it needs from the bin directory to a temporary location before locking and loading the dll into memory. Shadow Copy allows us to overwrite any dll in the bin directory during an update without taking the web application offline.

This problem is also a evident when you want to compile and application. If you run and debug an app then stop, change something, then recompile you get and error saying "file cannot be copied as it is used by another process" (Same error). The only way to fix this is to remove the dependency injection from the web.config or wait about 3 minutes for the lock to timeout and expire.

I had a bit of a look around on the web for a solution to this problem. Maybe the following links could help.

http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx http://blogs.msdn.com/ericgu/archive/2007/06/05/app-domains-and-dynamic-loading-the-lost-columns.aspx

I think when more people start using the dependency injection functionality of LLBLGen (which is great may I add) the more this issue will crop up. You should be able to replace all contents of a web directory without the need to restart the application.

Matt.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 26-Nov-2007 09:18:01   

matdone wrote:

Thanks for the reply but I don't believe the statement you made is entirely correct:

It's not really necessary either, as you have to restart the website's appdomain to make it re-load the new dll anyway. So if you will be able to copy over the dll into the bin folder it will have no effect, the site will have to be restarted anyway.

The explanation as provided by http://www.odetocode.com/Articles/305.aspx is the way I understood shadow coping to work.

Once an assembly is loaded into an AppDomain, there is no way to remove the assembly from the AppDomain. It is possible, however, to remove an AppDomain from a process.

If you copy an updated dll into an application’s bin subdirectory, the ASP.NET runtime recognizes there is new code to execute. Since ASP.NET cannot swap the dll into the existing AppDomain , it starts a new AppDomain. The old application domain is “drain stopped”, that is, existing requests are allowed to finish executing, and once they are all finished the AppDomain can unload. The new AppDomain starts with the new code and begins taking all new requests.

Typically, when a dll loads into a process, the process locks the dll and you cannot overwrite the file on disk. However, AppDomains have a feature known as Shadow Copy that allows assemblies to remain unlocked and replaceable on disk.

The runtime initializes ASP.NET with Shadow Copy enabled for the bin directory. The AppDomain will copy any dll it needs from the bin directory to a temporary location before locking and loading the dll into memory. Shadow Copy allows us to overwrite any dll in the bin directory during an update without taking the web application offline.

All fine, but the applications needs to restart anyway. The code won't re-load the assemblies for Dependency Injection, only at startup.

This problem is also a evident when you want to compile and application. If you run and debug an app then stop, change something, then recompile you get and error saying "file cannot be copied as it is used by another process" (Same error). The only way to fix this is to remove the dependency injection from the web.config or wait about 3 minutes for the lock to timeout and expire.

I had a bit of a look around on the web for a solution to this problem. Maybe the following links could help.

http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx http://blogs.msdn.com/ericgu/archive/2007/06/05/app-domains-and-dynamic-loading-the-lost-columns.aspx

I think when more people start using the dependency injection functionality of LLBLGen (which is great may I add) the more this issue will crop up. You should be able to replace all contents of a web directory without the need to restart the application. Matt.

The DI assemblies have to be loaded into the appdomain of the entities and thus the webapplication. Assembly discovery is done in a separate appdomain. DI assemblies can't be loaded into a different appdomain, so if that's the solution, it's not going to work.

I've no idea why the locking takes place if shadow copy is something which is performed on dlls in the bin folder, as the DI code simply loads the assembly from disk, what else can it do? It doesn't know if it is in a web application or not, nor should that matter. The main problem is that if it has to know if it's in a webapplication, it's a problem, because the code can't know that, without a dirty hack by checking which assemblies are loaded and assume it's a webapp, but even then that could go wrong.

But everything aside: MS places the lock on the assembly, for whatever reason... I don't know why they don't copy the dynamically loaded assemblies to a temp dir as well, and I also don't know how to get a hold on the temp folder to copy the assembly to...

Sorry to be so stubborn, but after a couple of years of dealing with MS problems I'm getting pretty tired of having to work around trouble THEY create.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 26-Nov-2007 10:18:16   

Ok, reading a bit about the shadowcopy stuff, I don't set the shadowcopy flag on the appdomain which does the discovery.

The thing is though: that appdomain is unloaded. This means that the locks should be removed. confused

What exactly do you use for DI settings in your config file? Could you post these exact settings?

The thing is that: 1) either the DI assembly is loaded into the webapp's appdomain, which means it should be shadow copied or 2) the appdomain for the discovery isn't unloaded properly.

(edit). I think I know what's going on: 1) you have auto-discovery enabled 2) the discoverer appdomain is created without shadowcopy 3) all assemblies to discover are loaded into THAT appdomain. It will find the types for DI and store that in a structure. That structure is returned 4) because of that, the assemblies containing these types are now in the appdomain of the main app 5) the discoverer appdomain is unloaded. This unloads any assemblies not used for DI.

So I think, that this is a loophole around shadowcopy: you can load an assembly A into an appdomain which has shadowcopy enabled without getting it shadow copied using this. Not sure if this is a bug in .net or not, that's also not important, they won't fix it anyway.

So it's now important to know if you have auto-discovery enabled or not. If so, please see the note in the documentation that auto-discovery isn't going to work in a lot of cases in ASP.NET. You should enable DI via manual discovery by specifying DI info in the config file.

The thing is: if you don't have auto-discovery enabled, the assemblies specified in the DI info in the config file are loaded with either Load or LoadFrom, into the appdomain of the application, which IS the webapp appdomain which should shadowcopy the file.

There is a bug in ASP.NET where it can't really deal with strongly named assemblies in the bin folder: these sometimes get locked. Not sure if that's the problem or not. Anyway, let's first wait what you have enabled for DI info gathering. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
matdone
User
Posts: 12
Joined: 02-Oct-2007
# Posted on: 27-Nov-2007 01:38:02   

I have found a solution to the problem. You have to use the fullname attribute if you a specifically defining the auditor class in the dependencyInjectionInformation section. You must not use the FileName Attribute. My Section look like this.


    <dependencyInjectionInformation>
        <additionalAssemblies>
            <assembly fullName="MBL.CAG.FOD.RACS.Helper"/>
         </additionalAssemblies>
    </dependencyInjectionInformation>

I assume this will use the shadow copied assembly and not reference the bin directory directly.

Alternatively you could use the autoDependencyInjectionDiscovery set to true and do not specify any dependencyInjectionInformation section. This also works.

Thanks for you help Frans. Matt.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39749
Joined: 17-Aug-2003
# Posted on: 27-Nov-2007 12:09:52   

matdone wrote:

I have found a solution to the problem. You have to use the fullname attribute if you a specifically defining the auditor class in the dependencyInjectionInformation section. You must not use the FileName Attribute. My Section look like this.


    <dependencyInjectionInformation>
        <additionalAssemblies>
            <assembly fullName="MBL.CAG.FOD.RACS.Helper"/>
         </additionalAssemblies>
    </dependencyInjectionInformation>

I assume this will use the shadow copied assembly and not reference the bin directory directly.

Alternatively you could use the autoDependencyInjectionDiscovery set to true and do not specify any dependencyInjectionInformation section. This also works.

Thanks for you help Frans. Matt.

aha! fileName uses Assembly.LoadFile(...) and the others use Assembly.Load() I think that's the problem. I see now that : http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx#70092 refers to exactly this: LoadFile doesn't get shadowcopy applied to it, because it bypasses fusion (for whatever reason).

That's thus the reason why this happens. Thanks for the info simple_smile

Frans Bouma | Lead developer LLBLGen Pro