Skip to content

Commit 7bbf538

Browse files
authored
Merge pull request #1567 from NativeScript/darind/async-wasm
fix: WebAssembly promise callbacks are now properly resolved (#1558)
2 parents d823f4e + d11015b commit 7bbf538

File tree

8 files changed

+270
-1
lines changed

8 files changed

+270
-1
lines changed

test-app/app/src/main/assets/app/mainpage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ shared.runRequireTests();
2020
shared.runWeakRefTests();
2121
shared.runRuntimeTests();
2222
shared.runWorkerTests();
23+
require("./tests/testWebAssembly");
2324
require("./tests/testInterfaceDefaultMethods");
2425
require("./tests/testInterfaceStaticMethods");
2526
require("./tests/testMetadata");
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
describe("Test WebAssembly ", () => {
2+
// https://wasdk.github.io/WasmFiddle/?15acre
3+
//
4+
// #include <stdio.h>
5+
// #include <sys/uio.h>
6+
//
7+
// #define WASM_EXPORT __attribute__((visibility("default")))
8+
//
9+
// extern double logarithm(double value);
10+
//
11+
// WASM_EXPORT int log(double value) {
12+
// return logarithm(value);
13+
// }
14+
let wasmCode = new Uint8Array([
15+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x02,
16+
0x60, 0x01, 0x7c, 0x01, 0x7c, 0x60, 0x01, 0x7c, 0x01, 0x7f, 0x02, 0x91, 0x80, 0x80, 0x80,
17+
0x00, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x09, 0x6c, 0x6f, 0x67, 0x61, 0x72, 0x69, 0x74, 0x68,
18+
0x6d, 0x00, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x04, 0x84, 0x80, 0x80,
19+
0x80, 0x00, 0x01, 0x70, 0x00, 0x00, 0x05, 0x83, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x01,
20+
0x06, 0x81, 0x80, 0x80, 0x80, 0x00, 0x00, 0x07, 0x90, 0x80, 0x80, 0x80, 0x00, 0x02, 0x06,
21+
0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, 0x6c, 0x6f, 0x67, 0x00, 0x01, 0x0a,
22+
0x8d, 0x80, 0x80, 0x80, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x10,
23+
0x00, 0xaa, 0x0b
24+
]);
25+
26+
it("Handle compilation failures", done => {
27+
WebAssembly.compile(new Uint8Array([ 1, 2, 3, 4 ])).then(moduleInstance => {
28+
expect(true).toBe(false, "The success callback of the compilation promise was called");
29+
done();
30+
}).catch(e => {
31+
expect(e.name).toEqual("CompileError");
32+
expect(e.message).toEqual("WebAssembly.compile(): expected magic word 00 61 73 6d, found 01 02 03 04 @+0");
33+
done();
34+
});
35+
});
36+
37+
it("Compile and instantiate a WebAssembly module asynchronously", done => {
38+
let importsObj = {
39+
env: {
40+
logarithm: Math.log
41+
}
42+
};
43+
44+
WebAssembly.compile(wasmCode).then(wasmModule => {
45+
WebAssembly.instantiate(wasmModule, importsObj).then(moduleInstance => {
46+
expect(moduleInstance).toBeDefined();
47+
expect(moduleInstance.exports).toBeDefined();
48+
expect(moduleInstance.exports.log).toEqual(jasmine.any(Function));
49+
let actual = moduleInstance.exports.log(Math.E);
50+
expect(actual).toEqual(1);
51+
done();
52+
}).catch(e => {
53+
expect(true).toBe(false, "An unexpected error occurred while instantiating the WebAssembly module: " + e.toString());
54+
done();
55+
});
56+
}).catch(e => {
57+
expect(true).toBe(false, "An unexpected error occurred while compiling the WebAssembly module: " + e.toString());
58+
done();
59+
});
60+
});
61+
62+
it("Compile and instantiate a WebAssembly module inside a worker", done => {
63+
let worker = new Worker("./testWebAssemblyWorker");
64+
65+
worker.onmessage = msg => {
66+
expect(msg.data).toEqual(1);
67+
worker.terminate();
68+
done();
69+
};
70+
71+
worker.postMessage(Array.from(wasmCode));
72+
});
73+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
self.onmessage = function(msg) {
2+
let wasmCode = new Uint8Array(msg.data);
3+
4+
let importsObj = {
5+
env: {
6+
logarithm: Math.log
7+
}
8+
};
9+
10+
WebAssembly.compile(wasmCode).then(wasmModule => {
11+
WebAssembly.instantiate(wasmModule, importsObj).then(moduleInstance => {
12+
let result = moduleInstance.exports.log(Math.E);
13+
self.postMessage(result);
14+
});
15+
});
16+
}

test-app/runtime/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ add_library(
152152
src/main/cpp/JSONObjectHelper.cpp
153153
src/main/cpp/Logger.cpp
154154
src/main/cpp/ManualInstrumentation.cpp
155+
src/main/cpp/MessageLoopTimer.cpp
155156
src/main/cpp/MetadataMethodInfo.cpp
156157
src/main/cpp/MetadataNode.cpp
157158
src/main/cpp/MetadataReader.cpp
@@ -222,7 +223,8 @@ endif()
222223
# completing its build.
223224
find_library(system-log log)
224225
find_library(system-z z)
226+
find_library(system-android android)
225227

226228
# Command info: https://cmake.org/cmake/help/v3.4/command/target_link_libraries.html
227229
# Specifies libraries CMake should link to your target library.
228-
target_link_libraries(NativeScript ${system-log} ${system-z})
230+
target_link_libraries(NativeScript ${system-log} ${system-z} ${system-android})
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include "MessageLoopTimer.h"
2+
#include <android/looper.h>
3+
#include <unistd.h>
4+
#include <cerrno>
5+
#include <thread>
6+
#include "include/libplatform/libplatform.h"
7+
#include "NativeScriptAssert.h"
8+
#include "ArgConverter.h"
9+
#include "Runtime.h"
10+
11+
using namespace tns;
12+
using namespace v8;
13+
14+
static const int SLEEP_INTERVAL_MS = 100;
15+
16+
void MessageLoopTimer::Init(Local<Context> context) {
17+
this->RegisterStartStopFunctions(context);
18+
19+
std::string proxyScript = R"(
20+
(function () {
21+
// We proxy the WebAssembly's compile, compileStreaming, instantiate and
22+
// instantiateStreaming methods so that they can start and stop a
23+
// MessageLoopTimer inside the promise callbacks. This timer will call
24+
// the v8::platform::PumpMessageLoop method at regular intervals.
25+
// https://github.com/NativeScript/android-runtime/issues/1558
26+
27+
global.WebAssembly = new Proxy(WebAssembly, {
28+
get: (target, name) => {
29+
let origMethod = target[name];
30+
let proxyMethods = [
31+
"compile",
32+
"compileStreaming",
33+
"instantiate",
34+
"instantiateStreaming"
35+
];
36+
37+
if (proxyMethods.indexOf(name) < 0) {
38+
return origMethod;
39+
}
40+
41+
return function (...args) {
42+
__messageLoopTimerStart();
43+
let result = origMethod.apply(this, args);
44+
return result.then(x => {
45+
__messageLoopTimerStop();
46+
return x;
47+
}).catch(e => {
48+
__messageLoopTimerStop();
49+
throw e;
50+
});
51+
};
52+
}
53+
});
54+
})();
55+
)";
56+
57+
Isolate* isolate = context->GetIsolate();
58+
59+
auto source = ArgConverter::ConvertToV8String(isolate, proxyScript);
60+
Local<Script> script;
61+
bool success = Script::Compile(context, source).ToLocal(&script);
62+
assert(success && !script.IsEmpty());
63+
64+
Local<Value> result;
65+
success = script->Run(context).ToLocal(&result);
66+
assert(success);
67+
}
68+
69+
void MessageLoopTimer::RegisterStartStopFunctions(Local<Context> context) {
70+
Isolate* isolate = context->GetIsolate();
71+
Local<Object> global = context->Global();
72+
73+
Local<External> ext = External::New(isolate, this);
74+
Local<Function> startFunc;
75+
Local<Function> stopFunc;
76+
bool success = Function::New(context, MessageLoopTimer::StartCallback, ext).ToLocal(&startFunc);
77+
assert(success);
78+
success = Function::New(context, MessageLoopTimer::StopCallback, ext).ToLocal(&stopFunc);
79+
assert(success);
80+
81+
global->Set(context, ArgConverter::ConvertToV8String(isolate, "__messageLoopTimerStart"), startFunc);
82+
global->Set(context, ArgConverter::ConvertToV8String(isolate, "__messageLoopTimerStop"), stopFunc);
83+
}
84+
85+
void MessageLoopTimer::StartCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
86+
auto self = static_cast<MessageLoopTimer*>(info.Data().As<External>()->Value());
87+
if (self->m_isRunning) {
88+
return;
89+
}
90+
91+
self->m_isRunning = true;
92+
93+
auto looper = ALooper_forThread();
94+
if (looper == nullptr) {
95+
__android_log_print(ANDROID_LOG_ERROR, "TNS.Native", "Unable to get looper for the current thread");
96+
return;
97+
}
98+
99+
int status = pipe(self->m_fd);
100+
if (status != 0) {
101+
__android_log_print(ANDROID_LOG_ERROR, "TNS.Native", "Unable to create a pipe: %s", strerror(errno));
102+
return;
103+
}
104+
105+
Isolate* isolate = info.GetIsolate();
106+
ALooper_addFd(looper, self->m_fd[0], 0, ALOOPER_EVENT_INPUT, MessageLoopTimer::PumpMessageLoopCallback, isolate);
107+
108+
std::thread worker(MessageLoopTimer::WorkerThreadRun, self);
109+
110+
worker.detach();
111+
}
112+
113+
void MessageLoopTimer::StopCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
114+
auto self = static_cast<MessageLoopTimer*>(info.Data().As<External>()->Value());
115+
if (!self->m_isRunning) {
116+
return;
117+
}
118+
119+
self->m_isRunning = false;
120+
}
121+
122+
int MessageLoopTimer::PumpMessageLoopCallback(int fd, int events, void* data) {
123+
uint8_t msg;
124+
read(fd, &msg, sizeof(uint8_t));
125+
126+
auto isolate = static_cast<Isolate*>(data);
127+
v8::Isolate::Scope isolate_scope(isolate);
128+
v8::HandleScope handleScope(isolate);
129+
130+
while (v8::platform::PumpMessageLoop(Runtime::platform, isolate)) {
131+
isolate->RunMicrotasks();
132+
}
133+
134+
return msg;
135+
}
136+
137+
void MessageLoopTimer::WorkerThreadRun(MessageLoopTimer* timer) {
138+
while (timer->m_isRunning) {
139+
uint8_t msg = 1;
140+
write(timer->m_fd[1], &msg, sizeof(uint8_t));
141+
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_MS));
142+
}
143+
144+
uint8_t msg = 0;
145+
write(timer->m_fd[1], &msg, sizeof(uint8_t));
146+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#ifndef MESSAGELOOPTIMER_H
2+
#define MESSAGELOOPTIMER_H
3+
4+
#include "v8.h"
5+
6+
namespace tns {
7+
8+
class MessageLoopTimer {
9+
public:
10+
void Init(v8::Local<v8::Context> context);
11+
private:
12+
bool m_isRunning;
13+
int m_fd[2];
14+
15+
void RegisterStartStopFunctions(v8::Local<v8::Context> context);
16+
static void StartCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
17+
static void StopCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
18+
static int PumpMessageLoopCallback(int fd, int events, void* data);
19+
static void WorkerThreadRun(MessageLoopTimer* timer);
20+
};
21+
22+
}
23+
24+
#endif //MESSAGELOOPTIMER_H

