protected override Task StartServer(CancellationToken cancel) { // Kick off the actual processing to a new thread and return a Task for the processing thread. return(Task.Run(async delegate { while (true) { // We schedule approximately at the configured interval. There may be some small accumulation for the // part of the loop we do not measure but it is close enough to be acceptable for all practical scenarios. var duration = Stopwatch.StartNew(); try { using (var stream = new MemoryStream()) { var serializer = new TextSerializer(stream); // Do not pass CT because we only want to cancel after pushing, so a flush is always performed. await _registry.CollectAndSerializeAsync(serializer, default); stream.Position = 0; // StreamContent takes ownership of the stream. var response = await _httpClient.PostAsync(_targetUrl, new StreamContent(stream)); // If anything goes wrong, we want to get at least an entry in the trace log. response.EnsureSuccessStatusCode(); } } catch (ScrapeFailedException ex) { LogProxy.WriteLine($"Skipping metrics push due to failed scrape: {ex.Message}"); } catch (Exception ex) when(!(ex is OperationCanceledException)) { LogProxy.WriteLine(string.Format("Error in MetricPusher: {0}", ex)); } // We stop only after pushing metrics, to ensure that the latest state is flushed when told to stop. if (cancel.IsCancellationRequested) { break; } var sleepTime = _pushInterval - duration.Elapsed; // Sleep until the interval elapses or the pusher is asked to shut down. if (sleepTime > TimeSpan.Zero) { try { await Task.Delay(sleepTime, cancel); } catch (OperationCanceledException) { // The task was cancelled. // We continue the loop here to ensure final state gets pushed. continue; } } } }));
protected override Task StartServer(CancellationToken cancel) { // This will ensure that any failures to start are nicely thrown from StartServerAsync. _httpListener.Start(); // Kick off the actual processing to a new thread and return a Task for the processing thread. return(Task.Factory.StartNew(async delegate { try { while (!cancel.IsCancellationRequested) { // There is no way to give a CancellationToken to GCA() so, we need to hack around it a bit. var getContext = _httpListener.GetContextAsync(); getContext.Wait(cancel); var context = getContext.Result; var request = context.Request; var response = context.Response; try { try { // We first touch the response.OutputStream only in the callback because touching // it means we can no longer send headers (the status code). var serializer = new TextSerializer(delegate { response.ContentType = PrometheusConstants.ExporterContentType; response.StatusCode = 200; return response.OutputStream; }); await _registry.CollectAndSerializeAsync(serializer, cancel); response.OutputStream.Dispose(); } catch (ScrapeFailedException ex) { // This can only happen before anything is written to the stream, so it // should still be safe to update the status code and report an error. response.StatusCode = 503; if (!string.IsNullOrWhiteSpace(ex.Message)) { using (var writer = new StreamWriter(response.OutputStream)) writer.Write(ex.Message); } } } catch (Exception ex) when(!(ex is OperationCanceledException)) { Trace.WriteLine(string.Format("Error in MetricsServer: {0}", ex)); try { response.StatusCode = 500; } catch { // Might be too late in request processing to set response code, so just ignore. } } finally { response.Close(); } } } finally { _httpListener.Stop(); _httpListener.Close(); } }, TaskCreationOptions.LongRunning)); }