RemoteConsoleHost.cs :  » Script » IronPython » Microsoft » Scripting » Hosting » Shell » Remote » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Script » IronPython 
IronPython » Microsoft » Scripting » Hosting » Shell » Remote » RemoteConsoleHost.cs
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

#if !SILVERLIGHT // Remoting

using System;
using System.Diagnostics;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization;
using System.Threading;

namespace Microsoft.Scripting.Hosting.Shell.Remote{
    /// <summary>
    /// ConsoleHost where the ScriptRuntime is hosted in a separate process (referred to as the remote runtime server)
    /// 
    /// The RemoteConsoleHost spawns the remote runtime server and specifies an IPC channel name to use to communicate
    /// with each other. The remote runtime server creates and initializes a ScriptRuntime and a ScriptEngine, and publishes
    /// it over the specified IPC channel at a well-known URI. Note that the RemoteConsoleHost cannot easily participate
    /// in the initialization of the ScriptEngine as classes like LanguageContext are not remotable.
    /// 
    /// The RemoteConsoleHost then starts the interactive loop and executes commands on the ScriptEngine over the remoting channel.
    /// The RemoteConsoleHost listens to stdout of the remote runtime server and echos it locally to the user.
    /// </summary>
    public abstract class RemoteConsoleHost : ConsoleHost, IDisposable {
        Process _remoteRuntimeProcess;
        internal RemoteCommandDispatcher _remoteCommandDispatcher;
        private string _channelName = RemoteConsoleHost.GetChannelName();
        private IpcChannel _clientChannel;
        private AutoResetEvent _remoteOutputReceived = new AutoResetEvent(false);
        private ScriptScope _scriptScope;

        #region Private methods

        private static string GetChannelName() {
            return "RemoteRuntime-" + Guid.NewGuid().ToString();
        }

        private ProcessStartInfo GetProcessStartInfo() {
            ProcessStartInfo processInfo = new ProcessStartInfo();
            processInfo.Arguments = RemoteRuntimeServer.RemoteRuntimeArg + " " + _channelName;
            processInfo.CreateNoWindow = true;

            // Set UseShellExecute to false to enable redirection.
            processInfo.UseShellExecute = false;

            // Redirect the standard streams. The output streams will be read asynchronously using an event handler.
            processInfo.RedirectStandardError = true;
            processInfo.RedirectStandardOutput = true;
            // The input stream can be ignored as the remote server process does not need to read any input
            processInfo.RedirectStandardInput = true;

            CustomizeRemoteRuntimeStartInfo(processInfo);
            Debug.Assert(processInfo.FileName != null);
            return processInfo;
        }

        private void StartRemoteRuntimeProcess() {
            Process process = new Process();

            process.StartInfo = GetProcessStartInfo();

            process.OutputDataReceived += new DataReceivedEventHandler(OnOutputDataReceived);
            process.ErrorDataReceived += new DataReceivedEventHandler(OnErrorDataReceived);

            process.Exited += new EventHandler(OnRemoteRuntimeExited);
            _remoteRuntimeProcess = process;

            process.Start();

            // Start the asynchronous read of the output streams.
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            // wire up exited 
            process.EnableRaisingEvents = true;

            // Wait for the output marker to know when the startup output is complete
            _remoteOutputReceived.WaitOne();

            if (process.HasExited) {
                throw new RemoteRuntimeStartupException("Remote runtime terminated during startup with exitcode " + process.ExitCode);
            }
        }

        private T GetRemoteObject<T>(string uri) {
            T result = (T)Activator.GetObject(typeof(T), "ipc://" + _channelName + "/" + uri);

            // Ensure that the remote object is responsive by calling a virtual method (which will be executed remotely)
            Debug.Assert(result.ToString() != null);

            return result;
        }

