Skip to content

Commit 1ea01c8

Browse files
Added shared bind cache for improved performance and enhanced binder leak mitigation.
1 parent 7df5fa2 commit 1ea01c8

5 files changed

Lines changed: 105 additions & 23 deletions

File tree

ClearScript/BindSignature.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,15 @@ namespace Microsoft.ClearScript
6565
{
6666
internal class BindSignature : IEquatable<BindSignature>
6767
{
68+
private readonly Type context;
6869
private readonly TargetInfo targetInfo;
6970
private readonly string name;
7071
private readonly Type[] typeArgs;
7172
private readonly ArgInfo[] argData;
7273

73-
public BindSignature(HostTarget target, string name, Type[] typeArgs, object[] args)
74+
public BindSignature(Type context, HostTarget target, string name, Type[] typeArgs, object[] args)
7475
{
76+
this.context = context;
7577
targetInfo = new TargetInfo(target);
7678
this.typeArgs = typeArgs;
7779
this.name = name;
@@ -94,6 +96,7 @@ public override int GetHashCode()
9496
{
9597
var accumulator = new HashAccumulator();
9698

99+
accumulator.Update(context);
97100
targetInfo.UpdateHash(ref accumulator);
98101
accumulator.Update(name);
99102

@@ -121,6 +124,11 @@ public bool Equals(BindSignature that)
121124
return false;
122125
}
123126

127+
if (context != that.context)
128+
{
129+
return false;
130+
}
131+
124132
if (!targetInfo.Equals(that.targetInfo))
125133
{
126134
return false;

ClearScript/HostItem.InvokeMethod.cs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@
6060
//
6161

6262
using System;
63+
using System.Collections.Concurrent;
6364
using System.Collections.Generic;
6465
using System.Diagnostics;
6566
using System.Dynamic;
6667
using System.Linq;
6768
using System.Linq.Expressions;
6869
using System.Reflection;
70+
using System.Threading;
6971
using Microsoft.CSharp.RuntimeBinder;
7072
using Microsoft.ClearScript.Util;
7173
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
@@ -74,6 +76,13 @@ namespace Microsoft.ClearScript
7476
{
7577
internal partial class HostItem
7678
{
79+
#region data
80+
81+
private static readonly ConcurrentDictionary<BindSignature, object> coreBindCache = new ConcurrentDictionary<BindSignature, object>();
82+
private static long coreBindCount;
83+
84+
#endregion
85+
7786
#region internal members
7887

7988
private object InvokeMethod(string name, object[] args, object[] bindArgs)
@@ -138,7 +147,8 @@ private MethodBindResult BindMethod(string name, Type[] typeArgs, object[] args,
138147
// WARNING: BindSignature holds on to the specified typeArgs; subsequent modification
139148
// will result in bugs that are difficult to diagnose. Create a copy if necessary.
140149

141-
var signature = new BindSignature(target, name, typeArgs, bindArgs);
150+
var context = accessContext ?? engine.AccessContext;
151+
var signature = new BindSignature(context, target, name, typeArgs, bindArgs);
142152
MethodBindResult result;
143153

144154
object rawResult;
@@ -148,7 +158,7 @@ private MethodBindResult BindMethod(string name, Type[] typeArgs, object[] args,
148158
}
149159
else
150160
{
151-
result = BindMethodInternal(name, typeArgs, args, bindArgs);
161+
result = BindMethodInternal(context, name, typeArgs, args, bindArgs);
152162
if (!result.IsPreferredMethod(name))
153163
{
154164
if (result is MethodBindSuccess)
@@ -158,7 +168,7 @@ private MethodBindResult BindMethod(string name, Type[] typeArgs, object[] args,
158168

159169
foreach (var altName in GetAltMethodNames(name))
160170
{
161-
var altResult = BindMethodInternal(altName, typeArgs, args, bindArgs);
171+
var altResult = BindMethodInternal(context, altName, typeArgs, args, bindArgs);
162172
if (altResult.IsUnblockedMethod())
163173
{
164174
result = altResult;
@@ -173,11 +183,32 @@ private MethodBindResult BindMethod(string name, Type[] typeArgs, object[] args,
173183
return result;
174184
}
175185

176-
private MethodBindResult BindMethodInternal(string name, Type[] typeArgs, object[] args, object[] bindArgs)
186+
private MethodBindResult BindMethodInternal(Type context, string name, Type[] typeArgs, object[] args, object[] bindArgs)
187+
{
188+
var signature = new BindSignature(context, target, name, typeArgs, bindArgs);
189+
MethodBindResult result;
190+
191+
object rawResult;
192+
if (coreBindCache.TryGetValue(signature, out rawResult))
193+
{
194+
result = MethodBindResult.Create(name, rawResult, target, args);
195+
}
196+
else
197+
{
198+
result = BindMethodCore(context, name, typeArgs, args, bindArgs);
199+
coreBindCache.TryAdd(signature, result.RawResult);
200+
}
201+
202+
return result;
203+
}
204+
205+
private MethodBindResult BindMethodCore(Type context, string name, Type[] typeArgs, object[] args, object[] bindArgs)
177206
{
207+
Interlocked.Increment(ref coreBindCount);
208+
178209
// create C# member invocation binder
179210
const CSharpBinderFlags binderFlags = CSharpBinderFlags.InvokeSimpleName | CSharpBinderFlags.ResultDiscarded;
180-
var binder = Binder.InvokeMember(binderFlags, name, typeArgs, accessContext ?? engine.AccessContext, CreateArgInfoEnum(bindArgs));
211+
var binder = Binder.InvokeMember(binderFlags, name, typeArgs, context, CreateArgInfoEnum(bindArgs));
181212

182213
// perform default binding
183214
var binding = DynamicHelpers.Bind((DynamicMetaObjectBinder)binder, target, bindArgs);
@@ -275,6 +306,20 @@ private static CSharpArgumentInfo CreateStaticTypeArgInfo()
275306

276307
#endregion
277308

309+
#region unit test support
310+
311+
internal static void ResetCoreBindCount()
312+
{
313+
Interlocked.Exchange(ref coreBindCount, 0);
314+
}
315+
316+
internal static long GetCoreBindCount()
317+
{
318+
return Interlocked.Read(ref coreBindCount);
319+
}
320+
321+
#endregion
322+
278323
#region Nested type: MethodBindResult
279324

280325
private abstract class MethodBindResult

ClearScript/Windows/WindowsScriptEngine.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public abstract partial class WindowsScriptEngine : ScriptEngine
8484
{
8585
#region data
8686

87+
private static readonly object nullDispatch = new DispatchWrapper(null);
88+
8789
private ActiveScriptWrapper activeScript;
8890
private WindowsScriptEngineFlags engineFlags;
8991

@@ -428,7 +430,7 @@ internal override object MarshalToScript(object obj, HostItemFlags flags)
428430
{
429431
if (engineFlags.HasFlag(WindowsScriptEngineFlags.MarshalNullAsDispatch))
430432
{
431-
return new DispatchWrapper(null);
433+
return nullDispatch;
432434
}
433435

434436
return DBNull.Value;

ClearScriptTest/BindSignatureTest.cs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,50 +110,56 @@ public void BindSignature_General()
110110
};
111111

112112
{
113-
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
114-
var sig2 = new BindSignature(new HostVariable<string>(null), "foo", typeArgs1, args1);
113+
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
114+
var sig2 = new BindSignature(typeof(ClearScriptTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
115115
AssertNotEqual(sig1, sig2);
116116
}
117117

118118
{
119-
var sig1 = new BindSignature(new HostVariable<string>(null), "foo", typeArgs1, args1);
120-
var sig2 = new BindSignature(HostObject.Wrap("baz"), "foo", typeArgs1, args1);
119+
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
120+
var sig2 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>(null), "foo", typeArgs1, args1);
121121
AssertNotEqual(sig1, sig2);
122122
}
123123

124124
{
125-
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
126-
var sig2 = new BindSignature(HostObject.Wrap("baz"), "foo", typeArgs1, args1);
125+
var sig1 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>(null), "foo", typeArgs1, args1);
126+
var sig2 = new BindSignature(typeof(BindSignatureTest), HostObject.Wrap("baz"), "foo", typeArgs1, args1);
127127
AssertNotEqual(sig1, sig2);
128128
}
129129

130130
{
131-
var sig1 = new BindSignature(new HostVariable<string>(null), "foo", typeArgs1, args1);
132-
var sig2 = new BindSignature(new HostVariable<string>("baz"), "foo", typeArgs1, args1);
131+
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
132+
var sig2 = new BindSignature(typeof(BindSignatureTest), HostObject.Wrap("baz"), "foo", typeArgs1, args1);
133133
AssertNotEqual(sig1, sig2);
134134
}
135135

136136
{
137-
var sig1 = new BindSignature(new HostVariable<string>("baz"), "foo", typeArgs1, args1);
138-
var sig2 = new BindSignature(HostObject.Wrap("qux"), "foo", typeArgs1, args1);
137+
var sig1 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>(null), "foo", typeArgs1, args1);
138+
var sig2 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>("baz"), "foo", typeArgs1, args1);
139+
AssertNotEqual(sig1, sig2);
140+
}
141+
142+
{
143+
var sig1 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>("baz"), "foo", typeArgs1, args1);
144+
var sig2 = new BindSignature(typeof(BindSignatureTest), HostObject.Wrap("qux"), "foo", typeArgs1, args1);
139145
AssertEqual(sig1, sig2);
140146
}
141147

142148
{
143-
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
144-
var sig2 = new BindSignature(HostType.Wrap(typeof(string)), "bar", typeArgs1, args1);
149+
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
150+
var sig2 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "bar", typeArgs1, args1);
145151
AssertNotEqual(sig1, sig2);
146152
}
147153

148154
{
149-
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
150-
var sig2 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs2, args1);
155+
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
156+
var sig2 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs2, args1);
151157
AssertNotEqual(sig1, sig2);
152158
}
153159

154160
{
155-
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
156-
var sig2 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args2);
161+
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
162+
var sig2 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args2);
157163
AssertEqual(sig1, sig2);
158164
}
159165
}

ClearScriptTest/BugFixTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,27 @@ End Module
379379
results.CompiledAssembly.GetType("TestModule").InvokeMember("TestMethod", BindingFlags.InvokeMethod, null, null, MiscHelpers.GetEmptyArray<object>());
380380
}
381381

382+
[TestMethod, TestCategory("BugFix")]
383+
public void BugFix_CoreBindCache()
384+
{
385+
HostItem.ResetCoreBindCount();
386+
387+
engine.Dispose();
388+
for (var i = 0; i < 10; i++)
389+
{
390+
using (engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
391+
{
392+
engine.Script.host = new HostFunctions();
393+
Assert.AreEqual(' ', engine.Evaluate("host.toChar(32)"));
394+
Assert.AreEqual('A', engine.Evaluate("host.toChar(65)"));
395+
Assert.AreEqual(0.0F, engine.Evaluate("host.toSingle(0)"));
396+
Assert.AreEqual(1.0F, engine.Evaluate("host.toSingle(1)"));
397+
}
398+
}
399+
400+
Assert.AreEqual(2L, HostItem.GetCoreBindCount());
401+
}
402+
382403
// ReSharper restore InconsistentNaming
383404

384405
#endregion

0 commit comments

Comments
 (0)