Making Node.js Async Function with Libuv Thread Pool
libuv is a cross-platform C library for Node.js asynchronous I/O model. It implements Node.js event loop and uses a thread pool to avoid blocking the Node.js event loop with time-consuming I/O operations. The post shares how to use libuv to optimize Dynamsoft barcode addon for Node.js.
Learning Resources
- Node.js C/C++ Addons
- An Introduction to libuv
- Thread pool work scheduling
- .node-gyp\5.5.0\include\node\uv.h
How to Optimize Dynamsoft Barcode Addon for Node.js with Libuv
Prerequisites
- Dynamsoft Barcode Reader
- Node.js
- node-gyp:
npm install -g node-gyp
Quickly Build a Node.js C/C++ Barcode Addon
- Create dbr.cc with a function DecodeFile:
#include <node.h>
#include <node_buffer.h>
#include <string.h>
#include <uv.h>
#include "If_DBR.h"
#include "BarcodeFormat.h"
#include "BarcodeStructs.h"
#include "ErrorCode.h"
using namespace v8;
// Barcode format
const char * GetFormatStr(__int64 format)
{
if (format == CODE_39)
return "CODE_39";
if (format == CODE_128)
return "CODE_128";
if (format == CODE_93)
return "CODE_93";
if (format == CODABAR)
return "CODABAR";
if (format == ITF)
return "ITF";
if (format == UPC_A)
return "UPC_A";
if (format == UPC_E)
return "UPC_E";
if (format == EAN_13)
return "EAN_13";
if (format == EAN_8)
return "EAN_8";
if (format == INDUSTRIAL_25)
return "INDUSTRIAL_25";
if (format == QR_CODE)
return "QR_CODE";
if (format == PDF417)
return "PDF417";
if (format == DATAMATRIX)
return "DATAMATRIX";
return "UNKNOWN";
}
/*
* decodeFile(fileName, barcodeTypes, callback)
*/
void DecodeFile(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// get arguments
String::Utf8Value fileName(args[0]->ToString()); // convert v8 string to char *
char *pFileName = *fileName; // file name
__int64 llFormat = args[1]->IntegerValue(); // barcode types
Local<Function> cb = Local<Function>::Cast(args[2]); // javascript callback function
// initialize Dynamsoft Barcode Reader
int iMaxCount = 0x7FFFFFFF;
ReaderOptions ro = {0};
pBarcodeResultArray pResults = NULL;
ro.llBarcodeFormat = llFormat;
ro.iMaxBarcodesNumPerPage = iMaxCount;
// decode barcode image
int ret = DBR_DecodeFile(pFileName, &ro, &pResults);
if (ret)
printf("Detection error code: %d\n", ret);
int count = pResults->iBarcodeCount;
pBarcodeResult* ppBarcodes = pResults->ppBarcodes;
pBarcodeResult tmp = NULL;
// array for storing barcode results
Local<Array> barcodeResults = Array::New(isolate);
for (int i = 0; i < count; i++)
{
tmp = ppBarcodes[i];
Local<Object> result = Object::New(isolate);
result->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, GetFormatStr(tmp->llFormat)));
result->Set(String::NewFromUtf8(isolate, "value"), String::NewFromUtf8(isolate, tmp->pBarcodeData));
barcodeResults->Set(Number::New(isolate, i), result);
}
// release memory of barcode results
DBR_FreeBarcodeResults(&pResults);
// run the callback
const unsigned argc = 1;
Local<Value> argv[argc] = { barcodeResults };
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
}
void Init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "decodeFile", DecodeFile);
}
NODE_MODULE(dbr, Init)
- Create binding.gyp. Since Dynamsoft Barcode Reader is available for Windows, Linux, and macOS, you can make configuration for all supported platforms:
{
"targets": [
{
'target_name': "dbr",
'sources': [ "dbr.cc" ],
'conditions': [
['OS=="linux"', {
'defines': [
'LINUX_DBR',
],
'include_dirs': [
"/home/xiao/Dynamsoft/BarcodeReader4.0/Include"
],
'libraries': [
"-lDynamsoftBarcodeReaderx64", "-L/home/xiao/Dynamsoft/BarcodeReader4.0/Redist"
],
'copies': [
{
'destination': 'build/Release/',
'files': [
'/home/xiao/Dynamsoft/BarcodeReader4.0/Redist/libDynamsoftBarcodeReaderx64.so'
]
}]
}],
['OS=="win"', {
'defines': [
'WINDOWS_DBR',
],
'include_dirs': [
"E:\Program Files (x86)\Dynamsoft\Barcode Reader 4.3\Components\C_C++\Include"
],
'libraries': [
"-lE:\Program Files (x86)\Dynamsoft\Barcode Reader 4.3\Components\C_C++\Lib\DBRx64.lib"
],
'copies': [
{
'destination': 'build/Release/',
'files': [
'E:\Program Files (x86)\Dynamsoft\Barcode Reader 4.3\Components\C_C++\Redist\DynamsoftBarcodeReaderx64.dll'
]
}
]
}],
['OS=="mac"', {
'defines': [
'MAC_DBR',
],
'include_dirs' : [
"/Applications/Dynamsoft/Barcode\ Reader\ 4.1/Include"
],
'libraries': [
"-lDynamsoftBarcodeReader"
]
}]
]
}]
}
- Configure build environment:
node-gyp configure
- Build the project to generate the compiled dbr.node file:
node-gyp build
- Use the addon in dbr.js:
var dbr = require('./build/Release/dbr');
var readline = require('readline');
var fs = require('fs');
var barcodeTypes = 0x3FF | 0x2000000 | 0x8000000 | 0x4000000; // 1D, QRCODE, PDF417, DataMatrix
function decodeFile(fileName) {
dbr.decodeFile(
fileName, barcodeTypes,
function(msg) {
var result = null;
for (index in msg) {
result = msg[index]
console.log("Format: " + result['format']);
console.log("Value : " + result['value']);
console.log("##################");
}
}
);
}
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Please input a barcode image path: ", function(answer) {
decodeFile(answer);
rl.close();
});
Asynchronous Function with Libuv Thread Pool
The above code only implements a synchronous interface. When barcode detection takes too much time, it apparently will block the JavaScript event loop. Therefore, we have to solve this issue with the libuv thread pool.
To send heavy work to the thread pool, we just need to call the API uv_queue_work
(uv_loop_t_ loop, uv_work_t_ req, uv_work_cb work_cb, uv_after_work_cb after_work_cb).
Create a new function decodeFileAsync:
/*
* decodeFileAsync(fileName, barcodeTypes, callback)
*/
void DecodeFileAsync(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// get arguments
String::Utf8Value fileName(args[0]->ToString()); // file name
char *pFileName = *fileName;
__int64 llFormat = args[1]->IntegerValue(); // barcode types
Local<Function> cb = Local<Function>::Cast(args[2]); // javascript callback function
// initialize BarcodeWorker
BarcodeWorker *worker = new BarcodeWorker;
worker->request.data = worker;
strcpy(worker->filename, pFileName);
worker->callback.Reset(isolate, cb);
worker->llFormat = llFormat;
worker->pResults = NULL;
worker->buffer = NULL;
uv_queue_work(uv_default_loop(), &worker->request, (uv_work_cb)DetectionWorking, (uv_after_work_cb)DetectionDone);
}
void Init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "decodeFile", DecodeFile);
NODE_SET_METHOD(exports, "decodeFileAsync", DecodeFileAsync);
}
Move barcode detection work to a uv_work_cb callback function that executed in a worker thread:
/*
* uv_work_cb
*/
static void DetectionWorking(uv_work_t *req)
{
// get the reference to BarcodeWorker
BarcodeWorker *worker = static_cast<BarcodeWorker *>(req->data);
// initialize Dynamsoft Barcode Reader
int iMaxCount = 0x7FFFFFFF;
ReaderOptions ro = {0};
pBarcodeResultArray pResults = NULL;
ro.llBarcodeFormat = worker->llFormat;
ro.iMaxBarcodesNumPerPage = iMaxCount;
// decode barcode image
int ret = 0;
if (worker->buffer)
{
ret = DBR_DecodeStream(worker->buffer, worker->size, &ro, &pResults);
}
else
{
ret = DBR_DecodeFile(worker->filename, &ro, &pResults);
}
if (ret)
printf("Detection error code: %d\n", ret);
// save results to BarcodeWorker
worker->errorCode = ret;
worker->pResults = pResults;
}
Display barcode results in a uv_after_work_cb callback function that executed in the JavaScript thread:
/*
* uv_after_work_cb
*/
static void DetectionDone(uv_work_t *req,int status)
{
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// get the reference to BarcodeWorker
BarcodeWorker *worker = static_cast<BarcodeWorker *>(req->data);
// get barcode results
pBarcodeResultArray pResults = worker->pResults;
int errorCode = worker->errorCode;
int count = pResults->iBarcodeCount;
pBarcodeResult* ppBarcodes = pResults->ppBarcodes;
pBarcodeResult tmp = NULL;
// array for storing barcode results
Local<Array> barcodeResults = Array::New(isolate);
for (int i = 0; i < count; i++)
{
tmp = ppBarcodes[i];
Local<Object> result = Object::New(isolate);
result->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, GetFormatStr(tmp->llFormat)));
result->Set(String::NewFromUtf8(isolate, "value"), String::NewFromUtf8(isolate, tmp->pBarcodeData));
barcodeResults->Set(Number::New(isolate, i), result);
}
// release memory of barcode results
DBR_FreeBarcodeResults(&pResults);
// run the callback
const unsigned argc = 1;
Local<Value> argv[argc] = {barcodeResults};
Local<Function> cb = Local<Function>::New(isolate, worker->callback);
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
// release memory of BarcodeWorker
delete worker;
}
Modify dbr.js and run it again:
var dbr = require('./build/Release/dbr');
var readline = require('readline');
var fs = require('fs');
var barcodeTypes = 0x3FF | 0x2000000 | 0x8000000 | 0x4000000; // 1D, QRCODE, PDF417, DataMatrix
function decodeFileAsync(fileName) {
dbr.decodeFileAsync(
fileName, barcodeTypes,
function(msg) {
var result = null;
for (index in msg) {
result = msg[index]
console.log("Format: " + result['format']);
console.log("Value : " + result['value']);
console.log("##################");
}
}
);
}
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Please input a barcode image path: ", function(answer) {
decodeFileAsync(answer);
rl.close();
});
Source Code
https://github.com/yushulx/barcode4nodejs
Disclaimer:
The wrappers and sample code on Dynamsoft Codepool are community editions, shared as-is and not fully tested. Dynamsoft is happy to provide technical support for users exploring these solutions but makes no guarantees.