GithubHelp home page GithubHelp logo

v8plus's Introduction

v8+: Node.js addon C++ to C boundary layer

This layer offers a way to write at least simple Node.js addons in C without all the horrible C++ goop you'd otherwise be expected to use. That goop still exists, but you don't have to write it. More importantly, you can write your module in a sane programming environment, avoiding the confusing and error-prone C++ semantics.

Usage

Unlike most Node.js modules, v8+ does nothing by itself. It is intended to be used as a build-time dependency of your native addon, providing you with an alternate programming environment.

For full docs, read the source code.

Node.js Support

v8+ works with, and has been tested to some extent with, Node.js 0.6.18, 0.8.1, 0.8.26, 0.10.24, and 0.11.10. It most likely works with other micro versions in the 0.6 and 0.8 series as well; if you are using 0.10, you will need 0.10.24 or later so that you have headers to build against. Note that this does not mean you can necessarily expect an addon built against a particular minor release of Node.js to work with any other minor release of Node.js.

Node 0.11.10 and later are also supported, and contain a new module API that v8plus can leverage to provide an entirely new model for building and using C modules.

Building and Installing

The v8+ source code is compiled into your module directly along with your code. There is no separate v8+ library or node module, so the v8+ source, tools, and makefiles are required to be present at the time your module is built. They are not required at runtime.

Normally, your addon module will depend on the v8plus package and install it using npm. The v8+ makefiles are set up to accommodate the installation of v8+ anywhere node(1) would be able to find it using require() if it were a normal JavaScript module, so simply including it as a dependency in your package.json will work correctly. In addition, you will need to create a (normally trivial) makefile for your module that includes the makefiles distributed as part of v8+. Once you have done so, it is sufficient to run gmake to generate the native loadable module used by Node.js.

The overall outline for creating a v8+ module looks something like this:

  1. Write the C code that does whatever your module does. Be sure to #include "v8plus_glue.h". Do not include any other v8+ headers.

  2. Create an appropriate package.json file. See below for details.

  3. Create a skeleton makefile. See below for details.

You should not (and need not) modify either of the delivered makefiles; override the definitions in Makefile.v8plus.defs in your makefile as appropriate.

Packaging Considerations

There are two essential properties your package.json must contain in order to use v8+ with npm:

  1. A dependency on v8plus.

  2. An appropriate script entry for building your module. It is strongly recommended that you use something like the following:

     "postinstall": "gmake $(eval echo ${MAKE_OVERRIDES})"
    

This will allow someone building your module to set make variables by adding them to the MAKE_OVERRIDES environment variable; e.g.,

    $ MAKE_OVERRIDES="CTFCONVERT=/bin/true CTFMERGE=/bin/true" npm install

Tying into the Makefiles

The makefiles shipped with v8+ do the great majority of the heavy lifting for you. A minimally functional makefile for your addon must contain four things:

  1. Variable definitions for V8PLUS and PREFIX_NODE. Alternately, you may choose to provide these on the command line or via the environment. It is recommended that these assignments be made exactly as follows, which will cause the addon to be built against the node that is found first in your path:

     PREFIX_NODE := $(shell dirname `bash -c 'hash node; hash -t node'`)/..
     V8PLUS :=      $(shell $(PREFIX_NODE)/bin/node -e 'require("v8plus");')
    

    Note that the mechanism for finding node will not work correctly if yours is a symlink. This invocation of node(1) uses a v8+ mechanism to locate v8+ sources anywhere that node(1) can find them and should not be modified unless you want to test an alternate v8+.

  2. The exact line:

     include $(V8PLUS)/Makefile.v8plus.defs
    
  3. Variable assignments specific to your module. In particular, you must define SRCS and MODULE Note that ERRNO_JSON is no longer required nor used in v8plus 0.3 and later. Additional customisation is optional.

  4. The exact line:

     include $(V8PLUS)/Makefile.v8plus.targ
    

Additional arbitrary customisation is possible using standard makefile syntax; most things that are useful to change already have variables defined in Makefile.v8plus.defs whose values you may append to or override. For example, you may cause additional system libraries to be linked in by appending -lname to the LIBS variable. By default, the makefiles assume that your sources are located in the src subdirectory of your module, and that you want the sole output of the build process to be called $(MODULE).node and located in the lib subdirectory. This can be changed by overriding the MODULE_DIR variable.

A simple example makefile may be found in the examples/ subdirectory, and additional examples may be found in existing consumers; see Consumers below. The GNU people also provide a good manual for make if you get really stuck; see http://www.gnu.org/software/make/manual/make.html. In general, writing the necessary makefile fragment is expected to be as easy as or easier than the equivalent task using node-waf or node-gyp, so if you're finding it unexpectedly difficult or complicated there's probably an easier way.

The makefiles follow GNU make syntax; other makes may not work but patches that correct this are generally welcome (in particular, Sun make and GNU make have different and incompatible ways to set a variable from the output of a shell command, and there is no way I know to accommodate both).

Binary Interface

By default, the resulting object is linked with the -zdefs option, which will cause the build to fail if any unresolved symbols remain. In order to accommodate this, a mapfile specifying the available global symbols in your node binary is automatically generated as part of the build process. This makes it much easier to debug missing libraries; otherwise, a module with unresolved symbols will fail to load at runtime with no useful explanation. Mapfile generation probably works only on illumos-derived systems. Patches that add support for other linkers are welcome.

Your module will have all symbols (other than init, which is used directly by Node.js) reduced to local visibility, which is strongly recommended. If for some reason you want your module's symbols to be visible to Node.js or to other modules, you will have to modify the script that generates the mapfile. See the $(MAPFILE) target in Makefile.v8plus.targ.

API

Your module is an object factory that instantiates and returns native objects, to which a fixed set of methods is attached as properties. The constructor, destructor, and methods all correspond 1-1 with C functions. In addition, you may create additional class methods associated with the native module itself, each of which will also have a 1-1 relationship to a set of C functions.

This functionality is generally sufficient to interface with the system in useful ways, but it is by no means exhaustive. Architectural limitations are noted throughout the documentation.

Subsequent sections describe the API in greater detail, along with most of the C functions that v8+ provides. Some utility functions may not be listed here; see v8plus_glue.h for additional commentary and functions that are available to you.

Constructors, Methods, and Functions

The interface between your module and v8+ consists of a handful of objects with fixed types and names. These are:

const v8plus_c_ctor_f v8plus_ctor = my_ctor;
const v8plus_c_dtor_f v8plus_dtor = my_dtor;
const char *v8plus_js_factory_name = "_new";
const char *v8plus_js_class_name = "MyObjectBinding";
const v8plus_method_descr_t v8plus_methods[] = {
        {
                md_name: "_my_method",
                md_c_func: my_method
        },
	...
};
const uint_t v8plus_method_count =
    sizeof (v8plus_methods) / sizeof (v8plus_methods[0]);

