{"id":1803,"date":"2009-05-14T18:39:33","date_gmt":"2009-05-14T18:39:33","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/dotnet\/2009\/05\/14\/why-is-appdomain-appendprivatepath-obsolete\/"},"modified":"2021-10-04T15:44:05","modified_gmt":"2021-10-04T22:44:05","slug":"why-is-appdomain-appendprivatepath-obsolete","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/why-is-appdomain-appendprivatepath-obsolete\/","title":{"rendered":"Why is AppDomain.AppendPrivatePath Obsolete?"},"content":{"rendered":"<p>This is the first in a series of posts where we discuss the reasoning behind &ldquo;obsoleting&rdquo; specific APIs.<\/p>\n<p>If you use <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.appdomain.appendprivatepath.aspx\">AppDomain.AppendPrivatePath<\/a>, or look at <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.appdomain.appendprivatepath.aspx\">MSDN<\/a>, you&rsquo;ll notice it&rsquo;s obsolete.&nbsp; This frustrates people because the alternative suggested (<a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.appdomainsetup.privatebinpath.aspx\">AppDomainSetup.PrivateBinPath<\/a>) requires you to do something entirely different (spin up a new AppDomain).&nbsp; It also doesn&rsquo;t shed any light on why the API is obsolete.&nbsp; <\/p>\n<p>You can use this general rule to determine whether you should continue to call an obsolete method:<\/p>\n<p>If a method is obsolete, <strong>stop using it!<\/strong><\/p>\n<p>Generally, the reason we obsolete methods is because we&rsquo;ve identified something problematic about the method that isn&rsquo;t fixable.&nbsp; In this case, we leave the method in place for compatibility. But remember, there&rsquo;s still something wrong with it!<\/p>\n<p>So, what&rsquo;s so bad about AppendPrivatePath?&nbsp; The short answer is that it lets you get into situations that introduce load order dependencies.&nbsp; In other words, your app can become a time bomb that can crash under seemingly random circumstances.&nbsp; Load order can be affected by anything from local, machine specific factors (CPU speed, # of processor cores), to external factors like transient network latency.&nbsp; So, if you are dependent on load order, you can end up with &ldquo;straightforward&rdquo; problems like loading unexpected assemblies, or failing to load an assembly.<\/p>\n<p>Consider the following scenario:<\/p>\n<ol>\n<li>AppendPrivatePath is called to add &ldquo;c:\\foo&rdquo; to the probing paths <\/li>\n<li>A reference to &ldquo;bar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=asdfghjkl&rdquo; is resolved due to JIT compiling a method, and the assembly is found in c:\\foo\\bar.dll<\/li>\n<\/ol>\n<p>Now, an optimization is made by the JIT team, and bar.dll is now aggressively loaded due to some inlining that occurs in the JIT compiler.&nbsp; Now, the order of the 2 steps is reversed and bar.dll fails to load because c:\\foo wasn&rsquo;t yet added to the probing path.<\/p>\n<p>In addition to the straightforward (easy to explain) problems, there are a number of unexpected (hard to explain) issues that can arise.&nbsp; Consider this scenario (same as above, but with step 3 added):<\/p>\n<ol>\n<li>AppendPrivatePath is called to add &ldquo;c:\\foo&rdquo; to the probing paths <\/li>\n<li>A reference to &ldquo;bar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=asdfghjkl&rdquo; is resolved due to JIT compiling a method, and the assembly is found in c:\\foo\\bar.dll <\/li>\n<li>Assembly.LoadFrom(&ldquo;c:\\foo\\bar.dll) is called to load the specific assembly, and is loaded successfully, and its types are equal to the ones loaded from the reference.<\/li>\n<\/ol>\n<p>Now something occurs that disrupts the load order, and Assembly.LoadFrom occurs first (perhaps it&rsquo;s called by a component that is loaded earlier due to user interaction).&nbsp; Can you guess the result?<\/p>\n<p>The reference to &ldquo;bar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=asdfghjkl&rdquo; will fail to load, despite the fact that it is available in the probing paths at the time the reference is resolved.&nbsp; Why does this happen?<\/p>\n<p>When you do Assembly.LoadFrom, we attempt to do something called &ldquo;context propagation&rdquo;.&nbsp; Load contexts (covered on <a href=\"http:\/\/blogs.msdn.com\/suzcook\/archive\/2003\/05\/29\/57143.aspx\">Suzanne Cook&rsquo;s blog<\/a>) are another mechanism meant to avoid load order dependencies (among other things).&nbsp; For LoadFrom, in order to determine whether an assembly gets propagated to the load context, we actually attempt a load.&nbsp; In this case, that load fails because the assembly is in &ldquo;c:\\foo&rdquo;, which is not yet in the probing path, so the assembly is not propagated to the load context.&nbsp; By the time the reference is resolved, &ldquo;c:\\foo&rdquo; is in the probing path, but we fail.&nbsp; Can you guess why?<\/p>\n<p>We already tried the same load (during LoadFrom) and it failed, so it&rsquo;s using the cache result of the previous bind!<\/p>\n<h4>What do you do instead?<\/h4>\n<p>So, hopefully you agree not to call this API anymore.&nbsp; If you&rsquo;re looking for an alternative, here are the scenarios you might fall into:<\/p>\n<h5>I just want to edit the probing path<\/h5>\n<p>Great, just use a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/823z9h8w.aspx\">config file<\/a> to set the path or <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.appdomainsetup.privatebinpath.aspx\">AppDomainSetup.PrivateBinPath<\/a> as suggested.<\/p>\n<h5>But, I need to do this dynamically (after the AppDomain has been created)<\/h5>\n<p>You can use the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.appdomain.assemblyresolve.aspx\">assembly resolve event<\/a>.&nbsp; Hook up a handler that probes wherever you want via <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/1009fa28.aspx\">Assembly.LoadFrom<\/a>.&nbsp; These will be visible to the load context (because they originated from Load) and will keep you from tripping over loader context problems.<\/p>\n<p>I hope this helps people who might otherwise use the obsolete method because they can&rsquo;t figure out what else to do.<\/p>\n<p>&nbsp;<\/p>\n<p>-Mark Miller<\/p>\n<p>SDET, CLR<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the first in a series of posts where we discuss the reasoning behind &ldquo;obsoleting&rdquo; specific APIs. If you use AppDomain.AppendPrivatePath, or look at MSDN, you&rsquo;ll notice it&rsquo;s obsolete.&nbsp; This frustrates people because the alternative suggested (AppDomainSetup.PrivateBinPath) requires you to do something entirely different (spin up a new AppDomain).&nbsp; It also doesn&rsquo;t shed any [&hellip;]<\/p>\n","protected":false},"author":342,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685],"tags":[],"class_list":["post-1803","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"acf":[],"blog_post_summary":"<p>This is the first in a series of posts where we discuss the reasoning behind &ldquo;obsoleting&rdquo; specific APIs. If you use AppDomain.AppendPrivatePath, or look at MSDN, you&rsquo;ll notice it&rsquo;s obsolete.&nbsp; This frustrates people because the alternative suggested (AppDomainSetup.PrivateBinPath) requires you to do something entirely different (spin up a new AppDomain).&nbsp; It also doesn&rsquo;t shed any [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1803","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/342"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=1803"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1803\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=1803"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=1803"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=1803"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}