[System.Web] Implement HostingEnvironment.QueueBackgroundWorkItem
authorMikayla Hutchinson <m.j.hutchinson@gmail.com>
Wed, 29 Mar 2017 23:47:50 +0000 (19:47 -0400)
committerMarek Safar <marek.safar@gmail.com>
Thu, 30 Mar 2017 17:05:04 +0000 (19:05 +0200)
From reference source.

This is required for the Azure Functions Host to run unmodified.

mcs/class/System.Web/System.Web.Hosting/HostingEnvironment.cs
mcs/class/System.Web/System.Web.dll.sources

index 4b8a52f036de2deb44f292da94fd47541e04a425..2894a9009760fc7330bcedf7761d740fa77bb8e6 100644 (file)
 
 
 using System;
+using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
 using System.Security.Permissions;
 using System.Threading;
+using System.Threading.Tasks;
 using System.Web.Configuration;
 using System.Web.Caching;
 using System.Web.Util;
@@ -53,6 +56,8 @@ namespace System.Web.Hosting {
                static VirtualPathProvider vpath_provider = (HttpRuntime.AppDomainAppVirtualPath == null) ? null :
                                                                new DefaultVirtualPathProvider ();
                static int busy_count;
+               static BackgroundWorkScheduler _backgroundWorkScheduler = null; // created on demand
+               static readonly Task<object> _completedTask = Task.FromResult<object>(null);
 
                internal static bool HaveCustomVPP {
                        get;
@@ -212,6 +217,73 @@ namespace System.Web.Hosting {
                        if (Host != null)
                                Host.UnregisterObject (obj);
                }
+
+               // Schedules a task which can run in the background, independent of any request.
+               // This differs from a normal ThreadPool work item in that ASP.NET can keep track
+               // of how many work items registered through this API are currently running, and
+               // the ASP.NET runtime will try not to delay AppDomain shutdown until these work
+               // items have finished executing.
+               //
+               // Usage notes:
+               // - This API cannot be called outside of an ASP.NET-managed AppDomain.
+               // - The caller's ExecutionContext is not flowed to the work item.
+               // - Scheduled work items are not guaranteed to ever execute, e.g., when AppDomain
+               //   shutdown has already started by the time this API was called.
+               // - The provided CancellationToken will be signaled when the application is
+               //   shutting down. The work item should make every effort to honor this token.
+               //   If a work item does not honor this token and continues executing it will
+               //   eventually be considered rogue, and the ASP.NET runtime will rudely unload
+               //   the AppDomain without waiting for the work item to finish.
+               //
+               // This overload of QueueBackgroundWorkItem takes a void-returning callback; the
+               // work item will be considered finished when the callback returns.
+               [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
+               public static void QueueBackgroundWorkItem(Action<CancellationToken> workItem) {
+                       if (workItem == null) {
+                               throw new ArgumentNullException("workItem");
+                       }
+
+                       QueueBackgroundWorkItem(ct => { workItem(ct); return _completedTask; });
+               }
+
+               // See documentation on the other overload for a general API overview.
+               //
+               // This overload of QueueBackgroundWorkItem takes a Task-returning callback; the
+               // work item will be considered finished when the returned Task transitions to a
+               // terminal state.
+               [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
+               public static void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem) {
+                       if (workItem == null) {
+                               throw new ArgumentNullException("workItem");
+                       }
+                       if (Host == null) {
+                               throw new InvalidOperationException(); // can only be called within an ASP.NET AppDomain
+                       }
+
+                       QueueBackgroundWorkItemInternal(workItem);
+               }
+
+               static void QueueBackgroundWorkItemInternal(Func<CancellationToken, Task> workItem) {
+                       Debug.Assert(workItem != null);
+
+                       BackgroundWorkScheduler scheduler = Volatile.Read(ref _backgroundWorkScheduler);
+
+                       // If the scheduler doesn't exist, lazily create it, but only allow one instance to ever be published to the backing field
+                       if (scheduler == null) {
+                               BackgroundWorkScheduler newlyCreatedScheduler = new BackgroundWorkScheduler(UnregisterObject, WriteUnhandledException);
+                               scheduler = Interlocked.CompareExchange(ref _backgroundWorkScheduler, newlyCreatedScheduler, null) ?? newlyCreatedScheduler;
+                               if (scheduler == newlyCreatedScheduler) {
+                                       RegisterObject(scheduler); // Only call RegisterObject if we just created the "winning" one
+                               }
+                       }
+
+                       scheduler.ScheduleWorkItem(workItem);
+               }
+
+               static void WriteUnhandledException (AppDomain appDomain, Exception exception)
+               {
+                       Console.Error.WriteLine ("Error in background work item: " + exception);
+               }
        }
 }
 
index 46f3627f416fa5f5342ba4ef05c35b8a56e65ba1..742d1b4eb09779e63e3f27c258dfc3d9e1847d89 100644 (file)
@@ -290,6 +290,7 @@ System.Web.Hosting/AppManagerAppDomainFactory.cs
 System.Web.Hosting/ApplicationInfo.cs
 System.Web.Hosting/ApplicationHost.cs
 System.Web.Hosting/ApplicationManager.cs
+../referencesource/System.Web/Hosting/BackgroundWorkScheduler.cs
 System.Web.Hosting/BareApplicationHost.cs
 System.Web.Hosting/DefaultVirtualDirectory.cs
 System.Web.Hosting/DefaultVirtualFile.cs
@@ -1199,6 +1200,7 @@ System.Web.UI/XhtmlMobileDocType.cs
 System.Web.UI/XhtmlTextWriter.cs
 System.Web.UI/XPathBinder.cs
 System.Web.Util/AltSerialization.cs
+../referencesource/System.Web/Util/CancellationTokenHelper.cs
 System.Web.Util/DataSourceHelper.cs
 System.Web.Util/DataSourceResolver.cs
 System.Web.Util/FileUtils.cs