10
10
using System . Linq ;
11
11
using System . Reflection ;
12
12
using System . Text ;
13
+ using System . Threading ;
13
14
using System . Threading . Tasks ;
14
15
using Microsoft . Azure . WebJobs . Host ;
15
16
using Microsoft . Azure . WebJobs . Script . Binding ;
@@ -32,13 +33,14 @@ public class CSharpFunctionInvoker : ScriptFunctionInvokerBase
32
33
private readonly Collection < FunctionBinding > _outputBindings ;
33
34
private readonly IFunctionEntryPointResolver _functionEntryPointResolver ;
34
35
private readonly IMetricsLogger _metrics ;
36
+ private readonly ReaderWriterLockSlim _functionValueLoaderLock = new ReaderWriterLockSlim ( ) ;
35
37
36
- private MethodInfo _function ;
37
38
private CSharpFunctionSignature _functionSignature ;
38
39
private FunctionMetadataResolver _metadataResolver ;
39
40
private Action _reloadScript ;
40
41
private Action _restorePackages ;
41
- private Action < object [ ] , object > _resultProcessor ;
42
+ private Action < MethodInfo , object [ ] , object > _resultProcessor ;
43
+ private FunctionValueLoader _functionValueLoader ;
42
44
43
45
private static readonly string [ ] WatchedFileTypes = { ".cs" , ".csx" , ".dll" , ".exe" } ;
44
46
@@ -59,6 +61,8 @@ internal CSharpFunctionInvoker(ScriptHost host, FunctionMetadata functionMetadat
59
61
InitializeFileWatcherIfEnabled ( ) ;
60
62
_resultProcessor = CreateResultProcessor ( ) ;
61
63
64
+ _functionValueLoader = FunctionValueLoader . Create ( CreateFunctionTarget ) ;
65
+
62
66
_reloadScript = ReloadScript ;
63
67
_reloadScript = _reloadScript . Debounce ( ) ;
64
68
@@ -97,41 +101,55 @@ protected override void OnScriptFileChanged(object sender, FileSystemEventArgs e
97
101
private void ReloadScript ( )
98
102
{
99
103
// Reset cached function
100
- _function = null ;
104
+ ResetFunctionValue ( ) ;
101
105
TraceWriter . Verbose ( string . Format ( CultureInfo . InvariantCulture , "Script for function '{0}' changed. Reloading." , Metadata . Name ) ) ;
102
106
103
107
TraceWriter . Verbose ( "Compiling function script." ) ;
104
- var stopwatch = new Stopwatch ( ) ;
105
- stopwatch . Start ( ) ;
106
108
107
109
Script < object > script = CreateScript ( ) ;
108
110
Compilation compilation = script . GetCompilation ( ) ;
109
111
ImmutableArray < Diagnostic > compilationResult = compilation . GetDiagnostics ( ) ;
110
112
111
- stopwatch . Stop ( ) ;
112
-
113
113
CSharpFunctionSignature signature = CSharpFunctionSignature . FromCompilation ( compilation , _functionEntryPointResolver ) ;
114
114
compilationResult = ValidateFunctionBindingArguments ( signature , compilationResult . ToBuilder ( ) ) ;
115
-
115
+
116
116
TraceCompilationDiagnostics ( compilationResult ) ;
117
117
118
118
bool compilationSucceeded = ! compilationResult . Any ( d => d . Severity == DiagnosticSeverity . Error ) ;
119
119
120
- TraceWriter . Verbose ( string . Format ( CultureInfo . InvariantCulture , "Compilation {0} ({1} ms) ." ,
121
- compilationSucceeded ? "succeeded" : "failed" , stopwatch . ElapsedMilliseconds ) ) ;
120
+ TraceWriter . Verbose ( string . Format ( CultureInfo . InvariantCulture , "Compilation {0}." ,
121
+ compilationSucceeded ? "succeeded" : "failed" ) ) ;
122
122
123
123
// If the compilation succeeded, AND:
124
124
// - We haven't cached a function (failed to compile on load), OR
125
125
// - We're referencing local function types (i.e. POCOs defined in the function) AND Our our function signature has changed
126
126
// Restart our host.
127
- if ( compilationSucceeded &&
128
- ( _functionSignature == null ||
127
+ if ( compilationSucceeded &&
128
+ ( _functionSignature == null ||
129
129
( _functionSignature . HasLocalTypeReference || ! _functionSignature . Equals ( signature ) ) ) )
130
130
{
131
131
_host . RestartEvent . Set ( ) ;
132
132
}
133
133
}
134
134
135
+ private void ResetFunctionValue ( )
136
+ {
137
+ _functionValueLoaderLock . EnterWriteLock ( ) ;
138
+ try
139
+ {
140
+ if ( _functionValueLoader != null )
141
+ {
142
+ _functionValueLoader . Dispose ( ) ;
143
+ }
144
+
145
+ _functionValueLoader = FunctionValueLoader . Create ( CreateFunctionTarget ) ;
146
+ }
147
+ finally
148
+ {
149
+ _functionValueLoaderLock . ExitWriteLock ( ) ;
150
+ }
151
+ }
152
+
135
153
private void RestorePackages ( )
136
154
{
137
155
TraceWriter . Verbose ( "Restoring packages." ) ;
@@ -146,7 +164,7 @@ private void RestorePackages()
146
164
return ;
147
165
}
148
166
149
- TraceWriter . Verbose ( "Packages restored." ) ;
167
+ TraceWriter . Verbose ( "Packages restored." ) ;
150
168
_reloadScript ( ) ;
151
169
} ) ;
152
170
}
@@ -162,8 +180,8 @@ public override async Task Invoke(object[] parameters)
162
180
163
181
parameters = ProcessInputParameters ( parameters ) ;
164
182
165
- MethodInfo function = GetFunctionTarget ( ) ;
166
-
183
+ MethodInfo function = await GetFunctionTargetAsync ( ) ;
184
+
167
185
object functionResult = function . Invoke ( null , parameters ) ;
168
186
169
187
if ( functionResult is Task )
@@ -173,7 +191,7 @@ public override async Task Invoke(object[] parameters)
173
191
174
192
if ( functionResult != null )
175
193
{
176
- _resultProcessor ( parameters , functionResult ) ;
194
+ _resultProcessor ( function , parameters , functionResult ) ;
177
195
}
178
196
179
197
TraceWriter . Verbose ( "Function completed (Success)" ) ;
@@ -208,55 +226,82 @@ private object[] ProcessInputParameters(object[] parameters)
208
226
return parameters ;
209
227
}
210
228
211
- internal MethodInfo GetFunctionTarget ( )
229
+ internal async Task < MethodInfo > GetFunctionTargetAsync ( int attemptCount = 0 )
212
230
{
213
- if ( _function == null )
231
+ FunctionValueLoader currentValueLoader ;
232
+ _functionValueLoaderLock . EnterReadLock ( ) ;
233
+ try
234
+ {
235
+ currentValueLoader = _functionValueLoader ;
236
+ }
237
+ finally
214
238
{
215
- // TODO:Get this from some context set in/by the host.
216
- bool debug = true ;
217
- MemoryStream assemblyStream = null ;
218
- MemoryStream pdbStream = null ;
239
+ _functionValueLoaderLock . ExitReadLock ( ) ;
240
+ }
219
241
220
- try
242
+ try
243
+ {
244
+ return await currentValueLoader ;
245
+ }
246
+ catch ( OperationCanceledException )
247
+ {
248
+ // If the current task we were awaiting on was cancelled due to a
249
+ // cache refresh, retry, which will use the new loader
250
+ if ( attemptCount > 2 )
221
251
{
222
- _assemblyLoader . ReleaseContext ( Metadata ) ;
252
+ throw ;
253
+ }
254
+ }
223
255
224
- Script < object > script = CreateScript ( ) ;
225
- Compilation compilation = GetScriptCompilation ( script , debug ) ;
226
- CSharpFunctionSignature functionSignature = CSharpFunctionSignature . FromCompilation ( compilation , _functionEntryPointResolver ) ;
256
+ return await GetFunctionTargetAsync ( ++ attemptCount ) ;
257
+ }
227
258
228
- ValidateFunctionBindingArguments ( functionSignature , throwIfFailed : true ) ;
259
+ private MethodInfo CreateFunctionTarget ( CancellationToken cancellationToken )
260
+ {
261
+ // TODO:Get this from some context set in/by the host.
262
+ bool debug = true ;
263
+ MemoryStream assemblyStream = null ;
264
+ MemoryStream pdbStream = null ;
229
265
230
- using ( assemblyStream = new MemoryStream ( ) )
231
- {
232
- using ( pdbStream = new MemoryStream ( ) )
233
- {
234
- var result = compilation . Emit ( assemblyStream , pdbStream ) ;
266
+ try
267
+ {
268
+ Script < object > script = CreateScript ( ) ;
269
+ Compilation compilation = GetScriptCompilation ( script , debug ) ;
270
+ CSharpFunctionSignature functionSignature = CSharpFunctionSignature . FromCompilation ( compilation , _functionEntryPointResolver ) ;
235
271
236
- if ( ! result . Success )
237
- {
238
- throw new CompilationErrorException ( "Script compilation failed." , result . Diagnostics ) ;
239
- }
272
+ ValidateFunctionBindingArguments ( functionSignature , throwIfFailed : true ) ;
240
273
241
- Assembly assembly = Assembly . Load ( assemblyStream . GetBuffer ( ) , pdbStream . GetBuffer ( ) ) ;
242
- _assemblyLoader . CreateContext ( Metadata , assembly , _metadataResolver ) ;
274
+ using ( assemblyStream = new MemoryStream ( ) )
275
+ {
276
+ using ( pdbStream = new MemoryStream ( ) )
277
+ {
278
+ var result = compilation . Emit ( assemblyStream , pdbStream ) ;
243
279
244
- // Get our function entry point
245
- System . Reflection . TypeInfo scriptType = assembly . DefinedTypes . FirstOrDefault ( t => string . Compare ( t . Name , ScriptClassName , StringComparison . Ordinal ) == 0 ) ;
246
- _function = _functionEntryPointResolver . GetFunctionEntryPoint ( scriptType . DeclaredMethods . ToList ( ) ) ;
247
- _functionSignature = functionSignature ;
280
+ // Check if cancellation was requested while we were compiling,
281
+ // and if so quit here.
282
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
283
+
284
+ if ( ! result . Success )
285
+ {
286
+ throw new CompilationErrorException ( "Script compilation failed." , result . Diagnostics ) ;
248
287
}
288
+
289
+ Assembly assembly = Assembly . Load ( assemblyStream . GetBuffer ( ) , pdbStream . GetBuffer ( ) ) ;
290
+ _assemblyLoader . CreateOrUpdateContext ( Metadata , assembly , _metadataResolver ) ;
291
+
292
+ // Get our function entry point
293
+ System . Reflection . TypeInfo scriptType = assembly . DefinedTypes . FirstOrDefault ( t => string . Compare ( t . Name , ScriptClassName , StringComparison . Ordinal ) == 0 ) ;
294
+ _functionSignature = functionSignature ;
295
+ return _functionEntryPointResolver . GetFunctionEntryPoint ( scriptType . DeclaredMethods . ToList ( ) ) ;
249
296
}
250
297
}
251
- catch ( CompilationErrorException ex )
252
- {
253
- TraceWriter . Error ( "Function compilation error" ) ;
254
- TraceCompilationDiagnostics ( ex . Diagnostics ) ;
255
- throw ;
256
- }
257
298
}
258
-
259
- return _function ;
299
+ catch ( CompilationErrorException ex )
300
+ {
301
+ TraceWriter . Error ( "Function compilation error" ) ;
302
+ TraceCompilationDiagnostics ( ex . Diagnostics ) ;
303
+ throw ;
304
+ }
260
305
}
261
306
262
307
private void TraceCompilationDiagnostics ( ImmutableArray < Diagnostic > diagnostics )
@@ -276,7 +321,7 @@ private ImmutableArray<Diagnostic> ValidateFunctionBindingArguments(CSharpFuncti
276
321
if ( ! functionSignature . Parameters . Any ( p => string . Compare ( p . Name , _triggerInputName , StringComparison . Ordinal ) == 0 ) )
277
322
{
278
323
string message = string . Format ( CultureInfo . InvariantCulture , "Missing a trigger argument named '{0}'." , _triggerInputName ) ;
279
- var descriptor = new DiagnosticDescriptor ( CSharpConstants . MissingTriggerArgumentCompilationCode ,
324
+ var descriptor = new DiagnosticDescriptor ( CSharpConstants . MissingTriggerArgumentCompilationCode ,
280
325
"Missing trigger argument" , message , "AzureFunctions" , DiagnosticSeverity . Error , true ) ;
281
326
282
327
resultBuilder . Add ( Diagnostic . Create ( descriptor , Location . None ) ) ;
@@ -294,7 +339,7 @@ private ImmutableArray<Diagnostic> ValidateFunctionBindingArguments(CSharpFuncti
294
339
if ( ! functionSignature . Parameters . Any ( p => string . Compare ( p . Name , binding . Name , StringComparison . Ordinal ) == 0 ) )
295
340
{
296
341
string message = string . Format ( CultureInfo . InvariantCulture , "Missing binding argument named '{0}'." , binding . Name ) ;
297
- var descriptor = new DiagnosticDescriptor ( CSharpConstants . MissingBindingArgumentCompilationCode ,
342
+ var descriptor = new DiagnosticDescriptor ( CSharpConstants . MissingBindingArgumentCompilationCode ,
298
343
"Missing binding argument" , message , "AzureFunctions" , DiagnosticSeverity . Warning , true ) ;
299
344
300
345
resultBuilder . Add ( Diagnostic . Create ( descriptor , Location . None ) ) ;
@@ -330,8 +375,9 @@ private Compilation GetScriptCompilation(Script<object> script, bool debug)
330
375
. RemoveAllSyntaxTrees ( )
331
376
. AddSyntaxTrees ( scriptTree ) ;
332
377
}
333
-
334
- return compilation . WithOptions ( compilation . Options . WithOptimizationLevel ( compilationOptimizationLevel ) ) ;
378
+
379
+ return compilation . WithOptions ( compilation . Options . WithOptimizationLevel ( compilationOptimizationLevel ) )
380
+ . WithAssemblyName ( FunctionAssemblyLoader . GetAssemblyNameFromMetadata ( Metadata , compilation . AssemblyName ) ) ;
335
381
}
336
382
337
383
private Script < object > CreateScript ( )
@@ -357,16 +403,16 @@ private static object GetTaskResult(Task task)
357
403
return null ;
358
404
}
359
405
360
- private Action < object [ ] , object > CreateResultProcessor ( )
406
+ private Action < MethodInfo , object [ ] , object > CreateResultProcessor ( )
361
407
{
362
408
var bindings = _inputBindings . Union ( _outputBindings ) . OfType < IResultProcessingBinding > ( ) ;
363
409
364
- Action < object [ ] , object > processor = null ;
410
+ Action < MethodInfo , object [ ] , object > processor = null ;
365
411
if ( bindings . Any ( ) )
366
412
{
367
- processor = ( args , result ) =>
413
+ processor = ( function , args , result ) =>
368
414
{
369
- ParameterInfo parameter = _function . GetParameters ( )
415
+ ParameterInfo parameter = function . GetParameters ( )
370
416
. FirstOrDefault ( p => string . Compare ( p . Name , _triggerInputName , StringComparison . Ordinal ) == 0 ) ;
371
417
372
418
if ( parameter != null )
@@ -383,7 +429,7 @@ private Action<object[], object> CreateResultProcessor()
383
429
} ;
384
430
}
385
431
386
- return processor ?? ( ( _ , __ ) => { /*noop*/ } ) ;
432
+ return processor ?? ( ( _ , __ , ___ ) => { /*noop*/ } ) ;
387
433
}
388
434
389
435
private static TraceLevel GetTraceLevelFromDiagnostic ( Diagnostic diagnostic )
0 commit comments