From a43b7fd39819847a73d6312363afeaed537ad365 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Thu, 29 Aug 2019 18:52:50 -0400 Subject: [PATCH 1/9] Add DiagnosticScenarios Add sample debug target for use in C# Guide diagnostics scenarios. --- .../Controllers/DiagnosticScenarios.cs | 162 ++++++++++++++++++ .../Controllers/ValuesController.cs | 45 +++++ .../DiagnosticScenarios.csproj | 11 ++ .../DiagnosticScenarios/Program.cs | 26 +++ .../Properties/launchSettings.json | 30 ++++ .../DiagnosticScenarios/Startup.cs | 57 ++++++ .../appsettings.Development.json | 9 + .../DiagnosticScenarios/appsettings.json | 10 ++ 8 files changed, 350 insertions(+) create mode 100644 core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs create mode 100644 core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs create mode 100644 core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj create mode 100644 core/diagnostics/DiagnosticScenarios/Program.cs create mode 100644 core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json create mode 100644 core/diagnostics/DiagnosticScenarios/Startup.cs create mode 100644 core/diagnostics/DiagnosticScenarios/appsettings.Development.json create mode 100644 core/diagnostics/DiagnosticScenarios/appsettings.json diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs new file mode 100644 index 00000000000..cfa7dcbabd3 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -0,0 +1,162 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using System.Threading; + +namespace testwebapi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class DiagScenarioController : ControllerBase + { + object o1 = new object(); + object o2 = new object(); + + private static Processor p = new Processor(); + + [HttpGet] + [Route("deadlock/")] + public ActionResult deadlock() + { + (new System.Threading.Thread(() => { + DeadlockFunc(); + })).Start(); + + Thread.Sleep(5000); + + Thread[] threads = new Thread[300]; + for(int i=0; i<300;i++) + { + (threads[i] = new Thread(() => { + lock (o1) {Thread.Sleep(100);} + })).Start(); + } + + foreach(Thread thread in threads) + { + thread.Join(); + } + + return "success:deadlock"; + } + + private void DeadlockFunc() + { + lock (o1) + { + (new Thread(() => { + lock (o2) { Monitor.Enter(o1); } + })).Start(); + + Thread.Sleep(2000); + Monitor.Enter(o2); + } + } + + [HttpGet] + [Route("memspike/{seconds}")] + public ActionResult memspike(int seconds) + { + Stopwatch watch=new Stopwatch(); + watch.Start(); + + while(true) + { + p = new Processor(); + watch.Stop(); + if(watch.ElapsedMilliseconds > seconds*1000) + break; + watch.Start(); + + int it = (200000*1000) / 100; + for(int i=0; i memleak(int kb) + { + int it = (kb*1000) / 100; + for(int i=0; i exception() + { + throw new Exception("bad, bad code"); + } + + + [HttpGet] + [Route("highcpu/{milliseconds}")] + public ActionResult highcpu(int milliseconds) + { + Stopwatch watch=new Stopwatch(); + watch.Start(); + + while (true) + { + watch.Stop(); + if(watch.ElapsedMilliseconds > milliseconds) + break; + watch.Start(); + } + + return "success:highcpu"; + } + + } + + class Customer + { + private string id; + + public Customer(string id) + { + this.id = id; + } + } + + class CustomerCache + { + private List cache = new List(); + + public void AddCustomer(Customer c) + { + cache.Add(c); + } + } + + class Processor + { + private CustomerCache cache = new CustomerCache(); + + public void ProcessTransaction(Customer customer) + { + cache.AddCustomer(customer); + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs b/core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs new file mode 100644 index 00000000000..c38be03dc86 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Controllers/ValuesController.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace DiagnosticScenarios.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj b/core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj new file mode 100644 index 00000000000..4fa8377c237 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/DiagnosticScenarios.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.0 + + + + + + + diff --git a/core/diagnostics/DiagnosticScenarios/Program.cs b/core/diagnostics/DiagnosticScenarios/Program.cs new file mode 100644 index 00000000000..9269655239d --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DiagnosticScenarios +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json b/core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json new file mode 100644 index 00000000000..ce8acd42589 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:26127", + "sslPort": 44359 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DiagnosticScenarios": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/core/diagnostics/DiagnosticScenarios/Startup.cs b/core/diagnostics/DiagnosticScenarios/Startup.cs new file mode 100644 index 00000000000..3fdcd61484e --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/Startup.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DiagnosticScenarios +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers() + .AddNewtonsoftJson(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/appsettings.Development.json b/core/diagnostics/DiagnosticScenarios/appsettings.Development.json new file mode 100644 index 00000000000..e203e9407e7 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/core/diagnostics/DiagnosticScenarios/appsettings.json b/core/diagnostics/DiagnosticScenarios/appsettings.json new file mode 100644 index 00000000000..d9d9a9bff6f --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} From 065b2934bf24b0fbedc99ae6b6f47059fbb50cd8 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Thu, 29 Aug 2019 19:42:18 -0400 Subject: [PATCH 2/9] Add readme --- .../diagnostics/DiagnosticScenarios/readme.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 core/diagnostics/DiagnosticScenarios/readme.md diff --git a/core/diagnostics/DiagnosticScenarios/readme.md b/core/diagnostics/DiagnosticScenarios/readme.md new file mode 100644 index 00000000000..b7a8dfbca42 --- /dev/null +++ b/core/diagnostics/DiagnosticScenarios/readme.md @@ -0,0 +1,32 @@ +# DiagnosticScenarios sample debug target + +This sample is for the [diagnostics tutorials](https://docs.microsoft.com/dotnet/core/diagnostics/tutorial/diagnostic-scenarios.md) in the .NET Core Guide. + +The scenarios are implemented using a `webapi` target with methods that trigger undesirable behaviors for us to diagnose. + +## Target methods + +### Deadlock + +http://localhost:5000/api/diagscenario/deadlock + +This method will cause the target to hang and accumulate many threads. + +### High CPU usage + +http://localhost:5000/api/diagscenario/highcpu/{milliseconds} + +The method will cause to target to heavily use the CPU for a duration specified by {milliseconds}. + +### Memory leak + +http://localhost:5000/api/diagscenario/memleak/{kb} + +This method will cause the target to leak memory (amount specified by {kb}). + +### Memory usage spike + +http://localhost:5000/api/diagscenario/memspike/{seconds} + +This method will cause intermittent memory spikes over the specified number of seconds. Memory will go from base line to spike and back to baseline several times. + From cbf39f3ac05d507d3fae39d969f21886b1c04523 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 12:12:01 -0400 Subject: [PATCH 3/9] Cleanup based on feedback Add whitespace arounf operators Prefer implicit types when right side type is immediately obvious --- .../Controllers/DiagnosticScenarios.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs index cfa7dcbabd3..ffaf974f33d 100644 --- a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -12,8 +12,8 @@ namespace testwebapi.Controllers [ApiController] public class DiagScenarioController : ControllerBase { - object o1 = new object(); - object o2 = new object(); + var o1 = new object(); + var o2 = new object(); private static Processor p = new Processor(); @@ -27,8 +27,8 @@ public ActionResult deadlock() Thread.Sleep(5000); - Thread[] threads = new Thread[300]; - for(int i=0; i<300;i++) + var threads = new Thread[300]; + for(int i = 0; i < 300; i++) { (threads[i] = new Thread(() => { lock (o1) {Thread.Sleep(100);} @@ -60,7 +60,7 @@ private void DeadlockFunc() [Route("memspike/{seconds}")] public ActionResult memspike(int seconds) { - Stopwatch watch=new Stopwatch(); + var watch = new Stopwatch(); watch.Start(); while(true) @@ -71,8 +71,8 @@ public ActionResult memspike(int seconds) break; watch.Start(); - int it = (200000*1000) / 100; - for(int i=0; i memspike(int seconds) // Cleanup p = null; + + // Call GC.Collect twice GC.Collect(); GC.Collect(); @@ -93,8 +95,8 @@ public ActionResult memspike(int seconds) [Route("memleak/{kb}")] public ActionResult memleak(int kb) { - int it = (kb*1000) / 100; - for(int i=0; i exception() [Route("highcpu/{milliseconds}")] public ActionResult highcpu(int milliseconds) { - Stopwatch watch=new Stopwatch(); + var watch = new Stopwatch(); watch.Start(); while (true) @@ -127,7 +129,6 @@ public ActionResult highcpu(int milliseconds) return "success:highcpu"; } - } class Customer From ae274556329b57b237d4760580bc8668c5e5b747 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 13:49:00 -0400 Subject: [PATCH 4/9] Add metadata for sample browser --- core/diagnostics/DiagnosticScenarios/readme.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/diagnostics/DiagnosticScenarios/readme.md b/core/diagnostics/DiagnosticScenarios/readme.md index b7a8dfbca42..ba8c199b40f 100644 --- a/core/diagnostics/DiagnosticScenarios/readme.md +++ b/core/diagnostics/DiagnosticScenarios/readme.md @@ -1,3 +1,13 @@ +--- +languages: + - csharp + products: + - dotnet-core +page_type: sample +name: "Diagnostic Scenarios" +urlFragment: "diagnostic-scenarios" +description: "A .NET Core sample with methods that trigger undesirable behaviors to diagnose." +--- # DiagnosticScenarios sample debug target This sample is for the [diagnostics tutorials](https://docs.microsoft.com/dotnet/core/diagnostics/tutorial/diagnostic-scenarios.md) in the .NET Core Guide. @@ -29,4 +39,3 @@ This method will cause the target to leak memory (amount specified by {kb}). http://localhost:5000/api/diagscenario/memspike/{seconds} This method will cause intermittent memory spikes over the specified number of seconds. Memory will go from base line to spike and back to baseline several times. - From 5f05389957226b09ad93d23f3e829a92949b7af4 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 13:55:37 -0400 Subject: [PATCH 5/9] Fix link --- core/diagnostics/DiagnosticScenarios/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/diagnostics/DiagnosticScenarios/readme.md b/core/diagnostics/DiagnosticScenarios/readme.md index ba8c199b40f..f4075602888 100644 --- a/core/diagnostics/DiagnosticScenarios/readme.md +++ b/core/diagnostics/DiagnosticScenarios/readme.md @@ -10,7 +10,7 @@ description: "A .NET Core sample with methods that trigger undesirable behaviors --- # DiagnosticScenarios sample debug target -This sample is for the [diagnostics tutorials](https://docs.microsoft.com/dotnet/core/diagnostics/tutorial/diagnostic-scenarios.md) in the .NET Core Guide. +This sample is for the [diagnostics tutorials](https://docs.microsoft.com/dotnet/core/diagnostics/tutorial/diagnostic-scenarios) in the .NET Core Guide. The scenarios are implemented using a `webapi` target with methods that trigger undesirable behaviors for us to diagnose. From 995624857e3bbfdfa81834158fcbcd033b855fd2 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 15:21:02 -0400 Subject: [PATCH 6/9] GC feedback --- .../DiagnosticScenarios/Controllers/DiagnosticScenarios.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs index ffaf974f33d..4e39f71058f 100644 --- a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -82,8 +82,9 @@ public ActionResult memspike(int seconds) // Cleanup p = null; - // Call GC.Collect twice + // GC GC.Collect(); + GC.WaitForPendingFinalizers(); GC.Collect(); Thread.Sleep(5000); // Sleep for 5 seconds before spiking memory again From 90d84c7970ddb13b36b748177cb48ada9cc78823 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 19:00:50 -0400 Subject: [PATCH 7/9] Fix build error --- .../Controllers/DiagnosticScenarios.cs | 4 +- core/tutorials/Unloading/Host/Host.csproj | 12 --- core/tutorials/Unloading/Host/Program.cs | 101 ------------------ .../Host/Properties/launchSettings.json | 7 -- .../Unloading/Interface/Interface.cs | 36 ------- .../Unloading/Interface/Interface.csproj | 7 -- core/tutorials/Unloading/Plugin/Plugin.csproj | 15 --- .../tutorials/Unloading/Plugin/PluginClass.cs | 44 -------- .../Unloading/PluginDependency/Logger.cs | 14 --- .../PluginDependency/PluginDependency.csproj | 7 -- core/tutorials/Unloading/Unloading.sln | 42 -------- .../TestLibrary/TestLibrary.csproj | 17 --- .../WordCounter/TestLibrary/TextUtilsTests.cs | 29 ----- .../WordCounter/TextUtils/TextUtils.csproj | 7 -- .../WordCounter/TextUtils/WordCount.cs | 29 ----- .../WordCounter/WordCounter.sln | 29 ----- .../WordCounter/WordCounterApp/Program.cs | 27 ----- .../WordCounterApp/WordCounterApp.csproj | 15 --- .../stringlibrary.vb | 15 --- 19 files changed, 2 insertions(+), 455 deletions(-) delete mode 100644 core/tutorials/Unloading/Host/Host.csproj delete mode 100644 core/tutorials/Unloading/Host/Program.cs delete mode 100644 core/tutorials/Unloading/Host/Properties/launchSettings.json delete mode 100644 core/tutorials/Unloading/Interface/Interface.cs delete mode 100644 core/tutorials/Unloading/Interface/Interface.csproj delete mode 100644 core/tutorials/Unloading/Plugin/Plugin.csproj delete mode 100644 core/tutorials/Unloading/Plugin/PluginClass.cs delete mode 100644 core/tutorials/Unloading/PluginDependency/Logger.cs delete mode 100644 core/tutorials/Unloading/PluginDependency/PluginDependency.csproj delete mode 100644 core/tutorials/Unloading/Unloading.sln delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs delete mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj delete mode 100644 core/tutorials/vb-library-with-visual-studio/stringlibrary.vb diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs index 4e39f71058f..453d9095a7f 100644 --- a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -12,8 +12,8 @@ namespace testwebapi.Controllers [ApiController] public class DiagScenarioController : ControllerBase { - var o1 = new object(); - var o2 = new object(); + object o1 = new object(); + object o2 = new object(); private static Processor p = new Processor(); diff --git a/core/tutorials/Unloading/Host/Host.csproj b/core/tutorials/Unloading/Host/Host.csproj deleted file mode 100644 index 67752e84b07..00000000000 --- a/core/tutorials/Unloading/Host/Host.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp3.0 - - - - - - - diff --git a/core/tutorials/Unloading/Host/Program.cs b/core/tutorials/Unloading/Host/Program.cs deleted file mode 100644 index 898e822bee8..00000000000 --- a/core/tutorials/Unloading/Host/Program.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Loader; - -namespace Host -{ - // This is a collectible (unloadable) AssemblyLoadContext that loads the dependencies - // of the plugin from the plugin's binary directory. - class HostAssemblyLoadContext : AssemblyLoadContext - { - // Resolver of the locations of the assemblies that are dependencies of the - // main plugin assembly. - private AssemblyDependencyResolver _resolver; - - public HostAssemblyLoadContext(string pluginPath) : base(isCollectible: true) - { - _resolver = new AssemblyDependencyResolver(pluginPath); - } - - // The Load method override causes all the dependencies present in the plugin's binary directory to get loaded - // into the HostAssemblyLoadContext together with the plugin assembly itself. - // NOTE: The Interface assembly must not be present in the plugin's binary directory, otherwise we would - // end up with the assembly being loaded twice. Once in the default context and once in the HostAssemblyLoadContext. - // The types present on the host and plugin side would then not match even though they would have the same names. - protected override Assembly Load(AssemblyName name) - { - string assemblyPath = _resolver.ResolveAssemblyToPath(name); - if (assemblyPath != null) - { - Console.WriteLine($"Loading assembly {assemblyPath} into the HostAssemblyLoadContext"); - return LoadFromAssemblyPath(assemblyPath); - } - - return null; - } - } - - class Program - { - // It is important to mark this method as NoInlining, otherwise the JIT could decide - // to inline it into the Main method. That could then prevent successful unloading - // of the plugin because some of the MethodInfo / Type / Plugin.Interface / HostAssemblyLoadContext - // instances may get lifetime extended beyond the point when the plugin is expected to be - // unloaded. - [MethodImpl(MethodImplOptions.NoInlining)] - static void ExecuteAndUnload(string assemblyPath, out WeakReference alcWeakRef) - { - // Create the unloadable HostAssemblyLoadContext - var alc = new HostAssemblyLoadContext(assemblyPath); - - // Create a weak reference to the AssemblyLoadContext that will allow us to detect - // when the unload completes. - alcWeakRef = new WeakReference(alc); - - // Load the plugin assembly into the HostAssemblyLoadContext. - // NOTE: the assemblyPath must be an absolute path. - Assembly a = alc.LoadFromAssemblyPath(assemblyPath); - - // Get the plugin interface by calling the PluginClass.GetInterface method via reflection. - Type pluginType = a.GetType("Plugin.PluginClass"); - MethodInfo getInterface = pluginType.GetMethod("GetInterface", BindingFlags.Static | BindingFlags.Public); - Plugin.Interface plugin = (Plugin.Interface)getInterface.Invoke(null, null); - - // Now we can call methods of the plugin using the interface - string result = plugin.GetMessage(); - Plugin.Version version = plugin.GetVersion(); - - Console.WriteLine($"Response from the plugin: GetVersion(): {version}, GetMessage(): {result}"); - - // This initiates the unload of the HostAssemblyLoadContext. The actual unloading doesn't happen - // right away, GC has to kick in later to collect all the stuff. - alc.Unload(); - } - - static void Main(string[] args) - { - WeakReference hostAlcWeakRef; - string currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); -#if DEBUG - string configName = "Debug"; -#else - string configName = "Release"; -#endif - string pluginFullPath = Path.Combine(currentAssemblyDirectory, $"..\\..\\..\\..\\Plugin\\bin\\{configName}\\netcoreapp3.0\\Plugin.dll"); - ExecuteAndUnload(pluginFullPath, out hostAlcWeakRef); - - // Poll and run GC until the AssemblyLoadContext is unloaded. - // You don't need to do that unless you want to know when the context - // got unloaded. You can just leave it to the regular GC. - for (int i = 0; hostAlcWeakRef.IsAlive && (i < 10); i++) - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - } - - Console.WriteLine($"Unload success: {!hostAlcWeakRef.IsAlive}"); - } - } -} diff --git a/core/tutorials/Unloading/Host/Properties/launchSettings.json b/core/tutorials/Unloading/Host/Properties/launchSettings.json deleted file mode 100644 index c61d3a8ef7e..00000000000 --- a/core/tutorials/Unloading/Host/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "Host": { - "commandName": "Project" - } - } -} \ No newline at end of file diff --git a/core/tutorials/Unloading/Interface/Interface.cs b/core/tutorials/Unloading/Interface/Interface.cs deleted file mode 100644 index 2fe8f8446b0..00000000000 --- a/core/tutorials/Unloading/Interface/Interface.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Plugin -{ - public struct Version - { - public Version(int major, int minor) - { - Major = major; - Minor = minor; - } - - public int Major { get; } - public int Minor { get; } - - public override string ToString() - { - return $"({Major}.{Minor})"; - } - } - - // The Interface is defined in an Assembly shared between the host and the plugin. - // That makes calling functions from the plugin easier (without having to use reflection - // to invoke all of the functions - we just use reflection once to get the Interface) - // NOTE: - // The Assembly that defines the Interface must be loaded into the AssemblyLoadContext - // of the host only. If it got loaded twice - once into the AssemblyLoadContext in which - // the plugin is loaded and once into the default AssemblyLoadContext where the host is loaded, - // the Interface would become two different types and it would not be possible to - // use Interface instance created on the plugin side on the host side - public interface Interface - { - Version GetVersion(); - string GetMessage(); - } -} diff --git a/core/tutorials/Unloading/Interface/Interface.csproj b/core/tutorials/Unloading/Interface/Interface.csproj deleted file mode 100644 index ea83d29686c..00000000000 --- a/core/tutorials/Unloading/Interface/Interface.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netcoreapp3.0 - - - diff --git a/core/tutorials/Unloading/Plugin/Plugin.csproj b/core/tutorials/Unloading/Plugin/Plugin.csproj deleted file mode 100644 index 80afe31d26f..00000000000 --- a/core/tutorials/Unloading/Plugin/Plugin.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netcoreapp3.0 - - - - - - false - - - - - diff --git a/core/tutorials/Unloading/Plugin/PluginClass.cs b/core/tutorials/Unloading/Plugin/PluginClass.cs deleted file mode 100644 index 9f7741b5821..00000000000 --- a/core/tutorials/Unloading/Plugin/PluginClass.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Loader; - -namespace Plugin -{ - public class PluginClass : Interface - { - public static Interface GetInterface() - { - PluginClass plugin = new PluginClass(); - - // We register handler for the Unloading event of the context that we are running in - // so that we can perform cleanup of stuff that would otherwise prevent unloading - // (Like freeing GCHandles for objects of types loaded into the unloadable AssemblyLoadContext, - // terminating threads running code in assemblies loaded into the unloadable AssemblyLoadContext, - // etc.) - // NOTE: this is optional and likely not required for basic scenarios - Assembly currentAssembly = Assembly.GetExecutingAssembly(); - AssemblyLoadContext currentContext = AssemblyLoadContext.GetLoadContext(currentAssembly); - currentContext.Unloading += OnPluginUnloadingRequested; - - return plugin; - } - - private static void OnPluginUnloadingRequested(AssemblyLoadContext obj) - { - PluginDependency.Logger.LogMessage("Cleanup of stuff preventing unloading"); - } - - // Plugin interface methods implementation - - public string GetMessage() - { - return "Hello from the unloadable plugin"; - } - - public Version GetVersion() - { - return new Version(1, 0); - } - } -} diff --git a/core/tutorials/Unloading/PluginDependency/Logger.cs b/core/tutorials/Unloading/PluginDependency/Logger.cs deleted file mode 100644 index ba562e58ba0..00000000000 --- a/core/tutorials/Unloading/PluginDependency/Logger.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace PluginDependency -{ - // This is a simple class to write message to console. It is present to demostrate - // how a dependency of the plugin gets loaded into the HostAssemblyLoadContext - public class Logger - { - public static void LogMessage(string msg) - { - Console.WriteLine(msg); - } - } -} diff --git a/core/tutorials/Unloading/PluginDependency/PluginDependency.csproj b/core/tutorials/Unloading/PluginDependency/PluginDependency.csproj deleted file mode 100644 index ea83d29686c..00000000000 --- a/core/tutorials/Unloading/PluginDependency/PluginDependency.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netcoreapp3.0 - - - diff --git a/core/tutorials/Unloading/Unloading.sln b/core/tutorials/Unloading/Unloading.sln deleted file mode 100644 index 742b9ee0f53..00000000000 --- a/core/tutorials/Unloading/Unloading.sln +++ /dev/null @@ -1,42 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28516.95 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Host", "Host\Host.csproj", "{7C1BDD5C-0167-4682-B67B-4D1AFB874F09}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin", "Plugin\Plugin.csproj", "{89ED8F97-E90B-453E-9086-130FAC5B99A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Interface", "Interface\Interface.csproj", "{C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginDependency", "PluginDependency\PluginDependency.csproj", "{0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Release|Any CPU.Build.0 = Release|Any CPU - {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Release|Any CPU.Build.0 = Release|Any CPU - {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Release|Any CPU.Build.0 = Release|Any CPU - {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {313B3DCB-6BC8-4578-9FCB-339E2DED8978} - EndGlobalSection -EndGlobal diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj deleted file mode 100644 index 689d978c700..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netcoreapp2.2 - - - - - - - - - - - - - diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs deleted file mode 100644 index 81ff8c1fd70..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Xunit; -using TextUtils; -using System.Diagnostics; - -namespace TestLibrary -{ - public class TextUtils_GetWordCountShould - { - [Fact] - public void IgnoreCasing() - { - var wordCount = WordCount.GetWordCount("Jack", "Jack jack"); - - Assert.Equal(2, wordCount); - } - - [Theory] - [InlineData(0, "Ting", "Does not appear in the string.")] - [InlineData(1, "Ting", "Ting appears once.")] - [InlineData(2, "Ting", "Ting appears twice with Ting.")] - public void CountInstancesCorrectly(int count, - string searchWord, - string inputString) - { - Assert.Equal(count, WordCount.GetWordCount(searchWord, - inputString)); - } - } -} diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj deleted file mode 100644 index b290d67fb72..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netstandard1.4 - - - diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs deleted file mode 100644 index 500ab4131d1..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Linq; - -namespace TextUtils -{ - public static class WordCount - { - public static int GetWordCount(string searchWord, string inputString) - { - // Null check these variables and determine if they have values. - if (string.IsNullOrEmpty(searchWord) || string.IsNullOrEmpty(inputString)) - { - return 0; - } - - // Convert the string into an array of words. - var source = inputString.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, - StringSplitOptions.RemoveEmptyEntries); - - // Create the query. Use ToLowerInvariant to match uppercase/lowercase strings. - var matchQuery = from word in source - where word.ToLowerInvariant() == searchWord.ToLowerInvariant() - select word; - - // Count the matches, which executes the query. Return the result. - return matchQuery.Count(); - } - } -} diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln deleted file mode 100644 index 18e597218e3..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln +++ /dev/null @@ -1,29 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextUtils", "TextUtils\TextUtils.csproj", "{78838D20-B7C5-4468-835D-C300BB236AD6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestLibrary", "TestLibrary\TestLibrary.csproj", "{0C799E9B-C539-4B0F-A768-B478AA14A631}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WordCounterApp", "WordCounterApp\WordCounterApp.csproj", "{EACA52F3-6AFC-4374-A6BC-55796804F35D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {78838D20-B7C5-4468-835D-C300BB236AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {78838D20-B7C5-4468-835D-C300BB236AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {78838D20-B7C5-4468-835D-C300BB236AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {78838D20-B7C5-4468-835D-C300BB236AD6}.Release|Any CPU.Build.0 = Release|Any CPU - {0C799E9B-C539-4B0F-A768-B478AA14A631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C799E9B-C539-4B0F-A768-B478AA14A631}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C799E9B-C539-4B0F-A768-B478AA14A631}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C799E9B-C539-4B0F-A768-B478AA14A631}.Release|Any CPU.Build.0 = Release|Any CPU - {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs deleted file mode 100644 index 8c13b3f5c52..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using TextUtils; - -namespace WordCounterApp -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Enter a search word:"); - var searchWord = Console.ReadLine(); - Console.WriteLine("Provide a string to search:"); - var inputString = Console.ReadLine(); - - var wordCount = WordCount.GetWordCount(searchWord, inputString); - - var pluralChar = "s"; - if (wordCount == 1) - { - pluralChar = string.Empty; - } - - Console.WriteLine($"The search word {searchWord} appears " + - $"{wordCount} time{pluralChar}."); - } - } -} diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj deleted file mode 100644 index 02118320b10..00000000000 --- a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - netcoreapp2.2 - - - - - - - - - - diff --git a/core/tutorials/vb-library-with-visual-studio/stringlibrary.vb b/core/tutorials/vb-library-with-visual-studio/stringlibrary.vb deleted file mode 100644 index 093b85fa323..00000000000 --- a/core/tutorials/vb-library-with-visual-studio/stringlibrary.vb +++ /dev/null @@ -1,15 +0,0 @@ -Imports System.Runtime.CompilerServices - -Namespace UtilityLibraries - Public Module StringLibrary - - Public Function StartsWithUpper(str As String) As Boolean - If String.IsNullOrWhiteSpace(str) Then - Return False - End If - - Dim ch As Char = str(0) - Return Char.IsUpper(ch) - End Function - End Module -End Namespace From 9683153b91dd09e701068b91ffc41036a5782a8f Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 19:02:13 -0400 Subject: [PATCH 8/9] Revert "Fix build error" This reverts commit 90d84c7970ddb13b36b748177cb48ada9cc78823. --- .../Controllers/DiagnosticScenarios.cs | 4 +- core/tutorials/Unloading/Host/Host.csproj | 12 +++ core/tutorials/Unloading/Host/Program.cs | 101 ++++++++++++++++++ .../Host/Properties/launchSettings.json | 7 ++ .../Unloading/Interface/Interface.cs | 36 +++++++ .../Unloading/Interface/Interface.csproj | 7 ++ core/tutorials/Unloading/Plugin/Plugin.csproj | 15 +++ .../tutorials/Unloading/Plugin/PluginClass.cs | 44 ++++++++ .../Unloading/PluginDependency/Logger.cs | 14 +++ .../PluginDependency/PluginDependency.csproj | 7 ++ core/tutorials/Unloading/Unloading.sln | 42 ++++++++ .../TestLibrary/TestLibrary.csproj | 17 +++ .../WordCounter/TestLibrary/TextUtilsTests.cs | 29 +++++ .../WordCounter/TextUtils/TextUtils.csproj | 7 ++ .../WordCounter/TextUtils/WordCount.cs | 29 +++++ .../WordCounter/WordCounter.sln | 29 +++++ .../WordCounter/WordCounterApp/Program.cs | 27 +++++ .../WordCounterApp/WordCounterApp.csproj | 15 +++ .../stringlibrary.vb | 15 +++ 19 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 core/tutorials/Unloading/Host/Host.csproj create mode 100644 core/tutorials/Unloading/Host/Program.cs create mode 100644 core/tutorials/Unloading/Host/Properties/launchSettings.json create mode 100644 core/tutorials/Unloading/Interface/Interface.cs create mode 100644 core/tutorials/Unloading/Interface/Interface.csproj create mode 100644 core/tutorials/Unloading/Plugin/Plugin.csproj create mode 100644 core/tutorials/Unloading/Plugin/PluginClass.cs create mode 100644 core/tutorials/Unloading/PluginDependency/Logger.cs create mode 100644 core/tutorials/Unloading/PluginDependency/PluginDependency.csproj create mode 100644 core/tutorials/Unloading/Unloading.sln create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs create mode 100644 core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj create mode 100644 core/tutorials/vb-library-with-visual-studio/stringlibrary.vb diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs index 453d9095a7f..4e39f71058f 100644 --- a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -12,8 +12,8 @@ namespace testwebapi.Controllers [ApiController] public class DiagScenarioController : ControllerBase { - object o1 = new object(); - object o2 = new object(); + var o1 = new object(); + var o2 = new object(); private static Processor p = new Processor(); diff --git a/core/tutorials/Unloading/Host/Host.csproj b/core/tutorials/Unloading/Host/Host.csproj new file mode 100644 index 00000000000..67752e84b07 --- /dev/null +++ b/core/tutorials/Unloading/Host/Host.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.0 + + + + + + + diff --git a/core/tutorials/Unloading/Host/Program.cs b/core/tutorials/Unloading/Host/Program.cs new file mode 100644 index 00000000000..898e822bee8 --- /dev/null +++ b/core/tutorials/Unloading/Host/Program.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; + +namespace Host +{ + // This is a collectible (unloadable) AssemblyLoadContext that loads the dependencies + // of the plugin from the plugin's binary directory. + class HostAssemblyLoadContext : AssemblyLoadContext + { + // Resolver of the locations of the assemblies that are dependencies of the + // main plugin assembly. + private AssemblyDependencyResolver _resolver; + + public HostAssemblyLoadContext(string pluginPath) : base(isCollectible: true) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + } + + // The Load method override causes all the dependencies present in the plugin's binary directory to get loaded + // into the HostAssemblyLoadContext together with the plugin assembly itself. + // NOTE: The Interface assembly must not be present in the plugin's binary directory, otherwise we would + // end up with the assembly being loaded twice. Once in the default context and once in the HostAssemblyLoadContext. + // The types present on the host and plugin side would then not match even though they would have the same names. + protected override Assembly Load(AssemblyName name) + { + string assemblyPath = _resolver.ResolveAssemblyToPath(name); + if (assemblyPath != null) + { + Console.WriteLine($"Loading assembly {assemblyPath} into the HostAssemblyLoadContext"); + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } + } + + class Program + { + // It is important to mark this method as NoInlining, otherwise the JIT could decide + // to inline it into the Main method. That could then prevent successful unloading + // of the plugin because some of the MethodInfo / Type / Plugin.Interface / HostAssemblyLoadContext + // instances may get lifetime extended beyond the point when the plugin is expected to be + // unloaded. + [MethodImpl(MethodImplOptions.NoInlining)] + static void ExecuteAndUnload(string assemblyPath, out WeakReference alcWeakRef) + { + // Create the unloadable HostAssemblyLoadContext + var alc = new HostAssemblyLoadContext(assemblyPath); + + // Create a weak reference to the AssemblyLoadContext that will allow us to detect + // when the unload completes. + alcWeakRef = new WeakReference(alc); + + // Load the plugin assembly into the HostAssemblyLoadContext. + // NOTE: the assemblyPath must be an absolute path. + Assembly a = alc.LoadFromAssemblyPath(assemblyPath); + + // Get the plugin interface by calling the PluginClass.GetInterface method via reflection. + Type pluginType = a.GetType("Plugin.PluginClass"); + MethodInfo getInterface = pluginType.GetMethod("GetInterface", BindingFlags.Static | BindingFlags.Public); + Plugin.Interface plugin = (Plugin.Interface)getInterface.Invoke(null, null); + + // Now we can call methods of the plugin using the interface + string result = plugin.GetMessage(); + Plugin.Version version = plugin.GetVersion(); + + Console.WriteLine($"Response from the plugin: GetVersion(): {version}, GetMessage(): {result}"); + + // This initiates the unload of the HostAssemblyLoadContext. The actual unloading doesn't happen + // right away, GC has to kick in later to collect all the stuff. + alc.Unload(); + } + + static void Main(string[] args) + { + WeakReference hostAlcWeakRef; + string currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); +#if DEBUG + string configName = "Debug"; +#else + string configName = "Release"; +#endif + string pluginFullPath = Path.Combine(currentAssemblyDirectory, $"..\\..\\..\\..\\Plugin\\bin\\{configName}\\netcoreapp3.0\\Plugin.dll"); + ExecuteAndUnload(pluginFullPath, out hostAlcWeakRef); + + // Poll and run GC until the AssemblyLoadContext is unloaded. + // You don't need to do that unless you want to know when the context + // got unloaded. You can just leave it to the regular GC. + for (int i = 0; hostAlcWeakRef.IsAlive && (i < 10); i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + Console.WriteLine($"Unload success: {!hostAlcWeakRef.IsAlive}"); + } + } +} diff --git a/core/tutorials/Unloading/Host/Properties/launchSettings.json b/core/tutorials/Unloading/Host/Properties/launchSettings.json new file mode 100644 index 00000000000..c61d3a8ef7e --- /dev/null +++ b/core/tutorials/Unloading/Host/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Host": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/core/tutorials/Unloading/Interface/Interface.cs b/core/tutorials/Unloading/Interface/Interface.cs new file mode 100644 index 00000000000..2fe8f8446b0 --- /dev/null +++ b/core/tutorials/Unloading/Interface/Interface.cs @@ -0,0 +1,36 @@ +using System; + +namespace Plugin +{ + public struct Version + { + public Version(int major, int minor) + { + Major = major; + Minor = minor; + } + + public int Major { get; } + public int Minor { get; } + + public override string ToString() + { + return $"({Major}.{Minor})"; + } + } + + // The Interface is defined in an Assembly shared between the host and the plugin. + // That makes calling functions from the plugin easier (without having to use reflection + // to invoke all of the functions - we just use reflection once to get the Interface) + // NOTE: + // The Assembly that defines the Interface must be loaded into the AssemblyLoadContext + // of the host only. If it got loaded twice - once into the AssemblyLoadContext in which + // the plugin is loaded and once into the default AssemblyLoadContext where the host is loaded, + // the Interface would become two different types and it would not be possible to + // use Interface instance created on the plugin side on the host side + public interface Interface + { + Version GetVersion(); + string GetMessage(); + } +} diff --git a/core/tutorials/Unloading/Interface/Interface.csproj b/core/tutorials/Unloading/Interface/Interface.csproj new file mode 100644 index 00000000000..ea83d29686c --- /dev/null +++ b/core/tutorials/Unloading/Interface/Interface.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp3.0 + + + diff --git a/core/tutorials/Unloading/Plugin/Plugin.csproj b/core/tutorials/Unloading/Plugin/Plugin.csproj new file mode 100644 index 00000000000..80afe31d26f --- /dev/null +++ b/core/tutorials/Unloading/Plugin/Plugin.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.0 + + + + + + false + + + + + diff --git a/core/tutorials/Unloading/Plugin/PluginClass.cs b/core/tutorials/Unloading/Plugin/PluginClass.cs new file mode 100644 index 00000000000..9f7741b5821 --- /dev/null +++ b/core/tutorials/Unloading/Plugin/PluginClass.cs @@ -0,0 +1,44 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace Plugin +{ + public class PluginClass : Interface + { + public static Interface GetInterface() + { + PluginClass plugin = new PluginClass(); + + // We register handler for the Unloading event of the context that we are running in + // so that we can perform cleanup of stuff that would otherwise prevent unloading + // (Like freeing GCHandles for objects of types loaded into the unloadable AssemblyLoadContext, + // terminating threads running code in assemblies loaded into the unloadable AssemblyLoadContext, + // etc.) + // NOTE: this is optional and likely not required for basic scenarios + Assembly currentAssembly = Assembly.GetExecutingAssembly(); + AssemblyLoadContext currentContext = AssemblyLoadContext.GetLoadContext(currentAssembly); + currentContext.Unloading += OnPluginUnloadingRequested; + + return plugin; + } + + private static void OnPluginUnloadingRequested(AssemblyLoadContext obj) + { + PluginDependency.Logger.LogMessage("Cleanup of stuff preventing unloading"); + } + + // Plugin interface methods implementation + + public string GetMessage() + { + return "Hello from the unloadable plugin"; + } + + public Version GetVersion() + { + return new Version(1, 0); + } + } +} diff --git a/core/tutorials/Unloading/PluginDependency/Logger.cs b/core/tutorials/Unloading/PluginDependency/Logger.cs new file mode 100644 index 00000000000..ba562e58ba0 --- /dev/null +++ b/core/tutorials/Unloading/PluginDependency/Logger.cs @@ -0,0 +1,14 @@ +using System; + +namespace PluginDependency +{ + // This is a simple class to write message to console. It is present to demostrate + // how a dependency of the plugin gets loaded into the HostAssemblyLoadContext + public class Logger + { + public static void LogMessage(string msg) + { + Console.WriteLine(msg); + } + } +} diff --git a/core/tutorials/Unloading/PluginDependency/PluginDependency.csproj b/core/tutorials/Unloading/PluginDependency/PluginDependency.csproj new file mode 100644 index 00000000000..ea83d29686c --- /dev/null +++ b/core/tutorials/Unloading/PluginDependency/PluginDependency.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp3.0 + + + diff --git a/core/tutorials/Unloading/Unloading.sln b/core/tutorials/Unloading/Unloading.sln new file mode 100644 index 00000000000..742b9ee0f53 --- /dev/null +++ b/core/tutorials/Unloading/Unloading.sln @@ -0,0 +1,42 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28516.95 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Host", "Host\Host.csproj", "{7C1BDD5C-0167-4682-B67B-4D1AFB874F09}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin", "Plugin\Plugin.csproj", "{89ED8F97-E90B-453E-9086-130FAC5B99A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Interface", "Interface\Interface.csproj", "{C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginDependency", "PluginDependency\PluginDependency.csproj", "{0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C1BDD5C-0167-4682-B67B-4D1AFB874F09}.Release|Any CPU.Build.0 = Release|Any CPU + {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89ED8F97-E90B-453E-9086-130FAC5B99A9}.Release|Any CPU.Build.0 = Release|Any CPU + {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9BCDE8B-51BA-40A6-B7A1-186F6C6B6CCE}.Release|Any CPU.Build.0 = Release|Any CPU + {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DFE9D18-D6B3-4097-AF87-D06B1B9DB4FA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {313B3DCB-6BC8-4578-9FCB-339E2DED8978} + EndGlobalSection +EndGlobal diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj new file mode 100644 index 00000000000..689d978c700 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TestLibrary.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs new file mode 100644 index 00000000000..81ff8c1fd70 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TestLibrary/TextUtilsTests.cs @@ -0,0 +1,29 @@ +using Xunit; +using TextUtils; +using System.Diagnostics; + +namespace TestLibrary +{ + public class TextUtils_GetWordCountShould + { + [Fact] + public void IgnoreCasing() + { + var wordCount = WordCount.GetWordCount("Jack", "Jack jack"); + + Assert.Equal(2, wordCount); + } + + [Theory] + [InlineData(0, "Ting", "Does not appear in the string.")] + [InlineData(1, "Ting", "Ting appears once.")] + [InlineData(2, "Ting", "Ting appears twice with Ting.")] + public void CountInstancesCorrectly(int count, + string searchWord, + string inputString) + { + Assert.Equal(count, WordCount.GetWordCount(searchWord, + inputString)); + } + } +} diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj new file mode 100644 index 00000000000..b290d67fb72 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/TextUtils.csproj @@ -0,0 +1,7 @@ + + + + netstandard1.4 + + + diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs new file mode 100644 index 00000000000..500ab4131d1 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/TextUtils/WordCount.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; + +namespace TextUtils +{ + public static class WordCount + { + public static int GetWordCount(string searchWord, string inputString) + { + // Null check these variables and determine if they have values. + if (string.IsNullOrEmpty(searchWord) || string.IsNullOrEmpty(inputString)) + { + return 0; + } + + // Convert the string into an array of words. + var source = inputString.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' }, + StringSplitOptions.RemoveEmptyEntries); + + // Create the query. Use ToLowerInvariant to match uppercase/lowercase strings. + var matchQuery = from word in source + where word.ToLowerInvariant() == searchWord.ToLowerInvariant() + select word; + + // Count the matches, which executes the query. Return the result. + return matchQuery.Count(); + } + } +} diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln new file mode 100644 index 00000000000..18e597218e3 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounter.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextUtils", "TextUtils\TextUtils.csproj", "{78838D20-B7C5-4468-835D-C300BB236AD6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestLibrary", "TestLibrary\TestLibrary.csproj", "{0C799E9B-C539-4B0F-A768-B478AA14A631}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WordCounterApp", "WordCounterApp\WordCounterApp.csproj", "{EACA52F3-6AFC-4374-A6BC-55796804F35D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {78838D20-B7C5-4468-835D-C300BB236AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78838D20-B7C5-4468-835D-C300BB236AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78838D20-B7C5-4468-835D-C300BB236AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78838D20-B7C5-4468-835D-C300BB236AD6}.Release|Any CPU.Build.0 = Release|Any CPU + {0C799E9B-C539-4B0F-A768-B478AA14A631}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C799E9B-C539-4B0F-A768-B478AA14A631}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C799E9B-C539-4B0F-A768-B478AA14A631}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C799E9B-C539-4B0F-A768-B478AA14A631}.Release|Any CPU.Build.0 = Release|Any CPU + {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EACA52F3-6AFC-4374-A6BC-55796804F35D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs new file mode 100644 index 00000000000..8c13b3f5c52 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/Program.cs @@ -0,0 +1,27 @@ +using System; +using TextUtils; + +namespace WordCounterApp +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Enter a search word:"); + var searchWord = Console.ReadLine(); + Console.WriteLine("Provide a string to search:"); + var inputString = Console.ReadLine(); + + var wordCount = WordCount.GetWordCount(searchWord, inputString); + + var pluralChar = "s"; + if (wordCount == 1) + { + pluralChar = string.Empty; + } + + Console.WriteLine($"The search word {searchWord} appears " + + $"{wordCount} time{pluralChar}."); + } + } +} diff --git a/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj new file mode 100644 index 00000000000..02118320b10 --- /dev/null +++ b/core/tutorials/using-on-mac-vs-full-solution/WordCounter/WordCounterApp/WordCounterApp.csproj @@ -0,0 +1,15 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + + + diff --git a/core/tutorials/vb-library-with-visual-studio/stringlibrary.vb b/core/tutorials/vb-library-with-visual-studio/stringlibrary.vb new file mode 100644 index 00000000000..093b85fa323 --- /dev/null +++ b/core/tutorials/vb-library-with-visual-studio/stringlibrary.vb @@ -0,0 +1,15 @@ +Imports System.Runtime.CompilerServices + +Namespace UtilityLibraries + Public Module StringLibrary + + Public Function StartsWithUpper(str As String) As Boolean + If String.IsNullOrWhiteSpace(str) Then + Return False + End If + + Dim ch As Char = str(0) + Return Char.IsUpper(ch) + End Function + End Module +End Namespace From 73eff18a1a1b692421f27dda704d6ae28ec31164 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Fri, 30 Aug 2019 19:02:42 -0400 Subject: [PATCH 9/9] Fix build error --- .../DiagnosticScenarios/Controllers/DiagnosticScenarios.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs index 4e39f71058f..453d9095a7f 100644 --- a/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs +++ b/core/diagnostics/DiagnosticScenarios/Controllers/DiagnosticScenarios.cs @@ -12,8 +12,8 @@ namespace testwebapi.Controllers [ApiController] public class DiagScenarioController : ControllerBase { - var o1 = new object(); - var o2 = new object(); + object o1 = new object(); + object o2 = new object(); private static Processor p = new Processor();