/// <summary> /// Runs the specified service using the command console as a user interface. /// </summary> /// <param name="service">The service.</param> /// <param name="runMode">The run mode.</param> /// <param name="defaultLogFormat">The default log format.</param> /// <param name="defaultLoggingLevels">The default logging levels.</param> /// <param name="token">The token.</param> /// <returns> /// An awaitable task. /// </returns> // ReSharper disable once CodeAnnotationAnalyzer public static async Task RunAsync( [NotNull] BaseService service, RunMode runMode = RunMode.Default, [CanBeNull] FormatBuilder defaultLogFormat = null, LoggingLevels defaultLoggingLevels = LoggingLevels.All, CancellationToken token = default(CancellationToken)) { if (service == null) throw new ArgumentNullException("service"); if (!ConsoleHelper.IsConsole) return; Console.Clear(); Log.SetTrace(validLevels: LoggingLevels.None); Log.SetConsole(defaultLogFormat ?? Log.ShortFormat, defaultLoggingLevels); await Log.Flush(token).ConfigureAwait(false); Impersonator impersonator = null; try { if (runMode.HasFlag(RunMode.Prompt)) { Debug.Assert(service.ServiceName != null); // Whether we start will depend on the selected option in prompt. runMode = runMode.Clear(RunMode.Start, true); Console.Title = ServiceResources.ConsoleConnection_RunAsync_ConfigureTitle + service.ServiceName; bool done = false; do { if (token.IsCancellationRequested) return; Dictionary<string, string> options = new Dictionary<string, string> { { "I", ServiceResources.ConsoleConnection_RunAsync_OptionInstall }, { "U", ServiceResources.ConsoleConnection_RunAsync_OptionUninstall }, { "S", ServiceResources.ConsoleConnection_RunAsync_OptionStart }, { "R", ServiceResources.ConsoleConnection_RunAsync_OptionRestart }, { "T", ServiceResources.ConsoleConnection_RunAsync_OptionStop }, { "P", ServiceResources.ConsoleConnection_RunAsync_OptionPause }, { "C", ServiceResources.ConsoleConnection_RunAsync_OptionContinue }, { "Y", ServiceResources.ConsoleConnection_RunAsync_OptionRunCmd }, { "V", ServiceResources.ConsoleConnection_RunAsync_OptionStartCmd }, { "W", ServiceResources.ConsoleConnection_RunAsync_OptionRunCmdNewCredentials }, { "Z", ServiceResources.ConsoleConnection_RunAsync_OptionRunNoInteraction }, { "X", ServiceResources.ConsoleConnection_RunAsync_OptionExit } }; if (!runMode.HasFlag(RunMode.Interactive)) { options.Remove("V"); options.Remove("Y"); options.Remove("W"); } bool isAdmin; string currentUser; try { WindowsIdentity identity = WindowsIdentity.GetCurrent(); currentUser = identity.Name; Debug.Assert(identity != null); WindowsPrincipal principal = new WindowsPrincipal(identity); isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator); } catch { isAdmin = false; currentUser = null; } if (!string.IsNullOrEmpty(currentUser)) { FormatBuilder fb = new FormatBuilder() .AppendForegroundColor(ConsoleColor.Cyan) .Append("Current User: "******" [Admin]"); fb.AppendLine().WriteToConsole(); } if (!isAdmin) { options.Remove("I"); options.Remove("U"); } if (Controller.ServiceIsInstalled(service.ServiceName)) { ServiceControllerStatus state = Controller.GetServiceStatus(service.ServiceName); new FormatBuilder() .AppendForegroundColor(ConsoleColor.White) .AppendFormatLine( ServiceResources.ConsoleConnection_RunAsync_ServiceInstalledState, service.ServiceName, state) .AppendResetForegroundColor() .WriteToConsole(); options.Remove("I"); switch (state) { case ServiceControllerStatus.StopPending: case ServiceControllerStatus.Stopped: // Service is stopped or stopping. options.Remove("C"); options.Remove("R"); options.Remove("T"); break; case ServiceControllerStatus.StartPending: case ServiceControllerStatus.ContinuePending: case ServiceControllerStatus.Running: // Service is starting or running. options.Remove("S"); options.Remove("C"); break; case ServiceControllerStatus.PausePending: case ServiceControllerStatus.Paused: // Service is paused or pausing. options.Remove("S"); options.Remove("R"); options.Remove("T"); options.Remove("P"); break; default: // Service is not installed - shouldn't happen. options.Remove("U"); options.Remove("S"); options.Remove("R"); options.Remove("T"); options.Remove("C"); options.Remove("P"); break; } options.Remove("V"); options.Remove("Y"); options.Remove("Z"); } else { // No service installed. options.Remove("U"); options.Remove("S"); options.Remove("R"); options.Remove("T"); options.Remove("P"); options.Remove("C"); } _promptInstall.WriteToConsole( null, // ReSharper disable once PossibleNullReferenceException (_, c) => !string.Equals(c.Tag, "options", StringComparison.CurrentCultureIgnoreCase) ? Resolution.Unknown : options); string key; do { key = Char.ToUpperInvariant(Console.ReadKey(true).KeyChar) .ToString(CultureInfo.InvariantCulture); } while (!options.ContainsKey(key)); try { string userName; string password; switch (key) { case "I": GetUserNamePassword(out userName, out password); service.Install(ConsoleTextWriter.Default, userName, password); Console.Write(ServiceResources.ConsoleConnection_RunAsync_WaitInstall); while (!Controller.ServiceIsInstalled(service.ServiceName)) { await Task.Delay(250, token).ConfigureAwait(false); Console.Write('.'); } Console.WriteLine(ServiceResources.Done); Console.WriteLine(); break; case "U": await service.Uninstall(ConsoleTextWriter.Default, token).ConfigureAwait(false); Console.Write(ServiceResources.ConsoleConnection_RunAsync_WaitUninstall); while (Controller.ServiceIsInstalled(service.ServiceName)) { await Task.Delay(250, token).ConfigureAwait(false); Console.Write('.'); } Console.WriteLine(ServiceResources.Done); Console.WriteLine(); break; case "R": Console.Write(ServiceResources.ConsoleConnection_RunAsync_AttemptingStop); await Controller.StopService(service.ServiceName, token).ConfigureAwait(false); Console.WriteLine(ServiceResources.Done); Console.WriteLine(); Console.Write(ServiceResources.ConsoleConnection_RunAsync_AttemptingStart); await Controller.StartService(service.ServiceName, null, token).ConfigureAwait(false); Console.WriteLine(ServiceResources.Done); Console.WriteLine(); break; case "S": Console.Write(ServiceResources.ConsoleConnection_RunAsync_AttemptingStart); await Controller.StartService(service.ServiceName, null, token).ConfigureAwait(false); Console.WriteLine(ServiceResources.Done); Console.WriteLine(); break; case "T": Console.Write(ServiceResources.ConsoleConnection_RunAsync_AttemptingStop); await Controller.StopService(service.ServiceName, token).ConfigureAwait(false); Console.WriteLine(ServiceResources.Done); Console.WriteLine(); break; case "P": Console.Write(ServiceResources.ConsoleConnection_RunAsync_AttemptingPause); await Controller.PauseService(service.ServiceName, token).ConfigureAwait(false); Console.WriteLine(ServiceResources.Done); break; case "C": Console.Write(ServiceResources.ConsoleConnection_RunAsync_AttemptingContinue); await Controller.ContinueService(service.ServiceName, token).ConfigureAwait(false); Console.WriteLine(ServiceResources.Done); Console.WriteLine(); break; case "V": runMode = runMode.Set(RunMode.Start, true).Set(RunMode.Interactive, true); done = true; break; case "Y": runMode = runMode.Set(RunMode.Interactive, true); done = true; break; case "W": GetUserNamePassword(out userName, out password); if (userName == null) break; Debug.Assert(password != null); Impersonator ei = impersonator; impersonator = null; if (ei != null) ei.Dispose(); // Run in new security context. impersonator = new Impersonator(userName, password); break; case "Z": runMode = runMode.Set(RunMode.Start, true).Clear(RunMode.Interactive, true); done = true; Console.WriteLine(ServiceResources.ConsoleConnection_RunAsync_RunningNonInteractive); Console.WriteLine( ServiceResources.ConsoleConnection_RunAsync_RunningNonInteractive2); Console.WriteLine(); break; default: return; } } catch (TaskCanceledException) { return; } catch (Exception e) { if (!token.IsCancellationRequested) Log.Add(e); } } while (!done); } else if (!runMode.HasFlag(RunMode.Interactive)) // If we don't show prompt and we're not interactive we should always start the service. runMode = runMode.Set(RunMode.Start, true); // Create connection Console.Title = ServiceResources.ConsoleConnection_RunAsync_RunningTitle + service.ServiceName; ConsoleConnection connection = new ConsoleConnection(defaultLogFormat, defaultLoggingLevels, token); Guid id = service.Connect(connection); // Combined cancellation tokens. ITokenSource tSource = token.CreateLinked(connection._cancellationTokenSource.Token); try { CancellationToken t = tSource.Token; if (t.IsCancellationRequested) return; if (runMode.HasFlag(RunMode.Start)) { // Start the service await service.StartService(ConsoleTextWriter.Default, null, t).ConfigureAwait(false); if (t.IsCancellationRequested) return; } if (!runMode.HasFlag(RunMode.Interactive)) { // Wait to be cancelled as nothing to do. await t.WaitHandle; return; } do { // Flush logs await Log.Flush(t).ConfigureAwait(false); if (t.IsCancellationRequested) break; WritePrompt(service); try { string commandLine = await Console.In.ReadLineAsync().ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(commandLine)) { bool completed = false; ICancelableTokenSource commandCancellationSource = t.ToCancelable(); CancellationToken commandToken = commandCancellationSource.Token; #pragma warning disable 4014 service.ExecuteAsync(id, commandLine, ConsoleTextWriter.Default, commandToken) .ContinueWith( task => { Debug.Assert(task != null); completed = true; if (task.IsCompleted || task.IsCanceled) return; if (task.IsFaulted) { Debug.Assert(task.Exception != null); _errorFormat.WriteToConsoleInstance(null, task.Exception); } }, TaskContinuationOptions.ExecuteSynchronously); #pragma warning restore 4014 while (!completed) { if (!commandCancellationSource.IsCancellationRequested && Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape) { // Cancel command Console.Write(ServiceResources.ConsoleConnection_RunAsync_Cancelling); commandCancellationSource.Cancel(); break; } await Task.Delay(100, token).ConfigureAwait(false); } } } catch (TaskCanceledException) { throw; } catch (Exception e) { if (!t.IsCancellationRequested) _errorFormat.WriteToConsoleInstance(null, e); } // Let any async stuff done by the command have a bit of time, also throttle commands. await Task.Delay(500, t).ConfigureAwait(false); } while (!t.IsCancellationRequested); } catch (TaskCanceledException) { } finally { tSource.Dispose(); // ReSharper disable MethodSupportsCancellation Log.Flush().Wait(); // ReSharper restore MethodSupportsCancellation service.Disconnect(id); Console.WriteLine(ServiceResources.ConsoleConnection_RunAsync_PressKeyToExit); Console.ReadKey(true); } } finally { if (impersonator != null) impersonator.Dispose(); } }
public IObservable<Response> Send( [NotNull] Request request, CancellationToken token = default(CancellationToken)) { OverlappingPipeClientStream stream = _stream; if (_state != PipeState.Connected || stream == null) // ReSharper disable once AssignNullToNotNullAttribute return Observable.Empty<Response>(); // ReSharper disable once AssignNullToNotNullAttribute return Observable.Create<Response>( async (observer, t) => { Debug.Assert(observer != null); using (ITokenSource tokenSource = token.CreateLinked(t)) { token = tokenSource.Token; ConnectedCommand cr = new ConnectedCommand(request, observer); _commandRequests.TryAdd(request.ID, cr); try { await stream.WriteAsync(request.Serialize(), token).ConfigureAwait(false); await cr.CompletionTask.WithCancellation(token).ConfigureAwait(false); } // ReSharper disable once EmptyGeneralCatchClause catch { } // If the command is not explicitly cancelled and is still running, and we've been cancelled // then ask the server to cancel. if (!cr.IsCancelled && !cr.IsCompleted && token.IsCancellationRequested) try { using (CancellationTokenSource cts = Constants.FireAndForgetTokenSource) await CancelCommand(request.ID, cts.Token).ConfigureAwait(false); } catch (TaskCanceledException) { } // Remove the command request. _commandRequests.TryRemove(request.ID, out cr); } }); }
/// <summary> /// The timer task executes the callback asynchronously after set delays. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> // ReSharper disable once FunctionComplexityOverflow private async Task TimerTask(CancellationToken cancellationToken) { long startTicks = long.MinValue; long endTicks = long.MinValue; while (!cancellationToken.IsCancellationRequested) try { CancellationTokenSource timeoutsChanged; // Check we're not set to run immediately if (Interlocked.Exchange(ref _runImmediate, 0) == 0) do { // Create new cancellation token source and set _timeOutsChanged to it in a thread-safe none-locking way. timeoutsChanged = new CancellationTokenSource(); CancellationTokenSource toc = Interlocked.Exchange(ref _timeOutsChanged, timeoutsChanged); if (ReferenceEquals(toc, null)) { toc = Interlocked.CompareExchange(ref _timeOutsChanged, null, timeoutsChanged); if (!ReferenceEquals(toc, null)) toc.Dispose(); return; } // If we have run immediate set at this point, we can't rely on the correct _timeOutsChanged cts being cancelled. if (Interlocked.Exchange(ref _runImmediate, 0) > 0) break; using (ITokenSource tokenSource = cancellationToken.CreateLinked(timeoutsChanged.Token)) { // Check for pausing. try { await _pauseToken.WaitWhilePausedAsync(tokenSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception exception) { if (!ReferenceEquals(_errorHandler, null)) _errorHandler(exception); } if (cancellationToken.IsCancellationRequested) return; // Get timeouts TimeOuts timeOuts = _timeOuts; if (ReferenceEquals(timeOuts, null)) return; if (timeOuts.DueTimeMs < 0 || (startTicks > timeOuts.DueTimeStamp && (timeOuts.MinimumGapMs < 0 || timeOuts.PeriodMs < 0))) { // If we have infinite waits then we are effectively awaiting cancellation // ReSharper disable once PossibleNullReferenceException await tokenSource.ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) return; continue; } // If all timeouts are zero we effectively run again immediately (after checking we didn't get a cancellation // indicating the value have changed again). if (timeOuts.DueTimeMs == 0 && timeOuts.MinimumGapMs == 0 && timeOuts.PeriodMs == 0) continue; int wait; if (startTicks > long.MinValue) { // Calculate the wait time based on the minimum gap and the period. long now = HighPrecisionClock.Instance.NowTicks; int a = timeOuts.PeriodMs - (int)((now - startTicks) / NodaConstants.TicksPerMillisecond); int b = timeOuts.MinimumGapMs - (int)((now - endTicks) / NodaConstants.TicksPerMillisecond); int c = (int)((timeOuts.DueTimeStamp - now) / NodaConstants.TicksPerMillisecond); wait = Math.Max(a, Math.Max(b, c)); } else // Wait the initial due time wait = (int) ((timeOuts.DueTimeStamp - HighPrecisionClock.Instance.NowTicks) / NodaConstants.TicksPerMillisecond); // If we don't need to wait run again immediately (after checking values haven't changed). if (wait < 1) continue; try { // Wait for set milliseconds // ReSharper disable PossibleNullReferenceException await Task.Delay(wait, tokenSource.Token).ConfigureAwait(false); // ReSharper restore PossibleNullReferenceException } catch (OperationCanceledException) { } catch (Exception exception) { if (!ReferenceEquals(_errorHandler, null)) _errorHandler(exception); } } // Recalculate wait time if 'cancelled' due to signal, and not set to run immediately; or if we're currently paused. } while ( _pauseToken.IsPaused || (timeoutsChanged.IsCancellationRequested && !cancellationToken.IsCancellationRequested && Interlocked.Exchange(ref _runImmediate, 0) < 1)); if (cancellationToken.IsCancellationRequested) return; try { Interlocked.CompareExchange( ref _callbackCompletionSource, new TaskCompletionSource<bool>(), null); startTicks = HighPrecisionClock.Instance.NowTicks; // ReSharper disable once PossibleNullReferenceException await _callback(cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) return; } catch (OperationCanceledException) { // Just finish as we're cancelled TaskCompletionSource<bool> callbackCompletionSource = Interlocked.Exchange(ref _callbackCompletionSource, null); // If the completion source is not null, then someone is awaiting last execution, so complete the task if (!ReferenceEquals(callbackCompletionSource, null)) callbackCompletionSource.TrySetCanceled(); return; } // ReSharper disable once EmptyGeneralCatchClause catch (Exception exception) { // Supress errors thrown by callback, unless someone is awaiting it. TaskCompletionSource<bool> callbackCompletionSource = Interlocked.Exchange(ref _callbackCompletionSource, null); // If the completion source is not null, then someone is awaiting last execution, so complete the task if (!ReferenceEquals(callbackCompletionSource, null)) callbackCompletionSource.TrySetException(exception); if (!ReferenceEquals(_errorHandler, null)) _errorHandler(exception); } finally { endTicks = HighPrecisionClock.Instance.NowTicks; // If run immediately was set whilst we were running, we can clear it. Interlocked.Exchange(ref _runImmediate, 0); TaskCompletionSource<bool> callbackCompletionSource = Interlocked.Exchange(ref _callbackCompletionSource, null); // If the completion source is not null, then someone is awaiting last execution, so complete the task if (!ReferenceEquals(callbackCompletionSource, null)) callbackCompletionSource.TrySetResult(true); } } catch (Exception exception) { if (!ReferenceEquals(_errorHandler, null)) _errorHandler(exception); } }
/// <summary> /// Initializes a new instance of the <see cref="NamedPipeClient" /> class. /// </summary> /// <param name="description">The client description.</param> /// <param name="server">The server.</param> /// <param name="onReceive">The action to call on receipt of a message.</param> /// <param name="token">The token.</param> private NamedPipeClient( [NotNull] string description, [NotNull] NamedPipeServerInfo server, [NotNull] Action<Message> onReceive, CancellationToken token = default(CancellationToken)) { if (description == null) throw new ArgumentNullException("description"); if (server == null) throw new ArgumentNullException("server"); if (onReceive == null) throw new ArgumentNullException("onReceive"); _server = server; _cancellationTokenSource = new CancellationTokenSource(); CancellationToken disposeToken = _cancellationTokenSource.Token; _clientTask = Task.Run( async () => { try { using (ITokenSource tokenSource = token.CreateLinked(disposeToken)) using ( OverlappingPipeClientStream stream = new OverlappingPipeClientStream( _server.Host, _server.FullName, PipeTransmissionMode.Message)) { _state = PipeState.Open; token = tokenSource.Token; // We need to support cancelling the connect. await stream.Connect(token).ConfigureAwait(false); ConnectResponse connectResponse = null; DisconnectResponse disconnectResponse = null; if (!token.IsCancellationRequested) { // Set the stream. _stream = stream; _state = PipeState.AwaitingConnect; // Kick off a connect request, but don't wait for it's result as we're the task that will receive it! ConnectRequest connectRequest = new ConnectRequest(description); await stream.WriteAsync(connectRequest.Serialize(), token).ConfigureAwait(false); // Keep going as long as we're connected. try { while (stream.IsConnected && !disposeToken.IsCancellationRequested) { // Read data in. byte[] data = await stream.ReadAsync(disposeToken).ConfigureAwait(false); if (data == null) break; // Deserialize the incoming message. Message message = Message.Deserialize(data); if (connectResponse == null) { // We require a connect response to start connectResponse = message as ConnectResponse; if (connectResponse == null || connectResponse.ID != connectRequest.ID) break; _state = PipeState.Connected; _serviceName = connectResponse.ServiceName; Log.Add( LoggingLevel.Notification, () => ClientResources.Not_NamedPipeClient_Connection, connectResponse.ServiceName); TaskCompletionSource<NamedPipeClient> ccs = Interlocked.Exchange(ref _connectionCompletionSource, null); if (ccs != null) ccs.TrySetResult(this); // Observer the message. onReceive(message); continue; } // Check for disconnect, we don't observe the message until the disconnect is complete. disconnectResponse = message as DisconnectResponse; if (disconnectResponse != null) break; // Observe the message. onReceive(message); Response response = message as Response; if (response == null) continue; ConnectedCommand connectedCommand; // Check for cancellation responses. CommandCancelResponse cancelResponse = response as CommandCancelResponse; if (cancelResponse != null) // Cancel the associated request if (_commandRequests.TryGetValue( cancelResponse.CancelledCommandId, out connectedCommand)) // ReSharper disable once PossibleNullReferenceException connectedCommand.Cancel(cancelResponse); // And fall through to complete the response... // Find command the response is related to, and notify it of the response. if (!_commandRequests.TryGetValue(response.ID, out connectedCommand)) continue; Debug.Assert(connectedCommand != null); if (connectedCommand.Received(response)) _commandRequests.TryRemove(response.ID, out connectedCommand); } } catch (TaskCanceledException) { } } // If we're still connected, and we haven't received a disconnect response, try to send a disconnect request. if (stream.IsConnected && disconnectResponse == null) { CancellationTokenSource cts = token.IsCancellationRequested ? new CancellationTokenSource(500) : null; try { CancellationToken t = cts != null ? cts.Token : token; // Try to send disconnect request. // ReSharper disable once PossibleNullReferenceException await Send(new DisconnectRequest(), t) .ToTask(t) .ConfigureAwait(false); } catch (TaskCanceledException) { } finally { if (cts != null) cts.Dispose(); } } // Remove the stream. _stream = null; _state = PipeState.Closed; _serviceName = null; // If we had a disconnect message observe it now that the disconnect has been actioned, // this prevents the receiver thinking the connection is still active. if (disconnectResponse != null) { onReceive(disconnectResponse); ConnectedCommand connectedCommand; if (_commandRequests.TryGetValue(disconnectResponse.ID, out connectedCommand)) { Debug.Assert(connectedCommand != null); if (connectedCommand.Received(disconnectResponse)) _commandRequests.TryRemove(disconnectResponse.ID, out connectedCommand); } } } } catch (IOException ioe) { if (!token.IsCancellationRequested) // Common exception caused by sudden disconnect, lower level Log.Add( ioe, LoggingLevel.Information, () => ClientResources.Err_NamedPipeClient_Failed); } catch (Exception exception) { TaskCanceledException tce = exception as TaskCanceledException; TaskCompletionSource<NamedPipeClient> ccs = Interlocked.Exchange( ref _connectionCompletionSource, null); if (ccs != null) if (tce != null) ccs.TrySetCanceled(); else ccs.TrySetException(exception); // We only log if this wasn't a cancellation exception. if (tce == null && !token.IsCancellationRequested) Log.Add( exception, LoggingLevel.Error, () => ClientResources.Err_NamedPipeClient_Failed); } finally { Dispose(); } }, disposeToken); }