Skip to content

Commit 4d50a92

Browse files
authored
Replace timer with while loop (#11071)
* Replace timer with simple while loop * Update release notes
1 parent a8b9483 commit 4d50a92

File tree

4 files changed

+147
-98
lines changed

4 files changed

+147
-98
lines changed

release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Fix invocation timeout when incoming request contains "x-ms-invocation-id" header (#10980)
66
- Warn if .azurefunctions folder does not exist (#10967)
77
- Memory allocation & CPU optimizations in `GrpcMessageExtensionUtilities.ConvertFromHttpMessageToExpando` (#11054)
8+
- Replace `Timer` with `while`-loop in `FlexConsumptionMetricsPublisher` (#11071)
89
- Memory allocation optimizations in `ReadLanguageWorkerFile` by reading files in buffered chunks, preventing LOH allocations (#11069)
910
- Enhancing the capability to send startup failure logs to AppInsights/Otel. (#11055)
1011
- Added support for collecting cross-platform perf traces and generating PGO JIT traces (#11062)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.Azure.WebJobs.Script.Diagnostics;
8+
using Microsoft.Azure.WebJobs.Script.Extensions;
9+
10+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Metrics
11+
{
12+
public partial class FlexConsumptionMetricsPublisher
13+
{
14+
private sealed class Lifecycle : IDisposable
15+
{
16+
private readonly FlexConsumptionMetricsPublisher _parent;
17+
private readonly TimeSpan _initialDelay;
18+
private readonly TimeSpan _intervalDelay;
19+
private readonly CancellationTokenSource _cts = new();
20+
21+
public Lifecycle(FlexConsumptionMetricsPublisher parent, TimeSpan initialDelay, TimeSpan intervalDelay)
22+
{
23+
_parent = parent;
24+
_initialDelay = initialDelay;
25+
_intervalDelay = intervalDelay;
26+
PublishLoopAsync(_cts.Token).Forget();
27+
}
28+
29+
public void Dispose()
30+
{
31+
if (!_cts.IsCancellationRequested)
32+
{
33+
// IsCancellationRequested serves as a dispose check.
34+
_cts.Cancel();
35+
}
36+
37+
_cts.Dispose();
38+
}
39+
40+
private async Task PublishLoopAsync(CancellationToken cancellation)
41+
{
42+
try
43+
{
44+
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
45+
await Task.Delay(_initialDelay, cancellation);
46+
while (!cancellation.IsCancellationRequested)
47+
{
48+
await _parent.OnPublishMetrics(DateTime.UtcNow, stopwatch);
49+
stopwatch = ValueStopwatch.StartNew();
50+
await Task.Delay(_intervalDelay, cancellation);
51+
}
52+
}
53+
catch (Exception ex) when (!ex.IsFatal())
54+
{
55+
// swallow
56+
}
57+
}
58+
}
59+
}
60+
}

src/WebJobs.Script.WebHost/Metrics/FlexConsumptionMetricsPublisher.cs

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,33 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5-
using System.IO;
65
using System.IO.Abstractions;
7-
using System.Linq;
86
using System.Threading;
97
using System.Threading.Tasks;
108
using Microsoft.Azure.WebJobs.Script.Diagnostics;
11-
using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions;
129
using Microsoft.Azure.WebJobs.Script.Metrics;
1310
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
1411
using Microsoft.Extensions.Logging;
1512
using Microsoft.Extensions.Options;
16-
using Newtonsoft.Json;
1713

1814
namespace Microsoft.Azure.WebJobs.Script.WebHost.Metrics
1915
{
20-
public class FlexConsumptionMetricsPublisher : IMetricsPublisher, IDisposable
16+
public sealed partial class FlexConsumptionMetricsPublisher : IMetricsPublisher, IDisposable
2117
{
2218
private readonly IOptionsMonitor<StandbyOptions> _standbyOptions;
2319
private readonly FlexConsumptionMetricsPublisherOptions _options;
2420
private readonly IEnvironment _environment;
2521
private readonly ILogger<FlexConsumptionMetricsPublisher> _logger;
2622
private readonly IHostMetricsProvider _metricsProvider;
27-
private readonly object _lock = new object();
23+
private readonly object _lock = new();
2824
private readonly IFileSystem _fileSystem;
2925
private readonly LegionMetricsFileManager _metricsFileManager;
3026

31-
private Timer _metricsPublisherTimer;
32-
private bool _started = false;
3327
private DateTime _currentActivityIntervalStart;
3428
private DateTime _activityIntervalHighWatermark = DateTime.MinValue;
35-
private ValueStopwatch _intervalStopwatch;
3629
private IDisposable _standbyOptionsOnChangeSubscription;
37-
private TimeSpan _metricPublishInterval;
38-
private TimeSpan _initialPublishDelay;
3930
private DateTime _lastPublishTime = DateTime.UtcNow;
31+
private Lifecycle _lifecycle;
4032

4133
public FlexConsumptionMetricsPublisher(IEnvironment environment, IOptionsMonitor<StandbyOptions> standbyOptions, IOptions<FlexConsumptionMetricsPublisherOptions> options,
4234
ILogger<FlexConsumptionMetricsPublisher> logger, IFileSystem fileSystem, IHostMetricsProvider metricsProvider)
@@ -71,29 +63,43 @@ public FlexConsumptionMetricsPublisher(IEnvironment environment, IOptionsMonitor
7163

7264
internal LegionMetricsFileManager MetricsFileManager => _metricsFileManager;
7365

66+
private bool IsStarted => _lifecycle is not null;
67+
7468
public void Start()
7569
{
76-
Initialize();
70+
if (_lifecycle is not null)
71+
{
72+
return;
73+
}
74+
75+
lock (_lock)
76+
{
77+
if (_lifecycle is not null)
78+
{
79+
return;
80+
}
81+
82+
IsAlwaysReady = _environment
83+
.GetEnvironmentVariable(EnvironmentSettingNames.FunctionsAlwaysReadyInstance) == "1";
7784

78-
_logger.LogInformation($"Starting metrics publisher (AlwaysReady={IsAlwaysReady}, MetricsPath='{_metricsFileManager.MetricsFilePath}').");
85+
_logger.LogInformation(
86+
$"Starting metrics publisher (AlwaysReady={IsAlwaysReady},"
87+
+ $" MetricsPath='{_metricsFileManager.MetricsFilePath}').");
7988

80-
_metricsPublisherTimer = new Timer(OnFunctionMetricsPublishTimer, null, _initialPublishDelay, _metricPublishInterval);
81-
_started = true;
89+
_lifecycle = new(
90+
this,
91+
TimeSpan.FromMilliseconds(_options.InitialPublishDelayMS),
92+
TimeSpan.FromMilliseconds(_options.MetricsPublishIntervalMS));
93+
}
8294
}
8395

84-
/// <summary>
85-
/// Initialize any environmentally derived state after specialization, prior to starting the publisher.
86-
/// </summary>
87-
internal void Initialize()
96+
public void Dispose()
8897
{
89-
_metricPublishInterval = TimeSpan.FromMilliseconds(_options.MetricsPublishIntervalMS);
90-
_initialPublishDelay = TimeSpan.FromMilliseconds(_options.InitialPublishDelayMS);
91-
_intervalStopwatch = ValueStopwatch.StartNew();
92-
93-
IsAlwaysReady = _environment.GetEnvironmentVariable(EnvironmentSettingNames.FunctionsAlwaysReadyInstance) == "1";
98+
Interlocked.Exchange(ref _lifecycle, null)?.Dispose();
99+
Interlocked.Exchange(ref _standbyOptionsOnChangeSubscription, null)?.Dispose();
94100
}
95101

96-
internal async Task OnPublishMetrics(DateTime now)
102+
internal async Task OnPublishMetrics(DateTime now, ValueStopwatch stopwatch)
97103
{
98104
try
99105
{
@@ -122,7 +128,7 @@ internal async Task OnPublishMetrics(DateTime now)
122128
{
123129
metrics = new Metrics
124130
{
125-
TotalTimeMS = (long)_intervalStopwatch.GetElapsedTime().TotalMilliseconds,
131+
TotalTimeMS = (long)stopwatch.GetElapsedTime().TotalMilliseconds,
126132
ExecutionCount = FunctionExecutionCount,
127133
ExecutionTimeMS = FunctionExecutionTimeMS,
128134
IsAlwaysReady = IsAlwaysReady,
@@ -149,15 +155,6 @@ internal async Task OnPublishMetrics(DateTime now)
149155
// ensure no background exceptions escape
150156
_logger.LogError(ex, $"Error publishing metrics.");
151157
}
152-
finally
153-
{
154-
_intervalStopwatch = ValueStopwatch.StartNew();
155-
}
156-
}
157-
158-
private async void OnFunctionMetricsPublishTimer(object state)
159-
{
160-
await OnPublishMetrics(DateTime.UtcNow);
161158
}
162159

163160
private void OnStandbyOptionsChange()
@@ -175,7 +172,7 @@ public void OnFunctionStarted(string functionName, string invocationId)
175172

176173
internal void OnFunctionStarted(string functionName, string invocationId, DateTime now)
177174
{
178-
if (!_started)
175+
if (!IsStarted)
179176
{
180177
return;
181178
}
@@ -199,7 +196,7 @@ public void OnFunctionCompleted(string functionName, string invocationId)
199196

200197
internal void OnFunctionCompleted(string functionName, string invocationId, DateTime now)
201198
{
202-
if (!_started)
199+
if (!IsStarted)
203200
{
204201
return;
205202
}
@@ -267,15 +264,6 @@ private static double RoundUp(double metric, int granularity)
267264
return Math.Ceiling(metric / granularity) * granularity;
268265
}
269266

270-
public void Dispose()
271-
{
272-
_metricsPublisherTimer?.Dispose();
273-
_metricsPublisherTimer = null;
274-
275-
_standbyOptionsOnChangeSubscription?.Dispose();
276-
_standbyOptionsOnChangeSubscription = null;
277-
}
278-
279267
internal class Metrics
280268
{
281269
/// <summary>

0 commit comments

Comments
 (0)