PageRenderTime 10ms CodeModel.GetById 1ms app.highlight 5ms RepoModel.GetById 2ms app.codeStats 0ms

/IronPython_Main/Runtime/Microsoft.Dynamic/Hosting/Shell/Remote/ConsoleRestartManager.cs

#
C# | 223 lines | 116 code | 31 blank | 76 comment | 18 complexity | ddf76683cb5db72ed6ad33644177eb65 MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Apache License, Version 2.0, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Apache License, Version 2.0.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16#if !SILVERLIGHT // Remoting
 17
 18using System;
 19using System.Collections.Generic;
 20using System.Diagnostics;
 21using System.Threading;
 22
 23namespace Microsoft.Scripting.Hosting.Shell.Remote {
 24    /// <summary>
 25    /// Supports detecting the remote runtime being killed, and starting up a new one.
 26    /// 
 27    /// Threading model:
 28    /// 
 29    /// ConsoleRestartManager creates a separate thread on which to create and execute the consoles. 
 30    /// There are usually atleast three threads involved:
 31    /// 
 32    /// 1. Main app thread: Instantiates ConsoleRestartManager and accesses its APIs. This thread has to stay 
 33    ///    responsive to user input and so the ConsoleRestartManager APIs cannot be long-running or blocking.
 34    ///    Since the remote runtime process can terminate asynchronously, the current RemoteConsoleHost can 
 35    ///    change at any time (if auto-restart is enabled). The app should typically not care which instance of 
 36    ///    RemoteConsoleHost is currently being used. The flowchart of this thread is:
 37    ///        Create ConsoleRestartManager
 38    ///        ConsoleRestartManager.Start
 39    ///        Loop:
 40    ///            Respond to user input | Send user input to console for execution | BreakExecution | RestartConsole | GetMemberNames
 41    ///        ConsoleRestartManager.Terminate
 42    ///    TODO: Currently, BreakExecution and GetMemberNames are called by the main thread synchronously.
 43    ///    Since they execute code in the remote runtime, they could take arbitrarily long. We should change
 44    ///    this so that the main app thread can never be blocked indefinitely.
 45    ///
 46    /// 2. Console thread: Dedicated thread for creating RemoteConsoleHosts and executing code (which could
 47    ///    take a long time or block indefinitely).
 48    ///        Wait for ConsoleRestartManager.Start to be called
 49    ///        Loop:
 50    ///            Create RemoteConsoleHost
 51    ///            Wait for signal for:
 52    ///                 Execute code | RestartConsole | Process.Exited
 53    ///
 54    /// 3. CompletionPort async callbacks:
 55    ///        Process.Exited | Process.OutputDataReceived | Process.ErrorDataReceived
 56    /// 
 57    /// 4. Finalizer thred
 58    ///    Some objects may have a Finalize method (which possibly calls Dispose). Not many (if any) types
 59    ///    should have a Finalize method.
 60    /// 
 61    /// </summary>
 62    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors")] // TODO: This is public only because the test (RemoteConsole.py) needs it to be so. The test should be rewritten
 63    public abstract class ConsoleRestartManager {
 64        private RemoteConsoleHost _remoteConsoleHost;
 65        private Thread _consoleThread;
 66        private bool _exitOnNormalExit;
 67        private bool _terminating;
 68
 69        /// <summary>
 70        /// Accessing _remoteConsoleHost from a thread other than console thread can result in race.
 71        /// If _remoteConsoleHost is accessed while holding _accessLock, it is guaranteed to be
 72        /// null or non-disposed.
 73        /// </summary>
 74        private object _accessLock = new object();
 75
 76        /// <summary>
 77        /// This is created on the "creating thread", and goes on standby. Start needs to be called for activation.
 78        /// </summary>
 79        /// <param name="exitOnNormalExit">A host might want one of two behaviors:
 80        /// 1. Keep the REPL loop alive indefinitely, even when a specific instance of the RemoteConsoleHost terminates normally
 81        /// 2. Close the REPL loop when an instance of the RemoteConsoleHost terminates normally, and restart the loop
 82        ///    only if the instance terminates abnormally.</param>
 83        public ConsoleRestartManager(bool exitOnNormalExit) {
 84            _exitOnNormalExit = exitOnNormalExit;
 85            _consoleThread = new Thread(Run);
 86            _consoleThread.Name = "Console thread";
 87        }
 88
 89        protected object AccessLock { get { return _accessLock; } }
 90
 91        public Thread ConsoleThread { get { return _consoleThread; } }
 92
 93        protected RemoteConsoleHost CurrentConsoleHost { get { return _remoteConsoleHost; } }
 94
 95        public abstract RemoteConsoleHost CreateRemoteConsoleHost();
 96
 97        /// <summary>
 98        /// Needs to be called for activation.
 99        /// </summary>
100        public void Start() {
101            Debug.Assert(Thread.CurrentThread != _consoleThread);
102
103            if (_consoleThread.IsAlive) {
104                throw new InvalidOperationException("Console thread is already running.");
105            }
106            _consoleThread.Start();
107        }
108
109        private void Run() {
110#if DEBUG
111            try {
112                RunWorker();
113            } catch (Exception e) {
114                Debug.Assert(false, "Unhandled exception on console thread:\n\n" + e.ToString());
115            }
116#else
117            RunWorker();
118#endif
119        }
120
121        private void RunWorker() {
122            Debug.Assert(Thread.CurrentThread == _consoleThread);
123
124            while (true) {
125                RemoteConsoleHost remoteConsoleHost = CreateRemoteConsoleHost();
126
127                // Reading _terminating and setting of _remoteConsoleHost should be done atomically. 
128                // Terminate() does the reverse operation (setting _terminating reading _remoteConsoleHost) atomically
129                lock (_accessLock) {
130                    if (_terminating) {
131                        return;
132                    }
133
134                    _remoteConsoleHost = remoteConsoleHost;
135                }
136
137                try {
138                    try {
139                        int exitCode = remoteConsoleHost.Run(new string[0]);
140
141                        if (_exitOnNormalExit && exitCode == 0) {
142                            return;
143                        }
144                    } catch (RemoteRuntimeStartupException) {
145                    }
146                } finally {
147                    lock (_accessLock) {
148                        remoteConsoleHost.Dispose();
149                        _remoteConsoleHost = null;
150                    }
151                }
152            }
153        }
154
155        // TODO: We have to catch all exceptions as we are executing user code in the remote runtime, and we cannot control what 
156        // exception it may throw. This could be fixed if we built our own remoting channel which returned an error code
157        // instead of propagating exceptions back from the remote runtime.
158        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
159        public IList<string> GetMemberNames(string expression) {
160            Debug.Assert(Thread.CurrentThread != _consoleThread);
161
162            lock (_accessLock) {
163                if (_remoteConsoleHost == null) {
164                    return null;
165                }
166
167                ScriptEngine engine = _remoteConsoleHost.Engine;
168                try {
169                    ScriptScope scope = _remoteConsoleHost.ScriptScope;
170                    ObjectOperations operations = engine.CreateOperations(scope);
171                    ScriptSource src = engine.CreateScriptSourceFromString(expression, SourceCodeKind.Expression);
172                    return operations.GetMemberNames(src.ExecuteAndWrap(scope));
173                } catch {
174                    return null;
175                }
176            }
177        }
178
179        public void BreakExecution() {
180            Debug.Assert(Thread.CurrentThread != _consoleThread);
181
182            lock (_accessLock) {
183                if (_remoteConsoleHost == null) {
184                    return;
185                }
186
187                try {
188                    _remoteConsoleHost.AbortCommand();
189                } catch (System.Runtime.Remoting.RemotingException) {
190                    // The remote runtime may be terminated or non-responsive
191                }
192            }
193        }
194
195        public void RestartConsole() {
196            Debug.Assert(Thread.CurrentThread != _consoleThread);
197
198            lock (_accessLock) {
199                if (_remoteConsoleHost == null) {
200                    return;
201                }
202
203                _remoteConsoleHost.Terminate(0);
204            }
205        }
206
207        /// <summary>
208        /// Request (from another thread) the console REPL loop to terminate
209        /// </summary>
210        public void Terminate() {
211            Debug.Assert(Thread.CurrentThread != _consoleThread);
212
213            lock (_accessLock) {
214                _terminating = true;
215                _remoteConsoleHost.Terminate(0);
216            }
217            
218            _consoleThread.Join();
219        }
220    }
221}
222
223#endif