@@ -243,24 +243,27 @@ private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding>
243
243
return ;
244
244
}
245
245
246
- var bindings = ( Dictionary < string , object > ) scriptExecutionContext [ "bindings" ] ;
246
+ var functionResultDictionary = ( IDictionary < string , object > ) functionResult ;
247
+ var returnValue = functionResultDictionary [ "returnValue" ] ;
248
+ var bindingValues = ( IDictionary < string , object > ) functionResultDictionary [ "bindingValues" ] ;
249
+
247
250
var returnValueBinding = outputBindings . SingleOrDefault ( p => p . Metadata . IsReturn ) ;
248
251
if ( returnValueBinding != null )
249
252
{
250
253
// if there is a $return binding, bind the entire function return to it
251
254
// if additional output bindings need to be bound, they'll have to use the explicit
252
255
// context.bindings mechanism to set values, not return value.
253
- bindings [ ScriptConstants . SystemReturnParameterBindingName ] = functionResult ;
256
+ bindingValues [ ScriptConstants . SystemReturnParameterBindingName ] = returnValue ;
254
257
}
255
258
else
256
259
{
257
260
// if the function returned binding values via the function result,
258
261
// apply them to context.bindings
259
- if ( functionResult is IDictionary < string , object > functionOutputs )
262
+ if ( returnValue is IDictionary < string , object > functionOutputs )
260
263
{
261
264
foreach ( var output in functionOutputs )
262
265
{
263
- bindings [ output . Key ] = output . Value ;
266
+ bindingValues [ output . Key ] = output . Value ;
264
267
}
265
268
}
266
269
}
@@ -269,13 +272,13 @@ private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding>
269
272
{
270
273
// get the output value from the script
271
274
object value = null ;
272
- bool haveValue = bindings . TryGetValue ( binding . Metadata . Name , out value ) ;
275
+ bool haveValue = bindingValues . TryGetValue ( binding . Metadata . Name , out value ) ;
273
276
if ( ! haveValue && string . Compare ( binding . Metadata . Type , "http" , StringComparison . OrdinalIgnoreCase ) == 0 )
274
277
{
275
278
// http bindings support a special context.req/context.res programming
276
279
// model, so we must map that back to the actual binding name if a value
277
280
// wasn't provided using the binding name itself
278
- haveValue = bindings . TryGetValue ( "res" , out value ) ;
281
+ haveValue = bindingValues . TryGetValue ( "res" , out value ) ;
279
282
}
280
283
281
284
// apply the value to the binding
@@ -348,70 +351,14 @@ protected override void Dispose(bool disposing)
348
351
349
352
private async Task < Dictionary < string , object > > CreateScriptExecutionContextAsync ( object input , DataType dataType , TraceWriter traceWriter , FunctionInvocationContext invocationContext )
350
353
{
351
- // create a TraceWriter wrapper that can be exposed to Node.js
352
- var log = ( ScriptFunc ) ( p =>
353
- {
354
- var logData = ( IDictionary < string , object > ) p ;
355
- string message = ( string ) logData [ "msg" ] ;
356
- if ( message != null )
357
- {
358
- try
359
- {
360
- TraceLevel level = ( TraceLevel ) logData [ "lvl" ] ;
361
- var evt = new TraceEvent ( level , message ) ;
362
-
363
- // Node captures the AsyncLocal value of the first invocation, which means that logs
364
- // are correlated incorrectly. Here we'll overwrite that value with the correct value
365
- // immediately before logging.
366
- using ( BeginFunctionScope ( invocationContext ) )
367
- {
368
- // TraceWriter already logs to ILogger
369
- traceWriter . Trace ( evt ) ;
370
- }
371
- }
372
- catch ( ObjectDisposedException )
373
- {
374
- // if a function attempts to write to a disposed
375
- // TraceWriter. Might happen if a function tries to
376
- // log after calling done()
377
- }
378
- }
379
-
380
- return Task . FromResult < object > ( null ) ;
381
- } ) ;
382
-
383
- var metric = ( ScriptFunc ) ( p =>
384
- {
385
- var metricObject = ( IDictionary < string , object > ) p ;
386
- string name = ( string ) metricObject [ "name" ] ;
387
- object valueObject = metricObject [ "value" ] ;
388
-
389
- if ( name != null && valueObject != null )
390
- {
391
- double value = Convert . ToDouble ( valueObject ) ;
392
-
393
- // properties are optional
394
- var properties = ( IDictionary < string , object > ) metricObject [ "properties" ] ;
395
-
396
- using ( BeginFunctionScope ( invocationContext ) )
397
- {
398
- invocationContext . Logger . LogMetric ( name , value , properties ) ;
399
- }
400
- }
401
-
402
- return Task . FromResult < object > ( null ) ;
403
- } ) ;
404
-
405
- var bindings = new Dictionary < string , object > ( ) ;
406
- var bind = ( ScriptFunc ) ( p =>
407
- {
408
- IDictionary < string , object > bindValues = ( IDictionary < string , object > ) p ;
409
- foreach ( var bindValue in bindValues )
410
- {
411
- bindings [ bindValue . Key ] = bindValue . Value ;
412
- }
413
- return Task . FromResult < object > ( null ) ;
414
- } ) ;
354
+ // It's important that we use WeakReferences here for objects captured by
355
+ // these function closures. Otherwise, due to the nature of how we're using Edge.js
356
+ // we'll get memory leaks (we're establishing bi-directional communication between
357
+ // .NET and Node).
358
+ var traceWriterReference = new WeakReference < TraceWriter > ( traceWriter ) ;
359
+ var invocationContextReference = new WeakReference < FunctionInvocationContext > ( invocationContext ) ;
360
+ var log = CreateLoggerFunc ( traceWriterReference , invocationContextReference ) ;
361
+ var metric = CreateMetricFunc ( invocationContextReference ) ;
415
362
416
363
var executionContext = new Dictionary < string , object >
417
364
{
@@ -420,14 +367,14 @@ private async Task<Dictionary<string, object>> CreateScriptExecutionContextAsync
420
367
[ "functionDirectory" ] = invocationContext . ExecutionContext . FunctionDirectory ,
421
368
} ;
422
369
370
+ var bindings = new Dictionary < string , object > ( ) ;
423
371
var context = new Dictionary < string , object > ( )
424
372
{
425
373
{ "invocationId" , invocationContext . ExecutionContext . InvocationId } ,
426
374
{ "executionContext" , executionContext } ,
427
- { "log" , log } ,
428
- { "_metric" , metric } ,
429
375
{ "bindings" , bindings } ,
430
- { "bind" , bind }
376
+ { "log" , log } ,
377
+ { "_metric" , metric }
431
378
} ;
432
379
433
380
if ( ! string . IsNullOrEmpty ( _entryPoint ) )
@@ -495,6 +442,71 @@ private async Task<Dictionary<string, object>> CreateScriptExecutionContextAsync
495
442
return context ;
496
443
}
497
444
445
+ private static ScriptFunc CreateLoggerFunc ( WeakReference < TraceWriter > traceWriterReference , WeakReference < FunctionInvocationContext > invocationContextReference )
446
+ {
447
+ return ( ScriptFunc ) ( p =>
448
+ {
449
+ var logData = ( IDictionary < string , object > ) p ;
450
+ string message = ( string ) logData [ "msg" ] ;
451
+ if ( message != null )
452
+ {
453
+ try
454
+ {
455
+ // Node captures the AsyncLocal value of the first invocation, which means that logs
456
+ // are correlated incorrectly. Here we'll overwrite that value with the correct value
457
+ // immediately before logging.
458
+ TraceWriter traceWriter = null ;
459
+ FunctionInvocationContext invocationContext = null ;
460
+ if ( traceWriterReference . TryGetTarget ( out traceWriter ) &&
461
+ invocationContextReference . TryGetTarget ( out invocationContext ) )
462
+ {
463
+ using ( BeginFunctionScope ( invocationContext ) )
464
+ {
465
+ // TraceWriter already logs to ILogger
466
+ TraceLevel level = ( TraceLevel ) logData [ "lvl" ] ;
467
+ var evt = new TraceEvent ( level , message ) ;
468
+ traceWriter . Trace ( evt ) ;
469
+ }
470
+ }
471
+ }
472
+ catch ( ObjectDisposedException )
473
+ {
474
+ // if a function attempts to write to a disposed
475
+ // TraceWriter. Might happen if a function tries to
476
+ // log after calling done()
477
+ }
478
+ }
479
+
480
+ return Task . FromResult < object > ( null ) ;
481
+ } ) ;
482
+ }
483
+
484
+ private static ScriptFunc CreateMetricFunc ( WeakReference < FunctionInvocationContext > invocationContextReference )
485
+ {
486
+ return ( ScriptFunc ) ( p =>
487
+ {
488
+ var metricObject = ( IDictionary < string , object > ) p ;
489
+ string name = ( string ) metricObject [ "name" ] ;
490
+ object valueObject = metricObject [ "value" ] ;
491
+
492
+ if ( name != null && valueObject != null )
493
+ {
494
+ double value = Convert . ToDouble ( valueObject ) ;
495
+ FunctionInvocationContext invocationContext = null ;
496
+ if ( invocationContextReference . TryGetTarget ( out invocationContext ) )
497
+ {
498
+ using ( BeginFunctionScope ( invocationContext ) )
499
+ {
500
+ var properties = ( IDictionary < string , object > ) metricObject [ "properties" ] ;
501
+ invocationContext . Logger . LogMetric ( name , value , properties ) ;
502
+ }
503
+ }
504
+ }
505
+
506
+ return Task . FromResult < object > ( null ) ;
507
+ } ) ;
508
+ }
509
+
498
510
private static IDisposable BeginFunctionScope ( FunctionInvocationContext context )
499
511
{
500
512
// Node captures the AsyncLocal value of the first invocation, which means that logs
0 commit comments