const v8plus_static_descr_t v8plus_static_methods[] = {
        {
                sd_name: "_my_function",
                sd_c_func: my_function
        },
	...
};
const uint_t v8plus_static_method_count =
    sizeof (v8plus_static_methods) / sizeof (v8plus_static_methods[0]);

All of these must be present even if they have zero length or are NULL. The prototypes and semantics of each function type are as follows:

nvlist_t *v8plus_c_ctor_f(const nvlist_t *ap, void **opp)

The constructor is responsible for creating the C object corresponding to the native JavaScript object being created. It is not a true constructor in that you are actually an object factory; the C++ function associated with the JavaScript constructor is called for you. Your encoded arguments are in ap. Allocate and populate a C object, stuff it into *opp, and return v8plus_void(). If you need to throw an exception you can do so by calling v8plus_throw_exception() or any of its wrappers. As of v8plus 0.3, you may no longer return an nvlist with an err member to throw an exception, and the _v8plus_errno global variable is no longer available.

void v8plus_c_dtor_f(void *op)

Free the C object op and anything else associated with it. Your object is going away. This function may be empty if the constructor did not allocate any memory (i.e., op is not a pointer to dynamically allocated memory).

nvlist_t *v8plus_c_method_f(void *op, const nvlist_t *ap)

When the JavaScript method is called in the context of your object, the corresponding C function is invoked. op is the C object associated with the JavaScript object, and ap is the encoded list of arguments to the function. Return an encoded object with a res member, or use one of the error/exception patterns.

nvlist_t *v8plus_c_static_method_f(const nvlist_t *ap)

In addition to methods on the native objects returned by your constructor, you can also provide a set of functions on the native binding object itself. This may be useful for providing bindings to libraries for which no object representation makes sense, or that have functions that operate outside the context of any particular object. Your arguments are once again encoded in ap, and your return values are an object containing res or an error.

Argument Handling

When JavaScript objects cross the boundary from C++ to C, they are converted from v8 C++ objects into C nvlists. This means that they are effectively passed by value, unlike in JavaScript or in native addons written in C++. The arguments to the JavaScript function are treated as an array and marshalled into a single nvlist whose properties are named "0", "1", and so on. Each such property is encoded as follows:

  • numbers and Number objects (regardless of size): double
  • strings and String objects: UTF-8 encoded C string
  • booleans and Boolean objects: boolean_value
  • undefined: boolean
  • null: byte, value 0
  • Objects, including Arrays: nvlist with own properties as members and the member ".__v8plus_type" set to the object's JavaScript type name. Note that the member name itself begins with a . to reduce the likelihood of a collision with an actual JavaScript member name.
  • JavaScript Functions are passed in a format suitable for use with nvlist_lookup_jsfunc() and v8plus_args() with the V8PLUS_TYPE_JSFUNC token. This type is restricted; see below.

Because JavaScript arrays may be sparse, we cannot use the libnvpair array types. Consider them reserved for internal use. JavaScript Arrays are represented as they really are in JavaScript: objects with properties whose names happen to be integers.

Other data types cannot be represented and will result in a TypeError being thrown. If your object has methods that need other argument types, you cannot use v8+.

Side effects within the VM, including modification of the arguments, are not supported. If you need them, you cannot use v8+.

While the standard libnvpair functions may be used to inspect the arguments to a method or function, v8+ also provides the v8plus_args() and v8plus_typeof() convenience functions, which simplify checking the types and obtaining the values of arguments.

int v8plus_args(const nvlist_t *lp, uint_t flags, v8plus_type_t t, ...)

This function checks lp for the exact sequence of arguments specified by the list of types provided in the parameter list. If V8PLUS_ARG_F_NOEXTRA is set in flags, the list of arguments must match exactly, with no additional arguments. The parameter list must be terminated by V8PLUS_TYPE_NONE.

Following flags is a list of argument data types and, for most data types, pointers to locations at which the native C value of that argument should be stored. The following JavaScript argument data types are supported; for each, the parameter immediately following the data type parameter must be of the indicated C type. This parameter may be NULL, in which case the value will not be stored anywhere.

  • V8PLUS_TYPE_NONE: used to terminate the parameter list only
  • V8PLUS_TYPE_STRING: char **
  • V8PLUS_TYPE_NUMBER: double *
  • V8PLUS_TYPE_BOOLEAN: boolean_t *
  • V8PLUS_TYPE_JSFUNC: v8plus_jsfunc_t *
  • V8PLUS_TYPE_OBJECT: nvlist_t **
  • V8PLUS_TYPE_NULL: no parameter
  • V8PLUS_TYPE_UNDEFINED: no parameter
  • V8PLUS_TYPE_INVALID: data_type_t (see below)
  • V8PLUS_TYPE_ANY: nvpair_t **
  • V8PLUS_TYPE_STRNUMBER64: uint64_t *
  • V8PLUS_TYPE_INL_OBJECT: illegal

In most cases, the behaviour is straightforward: the value pointer parameter provides a location into which the C value of the specified argument should be stored. If the entire argument list matches the template, each argument's C value is stored in its respective location. If not, no values are stored, in the return value locations, an exception is set pending, and -1 is returned.

Three data types warrant further explanation: an argument of type V8PLUS_TYPE_INVALID is any argument that may or may not match one of the acceptable types. Its nvpair data type tag is stored and the argument treated as matching. The value is ignored. V8PLUS_TYPE_STRNUMBER64 is used with strings that should be interpreted as 64-bit unsigned integers. If the argument is not a string, or is not parseable as a 64-bit unsigned integer, the argument will be treated as a mismatch. Finally, V8PLUS_TYPE_INL_OBJECT is not supported with v8plus_args(); JavaScript objects in the argument list must be individually inspected as nvlists.

A simple example:

double_t d;
boolean_t b;
char *s;
v8plus_jsfunc_t f;

/*
 * This function requires exactly four arguments: a number, a
 * boolean, a string, and a callback function.  It is not acceptable
 * to pass superfluous arguments to it.
 */
if (v8plus_args(ap, V8PLUS_ARG_F_NOEXTRA,
    V8PLUS_TYPE_NUMBER, &d,
    V8PLUS_TYPE_BOOLEAN, &b,
    V8PLUS_TYPE_STRING, &s,
    V8PLUS_TYPE_JSFUNC, &f,
    V8PLUS_TYPE_NONE) != 0)
	return (NULL);

v8plus_type_t v8plus_typeof(const nvpair_t *pp)

This function simply returns the v8+ data type corresponding to the name/value pair pp. If the value's type does not match the v8+ encoding rules, V8PLUS_TYPE_INVALID is returned. This function cannot fail and does not set pending any exceptions.

Returning Values

Similarly, when returning data across the boundary from C to C++, a pointer to an nvlist must be returned. This object will be decoded in the same manner as described above and returned to the JavaScript caller of your method. Note that booleans, strings, and numbers will be encoded as their primitive types, not objects. If you need to return something containing these object types, you cannot use v8+. Other data types cannot be represented. If you need to return them, you cannot use v8+.

