Пример #1
0
        /// <summary>
        /// Starts a JSON-RPC thread to handle client calls.
        /// </summary>
        /// <param name="threadsDoneEvent">The event to set when all RPC threads are finished.</param>
        /// <param name="commandLineInformation">The command line information (debug on start, pipe name, etc.).</param>
        /// <remarks>
        /// This code only catches exceptions it expects to encounter. If there is some other
        /// type of exception, can't start thread, can't create pipe, etc., then
        /// we want the process to crash so we can figure out the conditions under which
        /// it occurs and address it.
        /// The event source is owned by the new thread, and it must dispose of it when finished.
        /// </remarks>
        private static void CreateRPCServerThread(EventWaitHandle threadsDoneEvent, ServerStartParameters commandLineInformation)
        {
            if (commandLineInformation.DebugOnStart)
            {
                Debugger.Launch();
            }

            new Thread(new ThreadStart(() =>
            {
                Interlocked.Increment(ref rpcThreads);

                StartJsonRpcServer(commandLineInformation);

                var numberOfThreadsRemaining = Interlocked.Decrement(ref rpcThreads);

                // Release our mutex is trying to reduce the chance that while we are exiting
                // another process is trying to send command line information to us.
                if (numberOfThreadsRemaining == 0)
                {
                    alreadyRunningMutex?.Dispose();
                }

                if (numberOfThreadsRemaining == 0)
                {
                    // We need to send our events and dispose our event source
                    // before signaling all threads done. Otherwise
                    // the main thread exits and we lose events.
                    threadsDoneEvent.Set();
                }
            })).Start();
        }
Пример #2
0
        /// <summary>
        /// Handles startup of a secondary instance of the NMake JSON-RPC server.
        /// </summary>
        /// <param name="commandLineInformation">The command line information (debug on start, pipe name, etc.).</param>
        private static void StartupPrimaryInstance(ServerStartParameters commandLineInformation)
        {
            using (var threadsDone = new EventWaitHandle(false, EventResetMode.ManualReset))
            {
                using (var mappedDataReadySemaphore = new Semaphore(0, 1, memoryMappedFileDataReadyEventName))
                {
                    // In this case, we are the primary instance. Create a thread-pool wait for the memory mapped
                    // file to be written and signaled to be ready.
                    var mappedFileDataReadythreadPoolWait = ThreadPool.RegisterWaitForSingleObject(
                        mappedDataReadySemaphore,
                        (state, timedout) => // This starts the callback that occurs when the semaphore is released by another instance beginning.
                    {
                        if (timedout)
                        {
                            return;
                        }

                        // Open up a memory mapped file and read the command line data
                        // from the secondary instance.
                        using (var fileMapping = MemoryMappedFile.OpenExisting(fileMappingName))
                        {
                            using (var fileMapStream = fileMapping.CreateViewStream(0, 0, MemoryMappedFileAccess.Read))
                            {
                                var bf = new BinaryFormatter();
                                var secondaryInstanceCommandLineInformation = (ServerStartParameters)bf.Deserialize(fileMapStream);

                                // Let the first instance know that we are done. We no longer need to block
                                // it while creating the thread.
                                using (var dataReadEvent = new EventWaitHandle(false, EventResetMode.ManualReset, secondaryInstanceCommandLineInformation.DataReadEventName))
                                {
                                    dataReadEvent.Set();
                                }

                                // Now, go ahead and start up a thread to handle the JSON-RPC threads on behalf of the secondary instance.
                                // Don't confuse this with starting the primary instance JSON-RPC thread. That happens below.
                                CreateRPCServerThread(threadsDone, secondaryInstanceCommandLineInformation);
                            }
                        }
                    },
                        state: null,
                        millisecondsTimeOutInterval: -1,
                        executeOnlyOnce: false);

                    // This is the primary instance, so go ahead and create our JSON-RPC thread.
                    CreateRPCServerThread(threadsDone, commandLineInformation);

                    // Now wait for all the JSON-RPC instances to be complete.
                    // This blocks the main thread of the primary instance.
                    threadsDone.WaitOne();

                    // Now, shutdown the thread pool wait.
                    mappedFileDataReadythreadPoolWait.Unregister(null);
                }
            }
        }
