Merged PR 249141: Use transport context in execute

Use transport context in execute
This commit is contained in:
Asi Bross 2017-04-26 21:13:41 +00:00 коммит произвёл Asi Bross
Родитель 2cb740d3c4
Коммит 93f19e06ca
31 изменённых файлов: 340 добавлений и 233 удалений

Просмотреть файл

@ -17,4 +17,6 @@
<< std::endl; \
std::terminate(); \
} \
} while (false)
} while (false)
#define NAPA_FAIL(message) NAPA_ASSERT(false, message)

Просмотреть файл

@ -68,6 +68,14 @@ EXTERN_C NAPA_API napa_zone_handle napa_zone_create(napa_string_ref id);
/// </remarks>
EXTERN_C NAPA_API napa_zone_handle napa_zone_get(napa_string_ref id);
/// <summary> Retrieves the current zone. </summary>
/// <returns> The zone handle if this thread is associated with one, null otherwise. </returns>
/// <remarks>
/// This function returns a handle that must be release when it's no longer needed.
/// Napa keeps track of all zone handles and destroys the zone when all handles have been released.
/// </remarks>
EXTERN_C NAPA_API napa_zone_handle napa_zone_get_current();
/// <summary> Releases the zone handle. When all handles for a zone are released the zone is destoryed. </summary>
/// <param name="handle"> The zone handle. </param>
EXTERN_C NAPA_API napa_response_code napa_zone_release(napa_zone_handle handle);

Просмотреть файл

@ -112,6 +112,17 @@ namespace napa {
return std::unique_ptr<ZoneProxy>(new ZoneProxy(id, handle));
}
/// <summary> Creates a proxy to the current zone, throws if non is associated with this thread. </summary>
static std::unique_ptr<ZoneProxy> GetCurrent() {
auto handle = napa_zone_get_current();
if (!handle) {
throw std::runtime_error("The calling thread is not associated with a zone");
}
auto zoneId = NAPA_STRING_REF_TO_STD_STRING(napa_zone_get_id(handle));
return std::unique_ptr<ZoneProxy>(new ZoneProxy(std::move(zoneId), handle));
}
private:
/// <summary> Private constructor to create a C++ zone proxy from a C handle. </summary>

18
lib/napa-dispatcher.js Normal file
Просмотреть файл

@ -0,0 +1,18 @@
function __napa_function_dispatcher__(func, args, contextHandle) {
var transport = require('napajs').transport;
var transportContext = transport.createTransportContext(contextHandle);
var args = args.map((arg) => { return transport.unmarshall(arg, transportContext); });
return transport.marshall(func.apply(this, args), transportContext);
}
function __napa_module_dispatcher__(moduleName, functionName, args, contextHandle) {
var module = require(moduleName);
var func = module[functionName];
if (!func) {
throw new Error("Cannot find function '" + functionName + "' in module '" + moduleName + "'");
}
return __napa_function_dispatcher__(func, args, contextHandle);
}

Просмотреть файл

@ -2,9 +2,10 @@
<Import Project="$(EnvironmentConfig)" />
<ItemGroup>
<ProjectFile Include="lib\scripts.proj" />
<ProjectFile Include="napa\addon.vcxproj" />
<ProjectFile Include="node\addon.vcxproj" />
<ProjectFile Include="package\create-package.proj" />
<ProjectFile Include="package\napajs.proj" />
</ItemGroup>
<Import Project="$(ExtendedTargetsPath)\Traversal.targets" />

Просмотреть файл

