Skip to content

fix: WebAssembly promise callbacks are now properly resolved (#1558) #1567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test-app/app/src/main/assets/app/mainpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ shared.runRequireTests();
shared.runWeakRefTests();
shared.runRuntimeTests();
shared.runWorkerTests();
require("./tests/testWebAssembly");
require("./tests/testInterfaceDefaultMethods");
require("./tests/testInterfaceStaticMethods");
require("./tests/testMetadata");
Expand Down
73 changes: 73 additions & 0 deletions test-app/app/src/main/assets/app/tests/testWebAssembly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
describe("Test WebAssembly ", () => {
// https://wasdk.github.io/WasmFiddle/?15acre
//
// #include <stdio.h>
// #include <sys/uio.h>
//
// #define WASM_EXPORT __attribute__((visibility("default")))
//
// extern double logarithm(double value);
//
// WASM_EXPORT int log(double value) {
// return logarithm(value);
// }
let wasmCode = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x02,
0x60, 0x01, 0x7c, 0x01, 0x7c, 0x60, 0x01, 0x7c, 0x01, 0x7f, 0x02, 0x91, 0x80, 0x80, 0x80,
0x00, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x09, 0x6c, 0x6f, 0x67, 0x61, 0x72, 0x69, 0x74, 0x68,
0x6d, 0x00, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x04, 0x84, 0x80, 0x80,
0x80, 0x00, 0x01, 0x70, 0x00, 0x00, 0x05, 0x83, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x01,
0x06, 0x81, 0x80, 0x80, 0x80, 0x00, 0x00, 0x07, 0x90, 0x80, 0x80, 0x80, 0x00, 0x02, 0x06,
0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, 0x6c, 0x6f, 0x67, 0x00, 0x01, 0x0a,
0x8d, 0x80, 0x80, 0x80, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x10,
0x00, 0xaa, 0x0b
]);

it("Handle compilation failures", done => {
WebAssembly.compile(new Uint8Array([ 1, 2, 3, 4 ])).then(moduleInstance => {
expect(true).toBe(false, "The success callback of the compilation promise was called");
done();
}).catch(e => {
expect(e.name).toEqual("CompileError");
expect(e.message).toEqual("WebAssembly.compile(): expected magic word 00 61 73 6d, found 01 02 03 04 @+0");
done();
});
});

it("Compile and instantiate a WebAssembly module asynchronously", done => {
let importsObj = {
env: {
logarithm: Math.log
}
};

WebAssembly.compile(wasmCode).then(wasmModule => {
WebAssembly.instantiate(wasmModule, importsObj).then(moduleInstance => {
expect(moduleInstance).toBeDefined();
expect(moduleInstance.exports).toBeDefined();
expect(moduleInstance.exports.log).toEqual(jasmine.any(Function));
let actual = moduleInstance.exports.log(Math.E);
expect(actual).toEqual(1);
done();
}).catch(e => {
expect(true).toBe(false, "An unexpected error occurred while instantiating the WebAssembly module: " + e.toString());
done();
});
}).catch(e => {
expect(true).toBe(false, "An unexpected error occurred while compiling the WebAssembly module: " + e.toString());
done();
});
});

it("Compile and instantiate a WebAssembly module inside a worker", done => {
let worker = new Worker("./testWebAssemblyWorker");

worker.onmessage = msg => {
expect(msg.data).toEqual(1);
worker.terminate();
done();
};

worker.postMessage(Array.from(wasmCode));
});
});
16 changes: 16 additions & 0 deletions test-app/app/src/main/assets/app/tests/testWebAssemblyWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
self.onmessage = function(msg) {
let wasmCode = new Uint8Array(msg.data);

let importsObj = {
env: {
logarithm: Math.log
}
};

WebAssembly.compile(wasmCode).then(wasmModule => {
WebAssembly.instantiate(wasmModule, importsObj).then(moduleInstance => {
let result = moduleInstance.exports.log(Math.E);
self.postMessage(result);
});
});
}
4 changes: 3 additions & 1 deletion test-app/runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ add_library(
src/main/cpp/JSONObjectHelper.cpp
src/main/cpp/Logger.cpp
src/main/cpp/ManualInstrumentation.cpp
src/main/cpp/MessageLoopTimer.cpp
src/main/cpp/MetadataMethodInfo.cpp
src/main/cpp/MetadataNode.cpp
src/main/cpp/MetadataReader.cpp
Expand Down Expand Up @@ -222,7 +223,8 @@ endif()
# completing its build.
find_library(system-log log)
find_library(system-z z)
find_library(system-android android)

# Command info: https://cmake.org/cmake/help/v3.4/command/target_link_libraries.html
# Specifies libraries CMake should link to your target library.
target_link_libraries(NativeScript ${system-log} ${system-z})
target_link_libraries(NativeScript ${system-log} ${system-z} ${system-android})
146 changes: 146 additions & 0 deletions test-app/runtime/src/main/cpp/MessageLoopTimer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "MessageLoopTimer.h"
#include <android/looper.h>
#include <unistd.h>
#include <cerrno>
#include <thread>
#include "include/libplatform/libplatform.h"
#include "NativeScriptAssert.h"
#include "ArgConverter.h"
#include "Runtime.h"

using namespace tns;
using namespace v8;

static const int SLEEP_INTERVAL_MS = 100;

void MessageLoopTimer::Init(Local<Context> context) {
this->RegisterStartStopFunctions(context);

std::string proxyScript = R"(
(function () {
// We proxy the WebAssembly's compile, compileStreaming, instantiate and
// instantiateStreaming methods so that they can start and stop a
// MessageLoopTimer inside the promise callbacks. This timer will call
// the v8::platform::PumpMessageLoop method at regular intervals.
// https://github.com/NativeScript/android-runtime/issues/1558

global.WebAssembly = new Proxy(WebAssembly, {
get: (target, name) => {
let origMethod = target[name];
let proxyMethods = [
"compile",
"compileStreaming",
"instantiate",
"instantiateStreaming"
];

if (proxyMethods.indexOf(name) < 0) {
return origMethod;
}

return function (...args) {
__messageLoopTimerStart();
let result = origMethod.apply(this, args);
return result.then(x => {
__messageLoopTimerStop();
return x;
}).catch(e => {
__messageLoopTimerStop();
throw e;
});
};
}
});
})();
)";

Isolate* isolate = context->GetIsolate();

auto source = ArgConverter::ConvertToV8String(isolate, proxyScript);
Local<Script> script;
bool success = Script::Compile(context, source).ToLocal(&script);
assert(success && !script.IsEmpty());

Local<Value> result;
success = script->Run(context).ToLocal(&result);
assert(success);
}