The nvlist being returned must have a single member named: "res", an nvpair containing the result of the call to be returned. The use of "err" to decorate an exception is no longer supported as of v8plus 0.3. You may return a value of any decodable type.

For convenience, you may return v8plus_void() instead of an nvlist, which indicates successful execution of a function that returns nothing.

In addition, the v8plus_obj() routine is available for instantiating JavaScript objects to return.

nvlist_t *v8plus_void(void)

This function clears any pending exception and returns NULL. This is used to indicate to internal v8+ code that the method or function should not return a value.

nvlist_t *v8plus_obj(v8plus_type_t t, ...)

This function creates and populates an nvlist conforming to the encoding rules of v8+ for returning a value or creating an exception. It can be used to create anything from a single encoded value to arbitrarily nested objects. It is essentially the inverse of v8plus_args() above, with a few differences:

  • It cannot be used to encode invalid or illegal data types.
  • It accepts native C values, not pointers to them.
  • Each value must be named.
  • It can be used to encode nested objects inline using V8PLUS_TYPE_INL_OBJECT, followed by type, name, value triples, terminated with V8PLUS_TYPE_NONE.

This function can fail due to out-of-memory conditions, invalid or unsupported data types, or, most commonly, programmer error in casting the arguments to the correct type. It is extremely important that data values, particularly integers, be cast to the appropriate type (double) when passed into this function!

Following is a list of types and the C data types corresponding to their values:

  • V8PLUS_TYPE_NONE: used to terminate the parameter list only
  • V8PLUS_TYPE_STRING: char *
  • V8PLUS_TYPE_NUMBER: double
  • V8PLUS_TYPE_BOOLEAN: boolean_t
  • V8PLUS_TYPE_JSFUNC: v8plus_jsfunc_t
  • V8PLUS_TYPE_OBJECT: nvlist_t *
  • V8PLUS_TYPE_NULL: no parameter
  • V8PLUS_TYPE_UNDEFINED: no parameter
  • V8PLUS_TYPE_ANY: nvpair_t *
  • V8PLUS_TYPE_STRNUMBER64: uint64_t
  • V8PLUS_TYPE_INL_OBJECT: NONE-terminated type/value list

A simple example, in which we return a JavaScript object with two members, one number and one embedded object with a 64-bit integer property. Note that if this function fails, we will return NULL with an exception pending.

int x;
const char *s;

...
return (v8plus_obj(
    V8PLUS_TYPE_INL_OBJECT, "res",
	V8PLUS_TYPE_NUMBER, "value", (double)x,
	V8PLUS_TYPE_INL_OBJECT, "detail",
	    V8PLUS_TYPE_STRNUMBER64, "value64", s,
	    V8PLUS_TYPE_NONE,
	V8PLUS_TYPE_NONE,
    V8PLUS_TYPE_NONE));

The JSON representation of this object would be:

{
	"res": {
		"value": <x>,
		"detail": {
			"value64": "<s>"
		}
	}
}

v8plus_obj_setprops(nvlist_t *lp, v8plus_type_t t, ...)

You can also add or replace the values of properties in an existing nvlist, whether created using nvlist_alloc() directly or via v8plus_obj(). The effect is very similar to nvlist_merge(), where the second list is created on the fly from your argument list. The interpretation of the argument list is the same as for v8plus_obj(), and the two functions are implemented using the same logic.

Exceptions and Errors

Prior to v8plus 0.3.0, the v8plus_errno_t enumerated type was controlled by a consumer-supplied JSON file, allowing the consumer to specify the set of error values. In v8plus 0.3.0 and newer, this type is fixed and contains only a small set of basic errors that can be used with the v8plus_error() routine for compatibility with previous versions. In v8plus 0.3.0 and later, consumers should explicitly throw exceptions instead.

