Skip to content

Commit 10fd505

Browse files
committed
Simplifying node initialization, avoiding locks and removing sync paths
1 parent d4610ec commit 10fd505

File tree

5 files changed

+59
-35
lines changed

5 files changed

+59
-35
lines changed

src/WebJobs.Script/Description/DotNet/DotNetFunctionInvoker.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public sealed class DotNetFunctionInvoker : FunctionInvokerBase
3535

3636
private FunctionSignature _functionSignature;
3737
private IFunctionMetadataResolver _metadataResolver;
38-
private Action _reloadScript;
38+
private Func<Task> _reloadScript;
3939
private Action _shutdown;
4040
private Action _restorePackages;
4141
private Action<MethodInfo, object[], object[], object> _resultProcessor;
@@ -63,7 +63,7 @@ internal DotNetFunctionInvoker(ScriptHost host, FunctionMetadata functionMetadat
6363

6464
_functionLoader = new FunctionLoader<MethodInfo>(CreateFunctionTarget);
6565

66-
_reloadScript = ReloadScript;
66+
_reloadScript = ReloadScriptAsync;
6767
_reloadScript = _reloadScript.Debounce();
6868

6969
_shutdown = () => Host.Shutdown();
@@ -120,7 +120,7 @@ public override void OnError(Exception ex)
120120
TraceError(error);
121121
}
122122

123-
private void ReloadScript()
123+
private async Task ReloadScriptAsync()
124124
{
125125
// Reset cached function
126126
_functionLoader.Reset();
@@ -156,7 +156,7 @@ private void ReloadScript()
156156
(_functionSignature == null ||
157157
(_functionSignature.HasLocalTypeReference || !_functionSignature.Equals(signature))))
158158
{
159-
Host.Restart();
159+
await Host.RestartAsync().ConfigureAwait(false);
160160
}
161161
}
162162

@@ -205,7 +205,7 @@ internal async Task RestorePackagesAsync(bool reloadScriptOnSuccess = true)
205205
}
206206
else
207207
{
208-
_reloadScript();
208+
await _reloadScript();
209209
}
210210
}
211211
}

src/WebJobs.Script/Description/Node/NodeFunctionInvoker.cs

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

44
using System;
5-
using System.Collections;
65
using System.Collections.Generic;
76
using System.Collections.ObjectModel;
87
using System.Diagnostics;
@@ -12,6 +11,7 @@
1211
using System.Linq;
1312
using System.Net.Http;
1413
using System.Net.Http.Headers;
14+
using System.Threading;
1515
using System.Threading.Tasks;
1616
using EdgeJs;
1717
using Microsoft.Azure.WebJobs.Host;
@@ -30,15 +30,14 @@ public class NodeFunctionInvoker : FunctionInvokerBase
3030
private readonly string _script;
3131
private readonly BindingMetadata _trigger;
3232
private readonly string _entryPoint;
33-
private static readonly object _initializationSyncRoot = new object();
3433

3534
private Func<object, Task<object>> _scriptFunc;
3635
private static Func<object, Task<object>> _clearRequireCache;
3736
private static Func<object, Task<object>> _globalInitializationFunc;
3837
private static string _functionTemplate;
3938
private static string _clearRequireCacheScript;
4039
private static string _globalInitializationScript;
41-
private static bool _initialized = false;
40+
private static Lazy<Task> _initializer = new Lazy<Task>(InitializeAsync, LazyThreadSafetyMode.ExecutionAndPublication);
4241

