GAC Phobia


I was recently forwarded a link to a posting entitled “Avoid the GAC”. I would like to offer some insights into points addressed by this article, to help clarify misconceptions you may have about the GAC, and its intended usage.


The article argues that the GAC is the managed equivalent of the Windows system32 directory, and suffers from the same DLL sharing problems of the past (e.g. “DLL hell”). While it is pointed out that the GAC does facilitate side-by-side storage of DLLs based on the assembly’s strong identity (something that did not exist with system32), the author reasons that side-by-side storage isn’t useful because if a new version is available that your app didn’t use, then the update was pointless. Publisher policy is mentioned as a way to force all apps to use the update, and the argument concludes by saying that this ends up in exactly the same place we started with—system32.


A large source of confusion in this topic stems from what publisher policy really is and what it is intended for. There are two very different scenarios for updating an existing assembly:


1) Critical service updates (e.g. security fixes). These are tactical fixes intended to address specific problems with some previously released version of the component. No new features are in the update, and it is expected that the code is highly compatible with the existing version.


2) New feature releases. It may be the case that the new code is mostly compatible with the old version of the assembly, but there are no guarantees.


In case (1), it is clearly desirable to affect all applications on the machine. Because of the critical nature of security fixes, users (and administrators) want to be assured that if they apply a given security fix, that all applications are patched. This is a far better story than trying to hunt down n different applications on the machine that may be using the buggy component, and patching them individually. In the post-slammer-virus world, it should be evident that central servicing for security updates is a critical piece of ensuring machines are protected against malicious code.


In case (2), we recommend that component authors do not ship publisher policy statements which affect all applications. Because the compatibility of the assembly cannot be guaranteed against older versions, consuming the update should be done on an opt-in basis, rather than by default. You can selectively choose to use a new update for a particular application by utilizing application configuration policy.


To be clear, I am not advocating putting everything in the GAC. In fact, there was a fair amount of work put into supporting the xcopy deployment model, because of its simplicity and ease of use. An xcopied application is generally easier to move from machine to machine; there’s no complex setup necessary to install such an application.


If a publisher policy assembly containing a critical security fix is installed onto a given machine, it will affect all applications that use the older version of the assembly regardless of whether or not that assembly was xcopy deployed or installed into the GAC. But where does the assembly with the actual fixed bits go? The policy will affect every application that used the old version, and clearly there needs to be a place where those applications can find the fixed bits. The GAC is the natural choice here.


These two scenarios are somewhat confused in the “avoid the GAC” article. The author claims that there is no point in providing a new version if it is not intended to affect all applications—it may as well not be side-by-side. This is not true. As mentioned above, for security fixes, it is definitely desirable to affect all applications. Even so, this does not imply that security fixes should not be side-by-side. Inevitably, there will be cases where a critical security fix will break some application, and users/administrators will need to revert to the old functionality. Side-by-side helps you here, because it allows you to selectively roll back a given application to use the older version, while still allowing all other applications to use the fixed bits. This is a conscious decision by the user/administrator to bypass the security fix because this is an exceptional circumstance.


For non-security fixes, policy should not be applied to maintain maximum application compatibility. This, however, does not mean that one should avoid the GAC; these points are orthogonal. The GAC is designed to store multiple versions of the same component simultaneously. Two different functional/feature versions can happily live side-by-side without interfering with each other. Critical service updates can also live side-by-side with the pre-serviced version, in order to allow selective rollback where necessary.


A final point I would like to mention is that although disk space certainly is cheap in today’s world, sharing assemblies in a single location is still a very commonly requested feature. Sharing the same physical disk space location allows other optimizations, such as reduced memory pressure from not having to load the same copy of the DLL from many different locations (I will resist the urge to dive into related topics like domain neutrality and native images).


In summary, the old problems of “DLL hell” via DLL-name thrashing in system32 do not exist for the GAC. In the world of side-by-side, there are new considerations of how and when to update a component. While xcopy is definitely an attractive way to deploy your application/assembly, it is not the only way to do so, and there are legitimate reasons to make your component globally visible on the machine. Side-by-side allows users to selectively roll back applications which policy may have redirected forward.

