Skip to content

Commit 44ed277

Browse files
committed
Always execute Babel with non-pooled JS Engine
Babel uses a LOT of RAM, so let's just keep it totally separate from the pool for the moment. Potentially this will improve if we use V8 contexts rather than totally separate engines.
1 parent 0adb412 commit 44ed277

File tree

5 files changed

+49
-38
lines changed

5 files changed

+49
-38
lines changed

src/React.Core/Babel.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,9 @@ protected virtual JavaScriptWithSourceMap TransformWithHeader(
243243
/// <returns>JavaScript</returns>
244244
public virtual string Transform(string input, string filename = "unknown")
245245
{
246-
_environment.EnsureBabelLoaded();
247246
try
248247
{
249-
var output = _environment.ExecuteWithLargerStackIfRequired<string>(
248+
var output = _environment.ExecuteWithBabel<string>(
250249
"ReactNET_transform",
251250
input,
252251
_babelConfig,
@@ -272,10 +271,9 @@ public virtual JavaScriptWithSourceMap TransformWithSourceMap(
272271
string filename = "unknown"
273272
)
274273
{
275-
_environment.EnsureBabelLoaded();
276274
try
277275
{
278-
return _environment.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
276+
return _environment.ExecuteWithBabel<JavaScriptWithSourceMap>(
279277
"ReactNET_transform_sourcemap",
280278
input,
281279
_babelConfig,

src/React.Core/IReactEnvironment.cs

+6-8
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ public interface IReactEnvironment
5151
T Execute<T>(string function, params object[] args);
5252

5353
/// <summary>
54-
/// Attempts to execute the provided JavaScript code using the current engine. If an
55-
/// exception is thrown, retries the execution using a new thread (and hence a new engine)
54+
/// Attempts to execute the provided JavaScript code using a non-pooled JavaScript engine (ie.
55+
/// creates a new JS engine per-thread). This is because Babel uses a LOT of memory, so we
56+
/// should completely dispose any engines that have loaded Babel in order to conserve memory.
57+
///
58+
/// If an exception is thrown, retries the execution using a new thread (and hence a new engine)
5659
/// with a larger maximum stack size.
5760
/// This is required because JSXTransformer uses a huge stack which ends up being larger
5861
/// than what ASP.NET allows by default (256 KB).
@@ -61,7 +64,7 @@ public interface IReactEnvironment
6164
/// <param name="function">JavaScript function to execute</param>
6265
/// <param name="args">Arguments to pass to function</param>
6366
/// <returns>Result returned from JavaScript code</returns>
64-
T ExecuteWithLargerStackIfRequired<T>(string function, params object[] args);
67+
T ExecuteWithBabel<T>(string function, params object[] args);
6568

6669
/// <summary>
6770
/// Determines if the specified variable exists in the JavaScript engine
@@ -91,10 +94,5 @@ public interface IReactEnvironment
9194
/// Gets the JSX Transformer for this environment.
9295
/// </summary>
9396
IBabel Babel { get; }
94-
95-
/// <summary>
96-
/// Ensures that Babel has been loaded into the JavaScript engine.
97-
/// </summary>
98-
void EnsureBabelLoaded();
9997
}
10098
}

src/React.Core/ReactEnvironment.cs

+15-15
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,11 @@ public virtual string GetInitJavaScript()
297297
}
298298

299299
/// <summary>
300-
/// Attempts to execute the provided JavaScript code using the current engine. If an
301-
/// exception is thrown, retries the execution using a new thread (and hence a new engine)
300+
/// Attempts to execute the provided JavaScript code using a non-pooled JavaScript engine (ie.
301+
/// creates a new JS engine per-thread). This is because Babel uses a LOT of memory, so we
302+
/// should completely dispose any engines that have loaded Babel in order to conserve memory.
303+
///
304+
/// If an exception is thrown, retries the execution using a new thread (and hence a new engine)
302305
/// with a larger maximum stack size.
303306
/// This is required because JSXTransformer uses a huge stack which ends up being larger
304307
/// than what ASP.NET allows by default (256 KB).
@@ -307,18 +310,14 @@ public virtual string GetInitJavaScript()
307310
/// <param name="function">JavaScript function to execute</param>
308311
/// <param name="args">Arguments to pass to function</param>
309312
/// <returns>Result returned from JavaScript code</returns>
310-
public virtual T ExecuteWithLargerStackIfRequired<T>(string function, params object[] args)
313+
public virtual T ExecuteWithBabel<T>(string function, params object[] args)
311314
{
312-
// This hack is not required when pooling JavaScript engines, since pooled MSIE
313-
// engines already execute on their own thread with a larger stack.
314-
if (_config.ReuseJavaScriptEngines)
315-
{
316-
return Execute<T>(function, args);
317-
}
315+
var engine = _engineFactory.GetEngineForCurrentThread();
316+
EnsureBabelLoaded(engine);
318317

319318
try
320319
{
321-
return Execute<T>(function, args);
320+
return engine.CallFunctionReturningJson<T>(function, args);
322321
}
323322
catch (Exception)
324323
{
@@ -330,10 +329,11 @@ public virtual T ExecuteWithLargerStackIfRequired<T>(string function, params obj
330329
var thread = new Thread(() =>
331330
{
332331
// New engine will be created here (as this is a new thread)
333-
var engine = _engineFactory.GetEngineForCurrentThread();
332+
var threadEngine = _engineFactory.GetEngineForCurrentThread();
333+
EnsureBabelLoaded(threadEngine);
334334
try
335335
{
336-
result = engine.CallFunctionReturningJson<T>(function, args);
336+
result = threadEngine.CallFunctionReturningJson<T>(function, args);
337337
}
338338
catch (Exception threadEx)
339339
{
@@ -417,18 +417,18 @@ public virtual IReactSiteConfiguration Configuration
417417
/// <summary>
418418
/// Ensures that Babel has been loaded into the JavaScript engine.
419419
/// </summary>
420-
public void EnsureBabelLoaded()
420+
private void EnsureBabelLoaded(IJsEngine engine)
421421
{
422422
// If Babel is disabled in the config, don't even try loading it
423423
if (!_config.LoadBabel)
424424
{
425425
throw new BabelNotLoadedException();
426426
}
427427

428-
var babelLoaded = Engine.Evaluate<bool>("typeof global.Babel !== 'undefined'");
428+
var babelLoaded = engine.Evaluate<bool>("typeof global.Babel !== 'undefined'");
429429
if (!babelLoaded)
430430
{
431-
Engine.ExecuteResource("React.node_modules.babel_core.browser.js", typeof(ReactEnvironment).Assembly);
431+
engine.ExecuteResource("React.node_modules.babel_core.browser.js", typeof(ReactEnvironment).Assembly);
432432
}
433433
}
434434
}

src/React.Tests/Core/BabelTransformerTests.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void ShouldTransformJsx()
4949
const string input = "<div>Hello World</div>";
5050
_babel.Transform(input);
5151

52-
_environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
52+
_environment.Verify(x => x.ExecuteWithBabel<string>(
5353
"ReactNET_transform",
5454
"<div>Hello World</div>",
5555
It.IsAny<string>(),
@@ -60,7 +60,7 @@ public void ShouldTransformJsx()
6060
[Test]
6161
public void ShouldWrapExceptionsInJsxExeption()
6262
{
63-
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<string>(
63+
_environment.Setup(x => x.ExecuteWithBabel<string>(
6464
"ReactNET_transform",
6565
"<div>Hello World</div>",
6666
It.IsAny<string>(),
@@ -103,7 +103,7 @@ public void ShouldTransformJsxIfFileCacheHashInvalid()
103103
_fileSystem.Setup(x => x.ReadAsString("foo.generated.js")).Returns("/* filesystem cached invalid */");
104104
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("<div>Hello World</div>");
105105
_fileCacheHash.Setup(x => x.ValidateHash(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
106-
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
106+
_environment.Setup(x => x.ExecuteWithBabel<JavaScriptWithSourceMap>(
107107
"ReactNET_transform_sourcemap",
108108
It.IsAny<string>(),
109109
It.IsAny<string>(), // Babel config
@@ -120,7 +120,7 @@ public void ShouldTransformJsxIfNoCache()
120120
SetUpEmptyCache();
121121
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(false);
122122
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("<div>Hello World</div>");
123-
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
123+
_environment.Setup(x => x.ExecuteWithBabel<JavaScriptWithSourceMap>(
124124
"ReactNET_transform_sourcemap",
125125
It.IsAny<string>(),
126126
It.IsAny<string>(), // Babel config
@@ -135,7 +135,7 @@ public void ShouldTransformJsxIfNoCache()
135135
public void ShouldSaveTransformationResult()
136136
{
137137
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("<div>Hello World</div>");
138-
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
138+
_environment.Setup(x => x.ExecuteWithBabel<JavaScriptWithSourceMap>(
139139
"ReactNET_transform_sourcemap",
140140
It.IsAny<string>(),
141141
It.IsAny<string>(), // Babel config

src/React.Tests/Core/ReactEnvironmentTest.cs

+21-6
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,25 @@
1313
using JavaScriptEngineSwitcher.Core;
1414
using Moq;
1515
using NUnit.Framework;
16+
using React.Exceptions;
1617

1718
namespace React.Tests.Core
1819
{
1920
[TestFixture]
2021
public class ReactEnvironmentTest
2122
{
2223
[Test]
23-
public void ExecuteWithLargerStackIfRequiredWithNoNewThread()
24+
public void ExecuteWithBabelWithNoNewThread()
2425
{
2526
var mocks = new Mocks();
2627
var environment = mocks.CreateReactEnvironment();
2728

28-
environment.ExecuteWithLargerStackIfRequired<int>("foo");
29+
environment.ExecuteWithBabel<int>("foo");
2930
mocks.Engine.Verify(x => x.CallFunction<int>("foo"), Times.Exactly(1));
3031
}
3132

3233
[Test]
33-
public void ExecuteWithLargerStackIfRequiredWithNewThread()
34+
public void ExecuteWithBabelWithNewThread()
3435
{
3536
var mocks = new Mocks();
3637
var environment = mocks.CreateReactEnvironment();
@@ -40,7 +41,7 @@ public void ExecuteWithLargerStackIfRequiredWithNewThread()
4041
.Callback(() => mocks.Engine.Setup(x => x.CallFunction<int>("foo")))
4142
.Throws(new Exception("Out of stack space"));
4243

43-
environment.ExecuteWithLargerStackIfRequired<int>("foo");
44+
environment.ExecuteWithBabel<int>("foo");
4445
mocks.EngineFactory.Verify(
4546
x => x.GetEngineForCurrentThread(),
4647
Times.Exactly(2),
@@ -54,7 +55,7 @@ public void ExecuteWithLargerStackIfRequiredWithNewThread()
5455
}
5556

5657
[Test]
57-
public void ExecuteWithLargerStackIfRequiredShouldBubbleExceptions()
58+
public void ExecuteWithBabelShouldBubbleExceptions()
5859
{
5960
var mocks = new Mocks();
6061
var environment = mocks.CreateReactEnvironment();
@@ -64,7 +65,20 @@ public void ExecuteWithLargerStackIfRequiredShouldBubbleExceptions()
6465

6566
Assert.Throws<Exception>(() =>
6667
{
67-
environment.ExecuteWithLargerStackIfRequired<int>("foobar");
68+
environment.ExecuteWithBabel<int>("foobar");
69+
});
70+
}
71+
72+
[Test]
73+
public void ExecuteWithBabelShouldThrowIfBabelDisabled()
74+
{
75+
var mocks = new Mocks();
76+
mocks.Config.Setup(x => x.LoadBabel).Returns(false);
77+
var environment = mocks.CreateReactEnvironment();
78+
79+
Assert.Throws<BabelNotLoadedException>(() =>
80+
{
81+
environment.ExecuteWithBabel<string>("foobar");
6882
});
6983
}
7084

@@ -112,6 +126,7 @@ public Mocks()
112126
FileCacheHash = new Mock<IFileCacheHash>();
113127

114128
EngineFactory.Setup(x => x.GetEngineForCurrentThread()).Returns(Engine.Object);
129+
Config.Setup(x => x.LoadBabel).Returns(true);
115130
}
116131

117132
public ReactEnvironment CreateReactEnvironment()

0 commit comments

Comments
 (0)