Automatically generated RPC server for C functions to do CPU-intensive work in (out of) Node.
npm install cervo
(General ideas right now, still exploring implementation)
Write a set of C functions that will be your RPC interface functions, wrapped in a preprocessor command. At the beginning of the file should be a #include<cervo>
. At the end of the file, call a preprocessor command STARTRPC
, this will generate the main
function and all supporting functions to establish an RPC server. DO NOT COMPILE THIS FILE.
In your Node app, all you need to do is:
var cervo = require('cervo');
var rpcServer = cervo('./rpcServer.c');
rpcServer.start(function(err) {
if(err) {
console.log(err);
process.exit(1);
}
rpcServer.foo(42, function(err, result) {
...
});
});
Cervo will compile the C file with cc
(clang, gcc, etc) from your $PATH
(maybe allow this to be configurable?) if it doesn't already see the compiled results in its cache (a temp directory). Afterwards it will spawn the process and initiate RPC protocol over stdin/out, calling a hardwired methodDef
RPC call that gives it the listing of all RPC methods that can be called (generated by the preprocessor wrapping the RPC interface functions), and auto-creates client functions to use them.
Since it only compiles once, if you need a pool of CPU-intensive workers, simply do something like:
var cervo = require('cervo');
var async = require('async');
var cpus = require('os').cpus().length;
var workerPool = [];
for(var i = 0; i < cpus; i++) {
workerPool.push(cervo('./rpcServer.c'));
}
async.each(workerPool, function(worker, next) {
worker.start(next);
}, function(err) {
if(err) {
console.log(err);
process.exit(1);
}
...
});
A few reasons:
- They're a barrier to entry because they require you to learn how V8 internally represents Javascript types and there's some magic involved in crossing back and forth between JS and Native.
- They're C++, not C, so you have to recompile if you change versions of node. Annoying for devs when testing. Probably not an annoyance for just users of the module, though.
- They're in the same thread as the Node event loop, so if you don't want the JS and Native code to fight for CPU, you'll either have to spawn separate Node workers anyways and deal with IPC, or you'll have to dig into threading, which will quickly make you tied to a single platform, or have you pulling your hair out as you
#ifdef
your way to cross-platform threading.
More broadly, you're probably using Node.js because you want to avoid the compiling step in testing new code, or defining hard types early on in your exploration of the API design, or having hard types at all if your software is mostly glue between other systems that are also evolving, or you have a hard time not leaking memory like a sieve without a garbage collector, etc.
And now you have a hot loop in your code that you've already gone through the steps of making sure is a pure function that's defined just once, is given effectively static types so V8 can optimize it as well as it can, and you've confirmed the optimization isn't disappearing by running node with --trace_opt
and --trace_deopt
. You can't think of any way to rearchitect the code to avoid the hot loop in the first place, so you have to go native, but wow is Node's native interface a pile of crap!
Perhaps I'll write a similar library in the future that lets you easily wrap your native C code with standard types into Node's native module format and does the threading for you, but that'll be a lot of work (and I could reuse a lot of cervo for that purpose at that time).
Copyright (C) 2013 by David Ellis
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.