private async Task SubmitSnippetPostFeedback(string snippetId, string connectionId, long startTimestamp) { SnippetResult result; try { result = await hostConnector.RunSnippetAsync(snippetId); } catch (Exception ex) { result = new SnippetResult() { status = SnippetStatus.InitializationError, exception = ex.Message }; } var hubContext = GlobalHost.ConnectionManager.GetHubContext <ResultHub>(); result.totalTime = StopwatchExtensions.GetTimestampMillis() - startTimestamp; hubContext.Clients.Client(connectionId).SendResult(connectionId, snippetId, result, result.status.ToHealth().ToColor()); }
public async Task <ActionResult> SubmitRequest(string snippetId, string connectionId) { long startTimestamp = StopwatchExtensions.GetTimestampMillis(); if (String.IsNullOrEmpty(snippetId)) { return(new HttpStatusCodeResult(HttpStatusCode.BadRequest)); } if (connectionId != null) { Task.Run(() => SubmitSnippetPostFeedback(snippetId, connectionId, startTimestamp)).FireAndForget(); return(new HttpStatusCodeResult(HttpStatusCode.Accepted)); } else { SnippetResult result = await hostConnector.RunSnippetAsync(snippetId); result.totalTime = StopwatchExtensions.GetTimestampMillis() - startTimestamp; Response.StatusCode = (int)HttpStatusCode.OK; return(Json(new { connectionId = connectionId, message = result, newStatus = result.status.ToHealth().ToColor() })); } }
// This kind of customization (provide a custom HostSecurityManager, with custom DomainPolicy etc.) // is obsolete in .NET4. Setting the PermissionSet on the AppDomain (as we do here) is the right thing to do // See http://msdn.microsoft.com/en-us/library/ee191568(VS.100).aspx // and http://msdn.microsoft.com/en-us/library/bb763046(v=vs.100).aspx //public override HostSecurityManager HostSecurityManager { // get { return new SimpleHostSecurityManager(); } //} //[SecuritySafeCritical] internal SnippetResult InternalRun(AppDomain appDomain, byte[] assembly, string mainTypeName, string methodName, bool runningInSandbox) { // Here we already are on the new domain SnippetResult result = new SnippetResult(); try { // Use this "trick" to go through the standard loader path. // This MAY be something we want, or something to avoid // When loading the assembly, we need at least FileIOPermission. // Calling it with a full-trust stack. TODO: only what is needed (new PermissionSet(PermissionState.Unrestricted)).Assert(); var clientAssembly = appDomain.Load(assembly); //var clientAssembly = Assembly.Load(assembly, null, SecurityContextSource.CurrentAppDomain); //AssemblyName an = AssemblyName.GetAssemblyName(assemblyFileName); //var clientAssembly = appDomain.Load(an); CodeAccessPermission.RevertAssert(); //Load the MethodInfo for a method in the new Assembly. This might be a method you know, or //you can use Assembly.EntryPoint to get to the main function in an executable. var type = clientAssembly.GetTypes().Where(t => t.Name == mainTypeName || t.FullName == mainTypeName).SingleOrDefault(); if (type == null) { result.status = SnippetStatus.InitializationError; return(result); } var monitorField = type.GetField(Pumpkin.Monitor.MonitorFieldName, BindingFlags.Public | BindingFlags.Static); if (monitorField != null) { result.output = new List <string>(); var monitor = new Pumpkin.Monitor(result.output); monitorField.SetValue(null, monitor); } // To allow code to invoke any nonpublic member: Your code must be granted ReflectionPermission with the ReflectionPermissionFlag.MemberAccess flag // To allow code to invoke any nonpublic member, as long as the grant set of the assembly that contains the invoked member is // the same as, or a subset of, the grant set of the assembly that contains the invoking code: // Your code must be granted ReflectionPermission with the ReflectionPermissionFlag.RestrictedMemberAccess flag. // See http://msdn.microsoft.com/en-us/library/stfy7tfc%28v=vs.110%29.aspx var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; if (runningInSandbox) { bindingFlags = BindingFlags.Public | BindingFlags.Static; } var target = type.GetMethod(methodName, bindingFlags); if (target == null) { result.status = SnippetStatus.InitializationError; return(result); } //Now invoke the method. target.Invoke(null, null); result.status = SnippetStatus.Success; } catch (TargetInvocationException ex) { result.exception = (ex.InnerException == null ? ex.Message : ex.InnerException.Message); // When we print informations from a SecurityException extra information can be printed if we are //calling it with a full-trust stack. (new PermissionSet(PermissionState.Unrestricted)).Assert(); System.Diagnostics.Debug.WriteLine("Exception caught: =================" + result.exception.ToString() + "==================================="); CodeAccessPermission.RevertAssert(); result.status = SnippetStatus.ExecutionError; } catch (Exception ex) { (new PermissionSet(PermissionState.Unrestricted)).Assert(); System.Diagnostics.Debug.WriteLine("Exception caught: =================" + ex.ToString() + "==================================="); CodeAccessPermission.RevertAssert(); result.exception = ex.Message; result.status = SnippetStatus.ExecutionError; } return(result); }
private Thread CreateDomainThread(int threadIndex) { System.Diagnostics.Debug.WriteLine("CreateDomainThread: " + threadIndex); var myPoolDomain = poolDomains[threadIndex]; var thread = new Thread(() => { Interlocked.Increment(ref numberOfThreadsInPool); // Here we enforce the "one domain, one thread" relationship try { var appDomain = AppDomainHelpers.CreateSandbox("Host Sandbox"); var manager = (SimpleHostAppDomainManager)appDomain.DomainManager; lock (poolLock) { myPoolDomain.domainId = appDomain.Id; } while (!snippetsQueue.IsCompleted) { defaultDomainManager.ResetContextFor(myPoolDomain); // And here is were we "rent" one AppDomain and use it to run a snippet SnippetInfo snippetToRun = null; try { snippetToRun = snippetsQueue.Take(); } catch (InvalidOperationException) { // Someone called "complete". No more snippets in this process. // We want to exit the pool. return; } if (snippetToRun != null) { System.Diagnostics.Debug.WriteLine("Starting snippet '" + snippetToRun.methodName + "' in domain " + myPoolDomain.domainId); SnippetResult result = new SnippetResult(); bool recycleDomain = false; try { Interlocked.Increment(ref myPoolDomain.numberOfUsages); // Record when we started long startTimestamp = StopwatchExtensions.GetTimestampMillis(); System.Diagnostics.Debug.WriteLine("Starting execution at " + startTimestamp); Thread.VolatileWrite(ref myPoolDomain.timeOfSubmission, startTimestamp); // Thread transitions into the AppDomain // This function DOES NOT throw result = manager.InternalRun(appDomain, snippetToRun.assemblyFile, snippetToRun.mainTypeName, snippetToRun.methodName, true); // ...back to the main AppDomain Debug.Assert(AppDomain.CurrentDomain.IsDefaultAppDomain()); long currentTime = StopwatchExtensions.GetTimestampMillis(); result.executionTime = currentTime - Thread.VolatileRead(ref myPoolDomain.timeOfSubmission); // Flag it as "not executing" Thread.VolatileWrite(ref myPoolDomain.timeOfSubmission, 0); } catch (ThreadAbortException ex) { // Someone called abort on us. result.exception = ex.Message; // It may be possible to use this domain again, otherwise we will recycle if (Object.Equals(ex.ExceptionState, timeoutAbortToken)) { // The abort was issued by us because we timed out System.Diagnostics.Debug.WriteLine("Thread Abort due to timeout"); result.status = SnippetStatus.Timeout; } else if (Object.Equals(ex.ExceptionState, threadsExaustedAbortToken)) { System.Diagnostics.Debug.WriteLine("Thread Abort due to thread exaustion"); result.status = SnippetStatus.ResourceError; } else { // If it wasn't us, give us time to record the result; we will recycle (unload) the domain System.Diagnostics.Debug.WriteLine("Thread Abort due to external factors"); result.status = SnippetStatus.CriticalError; recycleDomain = true; } // Give us time to record the result; if needed, we will recycle (unload) the domain later Thread.ResetAbort(); } catch (Exception ex) { result.exception = ex.Message; // Check if someone is misbehaving, throwing something else to "mask" the TAE if (Thread.CurrentThread.ThreadState == System.Threading.ThreadState.AbortRequested) { result.status = SnippetStatus.CriticalError; recycleDomain = true; // Give us time to record the result; we will recycle (unload) the domain Thread.ResetAbort(); } else { result.status = SnippetStatus.ExecutionError; } } // "Although C# only allows you to throw objects of type Exception and types deriving from it, // other languages don’t have any such restriction." // http://weblogs.asp.net/kennykerr/introduction-to-msil-part-5-exception-handling // It turns out this is no longer necessary // http://blogs.msdn.com/b/jmanning/archive/2005/09/16/469091.aspx // catch { } // No need to catch StackOverflowException; the Host will escalate to a (rude) domain unload // for us // TODO: check that AppDomain.DomainUnload is called anyway! // Before looping, check if we are OK; we reuse the domain only if we are not leaking int threadsInDomain = defaultDomainManager.GetThreadCount(appDomain.Id); int memoryUsage = defaultDomainManager.GetMemoryUsage(appDomain.Id); System.Diagnostics.Debug.WriteLine("============= AppDomain {0} =============", appDomain.Id); System.Diagnostics.Debug.WriteLine("Finished in: {0}", result.executionTime); System.Diagnostics.Debug.WriteLine("Status: {0}", result.status); if (result.exception != null) { System.Diagnostics.Debug.WriteLine("Exception: " + result.exception); } System.Diagnostics.Debug.WriteLine("Threads: {0}", threadsInDomain); System.Diagnostics.Debug.WriteLine("Memory: {0}", memoryUsage); System.Diagnostics.Debug.WriteLine("========================================"); if (threadsInDomain > 1) { // The snippet is leaking threads // Flag snippet as "leaking" result.status = SnippetStatus.CriticalError; System.Diagnostics.Debug.WriteLine("Leaking snippet"); recycleDomain = true; } else if (MaxReuse > 0 && myPoolDomain.numberOfUsages >= MaxReuse) { System.Diagnostics.Debug.WriteLine("Domain too old"); // Same if the domain is too old recycleDomain = true; } // Return the result to the caller if (!String.IsNullOrEmpty(snippetToRun.submissionId)) { var completion = submissionMap[snippetToRun.submissionId]; // TCS is thread-safe (can be called cross-thread) completion.TrySetResult(result); } if (recycleDomain) { System.Diagnostics.Debug.WriteLine("Recycling domain..."); RecycleDomain(threadIndex); } else { // Otherwise, ensure that what was allocated by the snippet is freed GC.Collect(); System.Diagnostics.Debug.WriteLine("MonitoringSurvivedMemorySize {0}", appDomain.MonitoringSurvivedMemorySize); System.Diagnostics.Debug.WriteLine("MonitoringTotalAllocatedMemorySize {0}", appDomain.MonitoringTotalAllocatedMemorySize); System.Diagnostics.Debug.WriteLine("MonitoringTotalProcessorTime {0}", appDomain.MonitoringTotalProcessorTime); } } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Exception caught:\n" + ex.ToString()); RecycleDomain(threadIndex); } }); thread.Name = "DomainPool thread " + threadIndex; myPoolDomain.mainThread = thread; thread.Priority = ThreadPriority.BelowNormal; return(thread); }