        private void InitializeRemoteScriptEngine() {
            StartRemoteRuntimeProcess();

            _remoteCommandDispatcher = GetRemoteObject<RemoteCommandDispatcher>(RemoteRuntimeServer.CommandDispatcherUri);

            _scriptScope = _remoteCommandDispatcher.ScriptScope;
            Engine = _scriptScope.Engine;

            // Register a channel for the reverse direction, when the remote runtime process wants to fire events
            // or throw an exception
            string clientChannelName = _channelName.Replace("RemoteRuntime", "RemoteConsole");
            _clientChannel = RemoteRuntimeServer.CreateChannel(clientChannelName, clientChannelName);
            ChannelServices.RegisterChannel(_clientChannel, false);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")]
        protected virtual void OnRemoteRuntimeExited(object sender, EventArgs args) {
            Debug.Assert(((Process)sender).HasExited);
            Debug.Assert(sender == _remoteRuntimeProcess || _remoteRuntimeProcess == null);

            EventHandler remoteRuntimeExited = RemoteRuntimeExited;
            if (remoteRuntimeExited != null) {
                remoteRuntimeExited(sender, args);
            }

            // StartRemoteRuntimeProcess also blocks on this event. Signal it in case the 
            // remote runtime terminates during startup itself.
            _remoteOutputReceived.Set();

            // Nudge the ConsoleHost to exit the REPL loop
            Terminate(_remoteRuntimeProcess.ExitCode);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")] // TODO: This is protected only for test code, which could be rewritten to not require this to be protected
        protected virtual void OnOutputDataReceived(object sender, DataReceivedEventArgs eventArgs) {
            if (String.IsNullOrEmpty(eventArgs.Data)) {
                return;
            }

            string output = eventArgs.Data as string;

            if (output.Contains(RemoteCommandDispatcher.OutputCompleteMarker)) {
                Debug.Assert(output == RemoteCommandDispatcher.OutputCompleteMarker);
                _remoteOutputReceived.Set();
            } else {
                ConsoleIO.WriteLine(output, Style.Out);
            }
        }

        private void OnErrorDataReceived(object sender, DataReceivedEventArgs eventArgs) {
            if (!String.IsNullOrEmpty(eventArgs.Data)) {
                ConsoleIO.WriteLine((string)eventArgs.Data, Style.Error);
            }
        }

        #endregion

        public override void Terminate(int exitCode) {
            if (CommandLine == null) {
                // Terminate may be called during startup when CommandLine has not been initialized.
                // We could fix this by initializing CommandLine before starting the remote runtime process
                return;
            }

            base.Terminate(exitCode);
        }

        protected override CommandLine CreateCommandLine() {
            return new RemoteConsoleCommandLine(_scriptScope, _remoteCommandDispatcher, _remoteOutputReceived);
        }

        public ScriptScope ScriptScope { get { return CommandLine.ScriptScope; } }
        public Process RemoteRuntimeProcess { get { return _remoteRuntimeProcess; } }

        // TODO: We have to catch all exceptions as we are executing user code in the remote runtime, and we cannot control what 
        // exception it may throw. This could be fixed if we built our own remoting channel which returned an error code
        // instead of propagating exceptions back from the remote runtime.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected override void UnhandledException(ScriptEngine engine, Exception e) {
            ((RemoteConsoleCommandLine)CommandLine).UnhandledExceptionWorker(e);
        }
        /// <summary>
        /// Called if the remote runtime process exits by itself. ie. without the remote console killing it.
        /// </summary>
        internal event EventHandler RemoteRuntimeExited;

        /// <summary>
        /// Allows the console to customize the environment variables, working directory, etc.
        /// </summary>
        /// <param name="processInfo">At the least, processInfo.FileName should be initialized</param>
        public abstract void CustomizeRemoteRuntimeStartInfo(ProcessStartInfo processInfo);

        /// <summary>
        /// Aborts the current active call to Execute by doing Thread.Abort
        /// </summary>
        /// <returns>true if a Thread.Abort was actually called. false if there is no active call to Execute</returns>
        public bool AbortCommand() {
            return _remoteCommandDispatcher.AbortCommand();
        }

        public override int Run(string[] args) {
            var runtimeSetup = CreateRuntimeSetup();
            var options = new ConsoleHostOptions();
            ConsoleHostOptionsParser = new ConsoleHostOptionsParser(options, runtimeSetup);

            try {
                ParseHostOptions(args);
            } catch (InvalidOptionException e) {
                Console.Error.WriteLine("Invalid argument: " + e.Message);
                return ExitCode = 1;
            }

            _languageOptionsParser = CreateOptionsParser();

            // Create IConsole early (with default settings) in order to be able to display startup output
            ConsoleIO = CreateConsole(null, null, new ConsoleOptions());

            InitializeRemoteScriptEngine();
            Runtime = Engine.Runtime;

            ExecuteInternal();

            return ExitCode;
        }

        #region IDisposable Members

        public virtual void Dispose(bool disposing) {
            if (!disposing) {
                // Managed fields cannot be reliably accessed during finalization since they may already have been finalized
                return;
            }

            _remoteOutputReceived.Close();

            if (_clientChannel != null) {
                ChannelServices.UnregisterChannel(_clientChannel);
                _clientChannel = null;
            }

            if (_remoteRuntimeProcess != null) {
                _remoteRuntimeProcess.Exited -= OnRemoteRuntimeExited;

                // Closing stdin is a signal to the remote runtime to exit the process.
                _remoteRuntimeProcess.StandardInput.Close();
                _remoteRuntimeProcess.WaitForExit(5000);

                if (!_remoteRuntimeProcess.HasExited) {
                    _remoteRuntimeProcess.Kill();
                    _remoteRuntimeProcess.WaitForExit();
                }

                _remoteRuntimeProcess = null;
            }
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }

    [Serializable]
    public class RemoteRuntimeStartupException : Exception {
        public RemoteRuntimeStartupException() { }

        public RemoteRuntimeStartupException(string message)
            : base(message) {
        }

        public RemoteRuntimeStartupException(string message, Exception innerException)
            : base(message, innerException) {
        }

        protected RemoteRuntimeStartupException(SerializationInfo info, StreamingContext context)
            : base(info, context) {
        }
    }
}

#endif
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.