Comments (33)

  1. denny says:

    I think that the subject of the GAC and it’s correct use… also the use of Strong Names is very mis-understood and could use a lot of work.

    some folks for example are confused into thinking that a "strong Name" can provide some stong "copy protection" and complain when they find that the strong name can be removed … disasembled il and re-compile etc…

    I suspect that some folks are "shoting them selves in the foot" with thier lack of understanding of the gac and SN….

  2. Anonymous says:

    So far the problems we had with the GAC far outnumber any kind issue we every had with COM or Win32. Whatever the theory is, it just doesnt work. The GAC is the one thing that definetly got worse in .NET than it was before. You cant go and tell your customer "ohh, delete this from the GAC and write some .exe.config file to work around that". Brrrr…

  3. Jerry Pisk says:

    The problem with forcing apps to use a "critical update" version of an assembly is that sometimes the app relies on the broken behavior. You’ll break it if you fix your assembly (can anybody say WinNT SP4 with fixed memory management?). But there’s really not much that can be done, maybe symbolic links to shared code each app is using (so you can point different apps to different assemblies and update them to newer versions independently) would do.

  4. David Levine says:

    We haven’t yet seen the true downside or upside of the GAC, publisher policy, app.exe.config and binding redirects, etc. The system is just too new to be able to see all the issues except in the most general sense. It’s will be only after years of use, upgrades, updates, etc. that it will become clear if it worked or not.

    My own take on this is that the more knobs there are that have to be dialed in to make the system work the worse off we are; requiring every user to be a system adminstrator is a bad idea. Any system that relies on the user understanding all the pitfalls of installing an update is doomed. Hasn’t experience with COM shown that updating a common component fixes one app and breaks ten others?

    I’m inclined to avoid the GAC – I much prefer xcopy deployment, and if you have to ask if your hard drive is big enougb…then you can’t afford the software anyway.

  5. The GAC should be used in two situations:

    1) To release a critical security fix. This should be used in combination with a publisher policy file to force all or most applications to the fixed assembly version. This comes with a high risk of regressions so it should be performed only when absolutely necessary.

    2) To release a "Framework like" set of assemblies which may be used by a significant number of applications on a machine. The purpose of this is to take advantage of possibly significant disk/memory savings. The key here is that the set of assemblies is large (in physical size) and the number of applications which use it on any given machine is large.

    On number one we agree. Your number two is much less specific than mine.

    Can you please provide a scenario where you would recommend deploying an updated assembly version to the GAC and modifying the application configuration file instead of leveraging MSI, smart client or other update technology to update the application in its private application directory?

    Thanks,

    Maurice

  6. Aaron Lewis says:

    I agree 100% with whatever Dag said. When people break out the umlauts, you know they mean business.

  7. Dag König says:

    Det finns många åsikter om GAC:en, vad skall installeras där och hur skall uppdateringar ske, är några av de vanliga frågorna. Alan Shi, skriver ett intressant inlägg, som ett svar på en person som inte rekommendar GAC:en till speciellt mycket,

  8. Peter Thomas says:

    Since .Net apps copy refrenced assemblies to it’s local directory, we decided to place our assemblies in the GAC. We felt that if we did it that way we could update the GAC with our new assemblies and the .Net applications could pickup the new assemblies. Sounds good so far…

    It gets more complicated by the fact that we can’t just put our assembly in the GAC, all it’s dependencies must go there as well. Now we have 7 assemblies in the GAC. Say we need to patch one for a fuctional fix, now I have a few options, they all assume that I want everything to use the new assembly. (I’m of Sells mind, why would I release a patch if I don’t want it used? It should be tested for backwards compatibility anyway.)

    1) Add the fixed assembly to the GAC as well as a ton of publisher policy files.

    2) Release a new set of all the assemblies that go in the GAC even though only one has changed.

    This stuff is on top of all the dev headaches. With all these problems and issues to tackle, it’s hard to imagine that this all came from wanting to update all my clients like COM already did for me when I just registered the new version. In the end I hope to pull everything out of the GAC and have my install hunt down all the my clients itself and update them.

    I’VE GOT GAC PHOBIA! 🙂

    Peter Thomas

  9. Peter,

    What factors are forcing you to update the shared assemblies independantly of the applications which are using them?

    Thanks,

    Maurice

  10. mike says:

    I agree that the GAC is DLL hell.

    If I do load Assmebly A from a private directory called /foo/bar, then any dependent assembly resolving should be done from the same directory. Thats intuitive, easy, and avoids anyone else messing with my stuff.

    However, if foo is in the GAC, you completely lose control over where the dependent assemblies are loaded from.

    I wish the GAC did not exist. It makes life worse, not better. I’m in a situation where the Microsoft folks say, "you wouldn’t have this problem if you used the GAC". But that is the standard response from people that didn’t really think about the implications of what they are saying. The true answer is: "I wouldn’t have this problem if the GAC didn’t exist".

    Honestly, I think microsoft over designed this one. Its true that the GAC is capable of solving lots of complex problems. But the problem is that the GAC is so invasive and so damn complex that it gets in the way. Every developer has to take a 3-day class on what to do and not do in the GAC. ACK!

    ACK THE GAC!

    Sorry to rant.

  11. Alan Shi says:

    The comments about the GAC being complex and "not working" for side-by-side are still confusing the topics of GAC and policy. The GAC is simply a side-by-side storage facility so you can put multiple copies of the same assembly (with different versions) in a repository that is accessible by all managed applications on the machine. It’s policy, and how it is used (or abused) that is the source of complaints here.

    Chris’ original comment (voiced again by Peter) is a misuse of policy and not a deficiency of the GAC. Making a new version of an assembly that has functionality upgrades and forcing all apps to use the new version is a recipe for breaking apps ("patch" and "functionality" should not be uttered in the same breath). This is precisely what side-by-side was meant to avoid. If you update an assembly, you should only be forcing all old applications to use the new assembly if it is absolutely critical that they need to (security is a classic example). Why risk breaking everyone because you think you’ve written a new whiz-bang feature and just hope it’s compatible? Having a mindset that you must always force everyone to use the latest version of your stuff regardless of the types of changes in the new bits is a great way to frustrate your customers.

    It is true that if you put an assembly in the GAC, you will in most cases have to put the closure of that assembly also in the GAC. After all, if your GAC assembly is usable by all applications, they need to be able to find the dependencies of the GAC assembly in a place that is also globally scoped.

    I don’t follow Peter’s conclusion that his options are to either put publisher policy files for every assembly, or "release a new set even though only one has changed". If you have assembly A1 which depends on B1 and C1, and you deploy A1 to the GAC, you should also deploy B1 and C1 to the GAC. If you service A, say to version 1.1, there are two cases:

    a) A1.1 still depends on B1 and C1

    b) A1.1 depends on new B1.1 and C1.1

    In the first case, just release A1.1 and publisher policy for it. Apps that were using A1 will now us A1.1, and continue to use the existing B1 and C1. In the second case, you will need to issue publisher policy for redirect to all of A, B, and C and put them in the GAC just like you did for the v1 assemblies originally.

    Development-time issues regarding changing version numbers and how they affect your private testing is a different matter entirely, and I won’t address this here.

    On mike’s comment, remember that the GAC only stores strongly-named assemblies. If two assemblies have the same strong-name identity, they should be identical. It is an abuse of strong naming to release two sets of bits that are different, but have the same strong name. With this in mind, if you have an assembly in the app directory, and in the GAC, Fusion prefers the one in the GAC (after all, they should be identical). You shouldn’t need to care that where your dependencies are resolved from. If the dependency is not in the GAC, they will be resolved through the regular probing rules (if it is LoadFrom, it does precisely what you describe; the probing rules will also look in the parent LoadFrom location). You’re not losing control of anything–you’re simply benefiting from optimizations that rely on strong naming being able to uniquely identify a set of bits. This is a bit of a digression anyway, because now we’re not talking about sxs storage so much as we are about probing.

    On to two more comments. Maurice asks when you would ever deploy an updated assembly to the GAC and only use app.config policy to move apps to use the latest version. While it is certainly feasible to deploy updates this way, I don’t think there’s any real reason why one would choose to do it this way. If the assemblies were originally deployed to the application directory, and you only mean to update that particular application to use the new assembly, then it is probably simplest to just drop it in the application directory. Perhaps if you had a suite of applications that used the same updated assembly, you may want to put it in the GAC and update the individual app.config files to use it, so it can be shared (although if you went with this approach, you probably would have deployed the original assembly to the GAC, too).

    I can certainly sympathize with the fact that proper use of versioning policy is a complicated topic, and the mechanisms that enable it are overly rich and difficult to digest. We’ve received numerous pieces of feedback to this extent, and we’re actively finding ways to address this in the future.

  12. Peter Thomas says:

    Alan,

    I would agree that what I’m, and many other people, are doing doesn’t follow MS’s recommended policy for GAC use.

    The problem is, I used to live in the simple COM world where I could update one file and all its clients would use the new file. With .Net we have lost this ability. The closest we can get to it is our "misuse" of the GAC.

    Now I know what you’re thinking. You’ve already said it: "Making a new version of an assembly that has functionality upgrades and forcing all apps to use the new version is a recipe for breaking apps". Your point being that the assembly authors can’t assume that they won’t break all the client apps so let the client chose when/if to use the updated assembly. I don’t know about other people but our company develops enterprise software for very large companies and our clients expect that we have already tested this new assembly. They would not accept us asking them to "test the new assembly and then make the appropriate .config file changes". The GAC policy makes more sense for MS because there is an unmanageable number of client apps but in our case we know who is using our assemblies and how. Assuming we really do want every client to get the new assembly, GAC seemed to the best way to get what we already had in COM.

    In short: From what you and MS’s recommended GAC use is saying, GAC isn’t meant to do what COM did. That means we lost functionality because someone decided we needed to be protected from ourselves "yes, I really do want to update all clients and I accept that responsibility. And trust me; our clients really do want my update/patch".

    Don’t mean to sound angry, just hammering my point home :). All these people “misusing” the GAC are just trying to regain lost functionality.

    Maybe my statements aren’t accurate at all anyway, if not, I’d love to hear about it.

    Thanks

    Peter Thomas

  13. Alan Shi says:

    There are plenty of examples of bad COM programming that we’ve seen break apps. Slamming the COM registration information to point to your new COM server has the same potential to break apps as dropping a new DLL in system32. In fact, to address this problem, we introduced the ".local" feature (where you can force a DLL to be loaded from alongside the EXE, rather than where it’s LoadLibrary’ed from–even if loaded by full path). In retrospect, this was a very dangerous feature to add, but at the time there was no really good alternative.

    Certainly, there are times where you control the entire deployment space, and you know exactly what is using your component, and have very high confidence that the new component is not only backwards compatibile, but that it should be used in place of the old assembly. In that case, by all means, deploy policy. The recommendations I’m advocating here are only guidelines, and may or may not be applicable to your situation. As long as you understand the spirit of what is being presented here, you can make good decisions on when to apply policy and when not to.

    Mike Grier has an informative blog entry discussing similar issues here: http://blogs.msdn.com/mgrier/archive/2004/04/15/113663.aspx

  14. Neeru Kamra says:

    I don’t know if it is a good forum to raise my question?

    Actually what is happening is related to GAC only.

    In my application i am using both managed and unmanaged DLLs.

    However if while linking, i specify library order as "unmanaged.lib and managed.lib" then application works fine.

    But if i reverse the order i.e "managed.lib and unmanaged.lib" then application fails to load the assembly from GAC.

    Further with this if i remove Assembly from GAC and put it in the application path then also everything works fine.

    Now can anybody suggest me why is this happening and whether in such scenario whether i use or avoid using GAC.

  15. Phil Wilson says:

    The three big issues that I see in MSI deployment of assemblies into the GAC are these:

    1. Assemblies aren’t committed to the GAC until the end of the install (a two-phase commit is described in the docs). Anything reasonable you want to do during the install (start a service, call a custom action) falls flat on its face because the assemblies aren’t in the GAC yet. Every other use of installed files works fine, it’s just this GAC case that doesn’t work.

    2. Updating based on assembly version causes havoc. Replacement rules on disk use file version, replacement rules in the GAC use assembly version – that just ruins everyone’s automated build and install processes that have relied on file version for ever to cause file replacement. The 1.1 framework lets you put a FileVersion in the MsiAssemblyName table, but that’s little known and most build tools don’t do it for you automatically.

    3. Policy files are difficult. You can put the first one in to redirect clients, but later on if you need to replace it, you’re just back with the problem of how to replace an assembly in the GAC with a deployment project – it’s the same assembly-update problem you thought you’d solved by using a policy file.

    So the number one reason I advise people not to use the GAC is because its integration with MSI is inconsistent with the way people expect installs to work, and the GAC causes a lot of issues in this area.

  16. Alan Shi says:

    Phil, the reasons you list in your comment don’t directly point to why you shouldn’t use the GAC.

    For (1), this is a very real issue with MSI, that makes it very difficult to do things like ngen your assemblies after they are installed. I know that there are very nasty caveats you need to be aware of in order to have a custom action run after the commit phase (based on whether rollback is enabled or not), and it’s very difficult for people to be able to get this right. VS does this, but with much pain. This needs to be addressed in the short term with better documentation, and in the long term with better MSI support for running custom actions that need to work on assemblies it will installed.

    Replacement rules for the GAC are also based on file version. If the assembly version is different, there is no "replacement" at all–they are simply installed side-by-side. I don’t see why this "ruining everyone’s automated build…", but I will put in a plug for why updating only the file version and not the assembly version for production builds is a bad idea; this hurts the core reason why strong names were invented, and leads to all kinds of other problems.

    As for policy files being difficult, that’s certainly true. This issue is very much orthogonal to whether you should or shouldn’t use the GAC, though. Policy files are side-by-side, so there’s no in-place update problem even for them.

  17. Phil Wilson says:

    Let me put it this way: if installing and updating assemblies into the GAC causes assembly hell, that’s a good reason to avoid using it in the first place.

    Builds: In a well-regulated development process, developers don’t own the version. The gen process puts it into the codefiles, and all this is integrated with setup projects and installation designs that assume that a higher file version replaces a lower one. This has been true forever until assemblies in the GAC (skipping the FileVersion replacement of the 1.1 framework for now). So build processes needed to change to take account of the fact that the file version doesn’t help you ship fixes to replace broken GAC assemblies. Now you need a bunch of people building policy files and changing assembly versions (or you build a new process, either way it’s more work).

    It’s the 1.1 framework that allows replacement by FileVersion, but again, the 3rd party tools don’t yet support it, meaning that if you want to use FileVersion you need a post-build step to update the MsiAssemblyName table with FileVersion.

    Yes, changing assembly version results in side-by-side in the GAC, but you want to install a new assembly because it’s broken and used by some unknown number of clients who are bound to it by assembly version and strong name. So it’s extremely convenient to replace it, not install a new one and a policy file to redirect clients. Then if you need to ship the assembly again, you need to replace that policy file in the GAC, so you see that you’re back in the situation where you need to replace an assembly in the GAC (the policy one) to refer to yet another assembly. You’re back with the same problem again – replacing a policy assembly in the GAC because you need a new policy assembly that redirects clients to yet another assembly in the GAC with another assembly version. Phew! The model that was used for years was that a shared file that needs fixing just gets replaced, but that’s not the case for the GAC (again ignoring FileVersion).

    This will improve when tools start using FileVersion in the MsiAssemblyName table. It would also be a tremendous improvement to have files in the GAC available during installation so that custom actions (like your ngen)work, and so that the StartServices action in an MSI setup actually works when the Service uses an assembly in the GAC.

    I do think this is a really useful discussion, and long overdue. From my perspective, just as DLL Hell is a sharing and installation issue, GAC Phobia is the same, and installation and maintenance need to be an integral part of the design of new functionality, especially something like the GAC with its own rules.

    Phil

    MVP Windows Installer

    Definitive Guide to Windows Installer, Apress

  18. Alan Shi says:

    Side-by-side can only exist in a world where people are not abusing strong-names (e.g. re-using the same identity, but changing the bits). FileVersion is absolutely not the answer here; it was a horrible hack we had to introduce in v1.1, mostly due to the fact there is no "logical in-place update" technology available (and that service updates can be opt-ed out via the app.config file, which isn’t always desirable). The right long-term direction is not to invest heavily in tools that understand file version, but to make the system easy enough for developers to use and understand that it’s absolutely not necessary to have two version numbers to distinguish what the real identity of a set of bits are.

    Side-by-side is a brave new world which will only succeed if developers adopt new practices for versioning their components that are in-line with the side-by-side model. It will not be without pain, but it’s critically important the Microsoft makes it easy enough to follow these rules that the model is successful. We’ve received numerous pieces of customer feedback indicating how complex the system is today, and it’s a top priority of ours to address these problems in future releases.

    In any case, calling this "GAC hell" is misleading. The GAC itself isn’t a source of any of these problems; it’s just a global store for side-by-side components. You’re addressing "strong name versioning hell" caused by people not following the proper guidelines (e.g. only changing file version), which is in turn caused by complexity in the binding policy system that we currently have. Those points, while certainly valid, are orthogonal to where the bits are eventually deployed.

  19. Phil Wilson says:

    Microsoft set everyone’s expectations by describing it as a place for shared assemblies, not primarily as a repository for side-by-side installation. Practically every time documentation mentions the GAC, it mentions sharing assemblies between applications, but in reality any updated assemblies are actually installed side by side. The result is that Microsoft is effectively saying that the GAC is a place to put shared assemblies where they are installed side by side. So which is it? Shared or side by side? If an assembly really is shared between multiple clients, the expectation is that it can be replaced with an in-place update. That’s why an in-place update with FileVersion isn’t a hack, it’s a requirement for any reasonable implementation of a shared code file installed in one place. Policy files do not solve the deployment issues that result from being unable to replace a shared file.

    Describing these types of issues as orthogonal to where the bits are deployed seems to miss the point. DLL Hell is a deployment issue, because of shared DLLs and how to update them. It doesn’t mean that DLLs are bad. Similarly, GAC Hell is a deployment issue – it’s not that assemblies or the GAC or policy files are hell, just that deployment and update replacement of shared assemblies into the GAC is hell.

  20. Alan Shi says:

    Sharing and side-by-side are not mutually exclusive; you can have multiple versions of an assembly installed side-by-side and at the same time have n apps use one version, and m apps use the other.

    I’ve said this before, but it’s worth repeating: I am not saying all strongly-named assemblies must go in the GAC. Xcopy deploy is has been a key scenario from the beginning.

    Strong names were invented in order to uniquely identify a particular assembly. They were not created to identify a "series" of assemblies that are "in-place" updated with only a change of file version. In-place update defeats the whole purpose of strong naming, and this is exactly what causes DLL hell. If people continue to use file version (it is a hack), DLL-hell problems will continue to exist despite infrastructre that was put specifically in place to fix these problems. Shared code absolutely does not imply in-place servicing, nor does it preclude side-by-side!

  21. I know there are a few articles and posts out there concerning deploying .Net assemblies and how to service

  22. espn college football schedules 2004 2005