In v8plus 0.3.0 and later, the _v8plus_errno global no longer exists. If your code examined this variable, there are two alternatives:

  • If you were comparing against V8PLUSERR_NOERROR, instead use `v8plus_exception_pending() to determine whether a previously invoked function failed.
  • If you wish to inspect the previous error state, v8plus_pending_exception() will provide an nvlist-encoded representation of the pending exception object.

A survey of consumers indicated that custom error codes, _v8plus_errno, and nontrivial uses of _v8plus_error() did not exist in consumers; therefore this functionality has been removed.

All exceptions are generated and made pending by v8plus_throw_exception() or its wrappers, identified below. Only one exception may be pending at one time, and a call to v8plus_throw_exception() or its wrappers with an exception already pending has no effect. Functions are provided for clearing any pending exceptions, testing for the existence of a pending exception, and obtaining (to inspect or modify) the current pending exception; see API details below.

A pending exception will be ignored and will not be thrown if any of the following occurs prior to your C function (method, static method, or constructor) returning:

  • v8plus_clear_exception() is invoked, or
  • You return v8plus_void(), or
  • Your method or static method routine returns non-NULL, or
  • Your constructor sets its object pointer to a non-NULL value

It is programmer error for a constructor to set its object pointer to NULL (or to not set it at all) and return without an exception pending.

Because a common source of exceptions is out-of-memory conditions, the space used by exceptions is obtained statically and is limited in size. This allows for exceptions to be thrown into V8 reliably, with enough information to debug the original failure even if that failure was, or was caused by, an out of memory condition. V8 may or may not provide a similar mechanism for ensuring that the C++ representation of exceptions is reliable.

Exceptions may be raised in any context; however, raising an exception in a context other than the V8 event thread will not by itself cause any JavaScript exception to be thrown; it is the consumer's responsibility to provide for an exception to be set pending in the event thread if it is to be made visible from JavaScript. Functions used to inspect or alter the state of the pending exception, if any, also work in any context.

nvlist_t *v8plus_error(v8plus_errno_t e, const char *fmt, ...)

This function generates and makes pending a default exception based on the value of e and a message based on the formatted string fmt using the argument list that follows. The format string and arguments are interpreted as by vsnprintf(3c). NULL is returned, suitable for returning directly from a C function that provides a method if no exception decoration is required.

If fmt is NULL, a generic default message is used.

This function is a wrapper for v8plus_throw_exception().

nvlist_t *v8plus_nverr(int err, const char *propname)

This function generates and makes pending an exception based on the system error code err and sets the error message to a non-localised explanation of the problem. The string propname, if non-NULL, is indicated in the message as the name of the nvlist property being manipulated when the error occurred. NULL is returned.

This function is a wrapper for v8plus_throw_exception().

nvlist_t *v8plus_syserr(int err, const char *fmt, ...)

Analogous to v8plus_error(), this function instead generates and sets pending an exception derived from the system error code err. Not all error codes can be mapped; those that are not known are mapped onto an unknown error string. The generated exception will contain additional properties similar to those provided by node.js's ErrnoException() routine. See also v8plus_throw_errno_exception().

This function is a wrapper for v8plus_throw_exception().

nvlist_t *v8plus_throw_exception(const char *type, const char *msg, v8plus_type_t t, ...)

Generate and set pending an exception whose JavaScript type is type, with message msg (or the empty string, if msg is NULL), and optionally additional properties as specified by a series type, name, value triples. These triples have the same syntax as the arguments to v8plus_obj() and are likewise terminated by V8PLUS_TYPE_NONE.

The generated JavaScript exception will be thrown upon return from the current constructor or method, unless v8plus_clear_exception() is invoked first, or v8plus_void() is returned. The exception may be obtained via v8plus_pending_exception() and its presence or absence tested via v8plus_exception_pending().

nvlist_t *v8plus_throw_errno_exception(int err, const char *syscall, const char *msg, const char *path, v8plus_type_t t, ...)

Generate and set pending an exception with type Error and message msg (if msg is NULL, the message will be automatically generated from your system's strerror() value for this error number). The exception will further be decorated with properties indicating the relevant system call and path, if the syscall and path arguments, respectively, are non-NULL, and any additional properties specified as in v8plus_throw_exception().

This function is a wrapper for v8plus_throw_exception().

boolean_t v8plus_exception_pending(void)

This function returns B_TRUE if and only if an exception is pending.

nvlist_t *v8plus_pending_exception(void)

This function returns a pointer to the nvlist-encoded pending exception, if any exists; NULL, otherwise. This object may be inspected and properties added to or removed from it.

void v8plus_clear_exception(void)

Clear the pending exception, if any.

void v8plus_rethrow_pending_exception(void)

Immediately throw the pending exception. This is appropriate only in the context of an asynchronous callback, in which there is no return value; in all other cases, return the exception as the err member of the function's return value. Note that this is slightly different from node::FatalException() in that it is still possible for a JavaScript caller to catch and handle it. If it is absolutely essential that the process terminate immediately, use v8plus_panic() instead.

The main purpose of this facility is to allow re-throwing an exception generated by a JavaScript callback invoked from an asynchronous completion routine. The completion routine has no way to return a value, so this is the only way to propagate the exception out of the native completion routine.

This function may be called only on the main event loop thread.

void v8plus_panic(const char *fmt, ...) __NORETURN

This function indicates a fatal runtime error. The format string fmt and subsequent arguments are interpreted as by vsnprintf(3c) and written to standard error, which is then flushed. abort(3c) or similar is then invoked to terminate the Node.js process in which the addon is running. Use of this function should be limited to those circumstances in which an internal inconsistency has been detected that renders further progress hazardous to user data or impossible.

Asynchrony

There are two main types of asynchrony supported by v8+. The first is the deferred work model (using uv_queue_work() or the deprecated eio_custom() mechanisms) frequently written about and demonstrated by various practitioners around the world. In this model, your method or function takes a callback argument and returns immediately after enqueuing a task to run on one of the threads in the Node.js worker thread pool. That task consists of a C function to be run on the worker thread, which may not use any V8 (or v8+) state, and a function to be run in the main event loop thread when that task has completed. The latter function is normally expected to invoke the caller's original callback. In v8+, this takes the following form:

void *
async_worker(void *cop, void *ctxp)
{
	my_object_t *op = cop;
	my_context_t *cp = ctxp;
	my_result_t *rp = ...;

	/*
	 * In thread pool context -- do not call any of the
	 * following functions:
	 * v8plus_obj_hold()
	 * v8plus_obj_rele_direct()
	 * v8plus_jsfunc_hold()
	 * v8plus_jsfunc_rele_direct()
	 * v8plus_call_direct()
	 * v8plus_method_call_direct()
	 * v8plus_defer()
	 *
	 * If you touch anything inside op, you may need locking to
	 * protect against functions called in the main thread.
	 */
	...

	return (rp);
}

void
async_completion(void *cop, void *ctxp, void *resp)
{
	my_object_t *op = cop;
	my_context_t *cp = ctxp;
	my_result_t *rp = resp;
	nvlist_t *cbap;
	nvlist_t *cbrp;

	...
	cbap = v8plus_obj(
	    V8PLUS_TYPE_WHATEVER, "0", rp->mr_value,
	    V8PLUS_TYPE_NONE);

	if (cbap != NULL) {
		cbrp = v8plus_call(cp->mc_callback, cbap);
		nvlist_free(cbap);
		nvlist_free(cbrp);
	}

	v8plus_jsfunc_rele(cp->mc_callback);
	free(cp);
	free(rp);
}

nvlist_t *
async(void *cop, const nvlist_t *ap)
{
	my_object_t *op = cop;
	v8plus_jsfunc_t cb;
	my_context_t *cp = malloc(sizeof (my_context_t));
	...
	if (v8plus_args(ap, 0, V8PLUS_TYPE_JSFUNC, &cb,
	    V8PLUS_TYPE_NONE) != 0) {
		free(cp);
		return (NULL);
	}

	v8plus_jsfunc_hold(cb);
	cp->mc_callback = cb;
	v8plus_defer(op, cp, async_worker, async_completion);

	return (v8plus_void());
}

This mechanism uses uv_queue_work() and as such will tie up one of the worker threads in the pool for as long as async_worker is running.

The other asynchronous mechanism is the Node.js EventEmitter model. This model requires some assistance from JavaScript code, because v8+ native objects do not inherit from EventEmitter. To make this work, you will need to create a JavaScript object (the object your consumers actually use) that inherits from EventEmitter, hang your native object off this object, and populate the native object with an appropriate method that will cause the JavaScript object to emit events when the native object invokes that method. A simple example might look like this:

var util = require('util');
var binding = require('./native_binding');
var events = require('events');

function
MyObjectWrapper()
{
	var self = this;

	events.EventEmitter.call(this);
	this._native = binding._create.apply(this,
	    Array.prototype.slice.call(arguments));
	this._native._emit = function () {
		var args = Array.prototype.slice.call(arguments);
		self.emit.apply(self, args);
	};
}
util.inherits(MyObjectWrapper, events.EventEmitter);

Then, in C code, you must arrange for libuv to call a C function in the context of the main event loop. The function v8plus_method_call() is safe to call from any thread: depending on the context in which it is invoked, it will either make the call directly or queue the call in the main event loop and block on a reply. Simply arrange to call back into your JavaScript object when you wish to post an event:

nvlist_t *eap;
nvlist_t *erp;
my_object_t *op = ...;
...
eap = v8plus_obj(
    V8PLUS_TYPE_STRING, "0", "my_event",
    ...,
    V8PLUS_TYPE_NONE);

if (eap != NULL) {
	erp = v8plus_method_call(op, "_emit", eap);
	nvlist_free(eap);
	nvlist_free(erp);
}

This example will generate an event named "my_event" and propagate it to listeners registered with the MyObjectWrapper instance. If additional arguments are associated with the event, they may be added to eap and will also be passed along to listeners as arguments to their callbacks.

void v8plus_obj_hold(const void *op)

Places a hold on the V8 representation of the specified C object. This is rarely necessary; v8plus_defer() performs this action for you, but other asynchronous mechanisms may require it. If you are returning from a method call but have stashed a reference to the object somewhere and are not calling v8plus_defer(), you must call this first. Holds and releases must be balanced. Use of the object within a thread after releasing is a bug. This hold includes an implicit event loop hold, as if v8plus_eventloop_hold() was called.

void v8plus_obj_rele(const void *op)

Releases a hold placed by v8plus_obj_hold(). This function may be called safely from any thread; releases from threads other than the main event loop are non-blocking and will occur some time in the future. Releases the implicit event loop hold obtained by v8plus_obj_hold().

void v8plus_jsfunc_hold(v8plus_jsfunc_t f)

Places a hold on the V8 representation of the specified JavaScript function. This is required when returning from a C function that has stashed a reference to the function, typically to use it asynchronously as a callback. All holds must be balanced with a release. Because a single hold is placed on such objects when passed to you in an argument list (and released for you when you return), it is legal to reference and even to invoke such a function without first placing an additional hold on it. This hold includes an implicit event loop hold, as if v8plus_eventloop_hold() was called.

void v8plus_jsfunc_rele(v8plus_jsfunc_t f)

Releases a hold placed by v8plus_jsfunc_hold(). This function may be called safely from any thread; releases from threads other than the main event loop thread are non-blocking and will occur some time in the future. Releases the implicit event loop hold obtained by v8plus_jsfunc_hold().

void v8plus_defer(void *op, void *ctx, worker, completion)

Enqueues work to be performed in the Node.js shared thread pool. The object op and context ctx are passed as arguments to worker executing in a thread from that pool. The same two arguments, along with the worker's return value, are passed to completion executing in the main event loop thread. See example above.

void v8plus_eventloop_hold(void)

Places a hold on the V8 event loop. V8 will terminate when it detects that there is no more work to do. This liveliness check includes things like open sockets or file descriptors, but only if they are tracked by the event loop itself. If you are using multiple threads, some of which may blocking waiting for input (e.g. a message subscription thread) then you will need to prevent V8 from terminating prematurely. This function must be called from within the main event loop thread. Each hold must be balanced with a release. Note that holds on objects or functions obtained via v8plus_obj_hold() or v8plus_jsfunc_hold() will implicitly hold the event loop for you.

void v8plus_eventloop_rele(void)

Release a hold on the V8 event loop. If there are no more pending events or input sources, then V8 will generally terminate the process shortly afterward. This function may be called safely from any thread; releases from threads other than the main event loop thread are non-blocking and will occur some time in the future.

nvlist_t *v8plus_call(v8plus_jsfunc_t f, const nvlist_t *ap)

Calls the JavaScript function referred to by f with encoded arguments ap. The return value is the encoded return value of the function. The argument and return value encoding match the encodings that are used by C functions that provide methods.

As JavaScript functions must be called from the event loop thread, v8plus_call() contains logic to determine whether we are in the correct context or not. If we are running on some other thread we will queue the request and sleep, waiting for the event loop thread to make the call. In the simple case, where we are already in the correct thread, we make the call directly.

Note that when passing JavaScript functions around as callbacks, you must use first use v8plus_jsfunc_hold() from within the main event loop thread. Once finished with the function, you may pass it to v8plus_jsfunc_rele() from any thread to clean up.

nvlist_t *v8plus_method_call(void *op, const char *name, const nvlist_t *ap)

Calls the method named by name in the native object op with encoded argument list ap. The method must exist and must be a JavaScript function. Such functions may be attached by JavaScript code as in the event emitter example above. The effects of using this function to call a native method are undefined.

When called from threads other than the main event loop thread, v8plus_method_call() uses the same queue-and-block logic as described above in v8plus_call().

FAQ

  • Why?

Because C++ is garbage. Writing good software is challenging enough without trying to understand a bunch of implicit side effects or typing templated identifiers that can't fit in 80 columns without falling afoul of the language's ambiguous grammar. Don't get me started.

  • Why not use FFI?

FFI is really cool; it offers us the ability to use C libraries without writing bindings at all. However, it also exposes a lot of C nastiness to JavaScript code, essentially placing the interface boundary in consuming code itself. This pretty much breaks the JavaScript interface model -- for example, you can't really have a function that inspects the types of its arguments -- and requires you to write an additional C library anyway if you want or need to do something natively that's not quite what the C library already does. Of course, one could use it to write "bindings" in JavaScript that actually look like a JavaScript interface, which may end up being the best answer, especially if those are autogenerated from CTF! In short, v8+ and FFI are different approaches to the problem. Use whichever fits your need, and note that they're not mutually exclusive, either.

  • What systems can I use this on?

illumos distributions, or possibly other platforms with a working libnvpair. I'm sorry if your system doesn't have it; it's open source and pretty easy to port.

There is an OSX port; see the ZFS port's implementation. Unfortunately this port lacks the requisite support for floating-point data (DATA_TYPE_DOUBLE) but you could easily add that from the illumos sources.

  • What about node-waf and node-gyp?

Fuck python, fuck WAF, and fuck all the hipster douchebags for whom make is too hard, too old, or "too Unixy". Make is simple, easy to use, and extremely reliable. It was building big, important pieces of software when your parents were young, and it Just Works. If you don't like using make here, you probably don't want to use v8+ either, so just go away. Write your CoffeeScript VM in something else, and gyp-scons-waf-rake your way to an Instagram retirement in Bali with all your hipster douchebag friends. Just don't bother me about it, because I don't care.

  • Why is Node failing in dlopen()?

Most likely, your module has a typo or needs to be linked with a library. Normally, shared objects like Node addons should be linked with -zdefs so that these problems are found at build time, but Node doesn't deliver a mapfile specifying its API so you're left with a bunch of undefined symbols you just have to hope are defined somewhere in your node process's address space. If they aren't, you're boned. LD_DEBUG=all will help you find the missing symbol(s).

As of 0.0.2, v8+ builds a mapfile for your node binary at the time you build your addon. It does not attempt to restrict the visibility of any symbols, so you will not be warned if your addon is using private or deprecated functionality in V8 or Node.js. Your build will, however, fail if you've neglected to link in any required libraries, typo'd a symbol name, etc.

  • Why can't I see my exception's decorative properties in JavaScript?

Be careful when decorating exceptions. There are several built-in hidden properties; if you decorate the exception with a property with the same name, you will change the hidden property's value but it will still be hidden. This almost certainly is not what you want, so you should prefix the decorative property names with something unique to your module to avoid stepping on V8's (or JavaScript's) property namespace.

  • What if the factory model doesn't work for me?

See "License" below. Note also that one can export plain functions as well.

  • Why do I always die with "invalid property type -3621" (or other garbage)?

You are passing an object with the wrong C type to v8plus_obj(). Like all varargs functions, it cannot tell the correct size or type of the objects you have passed it; they must match the preceding type argument or it will not work correctly. In this particular case, you've most likely done something like:

int foo = 0xdead;

v8plus_obj(V8PLUS_TYPE_NUMBER, "foo", foo, V8PLUS_TYPE_NONE);

An 'int' is 4 bytes in size, and the compiler reserves 4 bytes on the stack and sticks the value of foo there. When v8plus_obj goes to read it, it sees that the type is V8PLUS_TYPE_NUMBER, casts the address of the next argument slot to a double *, and dereferences it, then moves the argument list pointer ahead by the size of a double. Unfortunately, a double is usually 8 bytes long, meaning that (a) the value of the property is going to be comprised of the integer-encoded foo appended to the next data type, and (b) the next data type is going to be read from either undefined memory or from part of the address of the name of the next property. To cure this, always make sure that you cast your integral arguments properly when using V8PLUS_TYPE_NUMBER:

v8plus_obj(V8PLUS_TYPE_NUMBER, "foo", (double)foo, V8PLUS_TYPE_NONE);

License

MIT.

Bugs

See https://github.com/joyent/v8plus/issues.

Consumers

This is an incomplete list of native addons known to be using v8+. If your addon uses v8+, please let me know and I will include it here.

v8plus's People

Contributors

bcantrill avatar jclulow avatar khalfella avatar melloc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

v8plus's Issues

Use strerror(3C) on pthread_* errors

While looking through several firewaller core dumps, I found that they had died with the following stack:

> ::stack
libc.so.1`_lwp_kill+0x15(6d4e, 6, 7067, fed71000, fed71000, 8f03a20)
libc.so.1`raise+0x2b(6, 0, fdfceed0, fed71000, 0, 0)
libc.so.1`abort+0x10e(8f03a20, fe84c4c4, fdfcef34, fe845eca, fe85cb30, 98bc138)
0xfe845eff(fe84c4c4, 98bc168, fdfcef68, fe84656e, 1, 4c)
lockfd_binding.node`v8plus_cross_thread_call+0x128(2c, 0, 95428e8, 0, 1, fe84bd89)
lockfd_binding.node`lockfd_thread+0xe8(9376e38, 0, 0, 0)
libc.so.1`_thrp_setup+0x88(fe894240)
libc.so.1`_lwp_start(fe894240, 0, 0, 0, 0, 0)

