//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Hosting { using System; using System.Collections; using System.Collections.Generic; using System.Configuration; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Remoting; using System.Security; using System.Security.Permissions; using System.Threading; using System.Web; using System.Web.Configuration; using System.Web.Util; [ComImport, Guid("0ccd465e-3114-4ca3-ad50-cea561307e93"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IProcessHost { void StartApplication( [In, MarshalAs(UnmanagedType.LPWStr)] String appId, [In, MarshalAs(UnmanagedType.LPWStr)] String appPath, [MarshalAs(UnmanagedType.Interface)] out Object runtimeInterface); void ShutdownApplication([In, MarshalAs(UnmanagedType.LPWStr)] String appId); void Shutdown(); void EnumerateAppDomains( [MarshalAs(UnmanagedType.Interface)] out IAppDomainInfoEnum appDomainInfoEnum); } // Used by webengine4.dll for launching Helios applications via ProcessHost. [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("E2A1F244-70EB-483A-ACC8-DE6ACE5BF8B1")] internal interface IProcessHostLite { [return: MarshalAs(UnmanagedType.Interface)] IObjectHandle GetCustomLoader( [In, MarshalAs(UnmanagedType.LPWStr)] string appId, [In, MarshalAs(UnmanagedType.LPWStr)] string appConfigPath, [Out, MarshalAs(UnmanagedType.Interface)] out IProcessHostSupportFunctions supportFunctions, [Out, MarshalAs(UnmanagedType.Interface)] out AppDomain newlyCreatedAppDomain); void ReportCustomLoaderError( [In, MarshalAs(UnmanagedType.LPWStr)] string appId, [In] int hr, [In, MarshalAs(UnmanagedType.Interface)] AppDomain newlyCreatedAppDomain); [return: MarshalAs(UnmanagedType.BStr)] string GetFullExceptionMessage( [In] int hr, [In] IntPtr pErrorInfo); } // // App domain protocol manager // Note that this doesn't provide COM interop // public interface IAdphManager { void StartAppDomainProtocolListenerChannel( [In, MarshalAs(UnmanagedType.LPWStr)] String appId, [In, MarshalAs(UnmanagedType.LPWStr)] String protocolId, IListenerChannelCallback listenerChannelCallback); void StopAppDomainProtocolListenerChannel( [In, MarshalAs(UnmanagedType.LPWStr)] String appId, [In, MarshalAs(UnmanagedType.LPWStr)] String protocolId, int listenerChannelId, bool immediate); void StopAppDomainProtocol( [In, MarshalAs(UnmanagedType.LPWStr)] String appId, [In, MarshalAs(UnmanagedType.LPWStr)] String protocolId, bool immediate); } [ComImport, Guid("1cc9099d-0a8d-41cb-87d6-845e4f8c4e91"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IPphManager { void StartProcessProtocolListenerChannel( [In, MarshalAs(UnmanagedType.LPWStr)] String protocolId, IListenerChannelCallback listenerChannelCallback); void StopProcessProtocolListenerChannel( [In, MarshalAs(UnmanagedType.LPWStr)] String protocolId, int listenerChannelId, bool immediate); void StopProcessProtocol( [In, MarshalAs(UnmanagedType.LPWStr)] String protocolId, bool immediate); } [ComImport, Guid("9d98b251-453e-44f6-9cec-8b5aed970129"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IProcessHostIdleAndHealthCheck { [return: MarshalAs(UnmanagedType.Bool)] bool IsIdle(); void Ping(IProcessPingCallback callback); } [ComImport, Guid("5BC9C234-6CD7-49bf-A07A-6FDB7F22DFFF"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IAppDomainInfo { [return: MarshalAs(UnmanagedType.BStr)] string GetId(); [return: MarshalAs(UnmanagedType.BStr)] string GetVirtualPath(); [return: MarshalAs(UnmanagedType.BStr)] string GetPhysicalPath(); [return: MarshalAs(UnmanagedType.I4)] int GetSiteId(); [return: MarshalAs(UnmanagedType.Bool)] bool IsIdle(); } [ComImport, Guid("F79648FB-558B-4a09-88F1-1E3BCB30E34F"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IAppDomainInfoEnum { [return: MarshalAs(UnmanagedType.Interface)] IAppDomainInfo GetData(); [return: MarshalAs(UnmanagedType.I4)] int Count(); [return: MarshalAs(UnmanagedType.Bool)] bool MoveNext(); void Reset(); } public class AppDomainInfoEnum : IAppDomainInfoEnum { private AppDomainInfo[] _appDomainInfos; private int _curPos; internal AppDomainInfoEnum(AppDomainInfo[] appDomainInfos) { _appDomainInfos = appDomainInfos; _curPos = -1; } public int Count() { return _appDomainInfos.Length; } public IAppDomainInfo GetData() { return _appDomainInfos[_curPos]; } public bool MoveNext() { _curPos++; if (_curPos >= _appDomainInfos.Length) { return false; } return true; } public void Reset() { _curPos = -1; } } public class AppDomainInfo : IAppDomainInfo { private string _id; private string _virtualPath; private string _physicalPath; private int _siteId; private bool _isIdle; internal AppDomainInfo(string id, string vpath, string physPath, int siteId, bool isIdle) { _id = id; _virtualPath = vpath; _physicalPath = physPath; _siteId = siteId; _isIdle = isIdle; } public string GetId() { return _id; } public string GetVirtualPath() { return _virtualPath; } public string GetPhysicalPath() { return _physicalPath; } public int GetSiteId() { return _siteId; } public bool IsIdle() { return _isIdle; } } ///////////////////////////////////////////////////////////////////////////// // New for Dev10 [ComImport, Guid("AE54F424-71BC-4da5-AA2F-8C0CD53496FC"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IApplicationPreloadManager { [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="Util", Justification="Name must match IIS COM interface.")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="0#Util", Justification="Name must match IIS COM interface.")] void SetApplicationPreloadUtil( [In, MarshalAs(UnmanagedType.Interface)] IApplicationPreloadUtil preloadUtil); void SetApplicationPreloadState( [In, MarshalAs(UnmanagedType.LPWStr)] string context, [In, MarshalAs(UnmanagedType.LPWStr)] string appId, [In, MarshalAs(UnmanagedType.Bool)] bool enabled); } [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="Util", Justification="Name must match IIS COM interface.")] [ComImport, Guid("940D8ADD-9E40-4475-9A67-2CDCDF57995C"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IApplicationPreloadUtil { [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId="1#", Justification="Parameter kind must match IIS COM interface.")] [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId="2#", Justification="Parameter kind must match IIS COM interface.")] [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId="3#", Justification="Parameter kind must match IIS COM interface.")] void GetApplicationPreloadInfo( [In, MarshalAs(UnmanagedType.LPWStr)] string context, [Out, MarshalAs(UnmanagedType.Bool)] out bool enabled, [Out, MarshalAs(UnmanagedType.BStr)] out string startupObjType, [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] out string[] parametersForStartupObj); void ReportApplicationPreloadFailure( [In, MarshalAs(UnmanagedType.LPWStr)] string context, [In, MarshalAs(UnmanagedType.U4)] int errorCode, [In, MarshalAs(UnmanagedType.LPWStr)] string errorMessage); } /// public sealed class ProcessHost : MarshalByRefObject, IProcessHost, IProcessHostLite, ICustomRuntimeManager, IAdphManager, // process protocol handlers manager IPphManager, // appdomain protocol handlers manager IProcessHostIdleAndHealthCheck, IProcessSuspendListener, IApplicationPreloadManager { private static Object _processHostStaticLock = new Object(); private static ProcessHost _theProcessHost; [ThreadStatic] private static KeyValuePair _customLoaderStartupError; private readonly CustomRuntimeManager _customRuntimeManager = new CustomRuntimeManager(); private IProcessHostSupportFunctions _functions; private ApplicationManager _appManager; private ProtocolsSection _protocolsConfig; // process protocol handlers by prot id private Hashtable _protocolHandlers = new Hashtable(); private IApplicationPreloadUtil _preloadUtil = null; private System.Threading.Semaphore _preloadingThrottle = null; private ProtocolsSection ProtocolsConfig { get { if (_protocolsConfig == null) { lock (this) { if (_protocolsConfig == null) { if (HttpConfigurationSystem.IsSet) { _protocolsConfig = RuntimeConfig.GetRootWebConfig().Protocols; } else { Configuration c = WebConfigurationManager.OpenWebConfiguration(null); _protocolsConfig = (ProtocolsSection) c.GetSection("system.web/protocols"); } } } } return _protocolsConfig; } } // ctor only called via GetProcessHost [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reading this particular registry value is safe.")] private ProcessHost(IProcessHostSupportFunctions functions) { try { // remember support functions _functions = functions; // pass them along to the HostingEnvironment in the default domain HostingEnvironment.SupportFunctions = functions; // create singleton app manager _appManager = ApplicationManager.GetApplicationManager(); // For M3 we get the throttling limit from the registry. // Dev10\Beta1 work item 543420 is to investigate whether we need to get rid of the throttling int maxPreloadConcurrency = (int)Misc.GetAspNetRegValue(null, "MaxPreloadConcurrency", 0); if (maxPreloadConcurrency > 0) { _preloadingThrottle = new System.Threading.Semaphore(maxPreloadConcurrency, maxPreloadConcurrency); } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Cant_Create_Process_Host)}); Debug.Trace("internal", "ProcessHost::ctor failed with " + e.GetType().FullName + ": " + e.Message + "\r\n" + e.StackTrace); } throw; } } // ValidateType // // Validate and Get the Type that is sent in // // Note: Because ProtocolElement is outside of our assembly we need to do // that here, and because of that we need to hardcode the property // names!! // private Type ValidateAndGetType( ProtocolElement element, string typeName, Type assignableType, string elementPropertyName ) { Type handlerType; try { handlerType = Type.GetType(typeName, true /*throwOnError*/); } catch (Exception e) { PropertyInformation propInfo = null; string source = String.Empty; int lineNum = 0; if (element != null && null != element.ElementInformation) { propInfo = element.ElementInformation.Properties[elementPropertyName]; if (null != propInfo) { source = propInfo.Source; lineNum = propInfo.LineNumber; } } throw new ConfigurationErrorsException( e.Message, e, source, lineNum); } ConfigUtil.CheckAssignableType( assignableType, handlerType, element, elementPropertyName); return handlerType; } private Type GetAppDomainProtocolHandlerType(String protocolId) { Type t = null; try { // get app domaoin protocol handler type from config ProtocolElement configEntry = ProtocolsConfig.Protocols[protocolId]; if (configEntry == null) throw new ArgumentException(SR.GetString(SR.Unknown_protocol_id, protocolId)); t = ValidateAndGetType( configEntry, configEntry.AppDomainHandlerType, typeof(AppDomainProtocolHandler), "AppDomainHandlerType" ); } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Invalid_AppDomain_Prot_Type)} ); } } return t; } [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)] public override Object InitializeLifetimeService() { return null; // never expire lease } // called from ProcessHostFactoryHelper to get ProcessHost internal static ProcessHost GetProcessHost(IProcessHostSupportFunctions functions) { if (_theProcessHost == null) { lock (_processHostStaticLock) { if (_theProcessHost == null) { _theProcessHost = new ProcessHost(functions); } } } return _theProcessHost; } internal static ProcessHost DefaultHost { get { return _theProcessHost; // may be null } } internal IProcessHostSupportFunctions SupportFunctions { get { return _functions; } } // // IProcessHostProcessProtocolManager interface implementation // // starts process protocol handler on demand public void StartProcessProtocolListenerChannel(String protocolId, IListenerChannelCallback listenerChannelCallback) { try { if (protocolId == null) throw new ArgumentNullException("protocolId"); // validate protocol id ProtocolElement configEntry = ProtocolsConfig.Protocols[protocolId]; if (configEntry == null) throw new ArgumentException(SR.GetString(SR.Unknown_protocol_id, protocolId)); ProcessProtocolHandler protocolHandler = null; Type protocolHandlerType = null; protocolHandlerType = ValidateAndGetType( configEntry, configEntry.ProcessHandlerType, typeof(ProcessProtocolHandler), "ProcessHandlerType" ); lock (this) { // lookup or create protocol handler protocolHandler = _protocolHandlers[protocolId] as ProcessProtocolHandler; if (protocolHandler == null) { protocolHandler = (ProcessProtocolHandler)Activator.CreateInstance(protocolHandlerType); _protocolHandlers[protocolId] = protocolHandler; } } // call the handler to start listenerChannel if (protocolHandler != null) { protocolHandler.StartListenerChannel(listenerChannelCallback, this); } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Invalid_Process_Prot_Type)} ); } throw; } } public void StopProcessProtocolListenerChannel(String protocolId, int listenerChannelId, bool immediate) { try { if (protocolId == null) throw new ArgumentNullException("protocolId"); ProcessProtocolHandler protocolHandler = null; lock (this) { // lookup protocol handler protocolHandler = _protocolHandlers[protocolId] as ProcessProtocolHandler; } // call the handler to stop listenerChannel if (protocolHandler != null) { protocolHandler.StopListenerChannel(listenerChannelId, immediate); } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Stop_Listener_Channel)} ); } throw; } } public void StopProcessProtocol(String protocolId, bool immediate) { try { if (protocolId == null) throw new ArgumentNullException("protocolId"); ProcessProtocolHandler protocolHandler = null; lock (this) { // lookup and remove protocol handler protocolHandler = _protocolHandlers[protocolId] as ProcessProtocolHandler; if (protocolHandler != null) { _protocolHandlers.Remove(protocolId); } } if (protocolHandler != null) { protocolHandler.StopProtocol(immediate); } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Stop_Process_Prot)} ); } throw; } } // // IAppDomainProtocolManager // // starts app domain protocol handler on demand (called by process protocol handler public void StartAppDomainProtocolListenerChannel(String appId, String protocolId, IListenerChannelCallback listenerChannelCallback) { try { if (appId == null) throw new ArgumentNullException("appId"); if (protocolId == null) throw new ArgumentNullException("protocolId"); ISAPIApplicationHost appHost = CreateAppHost(appId, null); // get app domain protocol handler type from config Type handlerType = GetAppDomainProtocolHandlerType(protocolId); AppDomainProtocolHandler handler = null; LockableAppDomainContext ac = _appManager.GetLockableAppDomainContext(appId); lock (ac) { HostingEnvironmentParameters hostingParameters = new HostingEnvironmentParameters(); hostingParameters.HostingFlags = HostingEnvironmentFlags.ThrowHostingInitErrors; PreloadApplicationIfRequired(appId, appHost, hostingParameters, ac); // call app manager to create the handler handler = (AppDomainProtocolHandler)_appManager.CreateObjectInternal( appId, handlerType, appHost, false /*failIfExists*/, hostingParameters); // create a shim object that we can use for proxy unwrapping ListenerAdapterDispatchShim shim = (ListenerAdapterDispatchShim) _appManager.CreateObjectInternal( appId, typeof(ListenerAdapterDispatchShim), appHost, false /*failIfExists*/, hostingParameters); if (null != shim) { shim.StartListenerChannel(handler, listenerChannelCallback); // remove the shim ((IRegisteredObject)shim).Stop(true); } else { throw new HttpException(SR.GetString(SR.Failure_Create_Listener_Shim)); } } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Start_AppDomain_Listener)} ); } throw; } } public void StopAppDomainProtocolListenerChannel(String appId, String protocolId, int listenerChannelId, bool immediate) { try { if (appId == null) throw new ArgumentNullException("appId"); if (protocolId == null) throw new ArgumentNullException("protocolId"); // get app domaoin protocol handler type from config Type handlerType = GetAppDomainProtocolHandlerType(protocolId); AppDomainProtocolHandler handler = null; LockableAppDomainContext ac = _appManager.GetLockableAppDomainContext(appId); lock (ac) { // call app manager to create the handler handler = (AppDomainProtocolHandler)_appManager.GetObject(appId, handlerType); } // stop the listenerChannel if (handler != null) { handler.StopListenerChannel(listenerChannelId, immediate); } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Stop_AppDomain_Listener)} ); } throw; } } public void StopAppDomainProtocol(String appId, String protocolId, bool immediate) { try { if (appId == null) throw new ArgumentNullException("appId"); if (protocolId == null) throw new ArgumentNullException("protocolId"); // get app domaoin protocol handler type from config Type handlerType = GetAppDomainProtocolHandlerType(protocolId); AppDomainProtocolHandler handler = null; LockableAppDomainContext ac = _appManager.GetLockableAppDomainContext(appId); lock (ac) { // call app manager to create the handler handler = (AppDomainProtocolHandler)_appManager.GetObject(appId, handlerType); } // stop protocol if (handler != null) { handler.StopProtocol(immediate); } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Stop_AppDomain_Protocol)} ); } throw; } } public void StartApplication(String appId, String appPath, out Object runtimeInterface) { try { if (appId == null) throw new ArgumentNullException("appId"); if (appPath == null) throw new ArgumentNullException("appPath"); Debug.Assert(_functions != null, "_functions != null"); runtimeInterface = null; PipelineRuntime runtime = null; // // Fill app a Dictionary with 'binding rules' -- name value string pairs // for app domain creation // // if (appPath[0] == '.') { System.IO.FileInfo file = new System.IO.FileInfo(appPath); appPath = file.FullName; } if (!StringUtil.StringEndsWith(appPath, '\\')) { appPath = appPath + "\\"; } // Create new app host of a consistent type IApplicationHost appHost = CreateAppHost(appId, appPath); // // Create the AppDomain and a registered object in it // LockableAppDomainContext ac = _appManager.GetLockableAppDomainContext(appId); lock (ac) { // #1 WOS 1690249: ASP.Net v2.0: ASP.NET stress: 2nd chance exception: Attempted to access an unloaded AppDomain. // if an old AppDomain exists with a PipelineRuntime, remove it from // AppManager._appDomains so that a new AppDomain will be created // #2 WOS 1977425: ASP.NET apps continue recycling after touching machine.config once - this used to initiate shutdown, // but that can cause us to recycle the app repeatedly if we initiate shutdown before IIS initiates shutdown of the // previous app. _appManager.RemoveFromTableIfRuntimeExists(appId, typeof(PipelineRuntime)); // Preload (if required) the App Domain before letting the first request to be processed PreloadApplicationIfRequired(appId, appHost, null, ac); try { runtime = (PipelineRuntime)_appManager.CreateObjectInternal( appId, typeof(PipelineRuntime), appHost, true /* failIfExists */, null /* default */ ); } catch (AppDomainUnloadedException) { // munch it so we can retry again } if (null != runtime) { runtime.SetThisAppDomainsIsapiAppId(appId); runtime.StartProcessing(); runtimeInterface = new ObjectHandle(runtime); } } } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Start_Integrated_App)} ); } throw; } } public void ShutdownApplication(String appId) { try { // call into app manager _appManager.ShutdownApplication(appId); } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Stop_Integrated_App)} ); } throw; } } public void Shutdown() { try { // collect all protocols under lock ArrayList protocolList = new ArrayList(); int refCount = 0; lock (this) { // lookup protocol handler foreach (DictionaryEntry e in _protocolHandlers) { protocolList.Add(e.Value); } _protocolHandlers = new Hashtable(); } // stop all process protocols outside of lock foreach (ProcessProtocolHandler p in protocolList) { p.StopProtocol(true); } // call into app manager to shutdown _appManager.ShutdownAll(); // SupportFunctions interface provided by native layer // must be released now. // Otherwise the release of the COM object will have // to wait for GC. Native layer assumes that after // returning from Shutdown there is no reference // to the native objects from ProcessHost. // do { refCount = Marshal.ReleaseComObject( _functions ); } while( refCount != 0 ); } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_Shutdown_ProcessHost), e.ToString()} ); } throw; } } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", Justification = "See comment for why we're calling GC.Collect.")] IProcessResumeCallback IProcessSuspendListener.Suspend() { object resumeState = _appManager.SuspendAllApplications(); Action customRuntimeResumeCallback = _customRuntimeManager.SuspendAllCustomRuntimes(); IProcessResumeCallback callback = new SimpleProcessResumeCallbackDispatcher(() => { _appManager.ResumeAllApplications(resumeState); if (customRuntimeResumeCallback != null) { customRuntimeResumeCallback(); } }); // Per CLR team's suggestion, we perform one final GC to try to free // any pages that can be reclaimed. Ideally we would do this in the // unmanaged layer, but the ICLRGCManager is unavailable to us at the // time we would need to perform a collection. So we'll do it here // instead. GC.Collect(); return callback; } ICustomRuntimeRegistrationToken ICustomRuntimeManager.Register(ICustomRuntime customRuntime) { Debug.Assert(customRuntime != null); return _customRuntimeManager.Register(customRuntime); } public void EnumerateAppDomains( out IAppDomainInfoEnum appDomainInfoEnum ) { try { ApplicationManager appManager = ApplicationManager.GetApplicationManager(); AppDomainInfo [] infos; infos = appManager.GetAppDomainInfos(); appDomainInfoEnum = new AppDomainInfoEnum(infos); } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_AppDomain_Enum)} ); } throw; } } // IProcessHostIdleAndHealthCheck interface implementation public bool IsIdle() { bool result = false; try { result = _appManager.IsIdle(); } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_PMH_Idle)} ); } throw; } return result; } public void Ping(IProcessPingCallback callback) { try { if (callback != null) _appManager.Ping(callback); } catch (Exception e) { using (new ProcessImpersonationContext()) { Misc.ReportUnhandledException(e, new string[] { SR.GetString(SR.Failure_PMH_Ping)} ); } throw; } } // Users cannot provide any call stack that eventually leads to this method, as it will fail at some // point with a NullReferenceException. The ASP.NET runtime is the only entity that can call this // without something failing, and those call sites are safe. [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "See comment above.")] private ISAPIApplicationHost CreateAppHost(string appId, string appPath) { // // if we have a null physical path, we need // to use the PMH to resolve it // if (String.IsNullOrEmpty(appPath)) { string virtualPath; string physicalPath; string siteName; string siteID; _functions.GetApplicationProperties( appId, out virtualPath, out physicalPath, out siteName, out siteID); // // make sure physical app path ends with '\\' and virtual does not // if (!StringUtil.StringEndsWith(physicalPath, '\\')) { physicalPath = physicalPath + "\\"; } Debug.Assert( !String.IsNullOrEmpty(physicalPath), "!String.IsNullOrEmpty(physicalPath)"); appPath = physicalPath; } // // Create a new application host // This needs to be a coherent type across all // protocol types so that we get a consistent // environment regardless of which protocol initializes first // ISAPIApplicationHost appHost = new ISAPIApplicationHost( appId, appPath, false, /* validatePhysicalPath */ _functions ); return appHost; } public void SetApplicationPreloadUtil(IApplicationPreloadUtil applicationPreloadUtil) { // Do not allow setting PreloadUtil again if it has already has been set if (_preloadUtil != null) { throw new InvalidOperationException(SR.GetString(SR.Failure_ApplicationPreloadUtil_Already_Set)); } _preloadUtil = applicationPreloadUtil; } public void SetApplicationPreloadState(string context, string appId, bool enabled) { // Check params if (String.IsNullOrEmpty(context)) { throw ExceptionUtil.ParameterNullOrEmpty("context"); } if (String.IsNullOrEmpty(appId)) { throw ExceptionUtil.ParameterNullOrEmpty("appId"); } // _preloadUtil must be not null if we have an application preload enabled if (enabled && _preloadUtil == null) { throw new ArgumentException(SR.GetString(SR.Invalid_Enabled_Preload_Parameter), "enabled"); } LockableAppDomainContext ac = _appManager.GetLockableAppDomainContext(appId); lock (ac) { ac.PreloadContext = context; if (enabled) { PreloadApplicationIfRequired(appId, null, null, ac); } } } internal static void PreloadApplicationIfNotShuttingdown (string appId, LockableAppDomainContext ac) { // If GL_STOP_LISTENING wasn't triggered, the reset is likely due to a configuration change. if (ProcessHost.DefaultHost != null && !UnsafeIISMethods.MgdIsAppPoolShuttingDown()) { // Start the new app on another thread instead of hijacking the current app unloading thread ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object o) { lock (ac) { try { // NOTE: we don't know what HostingEnvironmentParameters were passed to our previous application instance // so we pass null (default for HTTP activation). We could have cached it in ApplicationContext if needed ProcessHost.DefaultHost.PreloadApplicationIfRequired(appId, null, null, ac); } catch (Exception e) { ProcessHost.DefaultHost.ReportApplicationPreloadFailureWithAssert( ac.PreloadContext, HResults.E_FAIL, Misc.FormatExceptionMessage( e, new string[] { SR.GetString(SR.Failure_Preload_Application_Initialization)})); } } })); } } // New for Dev10. // creates a new AppDomain, preloads and calls user code in it internal void PreloadApplicationIfRequired( string appId, IApplicationHost appHostParameter, HostingEnvironmentParameters hostingParameters, LockableAppDomainContext ac) { // NOTE1: Must never be called under lock (_appManager) // NOTE2: Must always be called under lock (ac) // We only need to preload if auto start is enabled and we have not already preloaded (i.e. HostingEnvironment doesn't exist) if (_preloadUtil == null || ac.PreloadContext == null || ac.HostEnv != null) { return; } // Get and verify the preload parameters string preloadObjTypeName; string[] paramsForStartupObj; bool stillEnabled; GetApplicationPreloadInfoWithAssert(ac.PreloadContext, out stillEnabled, out preloadObjTypeName, out paramsForStartupObj); // Dev10: 782385 ASP.NET autostart implementation should be tolerant of empty string for the provider type if (!stillEnabled || String.IsNullOrEmpty(preloadObjTypeName)) { return; } // Ready to load the App Domain if (_preloadingThrottle != null) { // Throttle the number of simultaneously created appdomains _preloadingThrottle.WaitOne(); } try { // Create the app-host and start a new App Domain IApplicationHost appHost = (appHostParameter == null) ? CreateAppHost(appId, null) : appHostParameter; // call app manager to create the PreloadHost PreloadHost preloadHostObj = (PreloadHost)_appManager.CreateObjectInternal( appId, typeof(PreloadHost), appHost, true /*failIfExists*/, hostingParameters); // Dev10 858421: File sharing violations on config files cause unnecessary process shutdown in autostart mode // // There are race conditions between whoever modifies the config files // and the application config system that reads from the config files // These file sharing violation lead to random application initialization failures // Service auto-start mode is more vulnerable to these sharing violations because // it starts a new app domain as soon as the file change notification is received // and when an error occurs IIS recycles the whole app pool rather than a particular app // // In most cases that we see in stress the inner most exception is System.IO.IOException // so if we see this exception we will give it another try before // reporting the errors to IIS and recycling the process // // Check for I/O exceptions during initialization and retry one time Exception appInitEx = preloadHostObj.InitializationException; if (GetInnerMostException(appInitEx) is IOException) { try { // prevent ApplicationManager.HostingEnvironmentShutdownInitiated from attempting to preload again ac.RetryingPreload = true; // shutdown old hosting environment ac.HostEnv.InitiateShutdownInternal(); } finally { ac.RetryingPreload = false; } // Create the app-host and start a new App Domain appHost = (appHostParameter == null) ? CreateAppHost(appId, null) : appHostParameter; // call app manager to create the PreloadHost preloadHostObj = (PreloadHost)_appManager.CreateObjectInternal( appId, typeof(PreloadHost), appHost, true /*failIfExists*/, hostingParameters); appInitEx = preloadHostObj.InitializationException; } // Check again for initialization exception and tell IIS to recycle the process if (appInitEx != null) { ReportApplicationPreloadFailureWithAssert( ac.PreloadContext, HResults.E_FAIL, Misc.FormatExceptionMessage( appInitEx, new string[] { SR.GetString(SR.Failure_Preload_Application_Initialization)} )); // we must throw if preload fails because we cannot allow the normal // startup path to continue and attempt to create a HostingEnvironment throw appInitEx; } // Call preload code in the App Domain try { preloadHostObj.CreateIProcessHostPreloadClientInstanceAndCallPreload(preloadObjTypeName, paramsForStartupObj); } catch (Exception e) { // report errors ReportApplicationPreloadFailureWithAssert( ac.PreloadContext, HResults.E_FAIL, Misc.FormatExceptionMessage( e, new string[] { SR.GetString(SR.Failure_Calling_Preload_Provider)} ).ToString()); throw; } } finally { if (_preloadingThrottle != null) { _preloadingThrottle.Release(); } } } private static Exception GetInnerMostException(Exception e) { if (e == null) { return null; } while (e.InnerException != null) { e = e.InnerException; } return e; } [PermissionSet(SecurityAction.Assert, Unrestricted = true)] private void GetApplicationPreloadInfoWithAssert( string context, out bool enabled, out string startupObjType, out string[] parametersForStartupObj) { _preloadUtil.GetApplicationPreloadInfo(context, out enabled, out startupObjType, out parametersForStartupObj); } [PermissionSet(SecurityAction.Assert, Unrestricted = true)] private void ReportApplicationPreloadFailureWithAssert(string context, int errorCode, string errorMessage) { _preloadUtil.ReportApplicationPreloadFailure(context, errorCode, errorMessage); } private sealed class SimpleProcessResumeCallbackDispatcher : IProcessResumeCallback { private readonly Action _callback; public SimpleProcessResumeCallbackDispatcher(Action callback) { Debug.Assert(callback != null); _callback = callback; } public void Resume() { _callback(); } } // The methods below are for propagating error information to ApplicationManager // so that we can display a YSOD when the application starts. We rely on the // fact that webengine4.dll will immediately call IProcessHost.StartApplication // if GetCustomLoader returns null, so TLS is appropriate. internal static ExceptionDispatchInfo GetExistingCustomLoaderFailureAndClear(string appId) { var copiedError = _customLoaderStartupError; if (String.Equals(copiedError.Key, appId, StringComparison.OrdinalIgnoreCase)) { _customLoaderStartupError = default(KeyValuePair); return copiedError.Value; } else { return null; } } private static void SetCustomLoaderFailure(string appId, ExceptionDispatchInfo error) { _customLoaderStartupError = new KeyValuePair(appId, error); } IObjectHandle IProcessHostLite.GetCustomLoader(string appId, string appConfigPath, out IProcessHostSupportFunctions supportFunctions, out AppDomain newlyCreatedAppDomain) { supportFunctions = null; newlyCreatedAppDomain = null; CustomLoaderHelperFunctions helperFunctions = new CustomLoaderHelperFunctions(_functions, appId); string appVirtualPath = helperFunctions.AppVirtualPath; try { string customLoaderAssemblyPhysicalPath = helperFunctions.MapPath("bin/AspNet.Loader.dll"); if (!File.Exists(customLoaderAssemblyPhysicalPath)) { return null; // no custom loader is in use; fall back to legacy hosting logic } // Technically there is a race condition between the file existence check above // and the assembly load that will take place shortly. We won't worry too much // about this since the window is very short and the application shouldn't be // modified during this process anyway. string appRootPhysicalPath = helperFunctions.AppPhysicalPath; string webConfigPhysicalPath = helperFunctions.MapPath("Web.config"); bool webConfigFileExists = File.Exists(webConfigPhysicalPath); // The CustomLoaderHelper class is defined in System.Web.ApplicationServices.dll // so that OOB frameworks don't need to take a hardcoded System.Web.dll dependency. // There might be weird issues if we try to load a GACed System.Web.dll into the // same AppDomain as a bin-deployed System.Web.dll. supportFunctions = _functions; return CustomLoaderHelper.GetCustomLoader( helperFunctions: helperFunctions, appConfigMetabasePath: appConfigPath, configFilePath: (webConfigFileExists) ? webConfigPhysicalPath : null, customLoaderPhysicalPath: customLoaderAssemblyPhysicalPath, newlyCreatedAppDomain: out newlyCreatedAppDomain); } catch (Exception ex) { SetCustomLoaderFailure(appId, ExceptionDispatchInfo.Capture(ex)); return null; } } void IProcessHostLite.ReportCustomLoaderError(string appId, int hr, AppDomain newlyCreatedAppDomain) { try { try { // If the failure originated in managed code (GetCustomLoader), this will actually // result in the original managed exception being rethrown, which is convenient // for us. Marshal.ThrowExceptionForHR(hr); } finally { // AD wasn't unloaded by CustomLoaderHelper, so kill it here. AppDomain.Unload(newlyCreatedAppDomain); } } catch (Exception ex) { SetCustomLoaderFailure(appId, ExceptionDispatchInfo.Capture(ex)); } } // Used to extract the full message of an exception, including class name and stack trace. string IProcessHostLite.GetFullExceptionMessage(int hr, IntPtr pErrorInfo) { // If no IErrorInfo is explicitly specified, provide -1 to suppress the automated // GetErrorInfo lookup, as it may have been overwritten and might be irrelevant. Exception ex = Marshal.GetExceptionForHR(hr, (pErrorInfo != IntPtr.Zero) ? pErrorInfo : (IntPtr)(-1)); if (ex != null) { return ex.ToString(); } else { // Should never hit this case, but just in case, return a dummy value. Debug.Fail("The provided HRESULT should've represented a failure."); return String.Empty; } } private sealed class CustomLoaderHelperFunctions : ICustomLoaderHelperFunctions { private static readonly bool? _isEnabled = GetIsEnabledValueFromRegistry(); private readonly IProcessHostSupportFunctions _supportFunctions; internal CustomLoaderHelperFunctions(IProcessHostSupportFunctions supportFunctions, string appId) { _supportFunctions = supportFunctions; string appVirtualPath, appPhysicalPath, siteName, siteId; _supportFunctions.GetApplicationProperties(appId, out appVirtualPath, out appPhysicalPath, out siteName, out siteId); AppId = appId; AppVirtualPath = appVirtualPath; AppPhysicalPath = appPhysicalPath; } public string AppId { get; private set; } public string AppPhysicalPath { get; private set; } public string AppVirtualPath { get; private set; } public bool? CustomLoaderIsEnabled { get { return _isEnabled; } } // true = always enabled, false = always disabled, null = check trust level private static bool? GetIsEnabledValueFromRegistry() { bool? isEnabled = null; try { int valueInRegistry = (int)Misc.GetAspNetRegValue(null, "CustomLoaderEnabled", -1); if (valueInRegistry == 1) { // explicitly enabled isEnabled = true; } else if (valueInRegistry == 0) { // explicitly disabled isEnabled = false; } } catch { // We don't care about errors, as we'll just query fallback logic. } return isEnabled; } public string GetTrustLevel(string appConfigMetabasePath) { object retVal; int hr = UnsafeIISMethods.MgdGetConfigProperty(appConfigMetabasePath, "system.web/trust", "level", out retVal); Marshal.ThrowExceptionForHR(hr); return (string)retVal; } public string MapPath(string relativePath) { return _supportFunctions.MapPathInternal(AppId, AppVirtualPath, relativePath); } } } internal sealed class ListenerAdapterDispatchShim : MarshalByRefObject, IRegisteredObject { void IRegisteredObject.Stop(bool immediate) { HostingEnvironment.UnregisterObject(this); } // this should run in an Hosted app domain (not in the default domain) internal void StartListenerChannel( AppDomainProtocolHandler handler, IListenerChannelCallback listenerCallback ) { Debug.Assert( HostingEnvironment.IsHosted, "HostingEnvironment.IsHosted" ); Debug.Assert( null != handler, "null != handler" ); IListenerChannelCallback unwrappedProxy = MarshalComProxy(listenerCallback); Debug.Assert(null != unwrappedProxy, "null != unwrappedProxy"); if (null != unwrappedProxy && null != handler) { handler.StartListenerChannel(unwrappedProxy); } } internal IListenerChannelCallback MarshalComProxy(IListenerChannelCallback defaultDomainCallback) { IListenerChannelCallback localProxy = null; // get the underlying COM object IntPtr pUnk = Marshal.GetIUnknownForObject(defaultDomainCallback); // this object isn't a COM object if (IntPtr.Zero == pUnk) { return null; } IntPtr ppv = IntPtr.Zero; try { // QI it for the interface Guid g = typeof(IListenerChannelCallback).GUID; int hresult = Marshal.QueryInterface(pUnk, ref g, out ppv); if (hresult < 0) { Marshal.ThrowExceptionForHR(hresult); } // create a RCW we can hold onto in this domain // this bumps the ref count so we can drop our refs on the raw interfaces localProxy = (IListenerChannelCallback)Marshal.GetObjectForIUnknown(ppv); } finally { // drop our explicit refs and keep the managed instance if (IntPtr.Zero != ppv) { Marshal.Release(ppv); } if (IntPtr.Zero != pUnk) { Marshal.Release(pUnk); } } return localProxy; } } }