test-app/runtime/src/main/cpp/Runtime.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Runtime::Runtime(JNIEnv* env, jobject runtime, int id)
9696
: m_env(env), m_id(id), m_isolate(nullptr), m_lastUsedMemory(0), m_gcFunc(nullptr), m_runGC(false) {
9797
m_runtime = m_env.NewGlobalRef(runtime);
9898
m_objectManager = new ObjectManager(m_runtime);
99+
m_loopTimer = new MessageLoopTimer();
99100
s_id2RuntimeCache.insert(make_pair(id, this));
100101

101102
if (GET_USED_MEMORY_METHOD_ID == nullptr) {
@@ -204,6 +205,7 @@ void Runtime::Init(jstring filesPath, jstring nativeLibDir, bool verboseLoggingE
204205

205206
Runtime::~Runtime() {
206207
delete this->m_objectManager;
208+
delete this->m_loopTimer;
207209
delete this->m_heapSnapshotBlob;
208210
delete this->m_startupData;
209211
}
@@ -718,6 +720,8 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native
718720

719721
m_arrayBufferHelper.CreateConvertFunctions(isolate, global, m_objectManager);
720722

723+
m_loopTimer->Init(context);
724+
721725
s_mainThreadInitialized = true;
722726

723727
return isolate;

test-app/runtime/src/main/cpp/Runtime.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ArrayBufferHelper.h"
1010
#include "Profiler.h"
1111
#include "ModuleInternal.h"
12+
#include "MessageLoopTimer.h"
1213
#include "File.h"
1314
#include <mutex>
1415

@@ -84,6 +85,8 @@ class Runtime {
8485

8586
Profiler m_profiler;
8687

88+
MessageLoopTimer* m_loopTimer;
89+
8790
v8::StartupData* m_startupData = nullptr;
8891
MemoryMappedFile* m_heapSnapshotBlob = nullptr;
8992

0 commit comments

Comments
 (0)