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