1- using System ;
1+ using System ;
22using System . Collections . Concurrent ;
33using System . Security . Cryptography ;
44using System . Text ;
55using System . Threading . Tasks ;
6+ using Foundatio . AsyncEx ;
67using Foundatio . Serializer ;
78using Jint ;
89using Jint . Native ;
@@ -20,25 +21,21 @@ public CalculatedIntegerFieldType(ScriptService scriptService)
2021 _scriptService = scriptService ;
2122 }
2223
23- public override Task < ProcessFieldValueResult > ProcessValueAsync < T > ( T document , object value , CustomFieldDefinition fieldDefinition ) where T : class
24+ public override async Task < ProcessFieldValueResult > ProcessValueAsync < T > ( T document , object value , CustomFieldDefinition fieldDefinition ) where T : class
2425 {
2526 if ( ! fieldDefinition . Data . TryGetValue ( "Expression" , out object expression ) )
26- return base . ProcessValueAsync ( document , value , fieldDefinition ) ;
27+ return await base . ProcessValueAsync ( document , value , fieldDefinition ) ;
2728
28- string functionName = _scriptService . EnsureExpressionFunction ( expression . ToString ( ) ) ;
29- _scriptService . SetSource ( document ) ;
30-
31- var calculatedValue = _scriptService . GetValue ( functionName ) ;
29+ var calculatedValue = await _scriptService . EvaluateForSourceAsync ( document , expression . ToString ( ) ) ;
3230
3331 // TODO: Implement a consecutive errors counter that disables badly behaving expressions
34-
3532 if ( calculatedValue . IsCancelled )
36- return Task . FromResult ( new ProcessFieldValueResult { Value = null } ) ;
33+ return new ProcessFieldValueResult { Value = null } ;
3734
38- if ( calculatedValue . Value is double doubleValue && Double . IsNaN ( doubleValue ) )
39- return Task . FromResult ( new ProcessFieldValueResult { Value = null } ) ;
35+ if ( calculatedValue . Value is Double . NaN )
36+ return new ProcessFieldValueResult { Value = null } ;
4037
41- return Task . FromResult ( new ProcessFieldValueResult { Value = calculatedValue . Value } ) ;
38+ return new ProcessFieldValueResult { Value = calculatedValue . Value } ;
4239 }
4340}
4441
@@ -47,6 +44,7 @@ public class ScriptService
4744 private readonly ITextSerializer _serializer ;
4845 private readonly ILogger < ScriptService > _logger ;
4946 private readonly ConcurrentDictionary < string , string > _registeredExpressions = new ( ) ;
47+ private readonly AsyncLock _lock = new ( ) ;
5048
5149 public ScriptService ( ITextSerializer jsonSerializer , ILogger < ScriptService > logger )
5250 {
@@ -57,20 +55,50 @@ public ScriptService(ITextSerializer jsonSerializer, ILogger<ScriptService> logg
5755
5856 public Engine Engine { get ; }
5957
58+ /// <summary>
59+ /// Thread-safe method that atomically evaluates an expression for a given source document.
60+ /// The Jint Engine is not thread-safe, so this method uses a lock to ensure only one evaluation occurs at a time.
61+ /// </summary>
62+ public async Task < ScriptValueResult > EvaluateForSourceAsync < T > ( T source , string expression ) where T : class
63+ {
64+ using ( await _lock . LockAsync ( ) . ConfigureAwait ( false ) )
65+ {
66+ string functionName = EnsureExpressionFunctionInternal ( expression ) ;
67+ SetSourceInternal ( source ) ;
68+ return GetValueInternal ( functionName ) ;
69+ }
70+ }
71+
6072 public string EnsureExpressionFunction ( string expression )
6173 {
62- if ( _registeredExpressions . TryGetValue ( expression , out var functionName ) )
74+ using ( _lock . Lock ( ) )
75+ {
76+ return EnsureExpressionFunctionInternal ( expression ) ;
77+ }
78+ }
79+
80+ private string EnsureExpressionFunctionInternal ( string expression )
81+ {
82+ if ( _registeredExpressions . TryGetValue ( expression , out string functionName ) )
6383 return functionName ;
6484
6585 functionName = "_" + ComputeSha256Hash ( expression ) ;
66- RegisterFunction ( functionName , expression ) ;
86+ RegisterFunctionInternal ( functionName , expression ) ;
6787
6888 _registeredExpressions . TryAdd ( expression , functionName ) ;
6989
7090 return functionName ;
7191 }
7292
7393 public void RegisterFunction ( string name , string body )
94+ {
95+ using ( _lock . Lock ( ) )
96+ {
97+ RegisterFunctionInternal ( name , body ) ;
98+ }
99+ }
100+
101+ private void RegisterFunctionInternal ( string name , string body )
74102 {
75103 if ( String . IsNullOrEmpty ( name ) )
76104 throw new ArgumentNullException ( nameof ( name ) ) ;
@@ -83,7 +111,7 @@ public void RegisterFunction(string name, string body)
83111
84112 body = body . Trim ( ) ;
85113 string script ;
86- if ( body . StartsWith ( "{" ) )
114+ if ( body . StartsWith ( '{' ) )
87115 {
88116 script = $ "function { name } () { body } ";
89117 }
@@ -100,15 +128,32 @@ public void RegisterFunction(string name, string body)
100128 }
101129
102130 public void SetSource ( object source )
131+ {
132+ using ( _lock . Lock ( ) )
133+ {
134+ SetSourceInternal ( source ) ;
135+ }
136+ }
137+
138+ private void SetSourceInternal ( object source )
103139 {
104140 string json = _serializer . SerializeToString ( source ) ;
105- if ( json == null )
141+ if ( json is null )
106142 json = "null" ;
143+
107144 string script = $ "var source = { json } ;";
108145 Engine . Execute ( script ) ;
109146 }
110147
111148 public ScriptValueResult GetValue ( string functionName )
149+ {
150+ using ( _lock . Lock ( ) )
151+ {
152+ return GetValueInternal ( functionName ) ;
153+ }
154+ }
155+
156+ private ScriptValueResult GetValueInternal ( string functionName )
112157 {
113158 string script = $ "{ functionName } ()";
114159 try
@@ -126,6 +171,14 @@ public ScriptValueResult GetValue(string functionName)
126171 }
127172
128173 public object ExecuteExpression ( string expression )
174+ {
175+ using ( _lock . Lock ( ) )
176+ {
177+ return ExecuteExpressionInternal ( expression ) ;
178+ }
179+ }
180+
181+ private object ExecuteExpressionInternal ( string expression )
129182 {
130183 if ( String . IsNullOrEmpty ( expression ) )
131184 throw new ArgumentNullException ( nameof ( expression ) ) ;
@@ -140,7 +193,7 @@ public object ExecuteExpression(string expression)
140193 }
141194 }
142195
143- private bool IsValidJavaScriptIdentifier ( string identifier )
196+ private static bool IsValidJavaScriptIdentifier ( string identifier )
144197 {
145198 if ( String . IsNullOrEmpty ( identifier ) )
146199 return false ;
@@ -152,15 +205,14 @@ private bool IsValidJavaScriptIdentifier(string identifier)
152205 return Array . TrueForAll ( identifier . ToCharArray ( ) , c => Char . IsLetterOrDigit ( c ) || c == '_' ) ;
153206 }
154207
155- private string ComputeSha256Hash ( string value )
208+ private static string ComputeSha256Hash ( string value )
156209 {
157210 using var sha256Hash = SHA256 . Create ( ) ;
158-
159211 byte [ ] bytes = sha256Hash . ComputeHash ( Encoding . UTF8 . GetBytes ( value ) ) ;
160212
161213 var builder = new StringBuilder ( ) ;
162- for ( int i = 0 ; i < bytes . Length ; i ++ )
163- builder . Append ( bytes [ i ] . ToString ( "x2" ) ) ;
214+ foreach ( byte b in bytes )
215+ builder . Append ( b . ToString ( "x2" ) ) ;
164216
165217 return builder . ToString ( ) ;
166218 }
@@ -196,7 +248,7 @@ public ScriptValueResult(object value, bool isCancelled = false)
196248 public object Value { get ; }
197249 public bool IsCancelled { get ; }
198250
199- public static ScriptValueResult Cancelled = new ScriptValueResult ( null , true ) ;
251+ public static readonly ScriptValueResult Cancelled = new ( null , true ) ;
200252}
201253
202254public class JintEnumConverter : IObjectConverter
0 commit comments