void MessageLoopTimer::RegisterStartStopFunctions(Local<Context> context) {
Isolate* isolate = context->GetIsolate();
Local<Object> global = context->Global();

Local<External> ext = External::New(isolate, this);
Local<Function> startFunc;
Local<Function> stopFunc;
bool success = Function::New(context, MessageLoopTimer::StartCallback, ext).ToLocal(&startFunc);
assert(success);
success = Function::New(context, MessageLoopTimer::StopCallback, ext).ToLocal(&stopFunc);
assert(success);

global->Set(context, ArgConverter::ConvertToV8String(isolate, "__messageLoopTimerStart"), startFunc);
global->Set(context, ArgConverter::ConvertToV8String(isolate, "__messageLoopTimerStop"), stopFunc);
}

void MessageLoopTimer::StartCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto self = static_cast<MessageLoopTimer*>(info.Data().As<External>()->Value());
if (self->m_isRunning) {
return;
}

self->m_isRunning = true;

auto looper = ALooper_forThread();
if (looper == nullptr) {
__android_log_print(ANDROID_LOG_ERROR, "TNS.Native", "Unable to get looper for the current thread");
return;
}

int status = pipe(self->m_fd);
if (status != 0) {
__android_log_print(ANDROID_LOG_ERROR, "TNS.Native", "Unable to create a pipe: %s", strerror(errno));
return;
}