@ -1,6 +1,6 @@
/// <summary> Barrel is a container that creates boundary for sharing objects across isolates. </summary>
/// TODO: @dapeng, implement add-on and connect with this class.
declare class Barrel {
export declare class Barrel {
/// <summary> ID of this barrel. </summary>
readonly id: number;

Просмотреть файл

@ -1,7 +1,16 @@
import * as zone from "./zone";
import * as transport from "../transport";
declare var __in_napa: boolean;
interface ExecuteRequest {
module: string;
function: string;
arguments: any[];
timeout: number;
transportContext: transport.TransportContext;
}
/// <summary> Zone consists of Napa isolates. </summary>
export class NapaZone implements zone.Zone {
private _nativeZone;
@ -14,11 +23,11 @@ export class NapaZone implements zone.Zone {
return this._nativeZone.getId();
}
public load(source: string) : Promise<zone.ResponseCode> {
public broadcast(source: string) : Promise<zone.ResponseCode> {
if (typeof __in_napa !== undefined) {
// Napa does not support async yet, we wrap this sync call in a promise
// to provide a uniform API.
return Promise.resolve(this._nativeZone.loadSync(source));
return Promise.resolve(this._nativeZone.broadcastSync(source));
}
return new Promise<zone.ResponseCode>(resolve => {
@ -26,20 +35,33 @@ export class NapaZone implements zone.Zone {
});
}
public execute(module: string, func: string, args: any[], timeout?: number) : Promise<zone.ResponseValue> {
// Convert all arguments to strings
args = args.map(x => { return JSON.stringify(x); });
public execute(module: string, func: string, args: any[], timeout?: number) : Promise<zone.ResponseValue> {
let transportContext: transport.TransportContext = transport.createTransportContext();
let request : ExecuteRequest = {
module: module,
function: func,
arguments: args.map(arg => { return transport.marshall(arg, transportContext); }),
timeout: timeout,
transportContext: transportContext
};
if (typeof __in_napa !== undefined) {
// Napa does not support async yet, we wrap this sync call in a promise
// to provide a uniform API.
return Promise.resolve(this._nativeZone.executeSync(module, func, args, timeout));
let response = this._nativeZone.executeSync(request);
if (response.code === 0) {
transportContext = transport.createTransportContext(response.contextHandle);
return Promise.resolve(transport.unmarshall(response.returnValue, transportContext));
} else {
return Promise.reject(response.errorMessage);
}
}
return new Promise<zone.ResponseValue>((resolve, reject) => {
this._nativeZone.execute(module, func, args, (response) => {
if (response.code === 0) {
resolve(response.returnValue);
transportContext = transport.createTransportContext(response.contextHandle);
resolve(transport.unmarshall(response.returnValue, transportContext));
} else {
reject(response.errorMessage);
}
@ -47,23 +69,5 @@ export class NapaZone implements zone.Zone {
});
}
public executeAll(module: string, func: string, args: any[]) : Promise<zone.ResponseCode> {
// Convert all arguments to strings
args = args.map(x => { return JSON.stringify(x); });
if (typeof __in_napa !== undefined) {
// Napa does not support async yet, we wrap this sync call in a promise
// to provide a uniform API.
return Promise.resolve(this._nativeZone.executeAllSync(module, func, args));
}
return new Promise<zone.ResponseCode>(resolve => {
this._nativeZone.executeAll(module, func, args, resolve);
});
}
public destory(): void {
this._nativeZone.destroy();
}
}

Просмотреть файл

@ -7,7 +7,7 @@ export class NodeZone implements zone.Zone {
return "node";
}
public load(source: string) : Promise<zone.ResponseCode> {
public broadcast(source: string) : Promise<zone.ResponseCode> {
// TODO @asib: add implementation
return undefined;
}
@ -16,13 +16,4 @@ export class NodeZone implements zone.Zone {
// TODO @asib: add implementation
return undefined;
}
public executeAll(module: string, func: string, args: string[]) : Promise<zone.ResponseCode> {
// TODO @asib: add implementation
return undefined;
}
public destory(): void {
// TODO @asib: add implementation
}
}

Просмотреть файл

@ -68,12 +68,10 @@ export function getZone(id: string) : zone.Zone {
}
/// <summary> Returns the current zone. </summary>
export let currentZone: zone.Zone = getCurrentZone();
function getCurrentZone() : zone.Zone {
export function getCurrentZone() : zone.Zone {
if (typeof __in_napa !== undefined) {
return new napa.NapaZone(addon.getCurrentZone());
}
return new node.NodeZone();
}

Просмотреть файл

@ -20,9 +20,9 @@ export interface Zone {
/// <summary> The zone id. </summary>
readonly id: string;
/// <summary> Loads the source code into the global scope in all zone workers. </summary>
/// <summary> Compiles and run the provided source code on all zone workers. </summary>
/// <param name="source"> A valid javascript source code. </param>
load(source: string) : Promise<ResponseCode>;
broadcast(source: string) : Promise<ResponseCode>;
/// <summary> Executes the function on one of the zone workers. </summary>
/// <param name="module"> The module that contains the function to execute. </param>
@ -30,14 +30,5 @@ export interface Zone {
/// <param name="args"> The arguments that will pass to the function. </param>
/// <param name="timeout"> The timeout of the execution in milliseconds, defaults to infinity. </param>
execute(module: string, func: string, args: any[], timeout?: number) : Promise<ResponseValue>;
/// <summary> Executes the function on all zone workers. </summary>
/// <param name="module"> The module that contains the function to execute. </param>
/// <param name="func"> The function to execute. </param>
/// <param name="args"> The arguments that will pass to the function. </param>
executeAll(module: string, func: string, args: any[]) : Promise<ResponseCode>;
/// <summary> Destroys the zone, cleaning all resources associated with it. </summary>
destory(): void;
}

Просмотреть файл

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(EnvironmentConfig)" />
<Import Project="$(NapaBuildRoot)\node\npm.targets" />
<ItemGroup>
<QCustomInput Include="$(NapaBuildRoot)\**\*" />
<QCustomInput Include="..\package.json" />
<QCustomInput Include="..\readme.md" />
<QCustomInput Include="**\*.ts" />
<QCustomInput Include="**\*.js" />
<QCustomInput Include="tsconfig.json" />
</ItemGroup>
<PropertyGroup>
<PackageJsonDirectory>..\</PackageJsonDirectory>
</PropertyGroup>
<Target Name="BuildScripts" AfterTargets="AfterBuild">
<CallTarget Targets="NpmInstall" />
<Message Text="Compile scripts." />
<Exec Command="$(TSCompile) --outDir $(IntermediateOutputPath)" />
<CallTarget Targets="NpmCleanup" />
</Target>
<Import Project="$(ExtendedTargetsPath)\NoTarget.targets" />
</Project>

Просмотреть файл

@ -33,8 +33,8 @@ export interface TransportContext {
var addon = require('../../bin/addon');
/// <summary> Create a transport context. </summary>
export function createTransportContext(): TransportContext {
return new addon.TransportContextWrap();
export function createTransportContext(handle? : memory.Handle): TransportContext {
return new addon.TransportContextWrap(handle);
}
/// <summary> Interface for transportable non-built-in JavaScript types. </summary>

Просмотреть файл

@ -9,6 +9,7 @@
"outDir": "./objd/amd64"
},
"exclude": [
"node_modules"
"objd",
"obj"
]
}

Просмотреть файл

@ -66,6 +66,14 @@ void GetZone(const v8::FunctionCallbackInfo<v8::Value>& args) {
#endif
}
void GetCurrentZone(const v8::FunctionCallbackInfo<v8::Value>& args) {
#ifdef NAPA_MODULE_EXTENSION
JS_ASSERT(v8::Isolate::GetCurrent(), false, "napa.getCurrentZone cannot be called from inside napa");
#else
ZoneWrap::NewInstance(ZoneWrap::ConstructorType::CURRENT, args);
#endif
}
void GetLoggingProvider(const v8::FunctionCallbackInfo<v8::Value>& args) {
LoggingProviderWrap::NewInstance(args);
}
@ -116,6 +124,7 @@ void InitAll(v8::Local<v8::Object> exports) {
NAPA_SET_METHOD(exports, "createZone", CreateZone);
NAPA_SET_METHOD(exports, "getZone", GetZone);
NAPA_SET_METHOD(exports, "getCurrentZone", GetCurrentZone);
NAPA_SET_METHOD(exports, "getLoggingProvider", GetLoggingProvider);
NAPA_SET_METHOD(exports, "getResponseCodeString", GetResponseCodeString);

Просмотреть файл

@ -3,6 +3,7 @@
#include "node-async-handler.h"
#include <napa.h>
#include <napa-assert.h>
#include <napa/v8-helpers.h>
#include <sstream>
@ -63,7 +64,9 @@ void ZoneWrap::NewCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
std::unique_ptr<napa::ZoneProxy> zoneProxy;
auto constructorType = static_cast<ConstructorType>(args[0]->Uint32Value(context).FromJust());
if (constructorType == ConstructorType::CREATE) {
switch (constructorType) {
case napa::binding::ZoneWrap::ConstructorType::CREATE: {
CHECK_ARG(isolate, args[1]->IsString(), "first argument to createZone must be a string");
v8::String::Utf8Value zoneId(args[1]->ToString());
@ -80,11 +83,26 @@ void ZoneWrap::NewCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
zoneProxy = std::make_unique<napa::ZoneProxy>(*zoneId, ss.str());
} else { // ConstructorType::GET
break;
}
case napa::binding::ZoneWrap::ConstructorType::GET: {
CHECK_ARG(isolate, args[1]->IsString(), "first argument to getZone must be a string");
v8::String::Utf8Value zoneId(args[1]->ToString());
zoneProxy = napa::ZoneProxy::Get(*zoneId);
try {
zoneProxy = napa::ZoneProxy::Get(*zoneId);
} catch (const std::exception &ex) {
JS_ASSERT(isolate, false, ex.what());
}
break;
}
case napa::binding::ZoneWrap::ConstructorType::CURRENT: {
zoneProxy = napa::ZoneProxy::GetCurrent();
break;
}
default:
NAPA_FAIL("Non existing constructor type for ZoneWrap");
}
auto obj = new ZoneWrap(std::move(zoneProxy));

Просмотреть файл

@ -17,8 +17,9 @@ namespace binding {
public:
enum class ConstructorType : uint32_t {
CREATE = 0, // Creates a new zone
GET = 1 // Retrieves an existing zone
CREATE = 0, // Creates a new zone
GET = 1, // Retrieves an existing zone
CURRENT = 2 // Retrieves the current zone
};
static void Init(v8::Isolate* isolate);

Просмотреть файл

@ -2,8 +2,8 @@
"name": "napajs",
"version": "0.1.0",
"author": "napajs",
"main": "./bin/addon",
"types": "./types/napajs.d.ts",
"main": "./lib/index.js",
"types": "./types/index.d.ts",
"dependencies": {
"@napajs/logger": ">= 0.0.1",
"commander": ">= 2.9.0"

35
modules/napajs/package/napajs.d.ts поставляемый
Просмотреть файл

@ -1,35 +0,0 @@
import logger = require('@napajs/logger');
export declare function initialize(): void;
export declare function initialize(settings: string): void;
export declare function initialize(settings: object): void;
export declare function shutdown(): void;
type ResponseCode = number;
export declare class Response {
code: ResponseCode;
errorMessage: string;
returnValue: string;
}
export interface Container {
load(source: string, callback: (responseCode: ResponseCode) => void): void;
loadSync(source: string): ResponseCode;
loadFile(file: string, callback: (responseCode: ResponseCode) => void): void;
loadFileSync(file: string): ResponseCode;
run(functionName: string, args: string[], callback: (response: Response) => void, timeout?: number): void;
runSync(functionName: string, args: string[], timeout?: number): Response;
runAll(functionName: string, args: string[], callback: (responseCode: ResponseCode) => void): void;
runAllSync(functionName: string, args: string[]): ResponseCode;
}
export declare function createContainer(settings?: object): Container;
export declare function getLoggingProvider(): logger.LoggingProvider;
export declare function getResponseCodeString(code: number): string;

Просмотреть файл

@ -4,11 +4,13 @@
<ItemGroup>
<QCustomProjectReference Include="$(NapaVanillaRoot)\lib\js-core-modules.proj" />
<QCustomProjectReference Include="..\lib\scripts.proj" />
<QCustomProjectReference Include="..\napa\addon.vcxproj" />
<QCustomProjectReference Include="..\node\addon.vcxproj" />
</ItemGroup>
<ItemGroup>
<QCustomInput Include="$(NapaBuildRoot)\**\*" />
<QCustomInput Include="$(NapaVanillaRoot)\inc\**\*" />
<QCustomInput Include="..\package.json" />
<QCustomInput Include="..\README.md" />
@ -21,28 +23,38 @@
</PropertyGroup>
<Import Project="$(NapaBuildRoot)\node\npm.targets" />
<Target Name="CreateNpmPackage" AfterTargets="AfterBuild">
<Message Text="Create NPM package." />
<Copy SourceFiles="..\node\$(O)\addon.node" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="..\napa\$(O)\addon.napa" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NapaVanillaRoot)\src\$(O)\napa.dll" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NapaVanillaRoot)\src\module\core-modules.json" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="napajs.d.ts" DestinationFolder="$(PackageOutPath)\types" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="napa-cl.js" DestinationFolder="$(PackageOutPath)\tools" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="napa-cl.bat" DestinationFolder="$(PackageOutPath)\tools" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="..\package.json" DestinationFolder="$(PackageOutPath)" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="..\README.md" DestinationFolder="$(PackageOutPath)" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NapaVanillaRoot)\src\$(O)\napa.dll" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NapaVanillaRoot)\src\$(O)\napa.lib" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NapaVanillaRoot)\src\module\core-modules.json" DestinationFolder="$(PackageOutPath)\bin" ContinueOnError="false" SkipUnchangedFiles="true" />
<!-- Core modules -->
<ItemGroup>
<JsCoreModuleFiles Include="$(NapaVanillaRoot)\lib\$(O)\**\*"/>
<JsCoreModuleFiles Include="$(NapaVanillaRoot)\lib\$(O)\**\*.js"/>
</ItemGroup>
<Copy SourceFiles="@(JsCoreModuleFiles)" DestinationFiles="@(JsCoreModuleFiles->'$(PackageOutPath)\lib\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" SkipUnchangedFiles="true" />
<Copy SourceFiles="@(JsCoreModuleFiles)" DestinationFiles="@(JsCoreModuleFiles->'$(PackageOutPath)\bin\lib\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" SkipUnchangedFiles="true" />
<!-- Compiled scripts -->
<ItemGroup>
<IncFiles Include="$(NapaVanillaRoot)\inc\**\*.js"/>
<JsFiles Include="..\lib\$(O)\**\*.js"/>
</ItemGroup>
<Copy SourceFiles="@(JsFiles)" DestinationFiles="@(JsFiles->'$(PackageOutPath)\lib\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" SkipUnchangedFiles="true" />
<!-- Declarations -->
<ItemGroup>
<DeclarationFiles Include="..\lib\$(O)\**\*.d.ts"/>
</ItemGroup>
<Copy SourceFiles="@(DeclarationFiles)" DestinationFiles="@(DeclarationFiles->'$(PackageOutPath)\types\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" SkipUnchangedFiles="true" />
<!-- Include files -->
<ItemGroup>
<IncFiles Include="$(NapaVanillaRoot)\inc\**\*"/>
</ItemGroup>
<Copy SourceFiles="@(IncFiles)" DestinationFiles="@(IncFiles->'$(PackageOutPath)\inc\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" SkipUnchangedFiles="true" />

Просмотреть файл

@ -47,6 +47,18 @@ napa_zone_handle napa_zone_get(napa_string_ref id) {
return new napa_zone { std::move(zoneId), std::move(zone) };
}
napa_zone_handle napa_zone_get_current() {
NAPA_ASSERT(_initialized, "Napa wasn't initialized");
auto zone = reinterpret_cast<ZoneImpl*>(napa::module::WorkerContext::Get(napa::module::WorkerContextItem::ZONE));
if (zone == nullptr) {
LOG_WARNING("Api", "Trying to get current zone from a thread that is not associated with a zone");
return nullptr;
}
return napa_zone_get(STD_STRING_TO_NAPA_STRING_REF(zone->GetId()));
}
napa_response_code napa_zone_init(napa_zone_handle handle, napa_string_ref settings) {
NAPA_ASSERT(_initialized, "Napa wasn't initialized");
NAPA_ASSERT(handle, "Zone handle is null");

Просмотреть файл

@ -66,7 +66,7 @@
<!-- Core modules -->
<Import Project="module\core-modules\core-modules.proj" />
<Import Project="$(PACKAGESROOT)\V8.Library\exports.props" />
<Import Project="$(Pkgboost)\build\native\boost.targets" />
<Import Project="$(Pkgboost_date_time_vc140)\build\native\boost_date_time-vc140.targets" />

Просмотреть файл

@ -34,7 +34,7 @@ void BroadcastTask::Execute() {
// Compile the source code.
auto compileResult = v8::Script::Compile(context, source, &sourceOrigin);
if (compileResult.IsEmpty()) {
LOG_ERROR("Broadcast", "Failed while compiling the provided source code");
LOG_ERROR("Broadcast", "Failed while compiling the provided source code. %s", _source.c_str());
_callback(NAPA_RESPONSE_BROADCAST_SCRIPT_ERROR);
return;
}

Просмотреть файл

@ -27,49 +27,49 @@ void ExecuteTask::Execute() {
v8::HandleScope scope(isolate);
auto context = isolate->GetCurrentContext();
v8::Local<v8::Function> function;
v8::Local<v8::Function> dispatcher;
std::vector<v8::Local<v8::Value>> args;
if (_module.empty()) {
// Lookup function in global scope
auto funcVal = context->Global()->Get(MakeExternalV8String(isolate, _func));
if (!funcVal->IsFunction()) {
std::string errorMessage = "Function '" + _func + "' not defined";
LOG_ERROR("Execute", errorMessage.c_str());
_callback({ NAPA_RESPONSE_EXECUTE_FUNC_ERROR, std::move(errorMessage), "", nullptr });
auto dispatcherValue = context->Global()->Get(MakeExternalV8String(isolate, "__napa_function_dispatcher__"));
NAPA_ASSERT(dispatcherValue->IsFunction(), "dispatcher function must exist in global scope");
dispatcher = v8::Local<v8::Function>::Cast(dispatcherValue);
auto funcValue = context->Global()->Get(MakeExternalV8String(isolate, _func));
if (!funcValue->IsFunction()) {
auto error = "Function '" + _func + "' does not exist in global scope";
_callback({ NAPA_RESPONSE_EXECUTE_FUNC_ERROR, std::move(error), "", nullptr });
return;
}
function = v8::Local<v8::Function>::Cast(funcVal);
// Create the V8 args for the function call.
args.reserve(_args.size());
for (const auto& arg : _args) {
args.emplace_back(MakeExternalV8String(isolate, arg));
}
args.reserve(3); // (func, args, contextHandle)
args.emplace_back(v8::Local<v8::Function>::Cast(funcValue));
} else {
// Use the global dispatcher function
auto dispatcherValue = context->Global()->Get(MakeExternalV8String(isolate, "__napa_execute_dispatcher__"));
// Get the dispatcher function from global scope.
auto dispatcherValue = context->Global()->Get(MakeExternalV8String(isolate, "__napa_module_dispatcher__"));
NAPA_ASSERT(dispatcherValue->IsFunction(), "dispatcher function must exist in global scope");
// The dispatcher function.
function = v8::Local<v8::Function>::Cast(dispatcherValue);
dispatcher = v8::Local<v8::Function>::Cast(dispatcherValue);
// Prepare args
args.reserve(3); // (module, func, args)
args.reserve(4); // (moduleName, functionName, args, contextHandle)
args.emplace_back(MakeExternalV8String(isolate, _module));
args.emplace_back(MakeExternalV8String(isolate, _func));
auto arr = v8::Array::New(isolate, static_cast<int>(_args.size()));
for (size_t i = 0; i < _args.size(); ++i) {
(void)arr->CreateDataProperty(context, static_cast<uint32_t>(i), MakeExternalV8String(isolate, _args[i]));
}
args.emplace_back(arr);
}
// Prepare function args
auto arr = v8::Array::New(isolate, static_cast<int>(_args.size()));
for (size_t i = 0; i < _args.size(); ++i) {
(void)arr->CreateDataProperty(context, static_cast<uint32_t>(i), MakeExternalV8String(isolate, _args[i]));
}
args.emplace_back(arr);
// Transport context handle
args.emplace_back(PtrToV8Uint32Array(isolate, _transportContext.get()));
// Execute the function.
v8::TryCatch tryCatch(isolate);
auto res = function->Call(context->Global(), static_cast<int>(args.size()), args.data());
auto res = dispatcher->Call(context->Global(), static_cast<int>(args.size()), args.data());
// Terminating an isolate may occur from a different thread, i.e. from timeout service.
// If the function call already finished successfully when the isolate is terminated it may lead
@ -92,10 +92,13 @@ void ExecuteTask::Execute() {
if (tryCatch.HasCaught()) {
auto exception = tryCatch.Exception();
v8::String::Utf8Value errorMessage(exception);
v8::String::Utf8Value exceptionStr(exception);
auto stackTrace = tryCatch.StackTrace();
v8::String::Utf8Value stackTraceStr(stackTrace);
LOG_ERROR("Execute", "Error occured while executing function '%s', error: %s", _func.c_str(), *errorMessage);
_callback({ NAPA_RESPONSE_EXECUTE_FUNC_ERROR, *errorMessage, "", nullptr });
LOG_ERROR("Execute", "JS exception thrown: %s - %s", *exceptionStr, *stackTraceStr);
_callback({ NAPA_RESPONSE_EXECUTE_FUNC_ERROR, *exceptionStr, "", nullptr });
return;
}

Просмотреть файл

@ -6,6 +6,7 @@
#include <napa-log.h>
#include <boost/dll.hpp>
#include <boost/filesystem.hpp>
#include <sstream>
@ -13,16 +14,16 @@
using namespace napa;
using namespace napa::scheduler;
// Forward declarations
static void BroadcastFromFile(const std::string& file, Scheduler& scheduler, bool withOrigin);
// Static members initialization
std::mutex ZoneImpl::_mutex;
std::unordered_map<std::string, std::weak_ptr<ZoneImpl>> ZoneImpl::_zones;
static const char* _dispatchFunction =
"function __napa_execute_dispatcher__(module, func, args) {\n"
" var f = require(module)[func];\n"
" if (!f) throw new Error(\"Cannot find function '\" + func + \"' in module '\" + module + \"'\");\n"
" return f.apply(this, args);\n"
"}";
// The path to the file containing the execute dispatcher function
static const std::string DISPATCHER_FILE = (boost::dll::this_line_location().parent_path() /
"lib\\napa-dispatcher.js").string();
std::shared_ptr<ZoneImpl> ZoneImpl::Create(const ZoneSettings& settings) {
std::lock_guard<std::mutex> lock(_mutex);
@ -38,12 +39,13 @@ std::shared_ptr<ZoneImpl> ZoneImpl::Create(const ZoneSettings& settings) {
MakeSharedEnabler(const ZoneSettings& settings) : ZoneImpl(settings) {}
};
std::shared_ptr<ZoneImpl> zone;
std::shared_ptr<ZoneImpl> zone = std::make_shared<MakeSharedEnabler>(settings);
_zones[settings.id] = zone;
try {
zone = std::make_shared<MakeSharedEnabler>(settings);
_zones[settings.id] = zone;
zone->Init();
} catch (const std::exception& ex) {
LOG_ERROR("Zone", "Failed to create zone '%s': %s", settings.id.c_str(), ex.what());
LOG_ERROR("Zone", "Failed to initialize zone '%s': %s", settings.id.c_str(), ex.what());
return nullptr;
}
return zone;
@ -69,36 +71,19 @@ std::shared_ptr<ZoneImpl> ZoneImpl::Get(const std::string& id) {
}
ZoneImpl::ZoneImpl(const ZoneSettings& settings) : _settings(settings) {
}
void ZoneImpl::Init() {
// Create the zone's scheduler.
_scheduler = std::make_unique<Scheduler>(_settings);
// Set the dispatcher function on all zone workers.
_scheduler->ScheduleOnAllWorkers(std::make_shared<BroadcastTask>(_dispatchFunction));
// Read dispatcher file content and broadcast it on all workers.
// Do not set origin to avoid changing the global context path.
BroadcastFromFile(DISPATCHER_FILE, *_scheduler, false);
// Read bootstrap file content and broadcast it on all workers.
if (!_settings.bootstrapFile.empty()) {
auto filePath = boost::filesystem::path(_settings.bootstrapFile);
if (filePath.is_relative()) {
filePath = (boost::filesystem::current_path() / filePath).normalize().make_preferred();
}
auto filePathString = filePath.string();
std::ifstream ifs;
ifs.open(filePathString);
if (!ifs.is_open()) {
throw std::runtime_error("Failed to open bootstrap file: " + filePathString);
}
std::stringstream buffer;
buffer << ifs.rdbuf();
auto fileContent = buffer.str();
if (fileContent.empty()) {
throw std::runtime_error("Bootstrap file content was empty: " + filePathString);
}
_scheduler->ScheduleOnAllWorkers(std::make_shared<BroadcastTask>(std::move(fileContent), filePath.string()));
BroadcastFromFile(_settings.bootstrapFile, *_scheduler, true);
}
}
@ -141,4 +126,30 @@ const ZoneSettings& ZoneImpl::GetSettings() const {
Scheduler* ZoneImpl::GetScheduler() {
return _scheduler.get();
}
static void BroadcastFromFile(const std::string& file, Scheduler& scheduler, bool withOrigin) {
auto filePath = boost::filesystem::path(file);
if (filePath.is_relative()) {
filePath = (boost::filesystem::current_path() / filePath).normalize().make_preferred();
}
auto filePathString = filePath.string();
std::ifstream ifs;
ifs.open(filePathString);
if (!ifs.is_open()) {
throw std::runtime_error("Failed to open file: " + filePathString);
}
std::stringstream buffer;
buffer << ifs.rdbuf();
auto fileContent = buffer.str();
if (fileContent.empty()) {
throw std::runtime_error("File content was empty: " + filePathString);
}
auto scriptOrigin = (withOrigin) ? filePathString : "";
scheduler.ScheduleOnAllWorkers(std::make_shared<BroadcastTask>(std::move(fileContent), scriptOrigin));
}

Просмотреть файл

@ -39,6 +39,7 @@ namespace napa {
private:
explicit ZoneImpl(const ZoneSettings& settings);
void Init();
ZoneSettings _settings;
std::unique_ptr<scheduler::Scheduler> _scheduler;

Просмотреть файл

@ -1,5 +1,7 @@
#include "catch.hpp"
#include "napa-initialization-guard.h"
#include <napa-memory.h>
#include <napa/memory/allocator-debugger.h>
#include <napa/stl/allocator.h>
@ -9,6 +11,9 @@
using namespace napa::memory;
// Make sure Napa is initialized exactly once.
static NapaInitializationGuard _guard;
namespace custom_allocator {
char buffer[1024];
size_t allocated = 0;
@ -132,34 +137,34 @@ struct Foo {
TEST_CASE("Memory helpers", "[memory-helpers]") {
NAPA_SET_DEFAULT_ALLOCATOR(custom_allocator::malloc, custom_allocator::free);
SECTION("NAPA_MAKE_UNIQUE") {
constexpr size_t count = 2;
int elems[count] = {1, 2};
{
auto pointer = NAPA_MAKE_UNIQUE<Foo>("hello world", count, elems);
REQUIRE(pointer != nullptr);
{
auto pointer = NAPA_MAKE_UNIQUE<Foo>("hello world", count, elems);
REQUIRE(pointer != nullptr);
REQUIRE(pointer->_str == "hello world");
REQUIRE(pointer->_vector.size() == 2);
REQUIRE(pointer->_map.size() == 2);
}
REQUIRE(custom_allocator::allocated == 0);
REQUIRE(pointer->_str == "hello world");
REQUIRE(pointer->_vector.size() == 2);
REQUIRE(pointer->_map.size() == 2);
}
REQUIRE(custom_allocator::allocated == 0);
custom_allocator::reset();
}
SECTION("NAPA_MAKE_SHARED") {
constexpr size_t count = 2;
int elems[count] = { 1, 2 };
{
auto pointer = NAPA_MAKE_SHARED<Foo>("hello world", count, elems);
REQUIRE(pointer != nullptr);
SECTION("NAPA_MAKE_SHARED") {
constexpr size_t count = 2;
int elems[count] = { 1, 2 };
{
auto pointer = NAPA_MAKE_SHARED<Foo>("hello world", count, elems);
REQUIRE(pointer != nullptr);
REQUIRE(pointer->_str == "hello world");
REQUIRE(pointer->_vector.size() == 2);
REQUIRE(pointer->_map.size() == 2);
}
REQUIRE(custom_allocator::allocated == 0);
REQUIRE(pointer->_str == "hello world");
REQUIRE(pointer->_vector.size() == 2);
REQUIRE(pointer->_map.size() == 2);
}
REQUIRE(custom_allocator::allocated == 0);
custom_allocator::reset();
}

Просмотреть файл

@ -26,6 +26,7 @@
<PropertyGroup>
<V8BinPath>$(PACKAGESROOT)\V8.Library\$(COMPILER_VERSION)\bin\$(BuildType)\$(BuildArchitecture)</V8BinPath>
<V8MiscPath>$(PACKAGESROOT)\V8.Library\misc\$(BuildType)\$(BuildArchitecture)</V8MiscPath>
<TestDir>$(OutDir)api-tests</TestDir>
</PropertyGroup>
<Import Project="$(ExtendedTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
@ -35,34 +36,22 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)\api-tests.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)\memory-tests.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)\zone-tests.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(NapaVanillaRoot)\src\napa.vcxproj" />
</ItemGroup>
<!-- Test artifacts binplace -->
<ItemGroup>
<Robocopy Include="$(NapaVanillaRoot)\src\$(IntermediateOutputPath)\napa.dll">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
<Robocopy Include="$(OutputPath)\$(TargetFileName)">
<DestinationFolder>$(IntermediateOutputPath)\api-tests\node_modules\napajs\bin</DestinationFolder>
</Robocopy>
<Robocopy Include="$(V8BinPath)\icui18n.dll">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
<Robocopy Include="$(NapaVanillaRoot)\modules\napajs\package\$(IntermediateOutputPath)\node_modules\napajs">
<DestinationFolder>$(IntermediateOutputPath)\api-tests\node_modules\napajs</DestinationFolder>
</Robocopy>
<Robocopy Include="$(V8BinPath)\icuuc.dll">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
</Robocopy>
<Robocopy Include="$(V8BinPath)\v8.dll">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
</Robocopy>
<Robocopy Include="$(V8MiscPath)\natives_blob.bin">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
</Robocopy>
<Robocopy Include="$(V8MiscPath)\snapshot_blob.bin">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
</Robocopy>
<Robocopy Include="$(MSBuildThisFileDirectory)\bootstrap.js">
<DestinationFolder>$(IntermediateOutputPath)</DestinationFolder>
<Robocopy Include="bootstrap.js">
<DestinationFolder>$(IntermediateOutputPath)\api-tests\node_modules\napajs\bin</DestinationFolder>
</Robocopy>
</ItemGroup>

Просмотреть файл

@ -0,0 +1,24 @@
#pragma once
#include <napa.h>
class NapaInitializationGuard {
public:
NapaInitializationGuard() {
static NapaInitialization initialization;
}
private:
class NapaInitialization {
public:
NapaInitialization() {
// Only call napa::Initialize once per process.
napa::Initialize("--loggingProvider console");
}
~NapaInitialization() {
// Only call napa::Shutdown once per process.
napa::Shutdown();
}
};
};

Просмотреть файл

@ -1,16 +1,15 @@
#include "catch.hpp"
#include "napa.h"
#include "napa-initialization-guard.h"
#include <napa.h>
#include <future>
std::once_flag flag;
// Make sure Napa is initialized exactly once.
static NapaInitializationGuard _guard;
TEST_CASE("zone apis", "[api]") {
// Only call napa::Initialize once per process.
std::call_once(flag, []() {
REQUIRE(napa::Initialize("--loggingProvider console") == NAPA_RESPONSE_SUCCESS);
});
SECTION("create zone with bootstrap file") {
napa::ZoneProxy zone("zone1", "--bootstrapFile bootstrap.js");
@ -20,7 +19,7 @@ TEST_CASE("zone apis", "[api]") {
auto response = zone.ExecuteSync(request);
REQUIRE(response.code == NAPA_RESPONSE_SUCCESS);
REQUIRE(response.returnValue == "bootstrap");
REQUIRE(response.returnValue == "\"bootstrap\"");
}
SECTION("broadcast valid javascript") {
@ -81,6 +80,9 @@ TEST_CASE("zone apis", "[api]") {
std::promise<napa::ExecuteResponse> promise;
auto future = promise.get_future();
// Warmup to avoid loading napajs on first call
zone.BroadcastSync("require('napajs');");
zone.Broadcast("function func(a, b) { return Number(a) + Number(b); }", [&promise, &zone](NapaResponseCode) {
napa::ExecuteRequest request;
request.function = NAPA_STRING_REF("func");
@ -103,6 +105,9 @@ TEST_CASE("zone apis", "[api]") {
std::promise<napa::ExecuteResponse> promise;
auto future = promise.get_future();
// Warmup to avoid loading napajs on first call
zone.BroadcastSync("require('napajs');");
zone.Broadcast("function func() { while(true) {} }", [&promise, &zone](NapaResponseCode) {
napa::ExecuteRequest request;
request.function = NAPA_STRING_REF("func");
@ -121,6 +126,9 @@ TEST_CASE("zone apis", "[api]") {
SECTION("execute 2 javascript functions, one succeeds and one times out") {
napa::ZoneProxy zone("zone1");
// Warmup to avoid loading napajs on first call
zone.BroadcastSync("require('napajs');");
auto res = zone.BroadcastSync("function f1(a, b) { return Number(a) + Number(b); }");
REQUIRE(res == NAPA_RESPONSE_SUCCESS);
res = zone.BroadcastSync("function f2() { while(true) {} }");
@ -169,7 +177,7 @@ TEST_CASE("zone apis", "[api]") {
auto response = zone.ExecuteSync(request);
REQUIRE(response.code == NAPA_RESPONSE_SUCCESS);
REQUIRE(response.returnValue == ".txt");
REQUIRE(response.returnValue == "\".txt\"");
}
SECTION("execute function in a module") {
@ -178,18 +186,10 @@ TEST_CASE("zone apis", "[api]") {
napa::ExecuteRequest request;
request.module = NAPA_STRING_REF("path");
request.function = NAPA_STRING_REF("extname");
request.arguments = { NAPA_STRING_REF("test.txt") };
request.arguments = { NAPA_STRING_REF("\"test.txt\"") };
auto response = zone.ExecuteSync(request);
REQUIRE(response.code == NAPA_RESPONSE_SUCCESS);
REQUIRE(response.returnValue == ".txt");
}
/// <note>
/// This must be the last test in this test suite, since napa initialize and shutdown
/// can only run once per process.
/// </note>
SECTION("shutdown napa") {
REQUIRE(napa::Shutdown() == NAPA_RESPONSE_SUCCESS);
REQUIRE(response.returnValue == "\".txt\"");
}
}

Просмотреть файл

@ -33,6 +33,9 @@ TEST_CASE("tasks", "[tasks]") {
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope contextScope(context);
// Set a simple dispatcher function
BroadcastTask("function __napa_function_dispatcher__(func, args) { return func.apply(this, args); }").Execute();
SECTION("load valid javascript") {
NapaResponseCode loadResponseCode;
BroadcastTask("var i = 3 + 5;", "", [&loadResponseCode](NapaResponseCode code) {
@ -87,7 +90,6 @@ TEST_CASE("tasks", "[tasks]") {
}).Execute();
REQUIRE(response.code == NAPA_RESPONSE_EXECUTE_FUNC_ERROR);
REQUIRE(response.errorMessage == "Function 'bar' not defined");
}
SECTION("execute fails when function throws exception") {

Просмотреть файл

@ -2,7 +2,8 @@
<Project ToolVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<QTestType>Console</QTestType>
<QTestDirToDeploy>$(OutDir)</QTestDirToDeploy>
<QTestDirToDeploy Condition="'$(TestDir)' == ''">$(OutDir)</QTestDirToDeploy>
<QTestDirToDeploy Condition="'$(TestDir)' != ''">$(TestDir)</QTestDirToDeploy>
<QTestAdditionalOptions>-r napa -o $(TargetName)$(TargetExt).xml</QTestAdditionalOptions>
</PropertyGroup>
<ItemDefinitionGroup>