Пример #3
0
        /// <summary>
        /// Handles startup of a secondary instance of the JSON-RPC server.
        /// </summary>
        /// <param name="commandLineInformation">The command line information (debug on start, pipe name, etc.).</param>
        private static void HandleSecondaryInstance(ServerStartParameters commandLineInformation)
        {
            // Create the named semaphore that lets the primary (first) instance know that there is data ready (command line arguments)
            // to read from a memory mapped file.
            using (var mappedDataReadySemaphore = new Semaphore(0, 1, memoryMappedFileDataReadyEventName))
            {
                var dataReadEventName = Guid.NewGuid().ToString();
                commandLineInformation.DataReadEventName = dataReadEventName;

                int  retries     = 0;
                bool shouldRetry = true;

                // This loop will only retry if the memory mapped file already
                // exists, indicating multiple instances are starting up and attempting to start and signal
                // the primary instance at the same time. This will be extremely rare (if it ever happens).
                // Which is the reason it is only done on specifically an already exists exception.
                while (shouldRetry && (retries++) < NumberOfMemoryMappedFileCreationAttempts)
                {
                    shouldRetry = false;
                    try
                    {
                        // This instance is a secondary instance. Signal the primary instance to start a JSON-RPC thread for us.
                        // There is a slim possibility that another instance is trying to do the same thing.
                        // So, we catch the already exist exception (and only that exception, and only that error code)
                        // and retry up to three times.
                        using (var fileMapping = MemoryMappedFile.CreateNew(fileMappingName, 1024, MemoryMappedFileAccess.ReadWrite))
                        {
                            // Write the data into the memory mapped file for the primary instance.
                            using (var fileMapStream = fileMapping.CreateViewStream(0, 0, MemoryMappedFileAccess.Write))
                            {
                                var bf = new BinaryFormatter();
                                bf.Serialize(fileMapStream, commandLineInformation);
                            }

                            using (var dataReadEvent = new EventWaitHandle(false, EventResetMode.ManualReset, dataReadEventName))
                            {
                                // Signal (release) the primary instance.
                                mappedDataReadySemaphore.Release(1);

                                // Wait for the primary instance to be complete.
                                dataReadEvent.WaitOne();
                            }
                        }
                    }
                    catch (System.IO.IOException e) when(e.HResult == AlreadyExistsErrorCode)
                    {
                        // If the memory mapped file already exists, then let's retry
                        // again after 1 second.
                        shouldRetry = true;
                        Thread.Sleep(MemoryMappedFileCreateRetryWaitTime);
                    }
                }
            }
        }
Пример #4
0
        /// <summary>
        /// The server entry point.
        /// </summary>
        /// <remarks>
        /// The server can only handle two command line options.
        /// The first is the local pipe name (--pipe) used for the JSON-RPC messages.D:\git\ShortStack\src\ShortStackServer\Program.cs
        /// The second can be used to break into the server on launch (--debugOnStart).
        /// </remarks>
        /// <param name="startParameters">Startup parameters.</param>
        /// <returns>A task for the server.</returns>
        public static Task KickOff(ServerStartParameters startParameters)
        {
            return(Tasks.PerformLongRunningOperation(
                       () =>
            {
                broadcastClientNotify = new BroadClassClientNotify(rpcServersLock, rpcServers);

                // The server will attempt to connect to an already running instance unless explicitly asked by the client to not do so.
                if (startParameters.ForceNewInstance)
                {
                    using (var threadsDone = new EventWaitHandle(false, EventResetMode.ManualReset))
                    {
                        CreateRPCServerThread(threadsDone, startParameters);

                        threadsDone.WaitOne();
                    }
                }
                else
                {
                    // This section of code handles making sure there is only a single instance of this server.

                    // The client has asked us to attempt to use a single instance. So set up the
                    // names used for the mutex, semaphore and memory mapped file.
                    SetupSingleInstanceNames();

                    // Create the named mutex that ensures that the process only runs once.

                    // If the mutex was already created, this signifies that there is already an instance
                    // of the application running.
                    alreadyRunningMutex = new Mutex(true, singleInstanceMutexName, out var createdNew);

                    if (createdNew)
                    {
                        StartupPrimaryInstance(startParameters);
                    }
                    else
                    {
                        HandleSecondaryInstance(startParameters);
                    }
                }
            }, CancellationToken.None));
        }
