Repository: microsoft/napajs
Branch: master
Commit: b38a2385c076
Files: 458
Total size: 2.3 MB
Directory structure:
gitextract_sogveh4o/
├── .gitignore
├── .npmignore
├── .travis.yml
├── CMakeLists.txt
├── LICENSE.txt
├── README.md
├── appveyor.yml
├── benchmark/
│ ├── README.md
│ ├── bench-utils.ts
│ ├── bench.ts
│ ├── execute-overhead.ts
│ ├── execute-scalability.ts
│ ├── node-napa-perf-comparison.ts
│ ├── store-overhead.ts
│ ├── transport-overhead.ts
│ └── tsconfig.json
├── build.bat
├── build.js
├── docs/
│ ├── api/
│ │ ├── index.md
│ │ ├── log.md
│ │ ├── memory.md
│ │ ├── metric.md
│ │ ├── module.md
│ │ ├── napa-globals.md
│ │ ├── node-api.md
│ │ ├── store.md
│ │ ├── sync.md
│ │ ├── transport.md
│ │ └── zone.md
│ └── design/
│ └── transport-js-builtins.md
├── examples/
│ ├── modules/
│ │ ├── README.md
│ │ ├── async-number/
│ │ │ ├── .gitignore
│ │ │ ├── .npmignore
│ │ │ ├── README.md
│ │ │ ├── binding.gyp
│ │ │ ├── lib/
│ │ │ │ ├── async-number.ts
│ │ │ │ └── tsconfig.json
│ │ │ ├── napa/
│ │ │ │ └── addon.cpp
│ │ │ ├── package.json
│ │ │ └── test/
│ │ │ ├── test.ts
│ │ │ └── tsconfig.json
│ │ ├── hello-world/
│ │ │ ├── .gitignore
│ │ │ ├── .npmignore
│ │ │ ├── README.md
│ │ │ ├── binding.gyp
│ │ │ ├── lib/
│ │ │ │ ├── hello-world.ts
│ │ │ │ └── tsconfig.json
│ │ │ ├── napa/
│ │ │ │ └── addon.cpp
│ │ │ ├── package.json
│ │ │ └── test/
│ │ │ ├── test.ts
│ │ │ └── tsconfig.json
│ │ └── plus-number/
│ │ ├── .gitignore
│ │ ├── .npmignore
│ │ ├── CMakeLists.txt
│ │ ├── README.md
│ │ ├── inc/
│ │ │ └── plus-number.h
│ │ ├── lib/
│ │ │ ├── plus-number.ts
│ │ │ └── tsconfig.json
│ │ ├── napa/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── addon.cpp
│ │ │ ├── plus-number-wrap.cpp
│ │ │ └── plus-number-wrap.h
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── CMakeLists.txt
│ │ │ └── plus-number.cpp
│ │ ├── test/
│ │ │ ├── test.ts
│ │ │ └── tsconfig.json
│ │ └── unittest/
│ │ ├── CMakeLists.txt
│ │ ├── catch/
│ │ │ ├── LICENSE.txt
│ │ │ └── catch.hpp
│ │ ├── main.cpp
│ │ └── run.js
│ └── tutorial/
│ ├── estimate-pi-in-parallel/
│ │ ├── README.md
│ │ ├── estimate-pi-in-parallel.js
│ │ └── package.json
│ ├── max-square-sub-matrix/
│ │ ├── README.md
│ │ ├── max-square-sub-matrix.js
│ │ └── package.json
│ ├── napa-runner/
│ │ ├── CMakeLists.txt
│ │ ├── README.md
│ │ ├── estimate-PI.js
│ │ ├── main.cpp
│ │ └── package.json
│ ├── parallel-quick-sort/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── parallel-quick-sort.js
│ ├── recursive-fibonacci/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── recursive-fibonacci.js
│ └── synchronized-loading/
│ ├── README.md
│ ├── package.json
│ ├── phone-book-data.json
│ ├── phone-book.js
│ └── synchronized-loading.js
├── inc/
│ ├── napa/
│ │ ├── assert.h
│ │ ├── async.h
│ │ ├── capi.h
│ │ ├── exports.h
│ │ ├── log.h
│ │ ├── memory/
│ │ │ ├── allocator-debugger.h
│ │ │ ├── allocator.h
│ │ │ └── common.h
│ │ ├── memory.h
│ │ ├── module/
│ │ │ ├── binding/
│ │ │ │ ├── basic-wraps.h
│ │ │ │ └── wraps.h
│ │ │ ├── binding.h
│ │ │ ├── common.h
│ │ │ ├── module-internal.h
│ │ │ ├── module-node-compat.h
│ │ │ ├── object-wrap.h
│ │ │ ├── shareable-wrap.h
│ │ │ └── transport-context-wrap.h
│ │ ├── module.h
│ │ ├── providers/
│ │ │ ├── logging.h
│ │ │ └── metric.h
│ │ ├── result-codes.inc
│ │ ├── stl/
│ │ │ ├── allocator.h
│ │ │ ├── deque.h
│ │ │ ├── list.h
│ │ │ ├── map.h
│ │ │ ├── queue.h
│ │ │ ├── set.h
│ │ │ ├── stack.h
│ │ │ ├── string.h
│ │ │ ├── unordered_map.h
│ │ │ ├── unordered_set.h
│ │ │ └── vector.h
│ │ ├── transport/
│ │ │ ├── transport-context.h
│ │ │ ├── transport.h
│ │ │ └── transportable.h
│ │ ├── transport.h
│ │ ├── types.h
│ │ ├── utils.h
│ │ ├── v8-helpers/
│ │ │ ├── array.h
│ │ │ ├── console.h
│ │ │ ├── conversion.h
│ │ │ ├── flow.h
│ │ │ ├── function.h
│ │ │ ├── json.h
│ │ │ ├── maybe.h
│ │ │ ├── object.h
│ │ │ ├── ptr.h
│ │ │ ├── string.h
│ │ │ └── time.h
│ │ ├── v8-helpers.h
│ │ ├── version.h
│ │ ├── zone/
│ │ │ ├── napa-async-runner.h
│ │ │ └── node-async-runner.h
│ │ └── zone.h
│ └── napa.h
├── lib/
│ ├── binding.js
│ ├── core/
│ │ ├── .gitignore
│ │ ├── .npmignore
│ │ ├── README.md
│ │ ├── core-modules.json
│ │ ├── timers/
│ │ │ ├── timer-api.ts
│ │ │ └── timer.ts
│ │ └── timers.ts
│ ├── index.ts
│ ├── log.ts
│ ├── memory/
│ │ ├── allocator.ts
│ │ ├── handle.ts
│ │ └── shareable.ts
│ ├── memory.ts
│ ├── metric.ts
│ ├── runtime/
│ │ └── platform.ts
│ ├── runtime.ts
│ ├── store/
│ │ ├── store-api.ts
│ │ └── store.ts
│ ├── store.ts
│ ├── sync/
│ │ └── lock.ts
│ ├── sync.ts
│ ├── transport/
│ │ ├── builtin-object-transporter.ts
│ │ ├── function-transporter.ts
│ │ ├── transport.ts
│ │ └── transportable.ts
│ ├── transport.ts
│ ├── tsconfig.json
│ ├── v8/
│ │ └── stack-trace.ts
│ ├── v8.ts
│ ├── zone/
│ │ ├── function-call.ts
│ │ ├── zone-impl.ts
│ │ └── zone.ts
│ └── zone.ts
├── node/
│ ├── CMakeLists.txt
│ ├── addon.cpp
│ ├── node-zone-delegates.cpp
│ └── node-zone-delegates.h
├── package.json
├── scripts/
│ ├── clean.js
│ ├── embedded.js
│ ├── install.js
│ └── paths.js
├── src/
│ ├── CMakeLists.txt
│ ├── api/
│ │ └── capi.cpp
│ ├── memory/
│ │ └── built-in-allocators.cpp
│ ├── module/
│ │ ├── core-modules/
│ │ │ ├── core-modules.h
│ │ │ ├── napa/
│ │ │ │ ├── allocator-debugger-wrap.cpp
│ │ │ │ ├── allocator-debugger-wrap.h
│ │ │ │ ├── allocator-wrap.cpp
│ │ │ │ ├── allocator-wrap.h
│ │ │ │ ├── call-context-wrap.cpp
│ │ │ │ ├── call-context-wrap.h
│ │ │ │ ├── lock-wrap.cpp
│ │ │ │ ├── lock-wrap.h
│ │ │ │ ├── metric-wrap.cpp
│ │ │ │ ├── metric-wrap.h
│ │ │ │ ├── napa-binding.cpp
│ │ │ │ ├── napa-binding.h
│ │ │ │ ├── shared-ptr-wrap.cpp
│ │ │ │ ├── shared-ptr-wrap.h
│ │ │ │ ├── store-wrap.cpp
│ │ │ │ ├── store-wrap.h
│ │ │ │ ├── timer-wrap.cpp
│ │ │ │ ├── timer-wrap.h
│ │ │ │ ├── transport-context-wrap-impl.cpp
│ │ │ │ ├── transport-context-wrap-impl.h
│ │ │ │ ├── zone-wrap.cpp
│ │ │ │ └── zone-wrap.h
│ │ │ └── node/
│ │ │ ├── console.cpp
│ │ │ ├── console.h
│ │ │ ├── file-system-helpers.cpp
│ │ │ ├── file-system-helpers.h
│ │ │ ├── file-system.cpp
│ │ │ ├── file-system.h
│ │ │ ├── os.cpp
│ │ │ ├── os.h
│ │ │ ├── path.cpp
│ │ │ ├── path.h
│ │ │ ├── process.cpp
│ │ │ ├── process.h
│ │ │ ├── tty-wrap.cpp
│ │ │ └── tty-wrap.h
│ │ ├── loader/
│ │ │ ├── binary-module-loader.cpp
│ │ │ ├── binary-module-loader.h
│ │ │ ├── core-module-loader.cpp
│ │ │ ├── core-module-loader.h
│ │ │ ├── javascript-module-loader.cpp
│ │ │ ├── javascript-module-loader.h
│ │ │ ├── json-module-loader.cpp
│ │ │ ├── json-module-loader.h
│ │ │ ├── module-cache.cpp
│ │ │ ├── module-cache.h
│ │ │ ├── module-file-loader.h
│ │ │ ├── module-loader-helpers.cpp
│ │ │ ├── module-loader-helpers.h
│ │ │ ├── module-loader.cpp
│ │ │ ├── module-loader.h
│ │ │ ├── module-resolver-cache.cpp
│ │ │ ├── module-resolver-cache.h
│ │ │ ├── module-resolver.cpp
│ │ │ └── module-resolver.h
│ │ └── module.cpp
│ ├── platform/
│ │ ├── dll.cpp
│ │ ├── dll.h
│ │ ├── filesystem.cpp
│ │ ├── filesystem.h
│ │ ├── os.cpp
│ │ ├── os.h
│ │ ├── platform.h
│ │ ├── process.cpp
│ │ ├── process.h
│ │ └── thread-local.h
│ ├── providers/
│ │ ├── console-logging-provider.h
│ │ ├── nop-logging-provider.h
│ │ ├── nop-metric-provider.h
│ │ ├── providers.cpp
│ │ └── providers.h
│ ├── settings/
│ │ ├── settings-parser.cpp
│ │ ├── settings-parser.h
│ │ └── settings.h
│ ├── store/
│ │ ├── store.cpp
│ │ └── store.h
│ ├── utils/
│ │ └── string.h
│ ├── v8-extensions/
│ │ ├── CMakeLists.txt
│ │ ├── array-buffer-allocator.cpp
│ │ ├── array-buffer-allocator.h
│ │ ├── deserializer.cpp
│ │ ├── deserializer.h
│ │ ├── externalized-contents.cpp
│ │ ├── externalized-contents.h
│ │ ├── serialized-data.cpp
│ │ ├── serialized-data.h
│ │ ├── serializer.cpp
│ │ ├── serializer.h
│ │ ├── v8-common.cpp
│ │ ├── v8-common.h
│ │ ├── v8-extensions-macros.h
│ │ ├── v8-extensions.cpp
│ │ └── v8-extensions.h
│ └── zone/
│ ├── async-complete-task.cpp
│ ├── async-complete-task.h
│ ├── async-context.h
│ ├── async-runner.cpp
│ ├── call-context.cpp
│ ├── call-context.h
│ ├── call-task.cpp
│ ├── call-task.h
│ ├── eval-task.cpp
│ ├── eval-task.h
│ ├── napa-zone.cpp
│ ├── napa-zone.h
│ ├── node-zone.cpp
│ ├── node-zone.h
│ ├── schedule-phase.h
│ ├── scheduler.cpp
│ ├── scheduler.h
│ ├── simple-thread-pool.cpp
│ ├── simple-thread-pool.h
│ ├── task-decorators.h
│ ├── task.h
│ ├── terminable-task.cpp
│ ├── terminable-task.h
│ ├── timer.cpp
│ ├── timer.h
│ ├── worker-context.cpp
│ ├── worker-context.h
│ ├── worker.cpp
│ ├── worker.h
│ └── zone.h
├── test/
│ ├── memory-test.ts
│ ├── module/
│ │ ├── addon/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── addon.cpp
│ │ │ ├── simple-object-wrap.cpp
│ │ │ └── simple-object-wrap.h
│ │ ├── cycle-a.js
│ │ ├── cycle-b.js
│ │ ├── jsmodule.js
│ │ ├── node_modules/
│ │ │ └── file.js
│ │ ├── resolution-tests.js
│ │ ├── sub-folder/
│ │ │ ├── addon/
│ │ │ │ └── mock-addon.napa
│ │ │ └── file.js
│ │ └── test.json
│ ├── module-test.ts
│ ├── napa-zone/
│ │ ├── function-as-module.ts
│ │ ├── test-main.ts
│ │ └── test.ts
│ ├── store-test.ts
│ ├── sync-test.ts
│ ├── timer-test.ts
│ ├── transport-test.ts
│ ├── tsconfig.json
│ └── zone-test.ts
├── third-party/
│ ├── args/
│ │ ├── LICENSE
│ │ └── args.hxx
│ ├── catch/
│ │ ├── LICENSE.txt
│ │ └── catch.hpp
│ └── rapidjson/
│ ├── LICENSE.txt
│ ├── allocators.h
│ ├── document.h
│ ├── encodedstream.h
│ ├── encodings.h
│ ├── error/
│ │ ├── en.h
│ │ └── error.h
│ ├── filereadstream.h
│ ├── filewritestream.h
│ ├── fwd.h
│ ├── internal/
│ │ ├── biginteger.h
│ │ ├── diyfp.h
│ │ ├── dtoa.h
│ │ ├── ieee754.h
│ │ ├── itoa.h
│ │ ├── meta.h
│ │ ├── pow10.h
│ │ ├── regex.h
│ │ ├── stack.h
│ │ ├── strfunc.h
│ │ ├── strtod.h
│ │ └── swap.h
│ ├── istreamwrapper.h
│ ├── memorybuffer.h
│ ├── memorystream.h
│ ├── msinttypes/
│ │ ├── inttypes.h
│ │ └── stdint.h
│ ├── ostreamwrapper.h
│ ├── pointer.h
│ ├── prettywriter.h
│ ├── rapidjson.h
│ ├── reader.h
│ ├── schema.h
│ ├── stream.h
│ ├── stringbuffer.h
│ └── writer.h
└── unittest/
├── CMakeLists.txt
├── main.cpp
├── module/
│ ├── file-system-helpers-tests.cpp
│ ├── module-resolver-cache-tests.cpp
│ ├── module-resolver-tests.cpp
│ └── test-files/
│ ├── node_modules/
│ │ ├── resolve-nm-file
│ │ ├── resolve-nm-file-js.js
│ │ ├── resolve-nm-file-js.json
│ │ ├── resolve-nm-file-js.napa
│ │ ├── resolve-nm-file-json.json
│ │ ├── resolve-nm-file-json.napa
│ │ ├── resolve-nm-file-napa.napa
│ │ ├── resolve-nm-file.js
│ │ ├── resolve-nm-file.json
│ │ ├── resolve-nm-file.napa
│ │ ├── resolver-nm/
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ ├── index.napa
│ │ │ ├── package.json
│ │ │ └── resolve-file
│ │ ├── resolver-nm-js/
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ └── index.napa
│ │ ├── resolver-nm-json/
│ │ │ ├── index.json
│ │ │ └── index.napa
│ │ └── resolver-nm-napa/
│ │ └── index.napa
│ ├── resolve-directory/
│ │ ├── resolver/
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ ├── index.napa
│ │ │ ├── package.json
│ │ │ └── resolve-file
│ │ ├── resolver-js/
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ └── index.napa
│ │ ├── resolver-json/
│ │ │ ├── index.json
│ │ │ └── index.napa
│ │ └── resolver-napa/
│ │ └── index.napa
│ ├── resolve-env/
│ │ ├── resolve-env-file
│ │ ├── resolve-env-file-js.js
│ │ ├── resolve-env-file-js.json
│ │ ├── resolve-env-file-js.napa
│ │ ├── resolve-env-file-json.json
│ │ ├── resolve-env-file-json.napa
│ │ ├── resolve-env-file-napa.napa
│ │ ├── resolve-env-file.js
│ │ ├── resolve-env-file.json
│ │ ├── resolve-env-file.napa
│ │ ├── resolver-env/
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ ├── index.napa
│ │ │ ├── package.json
│ │ │ └── resolve-file
│ │ ├── resolver-env-js/
│ │ │ ├── index.js
│ │ │ ├── index.json
│ │ │ └── index.napa
│ │ ├── resolver-env-json/
│ │ │ ├── index.json
│ │ │ └── index.napa
│ │ └── resolver-env-napa/
│ │ └── index.napa
│ ├── resolve-file
│ ├── resolve-file-js.js
│ ├── resolve-file-js.json
│ ├── resolve-file-js.napa
│ ├── resolve-file-json.json
│ ├── resolve-file-json.napa
│ ├── resolve-file-napa.napa
│ ├── resolve-file-no.js
│ ├── resolve-file.js
│ ├── resolve-file.json
│ └── resolve-file.napa
├── platform/
│ └── filesystem-tests.cpp
├── run.js
├── settings/
│ └── parser-tests.cpp
├── utils/
│ └── string-tests.cpp
└── zone/
├── scheduler-tests.cpp
└── timer-tests.cpp
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
bin
build
node_modules
types
# ignore all js files under lib except explicit ones specified here
lib/**/*.js
!lib/core/*.js
!lib/zone/zone-main.js
!lib/binding.js
# ignore all js files under test
test/**/*.js
test/module/test-dir
test/module/test-file
!test/module/node_modules
!test/module/**/*.js
!unittest/module/test-files/node_modules
# ignore all js files under benchmark
benchmark/**/*.js
package-lock.json
!tsconfig.json
# ignore all VSCode files
.vscode
# ignore npm related files
npm-debug.log
================================================
FILE: .npmignore
================================================
bin
build
examples
# Exclude TypeScript source files.
benchmark/**/*.ts
benchmark/tsconfig.json
lib/**/*.ts
lib/tsconfig.json
test/**/*.ts
test/module/test-dir
test/module/test-file
test/tsconfig.json
.vscode
.npmrc
.npmignore
.taskkey
.travis.yml
appveyor.yml
npm-debug.log
================================================
FILE: .travis.yml
================================================
language: node_js
compiler: default
matrix:
exclude:
# Disable the default build and use customized matrix only.
- compiler: default
include:
# Node 6.x Linux (Precise) G++5.4.1
- os: linux
dist: precise
node_js: '6'
compiler: g++-5
addons:
apt:
# The apt source 'ubuntu-toolchain-r-test' is for GCC 5+
# The apt source 'george-edison55-precise-backports' is for CMake 3.2+
sources:
- ubuntu-toolchain-r-test
- george-edison55-precise-backports
packages:
- g++-5
- cmake-data
- cmake
env:
- COMPILER_OVERRIDE="CXX=g++-5 CC=gcc-5"
# Node 6.x OS X (Yosemite) AppleClang 6.1
- os: osx
node_js: '6'
osx_image: xcode6.4
# Node LTS (8.x) Linux (Trusty) G++6.4.0
- os: linux
dist: trusty
node_js: '8'
compiler: g++-6
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-6
env:
- COMPILER_OVERRIDE="CXX=g++-6 CC=gcc-6"
# Node LTS (8.x) OS X (El Capitan) AppleClang 7.3
- os: osx
node_js: '8'
osx_image: xcode7.3
# Node (9.x) Linux (Trusty) G++6.4.0
- os: linux
dist: trusty
node_js: '9'
compiler: g++-6
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-6
env:
- COMPILER_OVERRIDE="CXX=g++-6 CC=gcc-6"
# Node (9.x) macOS (Sierra) AppleClang 8.1
- os: osx
node_js: '9'
osx_image: xcode8.3
# Node Current (10.x) Linux (Trusty) G++7.3.0
- os: linux
dist: trusty
node_js: '10'
compiler: g++-7
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-7
env:
- COMPILER_OVERRIDE="CXX=g++-7 CC=gcc-7"
# Node Current (10.x) macOS (High Sierra) AppleClang 9.1
- os: osx
node_js: '10'
osx_image: xcode9.3
before_install:
- |
if [ $TRAVIS_OS_NAME == linux ]; then
export ${COMPILER_OVERRIDE}
fi
install:
- npm install cmake-js -g
- npm install --no-fetch
script:
- npm test
- npm run unittest
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.2 FATAL_ERROR)
project("napa")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# Require Cxx14 features
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (NOT WIN32 AND NOT APPLE)
# Set symbol visibility to hidden by default.
# Napa shared library shares a few classes with napa-binding.node with different compile definition,
# exposing the same symbols from both shared libraries may cause improper behaviors under gcc.
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN)
# Prevent symbol relocations internal to our wrapper library to be overwritten by the application.
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-Bsymbolic -Wl,-Bsymbolic-functions")
set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-Bsymbolic -Wl,-Bsymbolic-functions")
# Mark object non-lazy runtime binding.
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,now")
set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,now")
endif ()
# Build napa shared library.
add_subdirectory(src)
if (CMAKE_JS_VERSION)
# Build napa addon for node.
add_subdirectory(node)
add_subdirectory(test/module/addon)
endif()
================================================
FILE: LICENSE.txt
================================================
Napa.JS
Copyright (c) Microsoft Corporation. All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
[](https://travis-ci.org/Microsoft/napajs)
[](https://ci.appveyor.com/project/napajs/napajs)
[](https://www.npmjs.com/package/napajs)
[](https://www.npmjs.com/package/napajs)
# Napa.js
Napa.js is a multi-threaded JavaScript runtime built on [V8](https://github.com/v8/v8), which was originally designed to develop highly iterative services with non-compromised performance in Bing. As it evolves, we find it useful to complement [Node.js](https://nodejs.org) in CPU-bound tasks, with the capability of executing JavaScript in multiple V8 isolates and communicating between them. Napa.js is exposed as a Node.js module, while it can also be embedded in a host process without Node.js dependency.
## Installation
Install the latest stable version:
```
npm install napajs
```
Other options can be found in [Build Napa.js](https://github.com/Microsoft/napajs/wiki/build-napa.js).
## Quick Start
```js
const napa = require('napajs');
const zone1 = napa.zone.create('zone1', { workers: 4 });
// Broadcast code to all 4 workers in 'zone1'.
zone1.broadcast('console.log("hello world");');
// Execute an anonymous function in any worker thread in 'zone1'.
zone1.execute(
(text) => text,
['hello napa'])
.then((result) => {
console.log(result.value);
});
```
More examples:
* [Estimate PI in parallel](./examples/tutorial/estimate-pi-in-parallel)
* [Max sub-matrix of 1s with layered parallelism](./examples/tutorial/max-square-sub-matrix)
* [Parallel Quick Sort](./examples/tutorial/parallel-quick-sort)
* [Recursive Fibonacci with multiple JavaScript threads](./examples/tutorial/recursive-fibonacci)
* [Synchronized loading](./examples/tutorial/synchronized-loading)
## Features
- Multi-threaded JavaScript runtime.
- Node.js compatible module architecture with NPM support.
- API for object transportation, object sharing and synchronization across JavaScript threads.
- API for pluggable logging, metric and memory allocator.
- Distributed as a Node.js module, as well as supporting embed scenarios.
## Documentation
- [Napa.js Home](https://github.com/Microsoft/napajs/wiki)
- [API Reference](./docs/api/index.md)
- [FAQ](https://github.com/Microsoft/napajs/wiki/FAQ)
# Contribute
You can contribute to Napa.js in following ways:
* [Report issues](https://github.com/Microsoft/napajs/issues) and help us verify fixes as they are checked in.
* Review the [source code changes](https://github.com/Microsoft/napajs/pulls).
* Contribute to core module compatibility with Node.
* Contribute bug fixes.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact opencode@microsoft.com with any additional questions or comments.
# License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [MIT](https://github.com/Microsoft/napajs/blob/master/LICENSE.txt) License.
================================================
FILE: appveyor.yml
================================================
environment:
matrix:
# Windows Server 2012 R2 Visual C++ Build Tools 2015
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
nodejs_version: 6
# Windows Server 2012 R2 Visual C++ Build Tools 2015
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
nodejs_version: 8
# Windows Server 2016 Visual C++ Build Tools 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
nodejs_version: 9
# Windows Server 2016 Visual C++ Build Tools 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
nodejs_version: 10
platform:
- x64
install:
- ps: Install-Product node $env:nodejs_version $env:platform
- npm install cmake-js -g
- npm install --no-fetch
# Skip MSBuild stage
build: off
test_script:
- npm test
- npm run unittest
================================================
FILE: benchmark/README.md
================================================
# Benchmark
## Summary
- JavaScript execution in napajs is on par with node, using the same version of V8, which is expected.
- `zone.execute` scales linearly on number of workers, which is expected.
- The overhead of calling `zone.execute` from node is around 0.1ms after warm-up. The cost of using anonymous function is neglectable.
- `transport.marshall` cost on small plain JavaScript values is about 3x of JSON.stringify.
- The overhead of `store.set` and `store.get` is around 0.06ms plus transport overhead on the objects.
We got this report on environment below:
| Name | Value |
|-------------------|---------------------------------------------------------------------------------------|
|**Processor** |Intel(R) Xeon(R) CPU L5640 @ 2.27GHz, 8 virtual processors |
|**System Type** |x64-based PC |
|**Physical Memory**|16.0 GB |
|**OS version** |Microsoft Windows Server 2012 R2 |
## Napa vs. Node on JavaScript execution
Please refer to [node-napa-perf-comparison.ts](node-napa-perf-comparison.ts).
| node time | napa time |
| --------- | --------- |
| 3026.76 | 3025.81 |
## Linear scalability
`zone.execute` scales linearly on number of workers. We performed 1M CRC32 calls on a 1024-length string on each worker, here are the numbers. We still need to understand why the time of more workers running parallel would beat less workers.
| | node | napa - 1 worker | napa - 2 workers | napa - 4 workers | napa - 8 workers |
| ------- | ----------- | --------------- | ---------------- | ---------------- | ---------------- |
| time | 8,649521600 | 6146.98 | 4912.57 | 4563.48 | 6168.41 |
| cpu% | ~15% | ~15% | ~27% | ~55% | ~99% |
Please refer to [execute-scalability.ts](./execute-scalability.ts) for test details.
## Execute overhead
The overhead of `zone.execute` includes
1. Marshalling cost of arguments in caller thread.
2. Queuing time before a worker can execute.
3. Unmarshalling cost of arguments in target worker.
4. Marshalling cost of return value from target worker.
5. Queuing time before caller callback is notified.
6. Unmarshalling cost of return value in caller thread.
In this section we will examine #2 and #5. So we use empty function with no arguments and no return value.
Transport overhead (#1, #3, #4, #6) varies by size and complexity of payload, will be benchmarked separately in [Transport Overhead](#transport-overhead) section.
Please refer to [execute-overhead.ts](./execute-overhead.ts) for test details.
### Overhead after warm-up
Average overhead is around 0.06ms to 0.12ms for `zone.execute`.
| repeat | zone.execute (ms) |
|----------|-------------------|
| 200 | 24.932 |
| 5000 | 456.893 |
| 10000 | 810.687 |
| 50000 | 3387.361 |
*10000 times of zone.execute on anonymous function is 807.241ms. The gap is within range of bench noise.
### Overhead during warm-up:
| Sequence of call | Time (ms) |
|------------------|-----------|
| 1 | 6.040 |
| 2 | 4.065 |
| 3 | 5.250 |
| 4 | 4.652 |
| 5 | 1.572 |
| 6 | 1.366 |
| 7 | 1.403 |
| 8 | 1.213 |
| 9 | 0.450 |
| 10 | 0.324 |
| 11 | 0.193 |
| 12 | 0.238 |
| 13 | 0.191 |
| 14 | 0.230 |
| 15 | 0.203 |
| 16 | 0.188 |
| 17 | 0.188 |
| 18 | 0.181 |
| 19 | 0.185 |
| 20 | 0.182 |
## Transport overhead
The overhead of `transport.marshall` includes
1. overhead of needing replacer callback during JSON.stringify. (even an empty callback will slow down JSON.stringify significantly)
2. traverse every value during JSON.stringify, to check value type and get `cid` to put into payload.
- a. If value doesn't need special care.
- b. If value is a transportable object that needs special care.
2.b is related to individual transportable classes, which may vary per individual class. Thus we examine #1 and #2.a in this test.
The overhead of `transport.unmarshall` includes
1. overhead of needing reviver callback during JSON.parse.
2. traverse every value during JSON.parse, to check if object has `_cid` property.
- a. If value doesn't have property `_cid`.
- b. Otherwise, find constructor and call the [`Transportable.marshall`](../docs/api/transport.md#transportable-marshall).
We also evaluate only #1, #2.a in this test.
Please refer to [transport-overhead.ts](./transport-overhead.ts) for test details.
\*All operations are repeated for 1000 times.
| payload type | size | JSON.stringify (ms) | transport.marshall (ms) | JSON.parse (ms) | transport.unmarshall (ms) |
| ---------------------------------- | ----- | ------------------- | ----------------------- | --------------- | ------------------------- |
| 1 level - 10 integers | 91 | 4.90 | 18.05 (3.68x) | 3.50 | 17.98 (5.14x) |
| 1 level - 100 integers | 1081 | 65.45 | 92.78 (1.42x) | 20.45 | 122.25 (5.98x) |
| 10 level - 2 integers | 18415 | 654.40 | 2453.37 (3.75x) | 995.02 | 2675.72 (2.69x) |
| 2 level - 10 integers | 991 | 19.74 | 66.82 (3.39x) | 27.85 | 138.45 (4.97x) |
| 3 level - 5 integers | 1396 | 33.66 | 146.33 (4.35x) | 51.54 | 189.07 (3.67x) |
| 1 level - 10 strings - length 10 | 201 | 3.81 | 10.17 (2.67x) | 9.46 | 20.81 (2.20x) |
| 1 level - 100 strings - length 10 | 2191 | 76.53 | 115.74 (1.51x) | 77.71 | 181.24 (2.33x) |
| 2 level - 10 strings - length 10 | 2091 | 30.15 | 97.65 (3.24x) | 95.51 | 213.20 (2.23x) |
| 3 level - 5 strings - length 10 | 2646 | 41.95 | 155.42 (3.71x) | 123.82 | 227.90 (1.84x) |
| 1 level - 10 strings - length 100 | 1101 | 7.74 | 12.19 (1.57x) | 17.34 | 29.83 (1.72x) |
| 1 level - 100 strings - length 100 | 11191 | 66.17 | 112.83 (1.71x) | 197.67 | 282.63 (1.43x) |
| 2 level - 10 strings - length 100 | 11091 | 68.46 | 149.99 (2.19x) | 202.85 | 298.19 (1.47x) |
| 3 level - 5 integers | 13896 | 89.46 | 208.21 (2.33x) | 265.25 | 418.42 (1.58x) |
| 1 level - 10 booleans | 126 | 2.84 | 8.14 (2.87x) | 3.06 | 14.20 (4.65x) |
| 1 level - 100 booleans | 1341 | 20.28 | 59.36 (2.93x) | 21.59 | 121.15 (5.61x) |
| 2 level - 10 booleans | 1341 | 23.92 | 89.62 (3.75x) | 31.84 | 137.92 (4.33x) |
| 3 level - 5 booleans | 1821 | 36.15 | 138.24 (3.82x) | 55.71 | 195.50 (3.51x) |
## Store access overhead
The overhead of `store.set` includes
1. Overhead of calling `transport.marshall` on value.
2. Overhead of put marshalled data and transport context into C++ map (with exclusive_lock).
The overhead of `store.get` includes
1. Overhead of getting marshalled data and transport context from C++ map (with shared_lock).
2. Overhead of calling `transport.unmarshall` on marshalled data.
For `store.set`, numbers below indicates the cost beyond marshall is around 0.07~0.4ms varies per payload size. (10B to 18KB). `store.get` takes a bit more: 0.06~0.9ms with the same payload size variance. If the value in store is not updated frequently, it's always good to cache it in JavaScript world.
Please refer to [store-overhead.ts](./store-overhead.ts) for test details.
\*All operations are repeated for 1000 times.
| payload type | size | transport.marshall (ms) | store.save (ms) | transport.unmarshall (ms) | store.get (ms) |
| ---------------------------------- | ----- | ----------------------- | --------------- | ------------------------- | -------------- |
| 1 level - 1 integers | 10 | 2.54 | 73.85 | 3.98 | 65.57 |
| 1 level - 10 integers | 91 | 8.27 | 98.55 | 17.23 | 90.89 |
| 1 level - 100 integers | 1081 | 97.10 | 185.31 | 144.75 | 274.39 |
| 10 level - 2 integers | 18415 | 2525.18 | 2973.17 | 3093.06 | 3927.80 |
| 2 level - 10 integers | 991 | 71.22 | 174.01 | 154.76 | 276.04 |
| 3 level - 5 integers | 1396 | 127.06 | 219.73 | 182.27 | 337.59 |
| 1 level - 10 strings - length 10 | 201 | 14.43 | 79.68 | 31.28 | 84.71 |
| 1 level - 100 strings - length 10 | 2191 | 104.40 | 212.44 | 173.32 | 239.09 |
| 2 level - 10 strings - length 10 | 2091 | 79.54 | 188.72 | 189.29 | 252.83 |
| 3 level - 5 strings - length 10 | 2646 | 155.14 | 257.78 | 276.22 | 342.95 |
| 1 level - 10 strings - length 100 | 1101 | 15.22 | 89.84 | 30.87 | 88.18 |
| 1 level - 100 strings - length 100 | 11191 | 119.89 | 284.05 | 287.17 | 403.77 |
| 2 level - 10 strings - length 100 | 11091 | 137.10 | 299.32 | 244.13 | 297.12 |
| 3 level - 5 integers | 13896 | 183.84 | 310.89 | 285.80 | 363.50 |
| 1 level - 10 booleans | 126 | 5.74 | 49.89 | 22.69 | 97.27 |
| 1 level - 100 booleans | 1341 | 57.41 | 157.80 | 106.30 | 218.05 |
| 2 level - 10 booleans | 1341 | 76.93 | 150.25 | 104.02 | 185.82 |
| 3 level - 5 booleans | 1821 | 102.47 | 171.44 | 150.42 | 207.27 |
================================================
FILE: benchmark/bench-utils.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/// Utility function to generate object for testing.
export function generateString(length: number): string {
return Array(length).join('x');
}
export function generateObject(keys: number, depth: number, valueType: string = "string", valueLength = 7) {
let object: any = {};
for (let i = 0; i < keys; ++i) {
let key = `key${i}`;
let value: any = null;
if (depth > 1) {
value = generateObject(keys, depth - 1, valueType, valueLength);
}
else if (valueType === 'string') {
// We try to make each string different.
value = generateString(valueLength - 1) + (depth * keys + i);
}
else if (valueType === 'number') {
value = i;
}
else if (valueType === 'boolean') {
value = i % 2 == 0;
}
object[key] = value;
}
return object;
}
export function timeDiffInMs(diff: [number, number]): number {
return (diff[0] * 1e9 + diff[1]) / 1e6;
}
export function formatTimeDiff(diff: number | [number, number], printUnit: boolean = false): string {
if (Array.isArray(diff)) {
diff = timeDiffInMs(diff);
}
let message = diff.toFixed(2);
if (printUnit) {
message += "ms"
}
return message;
}
export function formatRatio(dividend: number, divider: number): string {
return "(" + (dividend / divider).toFixed(2) + "x)";
}
================================================
FILE: benchmark/bench.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as napa from '../lib/index';
import * as nodeNapaPerfComp from './node-napa-perf-comparison';
import * as executeOverhead from './execute-overhead';
import * as executeScalability from './execute-scalability';
import * as transportOverhead from './transport-overhead';
import * as storeOverhead from './store-overhead';
export function bench(): Promise {
// Non-zone related benchmarks.
transportOverhead.bench();
storeOverhead.bench();
// Create zones for execute related benchmark.
let singleWorkerZone = napa.zone.create('single-worker-zone', { workers: 1});
let multiWorkerZone = napa.zone.create('multi-worker-zone', { workers: 8 });
return nodeNapaPerfComp.bench(singleWorkerZone)
.then(() => { return executeOverhead.bench(singleWorkerZone); })
.then(() => { return executeScalability.bench(multiWorkerZone);});
}
bench();
================================================
FILE: benchmark/execute-overhead.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as napa from '../lib/index';
import * as mdTable from 'markdown-table';
import { formatTimeDiff } from './bench-utils';
function batchExecuteOnNamedFunction(
zone: napa.zone.Zone,
repeat: number,
args: any[]): Promise {
return new Promise((resolve, reject) => {
let finished = 0;
let start = process.hrtime();
for (let i = 0; i < repeat; ++i) {
zone.execute("", "test", args).then(() => {
++finished;
if (finished === repeat) {
resolve();
}
});
}
});
}
function batchExecuteOnAnonymousFunction(
zone: napa.zone.Zone,
repeat: number,
args: any[]): Promise {
return new Promise((resolve, reject) => {
let finished = 0;
let start = process.hrtime();
for (let i = 0; i < repeat; ++i) {
zone.execute(() => {}, args).then(() => {
++finished;
if (finished === repeat) {
resolve();
}
});
}
});
}
export async function bench(zone: napa.zone.Zone): Promise {
console.log("Benchmarking execute overhead...");
// Prepare a empty function.
await zone.broadcast("function test() {}");
const ARGS = [1, "hello", {a: 1, b: true}];
// Warm-up.
const WARMUP_REPEAT: number = 20;
console.log("## Execute overhead during warmup\n")
let warmupTable = [];
warmupTable.push(["call #", "time (ms)"]);
for (let i = 0; i < WARMUP_REPEAT; ++i) {
let start = process.hrtime();
await zone.execute("", "test", ARGS);
warmupTable.push([i.toString(), formatTimeDiff(process.hrtime(start))]);
}
console.log(mdTable(warmupTable));
// execute after warm-up
const REPEAT: number = 10000;
console.log("## `zone.execute` overhead (use function name)\n");
let start = process.hrtime();
await batchExecuteOnNamedFunction(zone, REPEAT, ARGS);
console.log(`Elapse of running empty function by name for ${REPEAT} times: ${formatTimeDiff(process.hrtime(start), true)}\n`);
console.log("## `zone.execute` overhead (use anonymous function)\n");
start = process.hrtime();
await batchExecuteOnAnonymousFunction(zone, REPEAT, ARGS);
console.log(`Elapse of running empty anonymous function for ${REPEAT} times: ${formatTimeDiff(process.hrtime(start), true)}\n`);
return;
}
================================================
FILE: benchmark/execute-scalability.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as napa from '../lib/index';
import * as assert from 'assert';
import * as mdTable from 'markdown-table';
import { formatTimeDiff } from './bench-utils';
function makeCRCTable(){
var c;
var crcTable = [];
for(var n =0; n < 256; n++){
c = n;
for(var k =0; k < 8; k++){
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
crcTable[n] = c;
}
return crcTable;
}
let crcTable = makeCRCTable();
function crc32(str: string) {
let crc = 0 ^ (-1);
for (var i = 0; i < str.length; i++ ) {
crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
};
function testCrc() {
const REPEAT: number = 1000000;
let result = 0;
let key = Array(1024).join('x');
for (let i = 0; i < REPEAT; ++i) {
let hash = crc32(key);
result = result ^ hash;
}
return result;
}
export async function bench(zone: napa.zone.Zone): Promise {
console.log("Benchmarking execute scalability...");
// Prepare a empty function.
await zone.broadcast(makeCRCTable.toString());
await zone.broadcast("var crcTable = makeCRCTable();");
await zone.broadcast(crc32.toString());
await zone.broadcast(testCrc.toString());
// Warm-up.
let crcResult = testCrc();
await zone.broadcast("testCrc()");
// Execute in Node with 1 thread.
let start = process.hrtime();
assert(testCrc() === crcResult);
let nodeTime = formatTimeDiff(process.hrtime(start));
let executeTime = {};
let scalabilityTest = function(workers: number): Promise {
let finished = 0;
let start = process.hrtime();
return new Promise((resolve, reject) => {
for (let i = 0; i < workers; ++i) {
zone.execute("", "testCrc", []).then((result: napa.zone.Result) => {
assert(crcResult === result.value);
++finished;
if (finished === workers) {
executeTime[workers] = formatTimeDiff(process.hrtime(start))
resolve();
}
});
}
})
};
// Execute from 1 worker to 8 workers.
await scalabilityTest(1);
await scalabilityTest(2);
await scalabilityTest(4);
await scalabilityTest(8);
console.log("## Execute scalability\n")
console.log(mdTable([
["node", "napa - 1 worker", "napa - 2 workers", "napa - 4 workers", "napa - 8 workers"],
[nodeTime, executeTime[1], executeTime[2], executeTime[4], executeTime[8]]
]));
console.log('');
}
================================================
FILE: benchmark/node-napa-perf-comparison.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as napa from '../lib/index';
import * as mdTable from 'markdown-table';
import { formatTimeDiff } from './bench-utils';
export function timeIt(func: () => void): [number, number] {
let start = process.hrtime();
func();
return process.hrtime(start);
}
export function test1(): [number, number] {
return timeIt(() => {
const REPEAT = 1000000000;
let sum = 0;
for (let i = 0; i < REPEAT; ++i) {
sum += i;
}
});
}
export async function bench(zone: napa.zone.Zone): Promise {
zone.broadcast(timeIt.toString());
zone.broadcast(test1.toString());
// Warm up.
test1();
await zone.execute('', 'test1', []);
// Actual test.
let table = [];
table.push(["node time", "napa time"]);
let nodeTime = formatTimeDiff(test1());
let napaTime = formatTimeDiff((await zone.execute('', 'test1', [])).value);
table.push([nodeTime, napaTime]);
console.log("## Node vs Napa JavaScript execution performance\n");
console.log(mdTable(table));
console.log('');
}
================================================
FILE: benchmark/store-overhead.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as napa from '../lib/index';
import * as assert from 'assert';
import * as mdTable from 'markdown-table';
import { generateObject, formatTimeDiff } from './bench-utils';
type BenchmarkSettings = [
string, // label
number, // # of keys per level
number, // # of levels
string, // value type
number // value length for string.
];
export function bench() {
console.log("Benchmarking store overhead...");
let settings: BenchmarkSettings[] = [
// Number
["1 level - 1 integers", 1, 1, "number", 0],
["1 level - 10 integers", 10, 1, "number", 0],
["1 level - 100 integers", 100, 1, "number", 0],
["10 level - 2 integers", 2, 10, "number", 0],
["2 level - 10 integers", 10, 2, "number", 0],
["3 level - 5 integers", 5, 3, "number", 0],
// String
["1 level - 10 strings - length 10", 10, 1, "string", 10],
["1 level - 100 strings - length 10", 100, 1, "string", 10],
["2 level - 10 strings - length 10", 10, 2, "string", 10],
["3 level - 5 strings - length 10", 5, 3, "string", 10],
["1 level - 10 strings - length 100", 10, 1, "string", 100],
["1 level - 100 strings - length 100", 100, 1, "string", 100],
["2 level - 10 strings - length 100", 10, 2, "string", 100],
["3 level - 5 integers", 5, 3, "string", 100],
// Boolean
["1 level - 10 booleans", 10, 1, "boolean", 0],
["1 level - 100 booleans", 100, 1, "boolean", 0],
["2 level - 10 booleans", 10, 2, "boolean", 0],
["3 level - 5 booleans", 5, 3, "boolean", 0],
];
let store = napa.store.create('store');
let table = [];
table.push(["payload type", "size", "transport.marshall (ms)", "store.save (ms)", "transport.unmarshall (ms)", "store.get (ms)"]);
for (let s of settings) {
const REPEAT = 1000;
let object = generateObject(s[1], s[2], s[3], s[4])
let payload = JSON.stringify(object);
let size = payload.length;
// transport.marshall
let start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
napa.transport.marshall(object, null);
}
let marshallTime = formatTimeDiff(process.hrtime(start));
// store.set
start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
store.set('key', object);
}
let storeSetTime = formatTimeDiff(process.hrtime(start));
assert.deepEqual(object, store.get('key'));
// transport.unmarshall
start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
napa.transport.unmarshall(payload, null);
}
let unmarshallTime = formatTimeDiff(process.hrtime(start));
// store.get
start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
store.get('key');
}
let storeGetTime = formatTimeDiff(process.hrtime(start));
table.push([s[0], size, marshallTime, storeSetTime, unmarshallTime, storeGetTime]);
}
console.log("## Store access overhead\n");
console.log(mdTable(table));
console.log('');
}
================================================
FILE: benchmark/transport-overhead.ts
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as napa from '../lib/index';
import * as assert from 'assert';
import * as mdTable from 'markdown-table';
import { generateObject, timeDiffInMs, formatTimeDiff, formatRatio } from './bench-utils';
type BenchmarkSettings = [
string, // label
number, // # of keys per level
number, // # of levels
string, // value type
number // value length for string.
];
export function bench() {
console.log("Benchmarking transport overhead...");
let settings: BenchmarkSettings[] = [
// Number
["1 level - 10 integers", 10, 1, "number", 0],
["1 level - 100 integers", 100, 1, "number", 0],
["10 level - 2 integers", 2, 10, "number", 0],
["2 level - 10 integers", 10, 2, "number", 0],
["3 level - 5 integers", 5, 3, "number", 0],
// String
["1 level - 10 strings - length 10", 10, 1, "string", 10],
["1 level - 100 strings - length 10", 100, 1, "string", 10],
["2 level - 10 strings - length 10", 10, 2, "string", 10],
["3 level - 5 strings - length 10", 5, 3, "string", 10],
["1 level - 10 strings - length 100", 10, 1, "string", 100],
["1 level - 100 strings - length 100", 100, 1, "string", 100],
["2 level - 10 strings - length 100", 10, 2, "string", 100],
["3 level - 5 integers", 5, 3, "string", 100],
// Boolean
["1 level - 10 booleans", 10, 1, "boolean", 0],
["1 level - 100 booleans", 100, 1, "boolean", 0],
["2 level - 10 booleans", 10, 2, "boolean", 0],
["3 level - 5 booleans", 5, 3, "boolean", 0],
];
let table = [];
table.push(["payload type", "size", "JSON.stringify (ms)", "transport.marshall (ms)", "JSON.parse (ms)", "transport.unmarshall (ms)"]);
for (let s of settings) {
const REPEAT = 1000;
let object = generateObject(s[1], s[2], s[3], s[4])
let payload = JSON.stringify(object);
let size = payload.length;
// JSON.stringify
let start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
JSON.stringify(object);
}
let stringifyTime = timeDiffInMs(process.hrtime(start));
let stringifyTimeText = formatTimeDiff(stringifyTime);
// transport.marshall
start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
napa.transport.marshall(object, null);
}
let marshallTime = timeDiffInMs(process.hrtime(start));
let marshallTimeText = formatTimeDiff(marshallTime) + " " + formatRatio(marshallTime, stringifyTime);
let marshalled = napa.transport.marshall(object, null);
assert.deepEqual(payload, marshalled);
// JSON.parse
start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
JSON.parse(payload);
}
let parseTime = timeDiffInMs(process.hrtime(start));
let parseTimeText = formatTimeDiff(parseTime);
start = process.hrtime();
for (let i = 0; i < REPEAT; ++i) {
napa.transport.unmarshall(payload, null);
}
let unmarshallTime = timeDiffInMs(process.hrtime(start));
let unmarshallTimeText = formatTimeDiff(unmarshallTime) + " " + formatRatio(unmarshallTime, parseTime);
table.push([s[0], size, stringifyTimeText, marshallTimeText, parseTimeText, unmarshallTimeText]);
}
console.log("## Transport overhead\n");
console.log(mdTable(table));
console.log('');
}
================================================
FILE: benchmark/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": false,
"declaration": false,
"sourceMap": false,
"lib": ["es2015"]
}
}
================================================
FILE: build.bat
================================================
:: Copyright (c) Microsoft Corporation. All rights reserved.
:: Licensed under the MIT license.
@echo off
set NODE_EXE=%~dp0\node.exe
if not exist "%NODE_EXE%" (
set NODE_EXE=node
)
%NODE_EXE% %~dp0\build.js %*
================================================
FILE: build.js
================================================
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// Distinguish between running this file as a script or loading it as a module.
if (module.parent) {
// Loaded as a module - 'require('./build.js')
exports.paths = require('./scripts/paths');
}
else {
// Loaded as a script - 'node build.js'
// Remove first two parameters which are node executable and this javascript file.
// Transform all parameters to lowercase for easier handling.
let params = process.argv.slice(2).map((item) => item.toLowerCase());
// Specify wheater cleanup is needed.
if (params.indexOf('clean') != -1) {
require('./scripts/clean').clean();
}
// Only build if embed was specified as a parameter.
if (params.indexOf('embed') != -1) {
let embedded = require('./scripts/embedded');
if (params.indexOf('debug') != -1) {
embedded.build('debug');
}
else {
embedded.build('release');
}
}
}
================================================
FILE: docs/api/index.md
================================================
# API References
## Core modules
- Napa.js specific [`global`](./napa-globals.md) variables
- Namespace [`zone`](./zone.md): Multi-thread JavaScript runtime
- Namespace [`transport`](./transport.md): Passing JavaScript values across threads
- Namespace [`store`](./store.md): Sharing JavaScript values by global storage
- Namespace [`sync`](./sync.md): Handling synchronization between threads
- Namespace [`memory`](./memory.md): Handling native objects and memory
- Namespace [`metric`](./metric.md): Pluggable metrics.
- Function [`log`](./log.md): Pluggable logging.
## Node compatibility
- [List of supported Node APIs](./node-api.md)
## API for creating modules
- [Writing Napa.js modules](./module.md)
================================================
FILE: docs/api/log.md
================================================
# Function `log`
## Table of Contents
- [Introduction](#intro)
- [C++ API](#cpp-api)
- [JavaScript API](#js-api)
- [`log(message: string): void`](#log)
- [`log(section: string, message: string): void`](#log-with-section)
- [`log(section: string, traceId: string, message: string): void`](#log-with-traceid)
- [`log.err(...)`](#log-err)
- [`log.warn(...)`](#log-warn)
- [`log.info(...)`](#log-info)
- [`log.debug(...)`](#log-debug)
- [Using custom logging providers](#use-custom-providers)
- [Developing custom logging providers](#develop-custom-providers)
## Introduction
Logging is a basic requirement for building services. `napajs` logging API enables developers to integrate their own logging capabilities in both JavaScript and C++ (addon) world.
A log row may contain the following information:
- (Optional) Section: Useful field to filter log rows. Treatment is defined by logging providers.
- (Optional) Trace ID: Useful field to join logs in the same transaction or request.
- (Required) Message: Log message.
- (Required) Logging level:
- Error: for application error.
- Warn: for warning information.
- Info: for notification.
- Debug: for debugging purpose.
## C++ API
Include header: ``
Macros:
- LOG_ERROR(section, format, ...)
- LOG_ERROR_WITH_TRACEID(section, traceId, format, ...)
- LOG_WARNING(section, format, ...)
- LOG_WARNING_WITH_TRACEID(section, traceId, format, ...)
- LOG_INFO(section, format, ...)
- LOG_INFO_WITH_TRACEID(section, traceId, format, ...)
- LOG_DEBUG(section, format, ...)
- LOG_DEBUG_WITH_TRACEID(section, traceId, format, ...)
```cpp
#include
void MyFunction() {
// ...
LOG_ERROR("init", "error: %s", errorMessage.c_str());
}
```
## JavaScript API
### log(message: string): void
It logs a message. Using info level.
* A `log` is a shortcut for `log.info`.*
Example:
```js
var napa = require('napajs');
napa.log('program started');
```
### log(section: string, message: string): void
It logs a message with a section. Using info level.
Example:
```js
napa.log('init', 'program started');
```
### log(section: string, traceId: string, message: string): void
It logs a message with a section, associating it with a traceId. Using info level.
Example:
```js
napa.log('request', 'A1B2C3D4', 'request received');
```
### log.err(...)
It logs an error message. Three variations of arguments are the same with the `log`.
### log.warn(...)
It logs a warning message. Three variations of arguments are the same with the `log`.
### log.info(...)
It logs an info message. Three variations of arguments are the same with the `log`.
### log.debug(...)
It logs a debug message. Three combinations of arguments are the same with the `log`.
## Using custom logging providers
Developers can hook up custom logging provider by calling the following before creation of any zones:
```js
napa.runtime.setPlatformSettings({
"loggingProvider": ""
}
```
## Developing custom logging providers
TBD
================================================
FILE: docs/api/memory.md
================================================
# Namespace `memory`
## Table of Contents
- [API](#api)
- Type [`Handle`](#handle)
- Interface [`Shareable`](#shareable)
- Interface [`Allocator`](#allocator)
- [`allocator.allocate(size: number): Handle`](#allocator-allocate)
- [`allocator.deallocate(handle: Handle, sizeHint: number): void`](#allocator-deallocate)
- [`allocator.type: string`](#allocator-type)
- Interface [`AllocatorDebugger`](#allocatordebugger)
- [`allocatorDebugger.getDebugInfo(): string`](#allocatordebugger-getdebuginfo)
- Function [`debugAllocator(allocator: Allocator): AllocatorDebugger`](#debugallocator)
- Object [`crtAllocator`](#crtallocator)
- Object [`defaultAllocator`](#defaultallocator)
- [Memory allocation in C++ addon](#memory-allocation-in-cpp-addon)
## API
## Type `Handle`
Handle is defined in TypeScript as below:
```ts
type Handle = [number, number]
```
It is a standard way to represent a 64-bit pointer in Napa.
## Interface `Shareable`
Interface for native object wrap that can be shared across multiple JavaScript threads.
## Interface `Allocator`
Interface for memory allocator that allocates memory for native objects.
### allocator.allocate(size: number): Handle
It allocates memory of requested size.
```js
var handle = allocator.allocate(10);
```
### allocator.deallocate(handle: Handle, sizeHint: number): void
It deallocates memory from a input handle, with a size hint which is helpful for some C++ allocator implementations for deallocating memory.
```js
allocator.deallocate(handle, 10);
```
### allocator.type: string
It gets a string type identifier for the allocator, which will be useful during debugging purpose.
## Interface `AllocatorDebugger`
`AllocatorDebugger` extends interface `Allocator`, with a member function `getDebugInfo` to expose debug information. Basically an allocator debugger will use a pass-in allocator for memory allocation, meanwhile intercepting it to keep track of allocation count and size.
### allocatorDebugger.getDebugInfo(): string
It gets the debug information for allocation. Implementations of interface `AllocatorDebugger` can have different schema on debug info.
## debugAllocator(allocator: Allocator): AllocatorDebugger
It returns a simple allocator debugger, which returns debug information like below:
```json
{
"allocate": 10,
"allocateSize": 1024,
"deallocate": 8,
"deallocateSize": 912
}
```
## Object `crtAllocator`
It returns a C-runtime allocator from Napa.js shared library. Its corresponding C++ part is `napa::memory::GetCrtAllocator()`.
## Object `defaultAllocator`
It returns the default allocator from Napa.js shared library. Its corresponding C++ part is `napa::memory::GetDefaultAllocator()`.
Users can set default allocation/deallocation callback in `napa_allocator_set` API.
## Memory allocation in C++ addon
Memory allocation in C++ addon is tricky. A common pitfall is to allocate memory in one dll, but deallocate in another. This can cause issue if C-runtime in these 2 dlls are not compiled the same way.
There are also advanced scenarios that user want to customize memory allocation. Napa.js provides APIs for customizing memory allocator as well.
### Recommended way of allocate memory
TBD
### Customize memory allocation
TBD
================================================
FILE: docs/api/metric.md
================================================
# Namespace `metric`
## Table of Contents
- [Introduction](#intro)
- [C++ API](#cpp-api)
- Interface [`Metric`](#cpp-metric)
- Interface [`MetricProvider`](#cpp-metricprovider)
- Function [`MetricProvider& GetMetricProvider()`](#cpp-getmetricprovider)
- [JavaScript API](#js-api)
- Enum [`MetricType`](#metrictype)
- Class [`Metric`](#metric)
- [`set(value: number, dimensions?: string[]): void`](#metric-set)
- [`increment(dimensions?: string[]): void`](#metric-increment);
- [`decrement(dimensions?: string[]): void`](#metric-decrement);
- Function [`get(section: string, name: string, type: MetricType, dimensionNames: string[])`](#get)
- [Using custom metric providers](#use-custom-providers)
- [Developing custom metric providers](#develop-custom-providers)
## Introduction
Similar as logging, metric is a basic requirement for creating monitorable services. Metric API enables developers to use their own metric system in both JavaScript and C++ (addon) world.
A metric has its identity containing following information:
- **Section**: The group or category of the metric.
- **Name**: Name of the metric. Section/Name combination should be unique in the system.
- **Type**: Type of the metric, which can be
- *Number*: An absolute number, e.g: PrivateBytes.
- *Rate*: A flowing volume in number, e.g: QueryPerSecond.
- *Percentile*: An absolute number that needs to be sampled by percentiles, e.g: SuccessLatency.
- **Dimensions**: A metric can have multiple dimensions, each dimension can bind with a string value at runtime. e.g: IncomingRequestRate can have 2 dimensions: ['client-id', 'request-type'].
Metrics are process-wise objects, which can be used across [zones](./zone.md#intro).
## C++ API
### Interface Metric
```cpp
/// Enumeration of metric type.
enum class MetricType {
Number = 0,
Rate,
Percentile,
};
/// Interface to represent a multi-dimensional metric with a maximum dimensionality of 64.
class Metric {
public:
/// Sets a metric value with variadic dimension arguments.
/// Int64 value.
/// Number of dimensions being set.
/// Array of dimension value names.
/// Success/Fail.
///
/// The number of dimension values must exactly match the number of dimensions provided when
/// creating this metric.
///
virtual bool Set(int64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0;
///
/// Increments a metric value with variadic dimension arguments.
/// Use mainly to simplify rate counters.
///
/// UInt64 value to increment.
/// Number of dimensions being set.
/// Array of dimension value names.
/// Success/Fail.
///
/// The number of dimension values must exactly match the number of dimensions
/// provided when creating this metric.
///
virtual bool Increment(uint64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0;
///
/// Decrements metric value with variadic dimension arguments.
/// Use mainly to simplify rate counters.
///
/// UInt64 value to decrement.
/// Number of dimensions being set.
/// Array of dimension value names.
/// Success/Fail.
///
/// The number of dimension values must exactly match the number of dimensions
/// provided when creating this metric.
///
virtual bool Decrement(uint64_t value, size_t numberOfDimensions, const char* dimensionValues[]) = 0;
/// Explicitly destroys the Metric.
///
/// Consumers are not required to call this.
/// The MetricProvider owns this class and will automatically perform cleanup on shutdown.
///
virtual void Destroy() = 0;
protected:
/// Prevent calling delete on the interface. Must use Destroy!
virtual ~Metric() = default;
};
```
### Interface MetricProvider
```cpp
/// Interface for a generic metric provider.
///
/// Ownership of this metric provider belongs to the shared library which created it. Hence the explicit
/// Destroy method in this class. To simplify memory management across multiple shared libraries, this class
/// can only be created via a factory method provided by the shared library. When it is no longer needed,
/// the caller may call Destroy() which will tell the shared library which created it to dispose of the object.
///
class MetricProvider {
public:
///
/// Gets or creates a N-dimensional metric. Metric objects are owned and cached by this class.
/// Up to 64 dimensions may be used.
/// Section of the metric.
/// Name of the metric.
/// Type of the metric.
///
/// Number of dimensions requested for this metric.
/// Represents the size of the array passed in for p_dimensionNames.
///
/// Array of dimension names being requested for this metric.
///
/// The IMetric class returned is owned and cached by this class.
/// Callers are not required to call destroy() on the Metric.
///
virtual Metric* GetMetric(
const char* section,
const char* name,
MetricType type,
size_t dimensions,
const char* dimensionNames[]) = 0;
/// Explicitly destroys the metric provider.
virtual void Destroy() = 0;
protected:
/// Prevent calling delete on the interface. Must use Destroy!
virtual ~MetricProvider() = default;
};
```
### function `MetricProvider& GetMetricProvider()`
```cpp
/// Exports a getter function for retrieves the configured metric provider.
NAPA_API MetricProvider& GetMetricProvider();
```
## JavaScript API
### enum `MetricType`
```ts
export enum MetricType {
Number = 0,
Rate,
Percentile,
}
```
### Class `Metric`
Class to manipulate metrics in JavaScript.
### `set(value: number, dimensions?: string[]): void`
Set absolute value on an instance of the metric constrained by dimension values.
Example:
```js
// Create a percentile metric to measure end-to-end latency, with 1 dimension of client-id.
latency = napa.metric.get(
'app1',
'end-to-end-latency',
napa.metric.MetricType.Percentile,
['client-id']);
// Set end-to-end latency of current request to 100, with client-id 'client1'.
latency.set(100, ['client1']);
```
#### `increment(dimensions?: string[]): void`
Increment the value of an instance of the metric constrained by dimension values.
Example:
```js
// Create a percentile metric to measure end-to-end latency, with 1 dimension of client-id.
latency = napa.metric.get(
'app1',
'qps',
napa.metric.MetricType.Rate,
['client-id']);
// Increment QPS of client-id 'client1'.
latency.increment(['client1']);
```
#### `decrement(dimensions?: string[]): void`
Decrement the value of an instance of the metric constrained by dimension values.
### function `get(section: string, name: string, type: MetricType, dimensions: string[] = []): Metric`
Create a metric with an identity consisting of section, name, type and dimensions. If a metric already exists with given parameters, returns existing one.
Example:
```ts
import * as napa from 'napajs';
let metric = napa.metric.get(
'app1',
'counter1',
napa.metric.MetricType.Number,
[]);
metric.increment([]);
```
## Using custom metric providers
Developers can hook up custom metric provider by calling the following before creation of any zones:
```ts
napa.runtime.setPlatformSettings({
"metricProvider": ""
}
```
## Developing custom metric providers
TBD
================================================
FILE: docs/api/module.md
================================================
# Napa.js Module
## Table of Contents
- [Introduction](#intro)
- [Developing modules](#develop-modules)
- [Module: JavaScript vs C++](#js-vs-cpp)
- [Quick reference](#quick-ref)
- [JavaScript module](#ref-js-module)
- [C++ module](#ref-cpp-module)
- [API](#api)
- [JavaScript API](#js-api)
- [C++ API](#cpp-api)
- [Exporting JavaScript class from C++ modules](#export-class)
- [V8 helpers](#v8helpers)
- [Using STL with custom allocators](#stl-with-allocator)
- [Special topics](#topics)
- [Topic #1: Make objects shareable across multiple JavaScript threads](#topic-shareable-objects)
- [Topic #2: Asynchronous functions](#topic-async-functions)
- [Topic #3: Memory management in C++ modules](#topic-memory-management)
## Introduction
Napa.js follows [Node.js' convention](https://nodejs.org/api/modules.html) to support modules, that means:
1) Both JavaScript modules and C++ modules are supported.
2) Module resolution follows the [same algorithm](https://nodejs.org/api/modules.html#modules_all_together), except instead of searching file extension `.node` for addons, Napa.JS searches `.napa`.
3) Supports NPM, with the [same way](https://docs.npmjs.com/getting-started/creating-node-modules) to create and publish modules.
4) API of creating C++ modules (addons) are similar. Napa.JS introduced macros that the same source code can be compiled to produce both Napa.js addon and Node.js addon.
But there are also differences:
1) C++ module that is designed/implemented for Napa.js can run on Node.JS (need different compile flags to produce '.napa' and '.node'). But not vice versa.
2) Napa.js doesn't support all Node.js API. Node API are supported [incrementally](./node-api.md) on the motivation of adding Node.js built-ins and core modules that are needed for computation heavy tasks. You can access full capabilities of Node exposed via [Node zone](./zone.md#node-zone).
3) Napa.js doesn't provide `uv` functionalities, thus built-ins and core modules have its own implementation. To write async function in addon, methods `DoAsyncWork`/`PostAsyncWork` are introduced to work for both Napa.js and Node.js.
4) Napa.js supports embed mode. C++ modules need separate compilation between Node mode and embed mode.
## Developing modules
### Module: JavaScript vs. C++
A quick glance at NPM will reveal that most modules are pure JavaScript. These are only a few reasons that you may want to create a C++ module.
- You want to expose JavaScript API for existing C/C++ functionalities.
- Code includes considerably amount of computation that is performance critical.
- Objects need to be shared across multiple JavaScript threads, marshalling/unmarshalling cost on these objects is not trivial (big payload size, complex structure, etc.), but it's reasonable cheap to expose JavaScript APIs from underlying native objects.
- In embed mode, you want to communicate with host process with native objects.
[This post](https://docs.npmjs.com/getting-started/creating-node-modules) gives a good introduction on creating a JavaScript module. For creating a Napa.JS C++ module, please refer to the [API](#api) section or checkout examples in the [quick reference](#quick-reference) section.
## Quick reference
### JavaScript module
| Description | Transportable | Example code |
| ------------------------------------------------------------ | ------------- | ------------ |
| Standard JavaScript module | | [Blog post](https://www.hacksparrow.com/how-to-write-node-js-modules.html) |
| Share JavaScript object across isolates | X | |
### C++ module
| Description | ObjectWrap | Transportable | Async function | Example code |
| ------------------------------------------------------------ | ---------- | ------------- | -------------- | ------------ |
| Export JavaScript function only | | | | hello-world [[.md](../../examples/modules/hello-world/README.md) [.cpp](../../examples/modules/hello-world/node/addon.cpp) [test](../../examples/modules/hello-world/test/test.ts)] |
| Export JavaScript object (ObjectWrap) | X | | | plus-number [[.md](../../examples/modules/plus-number/README.md) [.cpp](../../examples/modules/plus-number/node/addon.cpp) [test](../../examples/modules/plus-number/test/module-test/test.ts)] |
| Share C++ object across isolates | X | X | | allocator-wrap [[.h](../../src/module/core-modules/napa/allocator-wrap.h) [.cpp](../../src/module/core-modules/napa/allocator-wrap.cpp)] |
| Export asynchronous JavaScript function | X | | X | async-number [[.md](../../examples/modules/async-number/README.md) [.cpp](../../examples/modules/async-number/node/addon.cpp) [test](../../examples/modules/async-number/test/test.ts)] |
## API
### JavaScript
See [API reference](./index.md).
### C++
#### Exporting JavaScript classes from C++ modules
TBD
#### V8 helpers
TBD
#### Using STL with custom allocators
TBD
## Special topics
### Topic #1: Make objects shareable across multiple JavaScript threads
TBD
### Topic #2: Asynchronous functions
TBD
### Topic #3: Memory management in C++ modules
TBD
================================================
FILE: docs/api/napa-globals.md
================================================
# Napa.js specific global variables
This file describes Napa.js specific globals, please refer to [this documentation](./node-api.md#globals) for Node.js globals.
## `global.napa`
Shortcut to access `napajs` module in all Napa enabled isolates. This is helpful to avoid extra `require` when using `napajs` module in anonymous function during `broadcast` or `execute`.
Example:
```js
var napa = require('napajs');
var zone = napa.zone.create('zone1');
function test() {
global.napa.log('hi');
}
zone.execute(test);
```
================================================
FILE: docs/api/node-api.md
================================================
# Node.js Compatibility
Napa.js doesn't support full compatibility with node.js and necessary core modules will be added incrementally. Here are the list of what Napa.js currently supports. Please refer to https://nodejs.org/api/all.html for details.
## Assert
Since Napa doesn't support *Buffer* yet, assert is not working on *Buffer*.
* assert(value[, message])
* assert.deepEqual(actual, expected[, message])
* assert.doesNotThrow(block[, error][, message])
* assert.equal(actual, expected[, message])
* assert.fail(actual, expected, message, operator)
* assert.ifError(value)
* assert.notDeepEqual(actual, expected[, message])
* assert.notEqual(actual, expected[, message])
* assert.notStrictEqual(actual, expected[, message])
* assert.ok(value[, message])
* assert.strictEqual(actual, expected[, message])
* assert.throws(block[, error][, message])
## Console
* console.log([data][, ...args])
## Events
* Event: 'newListener'
* Event: 'removeListener'
* EventEmitter.listenerCount(emitter, eventName)
* EventEmitter.defaultMaxListeners
* emitter.addListener(eventName, listener)
* emitter.emit(eventName[, ...args])
* emitter.eventNames()
* emitter.getMaxListeners()
* emitter.listenerCount(eventName)
* emitter.listeners(eventName)
* emitter.on(eventName, listener)
* emitter.once(eventName, listener)
* emitter.prependListener(eventName, listener)
* emitter.prependOnceListener(eventName, listener)
* emitter.removeAllListeners([eventName])
* emitter.removeListener(eventName, listener)
* emitter.setMaxListeners(n)
## File system
* fs.readFileSync(path)
* fs.writeFileSync(file, data)
* fs.mkdirSync(path)
* fs.existsSync(path)
* fs.readdirSync(path)
## Globals
* __dirname
* __filename
* console
* exports
* global
* module
* module.exports
* module.id
* process
* require
## OS
* os.type
## Path
* path.basename(path[, ext])
* path.dirname(path)
* path.extname(path)
* path.format(pathObject)
* path.isAbsolute(path)
* path.join([...paths])
* path.normalize(path)
* path.relative(from, to)
* path.resolve([...paths])
* path.sep
## Process
* process.argv
* process.cwd()
* process.chdir(directory)
* process.env
* process.execPath
* process.exit(code)
* process.hrtime([time])
* process.pid
* process.platform
* process.umask([mask])
## TTY
* tty.isatty(fd)
## Util
* util.debuglog(section)
* util.format(format[, ...args])
* util.inherits(constructor, superConstructor)
* util.inspect(object[, options])
* util._extend(target, source)
================================================
FILE: docs/api/store.md
================================================
# Namespace `store`
## Table of Contents
- [Introduction](#intro)
- [API](#api)
- [`create(id: string): Store`](#create)
- [`get(id: string): Store`](#get)
- [`getOrCreate(id: string): Store`](#getorcreate)
- [`count: number`](#count)
- Interface [`Store`](#store)
- [`store.id: string`](#store-id)
- [`store.set(key: string, value: any): void`](#store-set)
- [`store.get(key: string): any`](#store-get)
- [`store.has(key: string): boolean`](#store-has)
- [`store.size: number`](#store-size)
## Introduction
Store API is a necessary complement of sharing [transportable](transport.md#transportable-types) objects across JavaScript threads, on top of passing objects via arguments. During [`store.set`](#store-set), values marshalled into JSON and stored in process heap, so all threads can access it, and unmarshalled while users retrieve them via [`store.get`](#store-get).
Though very convenient, it's not recommended to use store to pass values within a transaction or request, since its overhead is more than passing objects by arguments (there are extra locking, etc.). Besides, developers have the obligation to delete the key after usage, while it's automatically managed by reference counting in passing arguments.
## API
Following APIs are exposed to create, get and operate upon stores.
### create(id: string): Store
It creates a store by a string identifier that can be used to get the store later. When all references to the store from all JavaScript VMs are cleared, the store will be destroyed. Thus always keep a reference at global or module scope is usually a good practice using `Store`. Error will be thrown if the id already exists.
Example:
```js
var store = napa.store.create('store1');
```
### get(id: string): Store
It gets a reference of store by a string identifier. `undefined` will be returned if the id doesn't exist.
Example:
```js
var store = napa.store.get('store1');
```
### getOrCreate(id: string): Store
It gets a reference of store by a string identifier, or creates it if the id doesn't exist. This API is handy when you want to create a store in code that is executed by every worker of a zone, since it doesn't break symmetry.
Example:
```js
var store = napa.store.getOrCreate('store1');
```
### count: number
It returns count of living stores.
### Interface `Store`
Interface that let user to put and get objects across multiple JavaScript VMs.
### store.id: string
It gets the string identifier for the store.
### store.set(key: string, value: any): void
It puts a [transportable](transport.md#transportable-types) value into store with a string key. If key already exists, new value will override existing value.
Example:
```js
store.set('status', 1);
```
### store.get(key: string): any
It gets a [transportable](transportable.md#transportable-types) value from the store by a string key. If key doesn't exist, `undefined` will be returned.
Example:
```js
var value = store.get('status');
assert(value === 1);
```
### store.has(key: string): boolean
It tells if a key exists in current store.
Example:
```js
assert(store.has('status'))
```
### store.size: number
It tells how many keys are stored in current store.
================================================
FILE: docs/api/sync.md
================================================
# Namespace `sync`
## Table of Contents
- class [`Lock`](#interface-lock)
- [`lock.guardSync(func: (...params: any[]) => any, params?: any[]): any`](#lock-guard-sync-func-any-any)
## APIs
Namespace `sync` deal with synchronization between threads in Napa. `Lock` is provided for exclusive and shared mutex scenarios.
## Interface `Lock`
Exclusive Lock, which is [transportable](transport.md#transportable) across JavaScript threads.
Use `napa.sync.createLock()` to create a lock.
```ts
var lock = napa.sync.createLock();
```
### lock.guardSync(func: (...params: any[]) => any, params?: any[]): any
Run input function synchronously and obtain the lock during its execution, returns what the function returns, or throws error if input function throws. Lock will be released once execution finishes.
```ts
try {
var value = lock.guardSync(() => {
// DoSomething may throw.
return DoSomething();
});
console.log(value);
}
catch(error) {
console.log(error);
}
```
An example [Synchronized Loading](./../../examples/tutorial/synchronized-loading) demonstrated how to implement a shared, lazy-loading phone book.
================================================
FILE: docs/api/transport.md
================================================
# Namespace `transport`
## Table of Contents
- [Introduction](#intro)
- [Transportable types](#transportable-types)
- [Constructor ID](#constructor-id)
- [Transport context](#transport-context)
- [Transporting functions](#transporting-functions)
- [Transporting JavaScript built-in objects](#transporting-built-in)
- API
- [`isTransportable(jsValue: any): boolean`](#istransportable)
- [`register(transportableClass: new(...args: any[]) => any): void`](#register)
- [`marshall(jsValue: any, context: TransportContext): string`](#marshall)
- [`unmarshall(json: string, context: TransporteContext): any`](#unmarshall)
- class [`TransportContext`](#transportcontext)
- [`context.saveShared(object: memory.Shareable): void`](transportcontext-saveshared)
- [`context.loadShared(handle: memory.Handle): memory.Shareable`](transportcontext-loadshared)
- [`context.sharedCount: number`](transportcontext-sharedcount)
- interface [`Transportable`](#transportable)
- [`transportable.cid: string`](#transportable-cid)
- [`transportable.marshall(context: TransportContext): object`](#transportable-marshall)
- [`transportable.unmarshall(payload: object, context: TransportContext): void`](#transportable-unmarshall)
- abstract class [`TransportableObject`](#transportableobject)
- [`transportableObject.cid: string`](#transportableobject-cid)
- [`transportableObject.marshall(context: TransportContext): object`](#transportableobject-marshall)
- [`transportableObject.unmarshall(payload: object, context: TransportContext): void`](#transportableobject-unmarshall)
- abstract [`transportableObject.save(payload: object, context: TransportContext): void`](#transportableobject-save)
- abstract [`transportableObject.load(payload: object, context: TransportContext): void`](#transportableobject-load)
- decorator [`cid`](#decorator-cid)
## Introduction
Existing JavaScript engines are not designed for running JavaScript across multiple VMs, which means every VM manages their own heap. Passing values from one VM to another has to be marshalled/unmarshalled. The size of payload and complexity of object will greatly impact communication efficiency. In Napa, we try to work out a design pattern for efficient object sharing, based on the fact that all JavaScript VMs (exposed as workers) reside in the same process, and native objects can be wrapped and exposed as JavaScripts objects.
Following concepts are introduced to implement this pattern:
### Transportable types
Transportable types are JavaScript types that can be passed or shared transparently across Napa workers. They are used as value types for passing arguments in [`zone.broadcast`](zone.md#broadcast-function) / [`zone.execute`](zone.md#execute-anonymous-function), and sharing objects in key/value pairs via [`store.set`](store.md#store-set) / [`store.get`](store.md#store-get).
Transportable types are:
- JavaScript primitive types: undefined, null, boolean, number, string
- Object (TypeScript class) that implement [`Transportable`](#transportable) interface
- Function without referencing closures.
- JavaScript standard built-in objects in this whitelist.
* ArrayBuffer
* Float32Array
* Float64Array
* Int16Array
* Int32Array
* Int8Array
* SharedArrayBuffer
* Uint16Array
* Uint32Array
* Uint8Array
- Array or plain JavaScript object that is composite pattern of above.
### Constructor ID (cid)
For user classes that implement the [`Transportable`](#transportable) interface, Napa uses Constructor ID (`cid`) to lookup constructors for creating a right object from a string payload. `cid` is marshalled as a part of the payload. During unmarshalling, the transport layer will extract the `cid`, create an object instance using the constructor associated with it, and then call unmarshall on the object.
It's the class developer's responsibility to choose the right `cid` for your class. To avoid conflict, we suggest to use the combination of module.id and class name as `cid`. Developer can use class decorator [`cid`](#decorator-cid) to register a user Transportable class automatically, when using TypeScript with decorator feature enabled. Or call [`transport.register`](#register) manually during module initialization.
### Transport context
There are states that cannot be saved or loaded in serialized form (like std::shared_ptr), or it's very inefficient to serialize (like JavaScript function). Transport context is introduced to help in these scenarios. TransportContext objects can be passed from one JavaScript VM to another, or stored in the native world, so lifecycle of shared native objects extended by using TransportContext. An example of the `Transportable` implementation using TransportContext is [`ShareableWrap`](./../../inc/napa/module/shareable-wrap.h).
### Transporting functions
JavaScript function is a special transportable type, through marshalling its definition into a [store](./store.md#intro), and generate a new function from its definition on target thread.
Highlights on transporting functions are:
- For the same function, marshall/unmarshall is an one-time cost on each JavaScript thread. Once a function is transported for the first time, later transportation of the same function to previous JavaScript thread can be regarded as free.
- Closure cannot be transported, but you won't get an error when transporting a function. Instead, you will get runtime error complaining a variable (from closure) is undefined when you can the function later.
- `__dirname` / `__filename` can be accessed in transported function, which is determined by `origin` property of the function. By default, `origin` property is set to the current working directory.
### Transporting JavaScript built-in objects
JavaScript standard built-in objects in [the whitelist](#built-in-whitelist) can be transported among napa workers transparently. JavaScript Objects with properties in these types are also able to be transported. Please refer to [unit tests](./../../test/transport-test.ts) for detail.
An example [Parallel Quick Sort](./../../examples/tutorial/parallel-quick-sort) demonstrated transporting TypedArray (created from SharedArrayBuffer) among multiple Napa workers for efficient data sharing.
## API
### isTransportable(jsValue: any): boolean
It tells whether a JavaScript value is transportable or not.
```ts
// JS primitives
assert(transport.isTransportable(undefined));
assert(transport.isTransportable(null));
assert(transport.isTransportable(1));
assert(transport.isTransportable('string'));
assert(transport.isTransportable(true));
// Transportable addon
assert(transport.isTransportable(napa.memory.crtAllocator));
// Composite of transportable types.
assert(transport.isTransportable([
1,
"string",
{ a: napa.memory.crtAllocator }
]));
class B {
field1: number;
field2: string;
}
// Not transportable JS class. (not registered with @cid).
assert(!transport.isTransportable(new B()));
```
### register(transportableClass: new(...args: any[]) => any): void
Register a `Transportable` class before the transport layer can marshall/unmarshall its instances.
User can also use class decorator [`@cid`](#cid-decorator) for class registration.
Example:
```ts
class A extends transport.AutoTransportable {
field1: string,
method1(): string {
return this.field1;
}
}
// Explicitly register class A in transport.
transport.register(A);
```
### marshall(jsValue: any, context: TransportContext): string
Marshall a [transportable](#transportable-types) JavaScript value into a JSON payload with a [`TransportContext`](#transport-context).An Error will be thrown if the value is not transportable.
Example:
```js
var context = transport.createTransportContext();
var jsonPayload = transport.marshall(
[1, 'string', napa.memory.crtAllocator],
context);
console.log(jsonPayload);
```
### unmarshall(json: string, context: TransportContext): any
Unmarshall a [transportable](#transportable-types) JavaScript value from a JSON payload with a [`TransportContext`](#transport-context).An Error will be thrown if `cid` property is found and not registered with the transport layer.
Example:
```js
var value = transport.unmarshall(jsonPayload, context);
```
## Class `TransportContext`
Class for [Transport Context](#transport-context), that stores shared pointers and functions during marshall/unmarshall.
### context.saveShared(object: memory.Shareable): void
Save a shareable object in context.
### context.loadShared(handle: memory.Handle): memory.Shareable
Load a shareable object from handle.
### context.sharedCount: number
Count of shareable objects saved in the current context.
## Interface `Transportable`
Interface for the Transportable object.
### transportable.cid: string
Get accessor for [Constructor ID](#constructor-id). It is used to lookup constructor for the payload of the current class.
### transportable.marshall(context: TransportContext): object
Marshall transforms this object into a plain JavaScript object with the help of [TransportContext](#transport-context).
### transportable.unmarshall(payload: object, context: TransportContext): void
Unmarshall transforms marshalled payload into current object.
## Abstract class `TransportableObject`
TBD
### Decorator `cid`
TBD
================================================
FILE: docs/api/zone.md
================================================
# Namespace `zone`
## Table of Contents
- [Introduction](#intro)
- [Multiple workers vs Multiple zones](#worker-vs-zone)
- [Zone types](#zone-types)
- [Zone operations](#zone-operations)
- [API](#api)
- [`create(id: string, settings: ZoneSettings = DEFAULT_SETTINGS): Zone`](#create)
- [`get(id: string): Zone`](#get)
- [`current: Zone`](#current)
- [`node: Zone`](#node-zone)
- Interface [`ZoneSettings`](#zone-settings)
- [`settings.workers: number`](#zone-settings-workers)
- Object [`DEFAULT_SETTINGS: ZoneSettings`](#default-settings)
- Interface [`Zone`](#zone)
- [`zone.id: string`](#zone-id)
- [`zone.broadcast(code: string): Promise`](#broadcast-code)
- [`zone.broadcast(function: (...args: any[]) => void, args?: any[]): Promise`](#broadcast-function)
- [`zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise`](#execute-by-name)
- [`zone.execute(function: (...args[]) => any, args?: any[], options?: CallOptions): Promise`](#execute-anonymous-function)
- Interface [`CallOptions`](#call-options)
- [`options.timeout: number`](#call-options-timeout)
- Interface [`Result`](#result)
- [`result.value: any`](#result-value)
- [`result.payload: string`](#result-payload)
- [`result.transportContext: transport.TransportContext`](#result-transportcontext)
## Introduction
Zone is a key concept of napajs that exposes multi-thread capabilities in JavaScript world, which is a logical group of symmetric workers for specific tasks.
Please note that it's not the same `zone` concept of a context object for async calls in [Dart](https://www.dartlang.org/articles/libraries/zones), or [Angular](https://github.com/angular/zone.js), or a proposal in [TC39](https://github.com/domenic/zones).
### Multiple workers vs. Multiple zones
Zone consists of one or multiple JavaScript threads, we name each thread `worker`. Workers within a zone are symmetric, which means code executed on any worker from the zone should return the same result, and the internal state of every worker should be the same from a long-running point of view.
Multiple zones can co-exist in the same process, with each loading different code, bearing different states or applying different policies, like heap size, etc. The purpose of having multiple zone is to allow multiple roles for complex work, each role loads the minimum resources for its own usage.
### Zone types
There are two types of zone:
- **Napa zone** - zone consists of Napa.js managed JavaScript workers (V8 isolates). Can be multiple, each may contain multiple workers. Workers in Napa zone support partial Node.JS APIs.
- **Node zone** - a 'virtual' zone which exposes Node.js eventloop, has access to full Node.js capabilities.
### Zone operations
There are two operations, designed to reinforce the symmetry of workers within a zone:
1) **Broadcast** - run code that changes worker state on all workers, returning a promise for the pending operation. Through the promise, we can only know if the operation succeeded or failed. Usually we use `broadcast` to bootstrap the application, pre-cache objects, or change application settings. Function `broadcastSync` is also offered as a synchronized version of broadcast operations.
2) **Execute** - run code that doesn't change worker state on an arbitrary worker, returning a promise of getting the result. Execute is designed for doing the real work.
Zone operations are on a basis of first-come-first-serve, while `broadcast` takes higher priority over `execute`.
## API
### create(id: string, settings: ZoneSettings): Zone
It creates a Napa zone with a string id. If a zone with the id is already created, an error will be thrown. [`ZoneSettings`](#zone-settings) can be specified for creating zones.
Example 1: Create a zone with id 'zone1', using default ZoneSettings.
```js
var napa = require('napajs');
var zone1 = napa.zone.create('zone1');
```
Example 2: Create a zone with id 'zone2', with 1 worker.
```js
var zone2 = napa.zone.create('zone2', {
workers: 1
});
```
### get(id: string): Zone
It gets a reference of zone by an id. Error will be thrown if the zone doesn't exist.
Example:
```js
var zone = napa.zone.get('zone1');
```
### current: Zone
It returns a reference of the zone of the currently running isolate. If it's under node, it returns the [node zone](#node-zone).
Example: Get current zone.
```js
var zone = napa.zone.current;
```
### node: Zone
It returns a reference to the node zone. It is equivalent to `napa.zone.get('node')`;
Example:
```js
var zone = napa.zone.node;
```
## Interface `ZoneSettings`
Settings for zones, which will be specified during the creation of zones. If not specified, [DEFAULT_SETTINGS](#default-settings) will be used.
### settings.workers: number
Number of workers in the zone.
## Object `DEFAULT_SETTINGS`
Default settings for creating zones.
```js
{
workers: 2
}
```
## Interface `Zone`
Zone is the basic concept to execute JavaScript and apply policies in Napa. You can find its definition in [Introduction](#intro). Through the Zone API, developers can broadcast JavaScript code on all workers, or execute a function on one of them. When you program against a zone, it is the best practice to ensure all workers within a zone are symmetrical to each other, that is, you should not assume a worker may maintain its own states.
The two major sets of APIs are [`broadcast`](#broadcast-code) and [`execute`](#execute-by-name), which are asynchronous operations with a few variations on their inputs.
### zone.id: string
It gets the id of the zone.
### zone.broadcast(code: string): Promise\
It asynchronously broadcasts a snippet of JavaScript code in a string to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message.
Example:
```js
var napa = require('napajs');
var zone = napa.zone.get('zone1');
zone.broadcast('var state = 0;')
.then(() => {
console.log('broadcast succeeded.');
})
.catch((error) => {
console.log('broadcast failed.')
});
```
### zone.broadcastSync(code: string): void
It synchronously broadcasts a snippet of JavaScript code in a string to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message.
Remarks:
- It's not allowed to call `broadcastSync` on current zone. It will cause a deadlock
Example:
```js
var napa = require('napajs');
var zone = napa.zone.get('zone1');
try {
zone.broadcastSync('var state = 0;');
console.log('broadcast succeeded.');
} catch (error) {
console.log('broadcast failed.')
}
```
### zone.broadcast(function: (...args: any[]) => void | Promise\, args?: any[]): Promise\
It asynchronously broadcasts an anonymous function with its arguments to all workers, which returns a Promise of void. If any of the workers failed to execute the code, the promise will be rejected with an error message.
Remarks:
- If the function returns a Promise object, its state will be adopted to `broadcast`'s return value
- The function object cannot access variables from closure
- Unless the function object has an `origin` property, it will use the current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions))
- Transport context is not available in broadcast. All types that depend on [TransportContext](./transport.md#transport-context) (eg. [ShareableWrap](https://github.com/Microsoft/napajs/blob/master/inc/napa/module/shareable-wrap.h), [Transportable](./transport.md#-interface-transportable)) cannot be passed in arguments list.
Example:
```js
zone.broadcast((state) => {
require('some-module').setModuleState(state)
}, [{field1: 1}])
.then(() => {
console.log('broadcast succeeded.');
})
.catch((error) => {
console.log('broadcast failed:', error)
});
```
### zone.broadcastSync(function: (...args: any[]) => void | Promise\, args?: any[]): void
It synchronously broadcasts an anonymous function with its arguments to all workers. If any of the workers failed to execute the code, an exception will be thrown with an error message.
Remarks:
- It's not allowed to call `broadcastSync` on current zone. It will cause a deadlock
- If the function returns a Promise object, its state will be adopted. Function `broadcastSync` will not return until that Promise resolved or rejected.
- The function object cannot access variables from closure
- Unless the function object has an `origin` property, it will use the current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions))
- Transport context is not available in broadcast. All types that depend on [TransportContext](./transport.md#transport-context) (eg. [ShareableWrap](https://github.com/Microsoft/napajs/blob/master/inc/napa/module/shareable-wrap.h), [Transportable](./transport.md#-interface-transportable)) cannot be passed in arguments list.
Example:
```js
try {
zone.broadcastSync((state) => {
require('some-module').setModuleState(state)
}, [{field1: 1}]);
console.log('broadcast succeeded.');
} catch (error) {
console.log('broadcast failed:', error)
}
```
### zone.execute(moduleName: string, functionName: string, args?: any[], options?: CallOptions): Promise\
Execute a function asynchronously on an arbitrary worker via module name and function name. Arguments can be of any JavaScript type that is [transportable](transport.md#transportable-types). It returns a Promise of [`Result`](#result). If an error happens, either bad code, user exception, or timeout is reached, the promise will be rejected.
Example: Execute function `bar` in module `foo`, with arguments [1, 'hello', { field1: 1 }]. 300ms timeout is applied.
```js
zone.execute(
'foo',
'bar',
[1, "hello", {field1: 1}],
{ timeout: 300 })
.then((result) => {
console.log('execute succeeded:', result.value);
})
.catch((error) => {
console.log('execute failed:', error);
});
```
### zone.execute(function: (...args: any[]) => any, args?: any[], options?: CallOptions): Promise\
Execute a function object asynchronously on an arbitrary worker. Arguments can be of any JavaScript type that is [transportable](transport.md#transportable-types). It returns a Promise of [`Result`](#result). If an error happens, either bad code, user exception, or timeout is reached, promise will be rejected.
Remarks:
- If the function returns a Promise object, it will be adopted
- The function object cannot access variables from closure
- Unless the function object has an `origin` property, it will use the current file as `origin`, which will be used to set `__filename` and `__dirname`. (See [transporting functions](./transport.md#transporting-functions))
Example:
```js
zone.execute((a: number, b: string, c: object) => {
return a + b + JSON.stringify(c);
}, [1, "hello", {field1: 1}])
.then((result) => {
console.log('execute succeeded:', result.value);
})
.catch((error) => {
console.log('execute failed:', error);
});
```
Output:
```
execute succeeded: 1hello{"field1":1}
```
Another example demonstrates accessing `__filename` when executing an anonymous function:
```js
// File: /usr/file1.js
zone.execute(() => { console.log(__filename);});
```
Output:
```
/usr/file1.js
```
## Interface `CallOptions`
Interface for options to call functions in `zone.execute`.
### options.timeout: number
Timeout in milliseconds. Default value 0 indicates no timeout.
## Interface `Result`
Interface to access the return value of [`execute`](#execute-by-name).
### result.value: any
JavaScript value returned from the function which is invoked from zone.execute/executeSync. Napa marshalls/unmarshalls [transportable values](transport.md#transportable-types) between different workers (V8 isolates). Unmarshalling will happen when the first `result.value` is queried.
Example:
```js
var value = result.value;
```
### result.payload: string
Marshalled payload (in JSON) from the returned value. This field is for users that want to pass results through to its caller, where the unmarshalled value is not required.
Example:
```js
var payload = result.payload;
```
### result.transportContext: transport.TransportContext
[TransportContext](transport.md#transport-context) that is required to unmarshall [`result.payload`](#result-payload) into [`result.value`](#result-value).
Example:
```js
var napa = require('napajs');
var zone = napa.zone.create('zone1');
zone.execute(() => { return 0; }, [])
.then((result) => {
// Manually marshall.
var transportContext = result.transportContext;
var value = napa.transport.unmarshall(result.payload, result.transportContext);
// result.value and manual unmarshall from payload are the same.
assert.equal(value, result.value);
});
```
================================================
FILE: docs/design/transport-js-builtins.md
================================================
# Transport JavaScript standard built-in objects
## Incentives
The abstraction of 'Transportable' lies in the center of Napa.js to efficiently share objects between JavaScript VMs (Napa workers). Except JavaScript primitive types, an object needs to implement 'Transportable' interface to make it transportable. It means [JavaScript standard built-in objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) are not transportable unless wrappers or equivalent implementations for them are implemented by extending 'Transportable' interface. The development cost for these objects is not trivial, and new abstraction layer (wrappers or equivalent implementations) will create barriers for users to learn and adopt these new things. Moreover, developers also need to deal with the interaction between JavaScript standards objects and those wrappers or equivalent implementations.
The incentive of this design is to provide a solution to make JavaScript standard built-in objects transportable with requirements listed in the Goals section.
At the first stage, we will focus on an efficient solution to share data between Napa workers. Basically, it is about making *SharedArrayBuffer / TypedArray / DataView* transportable.
## Goals
Make Javascript standard built-in objects transportable with
- An efficient way to share structured data, like *SharedArrayBuffer*, among Napa workers
- Consistent APIs with ECMA standards
- No new abstraction layers for the simplest usage
- The least new concepts for advanced usage
- A scalable solution to make all JavaScript standard built-in objects transportable, avoiding to make them transportable one by one.
## Example
The below example shows how *SharedArrayBuffer* object is transported across multiple Napa workers. It will print the TypedArray 'ta' created from a *SharedArrayBuffer*, with all its elements set to 100 from different Napa workers.
```js
var napa = require("napajs");
var zone = napa.zone.create('zone', { workers: 4 });
function foo(sab, i) {
var ta = new Uint8Array(sab);
ta[i] = 100;
return i;
}
function run() {
var promises = [];
var sab = new SharedArrayBuffer(4);
for (var i = 0; i < 4; i++) {
promises[i] = zone.execute(foo, [sab, i]);
}
return Promise.all(promises).then(values => {
var ta = new Uint8Array(sab);
console.log(ta);
});
}
run();
```
## Solution
Here we just give a high-level description of the solution. Its API will go to [docs/api/transport](https://github.com/Microsoft/napajs/blob/master/docs/api/transport.md).
- V8 provides its value-serialization mechanism by ValueSerializer and ValueDeserializer, which is compatible with the HTML structured clone algorithm. It is a horizontal solution to serialize / deserialize JavaScript objects. ValueSerializer::Delegate and ValueDeserializer::Delegate are their inner class. They work as base classes from which developers can deprive to customize some special handling of external / shared resources, like memory used by a SharedArrayBuffer object.
- **napa::v8_extensions::ExternalizedContents**
- It holds the externalized contents (memory) of a SharedArrayBuffer instance once it is serialized via *napa::v8_extensions::Utils::SerializeValue()*.
- Only 1 instance of ExternalizedContents will be generated for each SharedArrayBuffer. If a SharedArrayBuffer had been externalized, it will reuse the ExternalizedContents instance created before in *napa::v8_extensions::Utils::SerializeValue()*.
- **napa::v8_extensions::SerializedData**
- It is generated by *napa::v8_extensions::Utils::SerializeValue()*. It holds the serialized data of a JavaScript object, which is required during its deserialization.
- **BuiltInObjectTransporter**
- **napa::v8_extensions::Serializer, derived from v8::ValueSerializer::Delegate**
- **napa::v8_extensions::Deserializer, derived from v8::ValueDeserializer::Delegate**
- **static std::shared_ptr\ v8_extensions::Utils::SerializeValue(Isolate\* isolate, Local\ value);**
- Generate the *SerializedData* instance given an input value.
- If any *SharedArrayBuffer* instances exist in the input value, their *ExternalizedContents* instances will be generated and attached to the *ShareArrayBuffer* instances respectively.
- **static MaybeLocal\ v8_extensions::Utils::DeserializeValue(Isolate\* isolate, std::shared_ptr\ data);**
- Restore a JavaScript value from its SerializedData instance generated by *v8_extensions::Utils::SerializeValue()* before.
- Currently, Napa relies on Transportable API and a registered constructor to make an object transportable. In [marshallTransform](https://github.com/Microsoft/napajs/blob/master/lib/transport/transport.ts), when a JavaScript object is detected to have a registered constructor, it will go with Napa way to marshall this object with the help of a **TransportContext** object, otherwise a non-transportable error is thrown.
- Instead of throwing an error when no registered constructor is detected, the **BuiltInObjectTransporter** can help handle this object. We can use a whitelist of object types to restrict this solution to those verified types at first.
```js
export function marshallTransform(jsValue: any, context: transportable.TransportContext): any {
if (jsValue != null && typeof jsValue === 'object' && !Array.isArray(jsValue)) {
let constructorName = Object.getPrototypeOf(jsValue).constructor.name;
if (constructorName !== 'Object') {
if (typeof jsValue['cid'] === 'function') {
return (jsValue).marshall(context);
} else if (_builtInTypeWhitelist.has(constructorName)) {
let serializedData = builtinObjectTransporter.serializeValue(jsValue);
if (serializedData) {
return { _serialized : serializedData };
} else {
throw new Error(`Failed to serialize object with type of \"${constructorName}\".`);
}
} else {
throw new Error(`Object type \"${constructorName}\" is not transportable.`);
}
}
}
return jsValue;
}
```
- The reverse process will be invoked in [unmarshallTransform](https://github.com/Microsoft/napajs/edit/master/lib/transport/transport.ts) if the payload is detected to have '_serialized' property.
```js
function unmarshallTransform(payload: any, context: transportable.TransportContext): any {
if (payload != null && payload._cid !== undefined) {
let cid = payload._cid;
if (cid === 'function') {
return functionTransporter.load(payload.hash);
}
let subClass = _registry.get(cid);
if (subClass == null) {
throw new Error(`Unrecognized Constructor ID (cid) "${cid}". Please ensure @cid is applied on the class or transport.register is called on the class.`);
}
let object = new subClass();
object.unmarshall(payload, context);
return object;
} else if (payload.hasOwnProperty('_serialized')) {
return builtinObjectTransporter.deserializeValue(payload['_serialized']);
}
return payload;
}
```
#### Lifecycle of SharedArrayBuffer (SAB)
- When a SAB participates transportation among Napa workers, its life cycle will be extended till the last reference this SAB. The reference of a SAB could be:
- SAB object in its original isolate.
- Received SAB transported from another Napa workers, including node zone of Napa.
- TypedArray or DataView created from the original SAB or a received SAB.
- The life cycle extension during transportation is achieved through the *ExternalizedContents* *SharedPtrWrap* of the SAB.
- When a SAB is transported for the first time, it will be externalized and its ExternalizedContents will be stored in its *SerializedData*. At the same time, the *SharedPtrWrap* of the *ExternalizedContents* will be set to the '_externalized' property of the original SAB.
- When a SAB is transported for the second time or later, it will skip externalization and find its *ExternalizedContents* from its '_externalized' property, and store it to its *SerializedData*.
- When a Napa worker tries to restore a transported SAB, it will find the pre-stored *ExternalizedContents*, and create a *SharedPtrWrap* for it, then set it to the to-be-restored SAB.
- The life cycle of the *SharedArrayBuffer* is extended by the *SharedPtrWrap* of its *ExternalizedContents*.
## Constraints
The above solution is based on the serialization / deserialization mechanism of V8. It may have the following constraints.
- Not all [JavaScripts standard built-in objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) are supported by Node (as a dependency of Napa in node mode) or V8 of a given version. We only provide transporting solution for those mature object types.
- Presently, Node does not explicitly support multiple V8 isolates. There may be inconsistencies in transporting objects between Node zones and Napa zones. Extra effort might be required to make it consistent.
================================================
FILE: examples/modules/README.md
================================================
## Napa module source tree
The source codes can be organized as the structure below.
```
+-- napa-module-example
+-- inc
| +-- example.h
+-- lib
| +-- example.ts
| +-- tsconfig.json
+-- napa
| +-- addon.cpp
| +-- CMakeLists.txt (cmake specific)
| +-- example-wrap.cpp
| +-- example-wrap.h
+-- src
| +-- CMakeLists.txt (cmake specific)
| +-- example.cpp
+-- test
| +-- test.ts
| +-- tsconfig.json
+-- binding.gyp (gyp specific)
+-- CMakeLists.txt (cmake specific)
+-- package.json
+-- README.md
```
## How to build and test ?
For the module examples, node-gyp or cmake-js build solution is provided for your reference.
Please make sure [node-gyp](https://github.com/nodejs/node-gyp#installation) or [cmake-js](https://github.com/cmake-js/cmake-js#installation) has been set up correctly at your client, then you could follow the steps below to build and test the module examples.
```
1. go to the directory of a module example
2. run "npm install" to build the module
3. run "npm test" to build and run the module test, as well as its unittest if any
```
================================================
FILE: examples/modules/async-number/.gitignore
================================================
bin
build
node_modules
types
# ignore all js files under lib
lib/*.js
# ignore all js files under test
test/*.js
package-lock.json
================================================
FILE: examples/modules/async-number/.npmignore
================================================
bin
build
# Exclude TypeScript source files.
lib/*.ts
lib/tsconfig.json
test/*.ts
test/tsconfig.json
================================================
FILE: examples/modules/async-number/README.md
================================================
# Asynchronous numbering
## APIs
Napa provides two functions to support asynchronous call and they work for both node.js and Napa.
```cpp
void napa::module::PostAsyncWork(v8::Local jsCallback,
std::function asyncWork,
std::function, void*)> asyncCompleteCallback);
```
It fires asynchronous work onto separate thread.
* *jsCallback*: Javascript callback given at Javascript land.
* *asyncWork*: C++ function to run at separate thread asynchronously.
* *asyncCompleteCallback*: C++ callback function, which has isolate instance and *jsCallback* as arguments. It's called at the same isolate with caller's one after *asyncWork* completes.
```cpp
void napa::module::DoAsyncWork(v8::Local jsCallback,
const std::function)>& asyncWork,
std::function, void*)> asyncCompleteCallback);
```
If you have a function already supporting async, use this API.
* *jsCallback*: Javascript callback given at Javascript land.
* *asyncWork*: Function to wrap C++ function supporting async, which callback must call Napa completion callback given as argument.
* *asyncCompleteCallback*: C++ callback function, which has isolate instance and *jsCallback* as arguments. It's called at the same isolate with caller's one after *asyncWork* completes.
### Diagram
This diagram shows how asynchronous call is working corresponding to the below example,
```diagram
|-------------------------| |------------------------| |------------------------------------| |-------------------|
(V8 thread)-----| Call *increase()* at JS |-----| Run the next statement |-------------------| Run *asyncCompleteCallback* at C++ |-----| Call *jsCallback* |
|-------------------------| |------------------------| |------------------------------------| |-------------------|
| +
| |
|----------| |
| |
+ |
|------------------| |--------------------------------------------| |
(thread)-----| Run *asyncWork* |-----| Post a task to run *asyncCompleteCallback* |---------|
|------------------| |--------------------------------------------|
```
## C++ module
This example shows how to create async module. It keeps one number and three APIs operating on it as follows,
* *Increase*: It increases a number by a given parameter in separate thread and post a completion to run Javascript callback at the next execution loop.
* *IncreaseSync*: It increases a number by a given parameter in the same thread and post a completion to run Javascript callback at the next execution loop.
* *Now*: It returns the current value of a number.
```cpp
#include
#include
#include
namespace napa {
namespace demo {
using namespace v8;
namespace {
std::atomic _now(0);
}
/// It increases a number by a given parameter asynchronously and runs a callback at the next execution loop.
void Increase(const FunctionCallbackInfo& args) {
auto isolate = args.GetIsolate();
CHECK_ARG(isolate,
args.Length() == 2 && args[0]->IsUint32() && args[1]->IsFunction(),
"It requires unsigned integer and callback as arguments");
auto value = args[0]->Uint32Value();
napa::module::PostAsyncWork(Local::Cast(args[1]),
[value]() {
// This runs in a separate thread.
_now += value;
return reinterpret_cast(static_cast(_now.load()));
},
[](auto jsCallback, void* result) {
// This runs in the same thread as the one Increase() is called in.
auto isolate = Isolate::GetCurrent();
int32_t argc = 1;
Local argv[] =
{ Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) };
jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv);
}
);
}
/// It increases a number by a given parameter synchronously and runs a callback at the next execution loop.
void IncreaseSync(const FunctionCallbackInfo& args) {
auto isolate = args.GetIsolate();
CHECK_ARG(isolate,
args.Length() == 2 && args[0]->IsUint32() && args[1]->IsFunction(),
"It requires unsigned integer and callback as arguments");
auto value = args[0]->Uint32Value();
napa::module::DoAsyncWork(Local::Cast(args[1]),
[value](auto complete) {
// This runs in the same thread.
_now += value;
complete(reinterpret_cast(static_cast(_now.load())));
},
[](auto jsCallback, void* result) {
// This runs in the same thread as the one IncreaseSync() is called in.
auto isolate = Isolate::GetCurrent();
int32_t argc = 1;
Local argv[] =
{ Integer::NewFromUnsigned(isolate, static_cast(reinterpret_cast(result))) };
jsCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv);
}
);
}
/// It returns the current value of a number.
void Now(const FunctionCallbackInfo& args) {
auto isolate = args.GetIsolate();
HandleScope scope(isolate);
args.GetReturnValue().Set(Integer::NewFromUnsigned(isolate, _now));
}
void Init(Local