Looking at the error message, we can see that they failed here:

> fe84c4c4/s
0xfe84c4c4:     could not wait on async call condvar

Unfortunately, I don't know what the value returned from pthread_cond_wait(3C) was, since the pthread functions return their error code and don't set errno. It would be nice if when they failed, we used strerror(3C) and passed the result to v8plus_panic().

function table lookup is spectacularly unnecessary

At present we maintain two tables of functions, one for static methods and one for object methods, and look them up in our proxy/demuxer routines. Instead, @tjfontaine notes that it's possible to attach the method declarations (along with any other metadata we like) to the functions themselves as v8::Externals, then retrieve them later. This is the approach I wanted to use initially but nothing in V8 offered an example or documented explanation for how to do it. Replace this code with that.

casting error in v8plus_subr.cc

When trying to build the example, I get as far as:

/usr/bin/g++ -D_GNU_SOURCE -Wall -Wextra -Werror -DBUILDING_NODE_EXTENSION -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DPIC -isystem ~/local/node/include/node -I. -I.. -g -Wall -Wextra -Werror -fPIC -O2 -c -o v8plus_subr.o ../v8plus_subr.cc
../v8plus_subr.cc: In function v8::Local<v8::Value> sexception(const char*, const nvlist_t*, const char*)': ../v8plus_subr.cc:305: error: ISO C++ forbids casting between pointer-to-function and pointer-to-object *** Error code 1 make: Fatal error: Command failed for targetv8plus_subr.o'