Isolate* isolate = info.GetIsolate();
ALooper_addFd(looper, self->m_fd[0], 0, ALOOPER_EVENT_INPUT, MessageLoopTimer::PumpMessageLoopCallback, isolate);

std::thread worker(MessageLoopTimer::WorkerThreadRun, self);

worker.detach();
}

void MessageLoopTimer::StopCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto self = static_cast<MessageLoopTimer*>(info.Data().As<External>()->Value());
if (!self->m_isRunning) {
return;
}

self->m_isRunning = false;
}

int MessageLoopTimer::PumpMessageLoopCallback(int fd, int events, void* data) {
uint8_t msg;
read(fd, &msg, sizeof(uint8_t));

auto isolate = static_cast<Isolate*>(data);
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handleScope(isolate);

while (v8::platform::PumpMessageLoop(Runtime::platform, isolate)) {
isolate->RunMicrotasks();
}

return msg;
}

void MessageLoopTimer::WorkerThreadRun(MessageLoopTimer* timer) {
while (timer->m_isRunning) {
uint8_t msg = 1;
write(timer->m_fd[1], &msg, sizeof(uint8_t));
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_MS));
}

uint8_t msg = 0;
write(timer->m_fd[1], &msg, sizeof(uint8_t));
}
24 changes: 24 additions & 0 deletions test-app/runtime/src/main/cpp/MessageLoopTimer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef MESSAGELOOPTIMER_H
#define MESSAGELOOPTIMER_H

#include "v8.h"

namespace tns {

class MessageLoopTimer {
public:
void Init(v8::Local<v8::Context> context);
private:
bool m_isRunning;
int m_fd[2];

void RegisterStartStopFunctions(v8::Local<v8::Context> context);
static void StartCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
static void StopCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
static int PumpMessageLoopCallback(int fd, int events, void* data);
static void WorkerThreadRun(MessageLoopTimer* timer);
};

}

#endif //MESSAGELOOPTIMER_H
4 changes: 4 additions & 0 deletions test-app/runtime/src/main/cpp/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Runtime::Runtime(JNIEnv* env, jobject runtime, int id)
: m_env(env), m_id(id), m_isolate(nullptr), m_lastUsedMemory(0), m_gcFunc(nullptr), m_runGC(false) {
m_runtime = m_env.NewGlobalRef(runtime);
m_objectManager = new ObjectManager(m_runtime);
m_loopTimer = new MessageLoopTimer();
s_id2RuntimeCache.insert(make_pair(id, this));

if (GET_USED_MEMORY_METHOD_ID == nullptr) {
Expand Down Expand Up @@ -205,6 +206,7 @@ void Runtime::Init(jstring filesPath, jstring nativeLibDir, bool verboseLoggingE

Runtime::~Runtime() {
delete this->m_objectManager;
delete this->m_loopTimer;
delete this->m_heapSnapshotBlob;
delete this->m_startupData;
}
Expand Down Expand Up @@ -724,6 +726,8 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native

m_arrayBufferHelper.CreateConvertFunctions(isolate, global, m_objectManager);

m_loopTimer->Init(context);

s_mainThreadInitialized = true;

return isolate;
Expand Down
3 changes: 3 additions & 0 deletions test-app/runtime/src/main/cpp/Runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "ArrayBufferHelper.h"
#include "Profiler.h"
#include "ModuleInternal.h"
#include "MessageLoopTimer.h"
#include "File.h"
#include <mutex>

Expand Down Expand Up @@ -84,6 +85,8 @@ class Runtime {

Profiler m_profiler;

MessageLoopTimer* m_loopTimer;

v8::StartupData* m_startupData = nullptr;
MemoryMappedFile* m_heapSnapshotBlob = nullptr;

Expand Down