Esempio n. 1
0
        private Task <SnippetResult> DoStuffAndReturnWhenIdFinished(Guid snippetId)
        {
            // Use a TCS for a Task without a "thread"
            // http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx
            var tcs = new TaskCompletionSource <SnippetResult>();

            try {
                // Post in queue, and return immediately
                // The pool/deque will "call us back" (set the status) when finished
                var snippetData = repository.Get(snippetId);
                var snippetInfo = new SnippetInfo()
                {
                    assemblyFile = snippetData.AssemblyBytes,
                    mainTypeName = SnippetData.SnippetTypeName,
                    methodName   = SnippetData.SnippetMethodName,
                    submissionId = Guid.NewGuid().ToString()
                };

                domainPool.SubmitSnippet(snippetInfo, tcs);
            }
            catch (Exception ex) {
                tcs.TrySetException(ex);
            }
            // Return immediately, with the task we can await on
            return(tcs.Task);
        }
Esempio n. 2
0
        internal void SubmitSnippet(SnippetInfo snippetInfo, TaskCompletionSource <SnippetResult> completion)
        {
            // check if we need to re-introduce some domains in the pool
            if (Interlocked.Read(ref numberOfThreadsInPool) < NumberOfDomainsInPool)
            {
                // A sort of "double-check optimization": we enter here only if there is a shortage of threads/domains, but
                // we actually create one only if there is a slot
                int emptySlotIdx = -1;
                lock (poolLock) {
                    // find the empty slot
                    for (int i = 0; i < NumberOfDomainsInPool; ++i)
                    {
                        if (poolDomains[i] == null)
                        {
                            poolDomains[i] = new PooledDomainData();
                            emptySlotIdx   = i;
                        }
                    }
                }

                if (emptySlotIdx >= 0)
                {
                    var thread = CreateDomainThread(emptySlotIdx);
                    thread.Start();
                }
            }

            // Store the continuation _before_ adding the snippet info the the processing queue
            if (!String.IsNullOrEmpty(snippetInfo.submissionId) && completion != null)
            {
                submissionMap[snippetInfo.submissionId] = completion;
            }
            snippetsQueue.Add(snippetInfo);
        }
Esempio n. 3
0
 internal void SubmitSnippet(SnippetInfo snippetInfo)
 {
     SubmitSnippet(snippetInfo, null);
 }
Esempio n. 4
0
        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);
        }