same results for both g++ 3.4.3 (OpenIndiana 151a4) and g++ 3.4.6 (SmartOS 20110802)

Could there be some environment setting that I'm missing?

fails on node 12

fresh clone

$ git clone [email protected]:joyent/v8plus.git
Cloning into 'v8plus'...
cd vremote: Counting objects: 295, done.
remote: Total 295 (delta 0), reused 0 (delta 0), pack-reused 295
Receiving objects: 100% (295/295), 137.01 KiB | 0 bytes/s, done.
Resolving deltas: 100% (185/185), done.
Checking connectivity... done.

install npm deps

$ cd v8plus
v8plus $ npm install
/

fail to compile example

v8plus $ cd examples
v8plus/examples $ make
NODE_PATH=../node_modules /opt/local/bin/node ../generrno.js -h -o v8plus_errno.h ../errno.json
/opt/local/bin/gcc  -DBUILDING_NODE_EXTENSION -DMODULE=example -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DPIC -isystem /opt/local/include/node -I. -I.. -g -Wall -Wextra -Werror -fPIC -O2 -std=c99 -c -o example.o example.c
/opt/onbld/bin/i386/ctfconvert -l v8plus_0.3.0 example.o
/opt/local/bin/gcc -D_GNU_SOURCE -D__EXTENSIONS__ -DBUILDING_NODE_EXTENSION -DMODULE=example -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DPIC -isystem /opt/local/include/node -I. -I.. -g -Wall -Wextra -Werror -fPIC -O2 -std=c99 -c -o v8plus_csup.o ../v8plus_csup.c
../v8plus_csup.c: In function 'v8plus_crossthread_init':
../v8plus_csup.c:333:6: error: passing argument 3 of 'uv_async_init' from incompatible pointer type [-Werror]
In file included from ../v8plus_csup.c:14:0:
/opt/local/include/node/uv.h:753:15: note: expected 'uv_async_cb' but argument is of type 'void (*)(struct uv_async_t *, int)'
cc1: all warnings being treated as errors
../Makefile.v8plus.targ:27: recipe for target 'v8plus_csup.o' failed
make: *** [v8plus_csup.o] Error 1

system

dave - arbiter sunos ~ $ uname -a
SunOS arbiter.rapture.com 5.11 joyent_20150709T171818Z i86pc i386 i86pc Solaris
dave - arbiter sunos ~ $ node -v
v0.12.5
dave - arbiter sunos ~ $ node -pe process.arch
ia32

NOTE: this works on node 10

Write panic messages out into their own buffer

Since panic strings are formatted by vfprintf(3C), trying to reconstruct the message in mdb, while possible, can be a bit annoying since it may involve grabbing multiple values. It would be nice if, in addition to printing the string to stderr, we also wrote the formatted result into a buffer, which could be easily printed out.

I've added a buffer, _v8plus_panic_buf. To test it out, I added an example v8plus_panic() call, and ran the program:

% pfexec mdb /var/cores/core.node.95550
Loading modules: [ libumem.so.1 libc.so.1 libnvpair.so.1 ld.so.1 ]
> _v8plus_panic_buf/s
lockfd_binding.node`_v8plus_panic_buf:
lockfd_binding.node`_v8plus_panic_buf:          test message
> 
``

want object-less v8plus_defer()