Пример #5
0
        /// <summary>
        /// The server entry point.
        /// </summary>
        /// <param name="args">The arguments passed to the server.</param>
        /// <remarks>
        /// The server can only handle two command line options.
        /// The first is the local pipe name (--pipe) used for the JSON-RPC messages.
        /// The second can be used to break into the server on launch (--debugOnStart).
        /// </remarks>
        public static void Main(string[] args)
        {
            var argsEnumerator         = args.GetEnumerator();
            var commandLineInformation = new ServerStartParameters
            {
                DebugOnStart      = false,
                PipeName          = null,
                DataReadEventName = null,
                ForceNewInstance  = false,
            };

            while (argsEnumerator.MoveNext())
            {
                if (((string)argsEnumerator.Current).Equals("--pipe", StringComparison.OrdinalIgnoreCase))
                {
                    if (argsEnumerator.MoveNext())
                    {
                        if (((string)argsEnumerator.Current).StartsWith(localPipeNamePrefix, StringComparison.OrdinalIgnoreCase))
                        {
                            commandLineInformation.PipeName = ((string)argsEnumerator.Current).Substring(localPipeNamePrefix.Length);
                        }
                    }
                }
                else if (((string)argsEnumerator.Current).Equals("--debugOnStart", StringComparison.OrdinalIgnoreCase))
                {
                    commandLineInformation.DebugOnStart = true;
                }
                else if (((string)argsEnumerator.Current).Equals("--forceNewInstance", StringComparison.OrdinalIgnoreCase))
                {
                    commandLineInformation.ForceNewInstance = true;
                }
            }

            if (string.IsNullOrEmpty(commandLineInformation.PipeName))
            {
                throw new ArgumentException("The pipe name was not passed on the command line.");
            }

            ServerManager.KickOff(commandLineInformation);
        }
Пример #6
0
        /// <summary>
        /// Starts a JSON-RPC server to handle client calls.
        /// </summary>
        /// <param name="commandLineInformation">The command line information (debug on start, pipe name, etc.).</param>
        /// <remarks>
        /// This function does not return until the JSON-RPC server exits.</remarks>
        private static void StartJsonRpcServer(ServerStartParameters commandLineInformation)
        {
            Debug.WriteLine("Starting server connection on " + commandLineInformation.PipeName);

            var jsonRpcStream = new NamedPipeClientStream(".", commandLineInformation.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);

            try
            {
                // The connect call will throw if the connection cannot be established within 5 seconds.
                // If a debugger is attached, wait indefinitely to aid debugging.
                if (commandLineInformation.DebugOnStart && Debugger.IsAttached)
                {
                    jsonRpcStream.Connect();
                }
                else
                {
                    jsonRpcStream.Connect(JsonRpcPipeWaitTime);
                }

                using (var server = new Server(jsonRpcStream, broadcastClientNotify))
                {
                    rpcServersLock.EnterWriteLock();
                    rpcServers.Add(server);
                    rpcServersLock.ExitWriteLock();

                    // Now for a bit of fun. Since VS doesn't like to shutdown properly (or it crashes a lot)
                    // we watch the pipe owner process, and if it goes away, we exit ourselves, so we don't stay around forever
                    if (NativeMethods.TryGetNamedPipeServerProcessId(jsonRpcStream, out var serverProcessId))
                    {
                        try
                        {
                            var serverProcess = Process.GetProcessById((int)serverProcessId);
                            ThreadPool.RegisterWaitForSingleObject(
                                waitObject: new ProcessWaitHandle(serverProcess),
                                callBack: (state, timedOut) =>
                            {
                                server.TryExit();
                            },
                                state: null,
                                millisecondsTimeOutInterval: -1,
                                executeOnlyOnce: true);
                        }
                        catch (ArgumentException)
                        {
                            // This will get thrown if for some reason our server that created
                            // the named pipe exits before we get a chance to watch it :)
                        }
                    }

                    // So if the server starts, we don't want to do Dispose twice.
                    jsonRpcStream = null;

                    server.WaitForExit();

                    rpcServersLock.EnterWriteLock();
                    rpcServers.Add(server);
                    rpcServersLock.ExitWriteLock();
                }
            }
            catch (TimeoutException)
            {
            }

            if (jsonRpcStream != null)
            {
                jsonRpcStream.Dispose();
            }
        }