diff --git a/bindings/nodejs/inc/nodejs_idle.h b/bindings/nodejs/inc/nodejs_idle.h index 22c58493..17baed94 100644 --- a/bindings/nodejs/inc/nodejs_idle.h +++ b/bindings/nodejs/inc/nodejs_idle.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. #ifndef NODEJS_IDLE_H @@ -19,6 +19,8 @@ namespace nodejs_module private: std::queue> m_callbacks; Lock m_lock; + bool m_initialized; + uv_async_t m_uv_async; /** * Private constructor to enforce singleton instance. @@ -34,6 +36,18 @@ namespace nodejs_module */ static NodeJSIdle* Get(); + /** + * Has object been initialized? + */ + bool IsInitialized() const; + + /** + * Initialize the object by registering the libuv + * async callback. + */ + bool Initialize(); + void DeInitialize(); + /** * Object lock/unlock methods. */ @@ -49,7 +63,7 @@ namespace nodejs_module /** * Idle callback function called from Node's event loop. */ - static void OnIdle(v8::Isolate* isolate); + static void OnIdle(uv_async_t* handle); private: void InvokeCallbacks(); @@ -60,6 +74,15 @@ namespace nodejs_module { LockGuard lock_guard{ *this }; m_callbacks.push(callback); + + // signal that the async function should be called on libuv's + // thread; libuv's doc says that multiple calls to this function + // will get coalesced if they occur before the call is actually + // made; so it's safe to call this multiple times + if (uv_is_active(reinterpret_cast(&m_uv_async))) + { + uv_async_send(&m_uv_async); + } } }; diff --git a/bindings/nodejs/src/modules_manager.cpp b/bindings/nodejs/src/modules_manager.cpp index eca68899..baf0ede1 100644 --- a/bindings/nodejs/src/modules_manager.cpp +++ b/bindings/nodejs/src/modules_manager.cpp @@ -21,17 +21,12 @@ using namespace nodejs_module; -#define NODE_STARTUP_SCRIPT "" \ -"(function () {" \ -" _gatewayInit.onNodeInitComplete();" \ -" global._gatewayExit = false;" \ -" function onTimer() {" \ -" if(global._gatewayExit === false) {" \ -" setTimeout(onTimer, 150);" \ -" }" \ -" }" \ -" onTimer();" \ -"})();" +#define NODE_STARTUP_SCRIPT "" \ +"(function () {" \ +" setTimeout(() => {" \ +" _gatewayInit.onNodeInitComplete();" \ +" }, 100);" \ +"})();" \ ModulesManager::ModulesManager() : m_nodejs_thread(nullptr), @@ -135,54 +130,63 @@ NODEJS_MODULE_HANDLE_DATA* ModulesManager::AddModule(const NODEJS_MODULE_HANDLE_ /*Codes_SRS_NODEJS_MODULES_MGR_13_005: [ AddModule shall acquire a lock on the ModulesManager object. ]*/ LockGuard lock_guard(*this); - // start NodeJS if not already started - if (m_nodejs_thread == nullptr) + // initialize idle processor if necessary + auto idler = NodeJSIdle::Get(); + if (idler->IsInitialized() == false && idler->Initialize() == false) { - /*Codes_SRS_NODEJS_MODULES_MGR_13_007: [ If this is the first time that this method is being called then it shall start a new thread and start up Node JS from the thread. ]*/ - StartNode(); + result = nullptr; } - - // if m_nodejs_thread is still NULL then StartNode failed - if (m_nodejs_thread != nullptr) + else { - auto module_id = m_moduleid_counter++; - try + // start NodeJS if not already started + if (m_nodejs_thread == nullptr) + { + /*Codes_SRS_NODEJS_MODULES_MGR_13_007: [ If this is the first time that this method is being called then it shall start a new thread and start up Node JS from the thread. ]*/ + StartNode(); + } + + // if m_nodejs_thread is still NULL then StartNode failed + if (m_nodejs_thread != nullptr) { - /*Codes_SRS_NODEJS_MODULES_MGR_13_008: [ AddModule shall add the module to it's internal collection of modules. ]*/ - m_modules.insert( - std::make_pair( - module_id, - NODEJS_MODULE_HANDLE_DATA(handle_data, module_id) - ) - ); - - // start up the module - if (StartModule(module_id) == true) + auto module_id = m_moduleid_counter++; + try { - result = &(m_modules[module_id]); + /*Codes_SRS_NODEJS_MODULES_MGR_13_008: [ AddModule shall add the module to it's internal collection of modules. ]*/ + m_modules.insert( + std::make_pair( + module_id, + NODEJS_MODULE_HANDLE_DATA(handle_data, module_id) + ) + ); + + // start up the module + if (StartModule(module_id) == true) + { + result = &(m_modules[module_id]); + } + else + { + /*Codes_SRS_NODEJS_MODULES_MGR_13_006: [ AddModule shall return NULL if an underlying API call fails. ]*/ + // remove the module from the map + LogError("Could not start Node JS module from path '%s'", handle_data.main_path.c_str()); + m_modules.erase(module_id); + result = nullptr; + } } - else + catch (std::bad_alloc& err) { /*Codes_SRS_NODEJS_MODULES_MGR_13_006: [ AddModule shall return NULL if an underlying API call fails. ]*/ - // remove the module from the map - LogError("Could not start Node JS module from path '%s'", handle_data.main_path.c_str()); - m_modules.erase(module_id); + LogError("Memory allocation error occurred with %s", err.what()); result = nullptr; } } - catch (std::bad_alloc& err) + else { /*Codes_SRS_NODEJS_MODULES_MGR_13_006: [ AddModule shall return NULL if an underlying API call fails. ]*/ - LogError("Memory allocation error occurred with %s", err.what()); + LogError("Could not start Node JS thread."); result = nullptr; } } - else - { - /*Codes_SRS_NODEJS_MODULES_MGR_13_006: [ AddModule shall return NULL if an underlying API call fails. ]*/ - LogError("Could not start Node JS thread."); - result = nullptr; - } return result; @@ -209,7 +213,7 @@ void ModulesManager::ExitNodeThread() const { NodeJSIdle::Get()->AddCallback([]() { NodeJSUtils::RunWithNodeContext([](v8::Isolate* isolate, v8::Local context) { - NodeJSUtils::RunScript(isolate, context, "global._gatewayExit = true;"); + NodeJSIdle::Get()->DeInitialize(); }); }); } @@ -249,7 +253,7 @@ int ModulesManager::NodeJSRunner() const char *p3 = p2 + sizeof("-e"); const char *pargv[] = { p1, p2, p3 }; - int result = node::Start(argc, const_cast(pargv), NodeJSIdle::OnIdle); + int result = node::Start(argc, const_cast(pargv)); // reset object state to indicate that the node thread has now // left the building diff --git a/bindings/nodejs/src/nodejs_idle.cpp b/bindings/nodejs/src/nodejs_idle.cpp index ffdab98f..791e8cb6 100644 --- a/bindings/nodejs/src/nodejs_idle.cpp +++ b/bindings/nodejs/src/nodejs_idle.cpp @@ -13,7 +13,8 @@ using namespace nodejs_module; -NodeJSIdle::NodeJSIdle() +NodeJSIdle::NodeJSIdle() : + m_initialized(false) {} NodeJSIdle::~NodeJSIdle() @@ -63,6 +64,48 @@ NodeJSIdle* NodeJSIdle::Get() return instance; } +bool nodejs_module::NodeJSIdle::IsInitialized() const +{ + LockGuard lock_guard{ *this }; + return m_initialized; +} + +bool nodejs_module::NodeJSIdle::Initialize() +{ + LockGuard lock_guard{ *this }; + + if (m_initialized == false) + { + auto loop = uv_default_loop(); + if (loop == nullptr) + { + LogError("uv_default_loop() failed."); + m_initialized = false; + } + else + { + if (uv_async_init(loop, &m_uv_async, NodeJSIdle::OnIdle) != 0) + { + LogError("uv_async_init() failed."); + m_initialized = false; + } + else + { + m_initialized = true; + } + } + } + + return m_initialized; +} + +void nodejs_module::NodeJSIdle::DeInitialize() +{ + LockGuard lock_guard{ *this }; + uv_close(reinterpret_cast(&m_uv_async), nullptr); + m_initialized = false; +} + void NodeJSIdle::AcquireLock() const { m_lock.AcquireLock(); @@ -87,7 +130,7 @@ void NodeJSIdle::InvokeCallbacks() ReleaseLock(); } -void NodeJSIdle::OnIdle(v8::Isolate* isolate) +void NodeJSIdle::OnIdle(uv_async_t* handle) { NodeJSIdle::Get()->InvokeCallbacks(); } diff --git a/tools/build_nodejs.cmd b/tools/build_nodejs.cmd index 0b49b267..c5606aa9 100644 --- a/tools/build_nodejs.cmd +++ b/tools/build_nodejs.cmd @@ -9,45 +9,10 @@ set current-path=%~dp0 rem Remove trailing slash set current-path=%current-path:~0,-1% -set build-root=%current-path%\..\build_nodejs +set build-root=%current-path%\.. rem Resolve to fully qualified path for %%i in ("%build-root%") do set build-root=%%~fi -rem Clear the nodejs build folder so we have a fresh build -rmdir /s/q %build-root% -mkdir %build-root% +@powershell -File "%build-root%\tools\build_nodejs.ps1" %* -pushd %build-root% - -rem Clone Node.js -git clone -b shared-691 --depth 1 https://github.com/avranju/node.git -pushd node - -rem Build Node.js -call vcbuild.bat release nosign dll %* -popd - -rem Create a 'dist' folder where the includes/libs live -mkdir dist\inc\libplatform -copy node\src\env.h dist\inc -copy node\src\env-inl.h dist\inc -copy node\src\node.h dist\inc -copy node\src\node_version.h dist\inc -copy node\deps\uv\include\*.h dist\inc -copy node\deps\v8\include\*.h dist\inc -copy node\deps\v8\include\libplatform\*.h dist\inc\libplatform - -mkdir dist\lib -copy node\Release\node.dll dist\lib -copy node\Release\node.lib dist\lib -copy node\Release\node.pdb dist\lib -copy node\Release\lib\*.lib dist\lib -copy node\build\Release\lib\*.lib dist\lib - -popd - -rem Set environment variables for where the include/lib files can be found -@endlocal & set NODE_INCLUDE=%build-root%\dist\inc\ & set NODE_LIB=%build-root%\dist\lib\ - -goto :eof diff --git a/tools/build_nodejs.ps1 b/tools/build_nodejs.ps1 new file mode 100644 index 00000000..1a0b77c4 --- /dev/null +++ b/tools/build_nodejs.ps1 @@ -0,0 +1,72 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. + +[CmdletBinding(PositionalBinding=$false)] +Param( + # Version of Node.js to build + [Alias("node-version")] + [string] + $NodeVersion="v6.10.1", + + # Other params to be passed to Node.js build script + [Parameter(ValueFromRemainingArguments=$true)] + [string[]] + $BuildParams +) + +# Get source root folder. +$buildRoot = [System.IO.Path]::GetFullPath( + [System.IO.Path]::Combine($PSScriptRoot, "..\build_nodejs") +) + +Write-Output "Building Node.js version $NodeVersion." + +# Clear the nodejs build folder so we have a fresh build. +if(Test-Path -Path $buildRoot) { + Write-Output "Deleting previous build." + Remove-Item -Recurse -Force $buildRoot +} +New-Item -ItemType Directory -Force -Path $buildRoot > $null + +Push-Location -Path $buildRoot + +# Clone Node.js +Write-Output "Cloning Node.js version $NodeVersion". +git clone -b $NodeVersion --depth 1 https://github.com/nodejs/node.git + +# Build Node.js +Write-Output "Building Node.js version $NodeVersion with build params $BuildParams." +Push-Location -Path (Join-Path -Path $buildRoot -ChildPath node) +cmd.exe /C "vcbuild.bat release nosign dll $BuildParams" +if(-not $?) { + Write-Output "Node.js build script failed." + Pop-Location + Return +} +Pop-Location + +# Create a 'dist' folder where the includes/libs live +Write-Output "Copying build to dist folder." +New-Item -ItemType Directory -Path dist\inc\libplatform > $null +Copy-Item node\src\env.h dist\inc +Copy-Item node\src\env-inl.h dist\inc +Copy-Item node\src\node.h dist\inc +Copy-Item node\src\node_version.h dist\inc +Copy-Item node\deps\uv\include\*.h dist\inc +Copy-Item node\deps\v8\include\*.h dist\inc +Copy-Item node\deps\v8\include\libplatform\*.h dist\inc\libplatform + +New-Item -ItemType Directory -Path dist\lib > $null +Copy-Item node\Release\node.dll dist\lib +Copy-Item node\Release\node.lib dist\lib +Copy-Item node\Release\node.pdb dist\lib +Copy-Item node\Release\lib\*.lib dist\lib +Copy-Item node\build\Release\lib\*.lib dist\lib + +Pop-Location # Undo Push-Location -Path $buildRoot + +Write-Output "`nNode JS has been built and the includes and library files are in:`n" +Write-Output "`t$buildRoot\dist" +Write-Output "`nSet the following variables so that the Azure IoT Gateway SDK build scripts can find these files." +Write-Output "`n`tset NODE_INCLUDE=`"$buildRoot\dist\inc`"" +Write-Output "`tset NODE_LIB=`"$buildRoot\dist\lib`"`n" diff --git a/tools/build_nodejs.sh b/tools/build_nodejs.sh index 115c066f..9bb0ec1b 100755 --- a/tools/build_nodejs.sh +++ b/tools/build_nodejs.sh @@ -7,13 +7,46 @@ set -e build_root=$(cd "$(dirname "$0")/.." && pwd) build_root=$build_root/build_nodejs +# Version of Node.js to build +node_version="v6.10.1" + +usage () +{ + echo "build_nodejs.sh [options]" + echo "options" + echo " --node-version Version of Node.js to build. Defaults to $node_version" + exit 1 +} + +process_args () +{ + save_next_arg=0 + + for arg in $* + do + if [ $save_next_arg == 1 ] + then + # save arg to pass to gcc + node_version="$arg" + save_next_arg=0 + else + case "$arg" in + "--node-version" ) save_next_arg=1;; + * ) usage;; + esac + fi + done +} + +process_args $* + # clear the Node.js build folder so we have a fresh build rm -rf $build_root mkdir -p $build_root # build Node.js pushd $build_root -git clone -b shared-691 --depth 1 https://github.com/avranju/node.git +git clone -b $node_version --depth 1 https://github.com/nodejs/node.git pushd node ./configure --shared make -j $(nproc)