There's no real need to have an object associated with the async work. It should be optional so that static methods can use this interface.

only first argument seen from constructor

In a test project using the latest available v8plus on npm I have

src/node_test.c

#include "v8plus_glue.h"

static char *ident = "foo";

static nvlist_t *
node_test_ctor(const nvlist_t *ap, void **cpp)
{
        printf("constructor called\n");
        nvlist_print_json(stdout, (nvlist_t *)ap);
        printf("\n");

        *cpp = ident;

        return v8plus_void();
}

static void
node_test_dtor(void *op)
{
        op = op; // -Werror
}

static nvlist_t *
node_test_foo(void *op, const nvlist_t *ap)
{
        op = op; // -Werror
        ap = ap; // -Werror
        return v8plus_void();
}

/*
 * v8+ boilerplate
 */
const v8plus_c_ctor_f v8plus_ctor = node_test_ctor;
const v8plus_c_dtor_f v8plus_dtor = node_test_dtor;
const char *v8plus_js_factory_name = "_new";
const char *v8plus_js_class_name = "TestBinding";
const v8plus_method_descr_t v8plus_methods[] = {
        {
                md_name: "foo",
                md_c_func: node_test_foo
        }
};
const uint_t v8plus_method_count = sizeof (v8plus_methods) / sizeof (v8plus_methods[0]);

const v8plus_static_descr_t v8plus_static_methods[] = {};
const uint_t v8plus_static_method_count = 0;

and

lib/index.js

var binding = require('./test_binding');
binding._new('foo', 'bar', 'baz');

when I run lib/index.js i see

$ node lib/index.js 
constructor called
{"0":"foo"}

it appears that only the first argument is being processed for some reason

want to be able to create and pass exceptions as arguments

It would be useful to support the model in which exceptions can be constructed as arguments and passed to callbacks for deferred work. Similar to Array handling, this requires special treatment of the object when marshalling it into C++; this handling already exists for pending thrown exceptions.

non-empty C args nvlist when JS constructor called without args

I could be misunderstanding this, but I don't think the argument conversion is working correctly when the JS constructor function is called with no arguments.

Take examples/test.js for example. The call to create() on line 15 is passed no arguments. So, I would expect the test for arg "0" to fail in example_ctor() (examples/sample.c line 63), and the else branch to initialize the object with the default value (0). However, the argument list passed to example_ctor() has one member: "0" with value 'undefined' (I think). This ends up working for the example because the value is set to the same default (0) by the call to example_set_impl() which matches the data type as DATA_TYPE_BOOLEAN.

