Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return Buffer object with handoff from unique ptr string #147

Merged
merged 4 commits into from
Jul 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/module_utils.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once
#include <memory>
#include <nan.h>
#include <string>

namespace utils {

Expand All @@ -24,4 +26,19 @@ inline void CallbackError(std::string message, v8::Local<v8::Function> func) {
v8::Local<v8::Value> argv[1] = {Nan::Error(message.c_str())};
Nan::Call(cb, 1, argv);
}

inline Nan::MaybeLocal<v8::Object> NewBufferFrom(std::unique_ptr<std::string>&& ptr) {
Nan::MaybeLocal<v8::Object> res = Nan::NewBuffer(
&(*ptr)[0],
ptr->size(),
[](char*, void* hint) {
delete static_cast<std::string*>(hint);
},
ptr.get());
if (!res.IsEmpty()) {
ptr.release();
}
return res;
}

} // namespace utils
56 changes: 43 additions & 13 deletions src/object_async/hello_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* @memberof HelloObjectAsync
* @param {Object} args - different ways to alter the string
* @param {boolean} args.louder - adds exclamation points to the string
* @param {buffer} args.buffer - returns object as a node buffer rather then string
* @param {Function} callback - from whence the hello comes, returns a string
* @returns {String}
* @example
Expand All @@ -44,7 +45,7 @@ namespace object_async {
// This avoids copying the value and duplicating memory allocation, which can
// negatively affect performance.
HelloObjectAsync::HelloObjectAsync(std::string&& name)
: name_(std::move(name)) {}
: name_(name) {}

// Triggered from Javascript world when calling "new HelloObjectAsync(name)"
NAN_METHOD(HelloObjectAsync::New) {
Expand Down Expand Up @@ -109,7 +110,7 @@ NAN_METHOD(HelloObjectAsync::New) {
// This function performs expensive allocation of std::map, querying, and string
// comparison, therefore threads are nice & busy.
// Also, notice that name is passed by reference (std::string const& name)
std::string do_expensive_work(bool louder, std::string const& name) {
std::unique_ptr<std::string> do_expensive_work(bool louder, std::string const& name) {

std::map<std::size_t, std::string> container;
std::size_t work_to_do = 100000;
Expand All @@ -132,10 +133,10 @@ std::string do_expensive_work(bool louder, std::string const& name) {
}
}

std::string result = "...threads are busy async bees...hello " + name;
std::unique_ptr<std::string> result = std::make_unique<std::string>("...threads are busy async bees...hello " + name);

if (louder) {
result += "!!!!";
*result += "!!!!";
}

return result;
Expand All @@ -156,9 +157,15 @@ struct AsyncHelloWorker : Nan::AsyncWorker // NOLINT to disable cppcoreguideline
// pointer member without the silly g++ warning of "error: ‘struct object_async::AsyncHelloWorker’ has pointer data members [-Werror=effc++]"
AsyncHelloWorker(AsyncHelloWorker const&) = delete;
AsyncHelloWorker& operator=(AsyncHelloWorker const&) = delete;
AsyncHelloWorker(bool louder, const std::string* name,
AsyncHelloWorker(bool louder,
bool buffer,
const std::string* name,
Nan::Callback* cb)
: Base(cb, "skel:object-async-worker"), louder_{louder}, name_{name} {}
: Base(cb, "skel:object-async-worker"),
result_(),
louder_(louder),
buffer_(buffer),
name_(name) {}

// The Execute() function is getting called when the worker starts to run.
// - You only have access to member variables stored in this worker.
Expand All @@ -181,16 +188,27 @@ struct AsyncHelloWorker : Nan::AsyncWorker // NOLINT to disable cppcoreguideline
void HandleOKCallback() override {
Nan::HandleScope scope;

const auto argc = 2u;
v8::Local<v8::Value> argv[argc] = {
Nan::Null(), Nan::New<v8::String>(result_).ToLocalChecked()};
if (buffer_) {
const auto argc = 2u;
v8::Local<v8::Value> argv[argc] = {
Nan::Null(), utils::NewBufferFrom(std::move(result_)).ToLocalChecked()};

// Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy
callback->Call(argc, static_cast<v8::Local<v8::Value>*>(argv), async_resource);
// Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy
callback->Call(argc, static_cast<v8::Local<v8::Value>*>(argv), async_resource);

} else {
const auto argc = 2u;
v8::Local<v8::Value> argv[argc] = {
Nan::Null(), Nan::New<v8::String>(*result_).ToLocalChecked()};

// Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy
callback->Call(argc, static_cast<v8::Local<v8::Value>*>(argv), async_resource);
}
}

std::string result_{};
std::unique_ptr<std::string> result_;
const bool louder_;
const bool buffer_;
// We use a pointer here to avoid copying the string data.
// This works because we know that the original string we are
// pointing to will be kept in scope/alive for the time while AsyncHelloWorker
Expand All @@ -214,6 +232,7 @@ NAN_METHOD(HelloObjectAsync::helloAsync) {
Nan::ObjectWrap::Unwrap<HelloObjectAsync>(info.Holder());

bool louder = false;
bool buffer = false;

// Check second argument, should be a 'callback' function.
// This allows us to set the callback so we can use it to return errors
Expand Down Expand Up @@ -243,6 +262,17 @@ NAN_METHOD(HelloObjectAsync::helloAsync) {
}
louder = louder_val->BooleanValue();
}
// Check options object for the "buffer" property, which should be a boolean
// value
if (options->Has(Nan::New("buffer").ToLocalChecked())) {
v8::Local<v8::Value> buffer_val =
options->Get(Nan::New("buffer").ToLocalChecked());
if (!buffer_val->IsBoolean()) {
return utils::CallbackError("option 'buffer' must be a boolean",
callback);
}
buffer = buffer_val->BooleanValue();
}

// Create a worker instance and queues it to run asynchronously invoking the
// callback when done.
Expand All @@ -251,7 +281,7 @@ NAN_METHOD(HelloObjectAsync::helloAsync) {
// - Nan::AsyncQueueWorker takes a pointer to a Nan::AsyncWorker and deletes
// the pointer automatically.
auto cb = std::make_unique<Nan::Callback>(callback);
auto worker = std::make_unique<AsyncHelloWorker>(louder, &h->name_, cb.release());
auto worker = std::make_unique<AsyncHelloWorker>(louder, buffer, &h->name_, cb.release());
Nan::AsyncQueueWorker(worker.release());
}

Expand Down
4 changes: 2 additions & 2 deletions src/object_sync/hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace object_sync {
// Custom constructor, assigns custom name passed in from Javascript world.
// This constructor uses member init list via the semicolon, aka "direct initialization"
// which is more efficient than using assignment operators.
HelloObject::HelloObject(std::string&& name) : name_(std::move(name)) {}
HelloObject::HelloObject(std::string&& name) : name_(name) {}

// Triggered from Javascript world when calling "new HelloObject(name)"
NAN_METHOD(HelloObject::New) {
Expand Down Expand Up @@ -167,4 +167,4 @@ void HelloObject::Init(v8::Local<v8::Object> target) {
Nan::Set(target, whoami, fn);
}

} // namespace object_sync
} // namespace object_sync
46 changes: 34 additions & 12 deletions src/standalone_async/hello_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* @name helloAsync
* @param {Object} args - different ways to alter the string
* @param {boolean} args.louder - adds exclamation points to the string
* @param {boolean} args.buffer - returns value as a node buffer rather than a string
* @param {Function} callback - from whence the hello comes, returns a string
* @returns {string}
* @example
Expand All @@ -32,7 +33,7 @@ namespace standalone_async {

// Expensive allocation of std::map, querying, and string comparison,
// therefore threads are busy
std::string do_expensive_work(bool louder) {
std::unique_ptr<std::string> do_expensive_work(bool louder) {

std::map<std::size_t, std::string> container;
std::size_t work_to_do = 100000;
Expand All @@ -54,10 +55,10 @@ std::string do_expensive_work(bool louder) {
}
}

std::string result = "...threads are busy async bees...hello world";
std::unique_ptr<std::string> result = std::make_unique<std::string>("...threads are busy async bees...hello world");

if (louder) {
result += "!!!!";
*result += "!!!!";
}

return result;
Expand All @@ -72,8 +73,8 @@ std::string do_expensive_work(bool louder) {
struct AsyncHelloWorker : Nan::AsyncWorker {
using Base = Nan::AsyncWorker;

AsyncHelloWorker(bool louder, Nan::Callback* cb)
: Base(cb, "skel:standalone-async-worker"), louder_{louder} {}
AsyncHelloWorker(bool louder, bool buffer, Nan::Callback* cb)
: Base(cb, "skel:standalone-async-worker"), result_(), louder_(louder), buffer_(buffer) {}

// The Execute() function is getting called when the worker starts to run.
// - You only have access to member variables stored in this worker.
Expand All @@ -98,15 +99,24 @@ struct AsyncHelloWorker : Nan::AsyncWorker {
void HandleOKCallback() override {
Nan::HandleScope scope;

const auto argc = 2u;
v8::Local<v8::Value> argv[argc] = {
Nan::Null(), Nan::New<v8::String>(result_).ToLocalChecked()};
// Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy
callback->Call(argc, static_cast<v8::Local<v8::Value>*>(argv), async_resource);
if (buffer_) {
const auto argc = 2u;
v8::Local<v8::Value> argv[argc] = {
Nan::Null(), utils::NewBufferFrom(std::move(result_)).ToLocalChecked()};
// Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy
callback->Call(argc, static_cast<v8::Local<v8::Value>*>(argv), async_resource);
} else {
const auto argc = 2u;
v8::Local<v8::Value> argv[argc] = {
Nan::Null(), Nan::New<v8::String>(*result_).ToLocalChecked()};
// Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy
callback->Call(argc, static_cast<v8::Local<v8::Value>*>(argv), async_resource);
}
}

std::string result_{};
std::unique_ptr<std::string> result_;
const bool louder_;
const bool buffer_;
};

// helloAsync is a "standalone function" because it's not a class.
Expand All @@ -115,6 +125,7 @@ struct AsyncHelloWorker : Nan::AsyncWorker {
NAN_METHOD(helloAsync) {

bool louder = false;
bool buffer = false;

// Check second argument, should be a 'callback' function.
// This allows us to set the callback so we can use it to return errors
Expand Down Expand Up @@ -144,6 +155,17 @@ NAN_METHOD(helloAsync) {
}
louder = louder_val->BooleanValue();
}
// Check options object for the "buffer" property, which should be a boolean
// value
if (options->Has(Nan::New("buffer").ToLocalChecked())) {
v8::Local<v8::Value> buffer_val =
options->Get(Nan::New("buffer").ToLocalChecked());
if (!buffer_val->IsBoolean()) {
return utils::CallbackError("option 'buffer' must be a boolean",
callback);
}
buffer = buffer_val->BooleanValue();
}

// Creates a worker instance and queues it to run asynchronously, invoking the
// callback when done.
Expand All @@ -152,7 +174,7 @@ NAN_METHOD(helloAsync) {
// - Nan::AsyncQueueWorker takes a pointer to a Nan::AsyncWorker and deletes
// the pointer automatically.
auto cb = std::make_unique<Nan::Callback>(callback);
auto worker = std::make_unique<AsyncHelloWorker>(louder, cb.release());
auto worker = std::make_unique<AsyncHelloWorker>(louder, buffer, cb.release());
Nan::AsyncQueueWorker(worker.release());
}

Expand Down
20 changes: 19 additions & 1 deletion test/hello_async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ test('success: prints regular busy world', function(t) {
});
});

test('success: buffer regular busy world', function(t) {
module.helloAsync({ buffer: true }, function(err, result) {
if (err) throw err;
t.equal(result.length, 44);
t.equal(typeof(result), 'object');
t.equal(result.toString(), '...threads are busy async bees...hello world');
t.end();
});
});

test('error: handles invalid louder value', function(t) {
module.helloAsync({ louder: 'oops' }, function(err, result) {
t.ok(err, 'expected error');
Expand All @@ -25,6 +35,14 @@ test('error: handles invalid louder value', function(t) {
});
});

test('error: handles invalid buffer value', function(t) {
module.helloAsync({ buffer: 'oops' }, function(err, result) {
t.ok(err, 'expected error');
t.ok(err.message.indexOf('option \'buffer\' must be a boolean') > -1, 'expected error message');
t.end();
});
});

test('error: handles invalid options value', function(t) {
module.helloAsync('oops', function(err, result) {
t.ok(err, 'expected error');
Expand All @@ -41,4 +59,4 @@ test('error: handles missing callback', function(t) {
t.ok(err.message.indexOf('second arg \'callback\' must be a function') > -1, 'expected error message');
t.end();
}
});
});
22 changes: 21 additions & 1 deletion test/hello_object_async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ test('success: prints loud busy world', function(t) {
});
});

test('success: return buffer busy world', function(t) {
var H = new module.HelloObjectAsync('world');
H.helloAsync({ buffer: true }, function(err, result) {
if (err) throw err;
t.equal(result.length, 44);
t.equal(typeof(result), 'object');
t.equal(result.toString(), '...threads are busy async bees...hello world');
t.end();
});
});

test('error: throws when passing empty string', function(t) {
try {
var H = new module.HelloObjectAsync('');
Expand Down Expand Up @@ -58,6 +69,15 @@ test('error: handles invalid louder value', function(t) {
});
});

test('error: handles invalid buffer value', function(t) {
var H = new module.HelloObjectAsync('world');
H.helloAsync({ buffer: 'oops' }, function(err, result) {
t.ok(err, 'expected error');
t.ok(err.message.indexOf('option \'buffer\' must be a boolean') > -1, 'expected error message');
t.end();
});
});

test('error: handles invalid options value', function(t) {
var H = new module.HelloObjectAsync('world');
H.helloAsync('oops', function(err, result) {
Expand Down Expand Up @@ -86,4 +106,4 @@ test('error: handles missing arg', function(t) {
t.ok(err.message.indexOf('must provide string arg') > -1, 'expected error message');
t.end();
}
});
});