4342
static NodeFunctionInvoker()
4443
{
@@ -109,7 +108,8 @@ private static Func<object, Task<object>> ClearRequireCacheFunc
109108

110109
protected override async Task InvokeCore(object[] parameters, FunctionInvocationContext context)
111110
{
112-
EnsureInitialized();
111+
// Ensure we're properly initialized
112+
await _initializer.Value.ConfigureAwait(false);
113113

114114
object input = parameters[0];
115115
string invocationId = context.ExecutionContext.InvocationId.ToString();
@@ -126,21 +126,6 @@ protected override async Task InvokeCore(object[] parameters, FunctionInvocation
126126
await ProcessOutputBindingsAsync(_outputBindings, input, context.Binder, bindingData, scriptExecutionContext, functionResult);
127127
}
128128

129-
private static void EnsureInitialized()
130-
{
131-
if (!_initialized)
132-
{
133-
lock (_initializationSyncRoot)
134-
{
135-
if (!_initialized)
136-
{
137-
Initialize();
138-
_initialized = true;
139-
}
140-
}
141-
}
142-
}
143-
144129
private async Task ProcessInputBindingsAsync(Binder binder, Dictionary<string, object> executionContext, Dictionary<string, object> bindingData)
145130
{
146131
var bindings = (Dictionary<string, object>)executionContext["bindings"];
@@ -238,9 +223,9 @@ private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding>
238223
}
239224
}
240225

241-
internal static void OnHostRestart()
226+
internal static async Task OnHostRestartAsync()
242227
{
243-
ClearRequireCacheFunc(null).GetAwaiter().GetResult();
228+
await ClearRequireCacheFunc(null);
244229
}
245230

246231
protected override void OnScriptFileChanged(object sender, FileSystemEventArgs e)
@@ -621,7 +606,7 @@ private static bool TryDeserializeJson<TResult>(string json, out TResult result)
621606
/// <summary>
622607
/// Performs required static initialization in the Edge context.
623608
/// </summary>
624-
private static void Initialize()
609+
private static async Task InitializeAsync()
625610
{
626611
var handle = (Func<object, Task<object>>)(err =>
627612
{
@@ -642,7 +627,7 @@ private static void Initialize()
642627
{ "handleUncaughtException", handle }
643628
};
644629

645-
GlobalInitializationFunc(context).GetAwaiter().GetResult();
630+
await GlobalInitializationFunc(context);
646631
}
647632
}
648633
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
8+
namespace Microsoft.Azure.WebJobs.Script
9+
{
10+
public static class FuncExtensions
11+
{
12+
public static Func<T, Task> Debounce<T>(this Func<T, Task> func, int milliseconds = 300)
13+
{
14+
var last = 0;
15+
16+
return async (arg) =>
17+
{
18+
var current = Interlocked.Increment(ref last);
19+
20+
await Task.Delay(milliseconds);
21+
22+
if (current == last)
23+
{
24+
await func(arg);
25+
}
26+
};
27+
}
28+
29+
public static Func<Task> Debounce(this Func<Task> targetAction, int milliseconds = 300)
30+
{
31+
Func<object, Task> action = _ => targetAction();
32+
action = action.Debounce(milliseconds);
33+
34+
return () => action(null);
35+
}
36+
}
37+
}

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class ScriptHost : JobHost
4242
internal const string GeneratedTypeName = "Functions";
4343
private readonly IScriptHostEnvironment _scriptHostEnvironment;
4444
private string _instanceId;
45-
private Action _restart;
45+
private Func<Task> _restart;
4646
private Action _shutdown;
4747
private AutoRecoveringFileSystemWatcher _scriptFileWatcher;
4848
private AutoRecoveringFileSystemWatcher _debugModeFileWatcher;
@@ -349,7 +349,7 @@ protected virtual void Initialize()
349349
// This allows us to deal with a large set of file change events that might
350350
// result from a bulk copy/unzip operation. In such cases, we only want to
351351
// restart after ALL the operations are complete and there is a quiet period.
352-
_restart = Restart;
352+
_restart = RestartAsync;
353353
_restart = _restart.Debounce(500);
354354

355355
_shutdown = Shutdown;
@@ -444,7 +444,7 @@ internal static Collection<CustomAttributeBuilder> CreateTypeAttributes(ScriptHo
444444
return customAttributes;
445445
}
446446

447-
internal void Restart()
447+
internal async Task RestartAsync()
448448
{
449449
if (_shutdownScheduled)
450450
{
@@ -458,7 +458,7 @@ internal void Restart()
458458
#if FEATURE_NODE
459459
// whenever we're restarting the host, we want to let the Node
460460
// invoker know so it can clear the require cache, etc.
461-
NodeFunctionInvoker.OnHostRestart();
461+
await NodeFunctionInvoker.OnHostRestartAsync();
462462
#endif
463463
}
464464

@@ -1233,11 +1233,12 @@ private void OnFileChanged(object sender, FileSystemEventArgs e)
12331233
}
12341234

12351235
TraceFileChangeRestart(e.ChangeType.ToString(), e.FullPath, shutdown);
1236-
ScheduleRestart(shutdown);
1236+
ScheduleRestartAsync(shutdown).ContinueWith(t => TraceWriter.Error($"Error restarting host (full shutdown: {shutdown})", t.Exception),
1237+
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted);
12371238
}
12381239
}
12391240

1240-
private void ScheduleRestart(bool shutdown)
1241+
private async Task ScheduleRestartAsync(bool shutdown)
12411242
{
12421243
if (shutdown)
12431244
{
@@ -1246,7 +1247,7 @@ private void ScheduleRestart(bool shutdown)
12461247
}
12471248
else
12481249
{
1249-
_restart();
1250+
await _restart();
12501251
}
12511252
}
12521253

src/WebJobs.Script/WebJobs.Script.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@
423423
<Compile Include="Extensions\AssemblyExtensions.cs" />
424424
<Compile Include="Extensions\ExceptionExtensions.cs" />
425425
<Compile Include="Extensions\FileUtility.cs" />
426+
<Compile Include="Extensions\FuncExtensions.cs" />
426427
<Compile Include="Extensions\HttpRequestMessageExtensions.cs" />
427428
<Compile Include="Extensions\IDictionaryExtensions.cs" />
428429
<Compile Include="Extensions\IMetricsLoggerExtensions.cs" />

0 commit comments

Comments
 (0)