I think v8plus is getting bad information from v8 regarding the arguments passed to the constructor (or we're expecting the wrong semantics). For a zero-argument case, I would expect v8::Arguments::Length() to return 0. This is not the case however, when v8plus::v8_Arguments_to_nvlist() is called -- args.Length() returns 1.

Other methods (besides the constructor) don't seem to have this problem.

v8plus should allow node exec path to be overridden

It would be neat to be able to override the path to the copy of node executed during the build (to run generrno.js). This would enable us to use a different copy of node to that which is delivered in $PREFIX_NODE, where we get the headers and the linker mapfile and which might not be runnable on the build machine itself.

would like an interface for returning system call errors consistently

Node's C++ "API" has an ErrnoException class that can be used to generate consistent Error objects for errors returned from system calls:
https://github.com/joyent/node/blob/master/src/node.cc#L737

It's typically used by modules like this:

  if (setuid(uid)) {
    return ThrowErrnoException(errno, "setuid");
  }

This creates an "Error" object with useful "syscall", "code", and "errno" properties, and a consistent (if dubious) message string:

> try { process.setuid(0) } catch (ex) { console.error(ex) }
{ [Error: EPERM, Operation not permitted] errno: 1, code: 'EPERM', syscall: 'setuid' }

It would be nice if v8plus exposed a similar function. I did a crude version by hand in illumos_contract:
https://github.com/joyent/node-contract/blob/39c214d9a8a9bc3420258a57ccd37d13c3961242/src/node_contract.c#L827-L834

v8plus should not hold the event loop open forever

The cross-thread calling infrastructure uses uv_async_send() to post into the event loop thread. Under the covers, this is a file descriptor represented by a uv_handle_t. The event loop polls on this file descriptor waiting to be woken, and will (by default) do this forever.

By default we should call uv_unref() on the async handle, and then provide an interface to allow the consumer to control our hold on the event loop.

v8plus doesn't work in node 0.10 and later

v8plus supports 0.6 and 0.8; however, it is broken in 0.10 by at least three changes:

  1. The elimination of "init" as a valid init function alternative to the structure-based NODE_MODULE() approach.
  2. Not having headers. See nodejs/node-v0.x-archive#5112.
  3. Many changes in v8, especially changes made in 0.11.3 that will render v8plus much more broken when node 0.12 comes out.

Since we'd like v8plus to work well on all versions of node 0.6 and later, the time to fix this up is now. This set of changes will also introduce the option to build v8plus as a single shared library, which any number of consumer C modules can depend upon. This will be the model we eventually use for the fully supported C-only module API that @tjfontaine is working on for node -- that is, that the library will simply be a part of node itself.

ACF_NOREPLY results in use after free

I noticed that firewaller was occasionally crashing every several months, with this stack:

> $C
fe25ee98 libc.so.1`_lwp_kill+0x15(2801, 6, e485, fed70000, fed70000, 8f03a20)
fe25eeb8 libc.so.1`raise+0x2b(6, 0, fe25eed0, fed70000, 0, 0)
fe25ef08 libc.so.1`abort+0x10e(8f03a20, fe84c4c4, fe25ef34, fe845eca, fe85cb30, 98ac3e8)
fe25ef28 0xfe845eff(fe84c4c4, 98ac418, fe25ef68, fe84656e, 1, 4c)
fe25ef68 lockfd_binding.node`v8plus_cross_thread_call+0x128(0, 0, 941b9a8, 0, 1, fe84bd89)
fe25efc8 lockfd_binding.node`lockfd_thread+0xe8(9387dd8, 0, 0, 0)
fe25efe8 libc.so.1`_thrp_setup+0x88(fe893a40)
fe25eff8 libc.so.1`_lwp_start(fe893a40, 0, 0, 0, 0, 0)

At first, I believed that this was during the call to v8plus_call that lockfd_thread makes, but eventually, while inspecting the contents of the stack, the return address made me realize that it was actually the v8plus_jsfunc_rele call just a little further down:

> fe25ef68,4/nap
0xfe25ef68:     
0xfe25ef68:     0xfe25efc8      
0xfe25ef6c:     lockfd_binding.node`lockfd_thread+0xe8
0xfe25ef70:     0               
0xfe25ef74:     0               
> lockfd_binding.node`lockfd_thread+0xe8::dis
lockfd_binding.node`lockfd_thread+0xc8: movl   %edi,(%esp)
lockfd_binding.node`lockfd_thread+0xcb: call   +0x4930  <lockfd_binding.node`nvlist_free>
lockfd_binding.node`lockfd_thread+0xd0: movl   0x38(%esi),%eax
lockfd_binding.node`lockfd_thread+0xd3: testl  %eax,%eax
lockfd_binding.node`lockfd_thread+0xd5: jne    +0x11    <lockfd_binding.node`lockfd_thread+0xe8>
lockfd_binding.node`lockfd_thread+0xd7: movl   (%esi),%eax
lockfd_binding.node`lockfd_thread+0xd9: movl   0x4(%esi),%edx
lockfd_binding.node`lockfd_thread+0xdc: movl   %eax,(%esp)
lockfd_binding.node`lockfd_thread+0xdf: movl   %edx,0x4(%esp)
lockfd_binding.node`lockfd_thread+0xe3: call   +0xcb8   <lockfd_binding.node`v8plus_jsfunc_rele>
lockfd_binding.node`lockfd_thread+0xe8: movl   %esi,(%esp)
lockfd_binding.node`lockfd_thread+0xeb: call   -0xadc   <PLT=libumem.so.1`free>
lockfd_binding.node`lockfd_thread+0xf0: addl   $0x4c,%esp
lockfd_binding.node`lockfd_thread+0xf3: xorl   %eax,%eax
lockfd_binding.node`lockfd_thread+0xf5: popl   %ebx
lockfd_binding.node`lockfd_thread+0xf6: popl   %esi
lockfd_binding.node`lockfd_thread+0xf7: popl   %edi
lockfd_binding.node`lockfd_thread+0xf8: popl   %ebp
lockfd_binding.node`lockfd_thread+0xf9: ret    
lockfd_binding.node`lockfd_thread+0xfa: leal   0x0(%esi),%esi
lockfd_binding.node`lockfd_thread+0x100:call   -0xb21   <PLT=libc.so.1`___errno>

v8plus_jsfunc_rele doesn't allocate its v8plus_async_call_t on the stack, but instead creates it with calloc(3C), sets ACF_NOREPLY, and lets v8plus_async_callback free it from inside the libuv event loop. (We should fix the ordering there so that we call nvlist_free first. I did that for the test program below, since otherwise we pass 0xdeadbeef to the function.)

If you call v8plus_jsfunc_rele from a non-libuv worker thread, then it uses v8plus_cross_thread_call, which calls uv_async_send and then looks at the struct's flags field. I modified those lines to the following:

        printf("vac = %p\n", vac);
        v8plus_async_call_flags_t flags = vac->vac_flags;
        uv_async_send(&_v8plus_uv_async);
        sleep(2);
        VERIFY(flags == vac->vac_flags);

And ran a test program:

cody@101901c9-6d66-ea32-fe42-f1fbebd4bf99 ~/src/node-lockfd-v1 : v1.x % UMEM_DEBUG=default,verbose LD_PRELOAD=libumem.so node foo.js
vac = fe012f04
vac = 8efcc58
assertion failed for thread 0xfe5d3240, thread-id 7: flags == vac->vac_flags, file /home/cody/src/node-lockfd-v1/node_modules/v8plus/v8plus_csup.c, line 231
zsh: IOT instruction (core dumped)  UMEM_DEBUG=default,verbose LD_PRELOAD=libumem.so node foo.js

Looking at the core dump:

cody@101901c9-6d66-ea32-fe42-f1fbebd4bf99 ~/src/node-lockfd-v1 : v1.x % pfexec mdb /var/cores/core.node.67060
Loading modules: [ libumem.so.1 libc.so.1 libnvpair.so.1 ld.so.1 ]
> 8efcc58::whatis
8efcc58 is 8efcc50+8, freed from umem_alloc_96:
            ADDR          BUFADDR        TIMESTAMP           THREAD
                            CACHE          LASTLOG         CONTENTS
         8efdf10          8efcc50   5ad99f4046b978                1
                          8d58010                0                0
                 libumem.so.1`umem_cache_free_debug+0x17f
                 libumem.so.1`umem_cache_free+0x40
                 libumem.so.1`umem_free+0xfb
                 libumem.so.1`process_free+0x57
                 libumem.so.1`umem_malloc_free+0x1a
                 _ZN2v88internal25Runtime_NotifyDeoptimizedENS0_9ArgumentsEPNS0_7IsolateE+0x99
                 0xbee0a236
                 0xbee1011c
                 0xbee2e045
                 0xafc29dcb
                 0xafc17389
                 0xafc1710d
                 0xafc16d05
                 0xafc3221e
                 0xbee0fba9

> 8efcc58::print v8plus_async_call_t
{
    vac_type = -0t559038737 (???)
    vac_flags = 0xdeadbeef (ACF_{COMPLETED|NOREPLY}|0xffffffffdeadbeec)
    vac_cop = 0xdeadbeef
    vac_name = 0xdeadbeef
    vac_func = 0xdeadbeefdeadbeef
    vac_lp = 0xdeadbeef
    vac_return = 0xdeadbeef
    vac_cv = {
        __pthread_cond_flags = {
            __pthread_cond_flag = [ 0xef, 0xbe, 0xad, 0xde ]
            __pthread_cond_type = 0xbeef
            __pthread_cond_magic = 0xdead
        }
        __pthread_cond_data = 0xdeadbeefdeadbeef
    }
    vac_mtx = {
        __pthread_mutex_flags = {
            __pthread_mutex_flag1 = 0xbeef
            __pthread_mutex_flag2 = 0xad
            __pthread_mutex_ceiling = 0xde
            __pthread_mutex_type = 0xbeef
            __pthread_mutex_magic = 0xdead
        }
        __pthread_mutex_lock = {
            __pthread_mutex_lock64 = {
                __pthread_mutex_pad = [ 0xef, 0xbe, 0xad, 0xde, 0xef, 0xbe, 0xad, 0xde ]
            }
            __pthread_mutex_lock32 = {
                __pthread_ownerpid = 0xdeadbeef
                __pthread_lockword = 0xdeadbeef
            }
            __pthread_mutex_owner64 = 0xdeadbeefdeadbeef
        }
        __pthread_mutex_data = 0xdeadbeefdeadbeef
    }
    vac_callq_entry = {
        stqe_next = 0xdeadbeef
    }
}

We should determine whether ACF_NOREPLY is set before calling uv_async_send, in order to make sure we don't read the fields once they become invalid.

fix for joyent/v8plus#7 is missing an eventloop hold

Unfortunately, while adding support for v8plus_eventloop_{hold,rele}(), and their implicit use in the v8plus_jsfunc_{hold,rele}() API, I missed the fact that callback handles are created with an existing reference -- that is, a count of one. We must, therefore, also create them with an implicit eventloop hold.

example.c:5:26: fatal error: sys/ccompile.h: No such file or directory

I came to issue here because I can't find the answer in google. could anyone tell me that how to get these lacking library files and install.
Could the examples explicitly list the detailed steps to make v8plus work , I really want to only use C to write the addons without C++.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.