1010using System . Linq ;
1111using System . Reflection ;
1212using System . Text ;
13+ using System . Threading ;
1314using System . Threading . Tasks ;
1415using Microsoft . Azure . WebJobs . Host ;
1516using Microsoft . Azure . WebJobs . Script . Binding ;
@@ -32,13 +33,14 @@ public class CSharpFunctionInvoker : ScriptFunctionInvokerBase
3233 private readonly Collection < FunctionBinding > _outputBindings ;
3334 private readonly IFunctionEntryPointResolver _functionEntryPointResolver ;
3435 private readonly IMetricsLogger _metrics ;
36+ private readonly ReaderWriterLockSlim _functionValueLoaderLock = new ReaderWriterLockSlim ( ) ;
3537
36- private MethodInfo _function ;
3738 private CSharpFunctionSignature _functionSignature ;
3839 private FunctionMetadataResolver _metadataResolver ;
3940 private Action _reloadScript ;
4041 private Action _restorePackages ;
41- private Action < object [ ] , object > _resultProcessor ;
42+ private Action < MethodInfo , object [ ] , object > _resultProcessor ;
43+ private FunctionValueLoader _functionValueLoader ;
4244
4345 private static readonly string [ ] WatchedFileTypes = { ".cs" , ".csx" , ".dll" , ".exe" } ;
4446
@@ -59,6 +61,8 @@ internal CSharpFunctionInvoker(ScriptHost host, FunctionMetadata functionMetadat
5961 InitializeFileWatcherIfEnabled ( ) ;
6062 _resultProcessor = CreateResultProcessor ( ) ;
6163
64+ _functionValueLoader = FunctionValueLoader . Create ( CreateFunctionTarget ) ;
65+
6266 _reloadScript = ReloadScript ;
6367 _reloadScript = _reloadScript . Debounce ( ) ;
6468
@@ -97,41 +101,55 @@ protected override void OnScriptFileChanged(object sender, FileSystemEventArgs e
97101 private void ReloadScript ( )
98102 {
99103 // Reset cached function
100- _function = null ;
104+ ResetFunctionValue ( ) ;
101105 TraceWriter . Verbose ( string . Format ( CultureInfo . InvariantCulture , "Script for function '{0}' changed. Reloading." , Metadata . Name ) ) ;
102106
103107 TraceWriter . Verbose ( "Compiling function script." ) ;
104- var stopwatch = new Stopwatch ( ) ;
105- stopwatch . Start ( ) ;
106108
107109 Script < object > script = CreateScript ( ) ;
108110 Compilation compilation = script . GetCompilation ( ) ;
109111 ImmutableArray < Diagnostic > compilationResult = compilation . GetDiagnostics ( ) ;
110112
111- stopwatch . Stop ( ) ;
112-
113113 CSharpFunctionSignature signature = CSharpFunctionSignature . FromCompilation ( compilation , _functionEntryPointResolver ) ;
114114 compilationResult = ValidateFunctionBindingArguments ( signature , compilationResult . ToBuilder ( ) ) ;
115-
115+
116116 TraceCompilationDiagnostics ( compilationResult ) ;
117117
118118 bool compilationSucceeded = ! compilationResult . Any ( d => d . Severity == DiagnosticSeverity . Error ) ;
119119
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" ) ) ;
122122
123123 // If the compilation succeeded, AND:
124124 // - We haven't cached a function (failed to compile on load), OR
125125 // - We're referencing local function types (i.e. POCOs defined in the function) AND Our our function signature has changed
126126 // Restart our host.
127- if ( compilationSucceeded &&
128- ( _functionSignature == null ||
127+ if ( compilationSucceeded &&
128+ ( _functionSignature == null ||
129129 ( _functionSignature . HasLocalTypeReference || ! _functionSignature . Equals ( signature ) ) ) )
130130 {
131131 _host . RestartEvent . Set ( ) ;
132132 }
133133 }
134134
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+
135153 private void RestorePackages ( )
136154 {
137155 TraceWriter . Verbose ( "Restoring packages." ) ;
@@ -146,7 +164,7 @@ private void RestorePackages()
146164 return ;
147165 }
148166
149- TraceWriter . Verbose ( "Packages restored." ) ;
167+ TraceWriter . Verbose ( "Packages restored." ) ;
150168 _reloadScript ( ) ;
151169 } ) ;
152170 }
@@ -162,8 +180,8 @@ public override async Task Invoke(object[] parameters)
162180
163181 parameters = ProcessInputParameters ( parameters ) ;
164182
165- MethodInfo function = GetFunctionTarget ( ) ;
166-
183+ MethodInfo function = await GetFunctionTargetAsync ( ) ;
184+
167185 object functionResult = function . Invoke ( null , parameters ) ;
168186
169187 if ( functionResult is Task )
@@ -173,7 +191,7 @@ public override async Task Invoke(object[] parameters)
173191
174192 if ( functionResult != null )
175193 {
176- _resultProcessor ( parameters , functionResult ) ;
194+ _resultProcessor ( function , parameters , functionResult ) ;
177195 }
178196
179197 TraceWriter . Verbose ( "Function completed (Success)" ) ;
@@ -208,55 +226,82 @@ private object[] ProcessInputParameters(object[] parameters)
208226 return parameters ;
209227 }
210228
211- internal MethodInfo GetFunctionTarget ( )
229+ internal async Task < MethodInfo > GetFunctionTargetAsync ( int attemptCount = 0 )
212230 {
213- if ( _function == null )
231+ FunctionValueLoader currentValueLoader ;
232+ _functionValueLoaderLock . EnterReadLock ( ) ;
233+ try
234+ {
235+ currentValueLoader = _functionValueLoader ;
236+ }
237+ finally
214238 {
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+ }
219241
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 )
221251 {
222- _assemblyLoader . ReleaseContext ( Metadata ) ;
252+ throw ;
253+ }
254+ }
223255
224- Script < object > script = CreateScript ( ) ;
225- Compilation compilation = GetScriptCompilation ( script , debug ) ;
226- CSharpFunctionSignature functionSignature = CSharpFunctionSignature . FromCompilation ( compilation , _functionEntryPointResolver ) ;
256+ return await GetFunctionTargetAsync ( ++ attemptCount ) ;
257+ }
227258
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 ;
229265
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 ) ;
235271
236- if ( ! result . Success )
237- {
238- throw new CompilationErrorException ( "Script compilation failed." , result . Diagnostics ) ;
239- }
272+ ValidateFunctionBindingArguments ( functionSignature , throwIfFailed : true ) ;
240273
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 ) ;
243279
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 ) ;
248287 }
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 ( ) ) ;
249296 }
250297 }
251- catch ( CompilationErrorException ex )
252- {
253- TraceWriter . Error ( "Function compilation error" ) ;
254- TraceCompilationDiagnostics ( ex . Diagnostics ) ;
255- throw ;
256- }
257298 }
258-
259- return _function ;
299+ catch ( CompilationErrorException ex )
300+ {
301+ TraceWriter . Error ( "Function compilation error" ) ;
302+ TraceCompilationDiagnostics ( ex . Diagnostics ) ;
303+ throw ;
304+ }
260305 }
261306
262307 private void TraceCompilationDiagnostics ( ImmutableArray < Diagnostic > diagnostics )
@@ -276,7 +321,7 @@ private ImmutableArray<Diagnostic> ValidateFunctionBindingArguments(CSharpFuncti
276321 if ( ! functionSignature . Parameters . Any ( p => string . Compare ( p . Name , _triggerInputName , StringComparison . Ordinal ) == 0 ) )
277322 {
278323 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 ,
280325 "Missing trigger argument" , message , "AzureFunctions" , DiagnosticSeverity . Error , true ) ;
281326
282327 resultBuilder . Add ( Diagnostic . Create ( descriptor , Location . None ) ) ;
@@ -294,7 +339,7 @@ private ImmutableArray<Diagnostic> ValidateFunctionBindingArguments(CSharpFuncti
294339 if ( ! functionSignature . Parameters . Any ( p => string . Compare ( p . Name , binding . Name , StringComparison . Ordinal ) == 0 ) )
295340 {
296341 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 ,
298343 "Missing binding argument" , message , "AzureFunctions" , DiagnosticSeverity . Warning , true ) ;
299344
300345 resultBuilder . Add ( Diagnostic . Create ( descriptor , Location . None ) ) ;
@@ -330,8 +375,9 @@ private Compilation GetScriptCompilation(Script<object> script, bool debug)
330375 . RemoveAllSyntaxTrees ( )
331376 . AddSyntaxTrees ( scriptTree ) ;
332377 }
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 ) ) ;
335381 }
336382
337383 private Script < object > CreateScript ( )
@@ -357,16 +403,16 @@ private static object GetTaskResult(Task task)
357403 return null ;
358404 }
359405
360- private Action < object [ ] , object > CreateResultProcessor ( )
406+ private Action < MethodInfo , object [ ] , object > CreateResultProcessor ( )
361407 {
362408 var bindings = _inputBindings . Union ( _outputBindings ) . OfType < IResultProcessingBinding > ( ) ;
363409
364- Action < object [ ] , object > processor = null ;
410+ Action < MethodInfo , object [ ] , object > processor = null ;
365411 if ( bindings . Any ( ) )
366412 {
367- processor = ( args , result ) =>
413+ processor = ( function , args , result ) =>
368414 {
369- ParameterInfo parameter = _function . GetParameters ( )
415+ ParameterInfo parameter = function . GetParameters ( )
370416 . FirstOrDefault ( p => string . Compare ( p . Name , _triggerInputName , StringComparison . Ordinal ) == 0 ) ;
371417
372418 if ( parameter != null )
@@ -383,7 +429,7 @@ private Action<object[], object> CreateResultProcessor()
383429 } ;
384430 }
385431
386- return processor ?? ( ( _ , __ ) => { /*noop*/ } ) ;
432+ return processor ?? ( ( _ , __ , ___ ) => { /*noop*/ } ) ;
387433 }
388434
389435 private static TraceLevel GetTraceLevelFromDiagnostic ( Diagnostic diagnostic )
0 commit comments