Skip to content

Commit 1c45ee7

Browse files
reseulrozele
authored andcommitted
Fix for NativeJavaScriptExecutor crashing after OS upgrade. (microsoft#1813)
NativeJavaScriptExecutor in "serialized bytes" mode keeps a cache (a file with bin extension) with the precompiled JS bundle. The format is OS version dependent though, so a failure to run the serialized script should be followed by a second chance attempt to run the original bundle.
1 parent 74c0ed0 commit 1c45ee7

File tree

3 files changed

+46
-9
lines changed

3 files changed

+46
-9
lines changed

ReactWindows/ChakraBridge/ChakraHost.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ JsErrorCode ChakraHost::RunSerializedScript(const wchar_t* szPath, const wchar_t
364364
}
365365
else
366366
{
367-
IfFailRet(LoadByteCode(szSerializedPath, &buffer, &hFile, &hMap, false));
367+
IfFailRet(LoadByteCode(szSerializedPath, &buffer, &hFile, &hMap, true));
368368
}
369369

370370
SerializedSourceContext* context = new SerializedSourceContext();
@@ -373,7 +373,13 @@ JsErrorCode ChakraHost::RunSerializedScript(const wchar_t* szPath, const wchar_t
373373
context->fileHandle = hFile;
374374
context->mapHandle = hMap;
375375

376-
IfFailRet(JsRunSerializedScriptWithCallback(&LoadSourceCallback, &UnloadSourceCallback, buffer, (JsSourceContext)context, szSourceUri, result));
376+
JsErrorCode error = JsRunSerializedScriptWithCallback(&LoadSourceCallback, &UnloadSourceCallback, buffer, (JsSourceContext)context, szSourceUri, result);
377+
if (error != JsNoError)
378+
{
379+
// UnloadSourceCallback is called even in error case (though LoadSourceCallback never is), so we can't fully delete the context
380+
context->Dispose();
381+
return error;
382+
}
377383
return JsNoError;
378384
}
379385

ReactWindows/ChakraBridge/SerializedSourceContext.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,22 @@ struct SerializedSourceContext
1212
BYTE* byteBuffer;
1313
wchar_t* scriptBuffer;
1414

15-
~SerializedSourceContext()
15+
void Dispose()
1616
{
1717
if (fileHandle != NULL)
1818
{
1919
UnmapViewOfFile(byteBuffer);
2020
CloseHandle(mapHandle);
2121
CloseHandle(fileHandle);
22+
fileHandle = NULL;
2223
}
23-
else
24-
{
25-
delete[] byteBuffer;
26-
}
24+
2725
delete[] scriptBuffer;
26+
scriptBuffer = NULL;
27+
}
28+
29+
~SerializedSourceContext()
30+
{
31+
Dispose();
2832
}
2933
};

ReactWindows/ReactNative/Chakra/Executor/NativeJavaScriptExecutor.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Newtonsoft.Json.Linq;
66
using ReactNative.Bridge;
77
using System;
8+
using System.Diagnostics;
89
using System.IO;
910
using System.Threading.Tasks;
1011
using Windows.Storage;
@@ -119,14 +120,39 @@ public void RunScript(string sourcePath, string sourceUrl)
119120
var srcFileInfo = new FileInfo(sourcePath);
120121
var binFileInfo = new FileInfo(binPath);
121122

123+
bool ranSuccessfully = false;
122124
// The idea is to run the JS bundle and generate bytecode for it on a background thread.
123125
// This eliminates the need to delay the first start when the app doesn't have bytecode.
124126
// Next time the app starts, it checks if bytecode is still good and runs it directly.
125127
if (binFileInfo.Exists && binFileInfo.LastWriteTime > srcFileInfo.LastWriteTime)
126128
{
127-
Native.ThrowIfError((JavaScriptErrorCode)_executor.RunSerializedScript(sourcePath, binPath, sourceUrl));
129+
try
130+
{
131+
Native.ThrowIfError((JavaScriptErrorCode)_executor.RunSerializedScript(sourcePath, binPath, sourceUrl));
132+
ranSuccessfully = true;
133+
}
134+
catch (JavaScriptUsageException exc)
135+
{
136+
if (exc.ErrorCode == JavaScriptErrorCode.BadSerializedScript)
137+
{
138+
// Bytecode format is dependent on Chakra engine version, so an OS upgrade may require a recompilation
139+
Debug.WriteLine("Serialized bytecode script is corrupted or wrong format, will generate new one");
140+
}
141+
else
142+
{
143+
// Some more severe error. We still have a chance (recompiling), so we keep continuing.
144+
Debug.WriteLine($"Failed to run serialized bytecode script ({exc.ToString()}), will generate new one");
145+
}
146+
147+
File.Delete(binPath);
148+
}
128149
}
129150
else
151+
{
152+
Debug.WriteLine("Serialized bytecode script doesn't exist or is obsolete, will generate one");
153+
}
154+
155+
if (!ranSuccessfully)
130156
{
131157
Task.Run(() =>
132158
{
@@ -141,8 +167,9 @@ public void RunScript(string sourcePath, string sourceUrl)
141167
Native.ThrowIfError((JavaScriptErrorCode)rt.SerializeScript(sourcePath, binPath));
142168
Native.ThrowIfError((JavaScriptErrorCode)rt.DisposeHost());
143169
}
144-
catch (Exception)
170+
catch (Exception ex)
145171
{
172+
Debug.WriteLine($"Failed to generate serialized bytecode script ({ex.ToString()}).");
146173
// It's fine if the bytecode couldn't be generated: RN can still use the JS bundle.
147174
}
148175
});

0 commit comments

Comments
 (0)