Skip to content

Commit

Permalink
Re-implement Node.js binding using libuv async
Browse files Browse the repository at this point in the history
  • Loading branch information
avranju committed Mar 25, 2017
1 parent c1687a8 commit c7d8349
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 87 deletions.
27 changes: 25 additions & 2 deletions bindings/nodejs/inc/nodejs_idle.h
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -19,6 +19,8 @@ namespace nodejs_module
private:
std::queue<std::function<void()>> m_callbacks;
Lock m_lock;
bool m_initialized;
uv_async_t m_uv_async;

/**
* Private constructor to enforce singleton instance.
Expand All @@ -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.
*/
Expand All @@ -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();
Expand All @@ -60,6 +74,15 @@ namespace nodejs_module
{
LockGuard<NodeJSIdle> 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<uv_handle_t*>(&m_uv_async)))
{
uv_async_send(&m_uv_async);
}
}
};

Expand Down
94 changes: 49 additions & 45 deletions bindings/nodejs/src/modules_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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<ModulesManager> 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;

Expand All @@ -209,7 +213,7 @@ void ModulesManager::ExitNodeThread() const
{
NodeJSIdle::Get()->AddCallback([]() {
NodeJSUtils::RunWithNodeContext([](v8::Isolate* isolate, v8::Local<v8::Context> context) {
NodeJSUtils::RunScript(isolate, context, "global._gatewayExit = true;");
NodeJSIdle::Get()->DeInitialize();
});
});
}
Expand Down Expand Up @@ -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<char**>(pargv), NodeJSIdle::OnIdle);
int result = node::Start(argc, const_cast<char**>(pargv));

// reset object state to indicate that the node thread has now
// left the building
Expand Down
47 changes: 45 additions & 2 deletions bindings/nodejs/src/nodejs_idle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

using namespace nodejs_module;

NodeJSIdle::NodeJSIdle()
NodeJSIdle::NodeJSIdle() :
m_initialized(false)
{}

NodeJSIdle::~NodeJSIdle()
Expand Down Expand Up @@ -63,6 +64,48 @@ NodeJSIdle* NodeJSIdle::Get()
return instance;
}

bool nodejs_module::NodeJSIdle::IsInitialized() const
{
LockGuard<NodeJSIdle> lock_guard{ *this };
return m_initialized;
}

bool nodejs_module::NodeJSIdle::Initialize()
{
LockGuard<NodeJSIdle> 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<NodeJSIdle> lock_guard{ *this };
uv_close(reinterpret_cast<uv_handle_t*>(&m_uv_async), nullptr);
m_initialized = false;
}

void NodeJSIdle::AcquireLock() const
{
m_lock.AcquireLock();
Expand All @@ -87,7 +130,7 @@ void NodeJSIdle::InvokeCallbacks()
ReleaseLock();
}

void NodeJSIdle::OnIdle(v8::Isolate* isolate)
void NodeJSIdle::OnIdle(uv_async_t* handle)
{
NodeJSIdle::Get()->InvokeCallbacks();
}
39 changes: 2 additions & 37 deletions tools/build_nodejs.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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
72 changes: 72 additions & 0 deletions tools/build_nodejs.ps1
Original file line number Diff line number Diff line change
@@ -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"
Loading

0 comments on commit c7d8349

Please sign in to comment.