14 Commits
Author SHA1 Message Date
Kenton Varda d8de1b49ad Disambiguate getsockname() call. 2019-11-23 11:19:17 -08:00
Kenton Varda 4df200effa Implement KJ's new whenWriteDisconnected(). 2019-02-09 15:31:44 -08:00
Kenton Varda cbb3ab40fb Fix age-old bug with flag combining.
I guess it never cauased a problem because the result was 1 which just happened to map to TAKE_OWNERSHIP and the other flags are safe to skip (just inefficient)...
2019-02-09 15:29:42 -08:00
Kenton Varda 1abd120c12 Update to C++14 as required by Cap'n Proto 0.7. 2019-01-07 13:40:56 -08:00
Kenton Varda ca30a96c1c Merge pull request #43 from qm3ster/patch-1
update capnp-js link
2018-05-14 15:27:29 -07:00
Mihal Malostanidis d2f4c72d64 update capnp-js link
Reorganization in progress
2018-05-08 00:02:48 +03:00
Kenton Varda 202e13c66f Hack: For Sandstorm build, catch segfaults and print stack traces. 2018-04-15 17:28:00 -07:00
Kenton Varda a3e975d43c Bump version number and publish again because apparently npm went and published a 13MB scratch folder I had in the directory last time, wtf. 2018-02-09 08:43:47 -08:00
Kenton Varda f1e9306cd6 Release 0.3.0 which supports node 8+. 2018-02-04 14:17:42 -08:00
Kenton Varda c6fe3c2624 Revert "Add temporary code to count local capability types and support dumping them to stderr periodically."
This reverts commit 74958750f1.

This is no longer needed.
2018-02-04 14:16:01 -08:00
Kenton Varda 7fb9018515 Correctly handle pipelining on a Capability. 2018-02-04 14:12:01 -08:00
Kenton Varda 742d6b5b79 Fix obvious, glaring memory leak. I dunno what to say. 2018-01-06 12:17:56 -08:00
Kenton Varda ec1721f298 Node 8: Properly support capabilities defined using ES6 classes.
I'm not sure why this worked with Node 4 / Meteor 1.5, but behavior changed in Node 8 / Meteor 1.6.
2017-11-18 16:50:37 -08:00
Kenton Varda 5aeffa328a Node 8: Transition from V8 hidden values to V8 private values. 2017-11-18 16:49:55 -08:00
6 changed files with 130 additions and 85 deletions

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ messages from complete Javascript objects. This actually makes the library
somewhat nicer syntactically than it would be otherwise, but it is not fast. somewhat nicer syntactically than it would be otherwise, but it is not fast.
A pure-Javascript implementation would likely be much faster. See A pure-Javascript implementation would likely be much faster. See
[capnp-js](https://github.com/capnp-js/serialization) for such an [capnp-js](https://github.com/capnp-js) for such an
implementation. Unfortunately, that implementation is incomplete and does not implementation. Unfortunately, that implementation is incomplete and does not
support [RPC](https://github.com/capnp-js/rpc) just yet. Hence, this hack was support [RPC](https://github.com/capnp-js/rpc) just yet. Hence, this hack was
created for short-term use. created for short-term use.
+2 -2
View File
@@ -4,12 +4,12 @@
'target_name': 'capnp', 'target_name': 'capnp',
'sources': ['src/node-capnp/capnp.cc'], 'sources': ['src/node-capnp/capnp.cc'],
'libraries': ['-lkj', '-lkj-async', '-lcapnp', '-lcapnpc', '-lcapnp-rpc'], 'libraries': ['-lkj', '-lkj-async', '-lcapnp', '-lcapnpc', '-lcapnp-rpc'],
'cflags_cc': ['-std=c++11'], 'cflags_cc': ['-std=c++14'],
'cflags_cc!': ['-fno-rtti', '-fno-exceptions'], 'cflags_cc!': ['-fno-rtti', '-fno-exceptions'],
'conditions': [ 'conditions': [
[ 'OS=="mac"', { [ 'OS=="mac"', {
'xcode_settings': { 'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS' : ['-std=c++11','-stdlib=libc++'], 'OTHER_CPLUSPLUSFLAGS' : ['-std=c++14','-stdlib=libc++'],
'OTHER_LDFLAGS': ['-stdlib=libc++'], 'OTHER_LDFLAGS': ['-stdlib=libc++'],
'GCC_ENABLE_CPP_RTTI': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "capnp", "name": "capnp",
"version": "0.2.0", "version": "0.3.1",
"description": "A wrapper for the C++ Cap'n Proto library", "description": "A wrapper for the C++ Cap'n Proto library",
"keywords": [ "capnproto", "serialization" ], "keywords": [ "capnproto", "serialization" ],
"homepage": "http://capnproto.org", "homepage": "http://capnproto.org",
@@ -26,7 +26,7 @@
"url": "git://github.com/kentonv/node-capnp.git" "url": "git://github.com/kentonv/node-capnp.git"
}, },
"engines": { "engines": {
"node": ">=0.12" "node": ">=8.0"
}, },
"license": "MIT" "license": "MIT"
} }
+38 -21
View File
@@ -263,31 +263,48 @@ child.stdio[1].once("readable", function() {
closed: false closed: false
}; };
var localCap = new capnp.Capability(pow, Calculator.Function); function usePow(pow) {
assert.equal(9, wait(localCap.call([3, 2])).value); var localCap = new capnp.Capability(pow, Calculator.Function);
assert(!pow.closed); assert.equal(9, wait(localCap.call([3, 2])).value);
localCap.close(); assert(!pow.closed);
assert(pow.closed); localCap.close();
assert(pow.closed);
var promise = calc.evaluate( var promise = calc.evaluate(
{call: {"function": subtract, params: [ {call: {"function": subtract, params: [
{call: {"function": add, params: [ {call: {"function": add, params: [
{literal: 123}, {literal: 456}]}}, {literal: 123}, {literal: 456}]}},
{literal: 321}]}}); {literal: 321}]}});
var value = promise.value; var value = promise.value;
assert.equal(258, wait(value.read()).value); assert.equal(258, wait(value.read()).value);
value.close(); value.close();
pow.closed = false; pow.closed = false;
value = calc.evaluate( value = calc.evaluate(
{call: {"function": pow, params: [{literal: 2}, {literal: 4}]}}).value; {call: {"function": pow, params: [{literal: 2}, {literal: 4}]}}).value;
assert.equal(16, wait(value.read()).value); assert.equal(16, wait(value.read()).value);
// Wait a moment to give the capability a chance to be dropped. // Wait a moment to give the capability a chance to be dropped.
wait(new Promise((resolve, reject) => setTimeout(resolve, 10))); wait(new Promise((resolve, reject) => setTimeout(resolve, 10)));
assert(pow.closed); assert(pow.closed);
value.close(); value.close();
}
usePow(pow);
// Try using a class.
class PowClass {
constructor() {
this.closed = false
}
call(params) {
return Math.pow(params[0], params[1]);
}
close() {
this.closed = true;
}
};
usePow(new PowClass());
// Try wrapping a promise as a capability -- calls are queued until resolution. // Try wrapping a promise as a capability -- calls are queued until resolution.
var resolvePromisedCalc; var resolvePromisedCalc;
+74 -48
View File
@@ -47,7 +47,6 @@
#include <set> #include <set>
#include <stdlib.h> #include <stdlib.h>
#include <sys/uio.h> #include <sys/uio.h>
#include <algorithm>
#include <typeinfo> #include <typeinfo>
#include <typeindex> #include <typeindex>
@@ -237,7 +236,7 @@ static int applyFlags(int fd, uint flags) {
static constexpr uint NEW_FD_FLAGS = static constexpr uint NEW_FD_FLAGS =
#if __linux__ #if __linux__
kj::LowLevelAsyncIoProvider::ALREADY_CLOEXEC || kj::LowLevelAsyncIoProvider::ALREADY_NONBLOCK || kj::LowLevelAsyncIoProvider::ALREADY_CLOEXEC | kj::LowLevelAsyncIoProvider::ALREADY_NONBLOCK |
#endif #endif
kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP; kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP;
// We always try to open FDs with CLOEXEC and NONBLOCK already set on Linux, but on other platforms // We always try to open FDs with CLOEXEC and NONBLOCK already set on Linux, but on other platforms
@@ -410,6 +409,13 @@ public:
KJ_SYSCALL(shutdown(fd, SHUT_WR)); KJ_SYSCALL(shutdown(fd, SHUT_WR));
} }
#if CAPNP_VERSION >= 8000
kj::Promise<void> whenWriteDisconnected() override {
// TODO(someday): Implement using UV_DISCONNECT?
return kj::NEVER_DONE;
}
#endif
private: private:
kj::Promise<size_t> tryReadInternal(void* buffer, size_t minBytes, size_t maxBytes, kj::Promise<size_t> tryReadInternal(void* buffer, size_t minBytes, size_t maxBytes,
size_t alreadyRead) { size_t alreadyRead) {
@@ -564,7 +570,7 @@ public:
struct sockaddr_in6 inet6; struct sockaddr_in6 inet6;
} addr; } addr;
addrlen = sizeof(addr); addrlen = sizeof(addr);
KJ_SYSCALL(getsockname(fd, &addr.generic, &addrlen)); KJ_SYSCALL(::getsockname(fd, &addr.generic, &addrlen));
switch (addr.generic.sa_family) { switch (addr.generic.sa_family) {
case AF_INET: return ntohs(addr.inet4.sin_port); case AF_INET: return ntohs(addr.inet4.sin_port);
case AF_INET6: return ntohs(addr.inet6.sin6_port); case AF_INET6: return ntohs(addr.inet6.sin6_port);
@@ -866,6 +872,17 @@ class Wrapper {
handle.SetWeak(this, &deleteCallback, v8::WeakCallbackType::kParameter); handle.SetWeak(this, &deleteCallback, v8::WeakCallbackType::kParameter);
} }
~WrappedObject() {
// Since destructors might perform non-trivial operations, including calling back into
// JavaScript, delay the destructor until the next turn of the event loop.
T* ptrCopy = ptr;
kj::evalLater([ptrCopy]() {
delete ptrCopy;
}).detach([](kj::Exception&& exception) {
KJ_LOG(ERROR, exception);
});
}
static void deleteCallback(const v8::WeakCallbackInfo<WrappedObject>& data) { static void deleteCallback(const v8::WeakCallbackInfo<WrappedObject>& data) {
delete reinterpret_cast<WrappedObject*>(data.GetParameter()); delete reinterpret_cast<WrappedObject*>(data.GetParameter());
} }
@@ -892,11 +909,14 @@ public:
if (getFunctionTemplate<T>()->HasInstance(obj)) { if (getFunctionTemplate<T>()->HasInstance(obj)) {
return *reinterpret_cast<WrappedObject<T>*>(obj->GetAlignedPointerFromInternalField(0))->ptr; return *reinterpret_cast<WrappedObject<T>*>(obj->GetAlignedPointerFromInternalField(0))->ptr;
} else { } else {
v8::Handle<v8::Value> native = obj->GetHiddenValue(newSymbol("capnp::native")); v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (native.IsEmpty() || native->IsUndefined()) { v8::Handle<v8::Value> native;
return nullptr; if (obj->GetPrivate(isolate->GetCurrentContext(),
} else { v8::Private::ForApi(isolate, newSymbol("capnp::native"))).ToLocal(&native) &&
!native->IsUndefined()) {
return tryUnwrap<T>(native); return tryUnwrap<T>(native);
} else {
return nullptr;
} }
} }
} }
@@ -1013,7 +1033,11 @@ void setNative(const v8::FunctionCallbackInfo<v8::Value>& args) {
// passed to fromJs(). // passed to fromJs().
if (args[0]->IsObject()) { if (args[0]->IsObject()) {
v8::Object::Cast(*args[0])->SetHiddenValue(newSymbol("capnp::native"), args[1]); v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Object::Cast(*args[0])->SetPrivate(
isolate->GetCurrentContext(),
v8::Private::ForApi(isolate, newSymbol("capnp::native")),
args[1]);
} }
} }
@@ -1876,7 +1900,11 @@ void fromBytes(const v8::FunctionCallbackInfo<v8::Value>& args) {
} }
} }
wrapper->SetHiddenValue(newSymbol("buffer"), bufferHandle); v8::Isolate* isolate = v8::Isolate::GetCurrent();
wrapper->SetPrivate(
isolate->GetCurrentContext(),
v8::Private::ForApi(isolate, newSymbol("capnp::buffer")),
bufferHandle);
return wrapper; return wrapper;
}); });
} }
@@ -2219,11 +2247,18 @@ void pipelineToJs(CapnpContext& context, capnp::DynamicStruct::Pipeline&& pipeli
v8::Handle<v8::Object> fieldValue; v8::Handle<v8::Object> fieldValue;
switch (proto.which()) { switch (proto.which()) {
case capnp::schema::Field::SLOT: case capnp::schema::Field::SLOT: {
switch (proto.getSlot().getType().which()) { auto type = field.getType();
switch (type.which()) {
case capnp::schema::Type::STRUCT: case capnp::schema::Type::STRUCT:
fieldValue = pipelineStructFieldToJs(context, pipeline, field, capConstructor); fieldValue = pipelineStructFieldToJs(context, pipeline, field, capConstructor);
break; break;
case capnp::schema::Type::ANY_POINTER:
if (type.whichAnyPointerKind() !=
capnp::schema::Type::AnyPointer::Unconstrained::CAPABILITY) {
continue;
}
// fallthrough
case capnp::schema::Type::INTERFACE: { case capnp::schema::Type::INTERFACE: {
auto cap = pipeline.get(field).releaseAs<capnp::DynamicCapability>(); auto cap = pipeline.get(field).releaseAs<capnp::DynamicCapability>();
capnp::Schema capSchema = cap.getSchema(); capnp::Schema capSchema = cap.getSchema();
@@ -2240,6 +2275,7 @@ void pipelineToJs(CapnpContext& context, capnp::DynamicStruct::Pipeline&& pipeli
continue; continue;
} }
break; break;
}
case capnp::schema::Field::GROUP: case capnp::schema::Field::GROUP:
fieldValue = pipelineStructFieldToJs(context, pipeline, field, capConstructor); fieldValue = pipelineStructFieldToJs(context, pipeline, field, capConstructor);
@@ -2339,51 +2375,17 @@ void cancel(const v8::FunctionCallbackInfo<v8::Value>& args) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Local caps // Local caps
struct TypeHash {
inline size_t operator()(const capnp::Type& t) const {
return t.hashCode();
}
};
std::unordered_map<capnp::Type, uint, TypeHash> localCapTypeCounts;
void dumpLocalCapTypeCounts(const v8::FunctionCallbackInfo<v8::Value>& args) {
liftKj(args, [&]() -> void {
typedef std::pair<capnp::Type, uint> E;
kj::Array<E> array = KJ_MAP(e, localCapTypeCounts) { return E(e); };
std::sort(array.begin(), array.end(),
[](const E& a, const E& b) { return a.second > b.second; });
auto lines = KJ_MAP(e, array) {
return kj::str('\n', e.second, " ", e.first.asInterface().getProto().getDisplayName());
};
auto typeStats = kj::strArray(lines, "");
KJ_LOG(WARNING, "dumping types of local capabilities", typeStats);
});
}
class LocalCap final: public capnp::DynamicCapability::Server { class LocalCap final: public capnp::DynamicCapability::Server {
public: public:
LocalCap(capnp::InterfaceSchema schema, v8::Handle<v8::Object> object, LocalCap(capnp::InterfaceSchema schema, v8::Handle<v8::Object> object,
CapnpContext& capnpContext, v8::Handle<v8::Value> capnpContextHandle) CapnpContext& capnpContext, v8::Handle<v8::Value> capnpContextHandle)
: capnp::DynamicCapability::Server(schema), : capnp::DynamicCapability::Server(schema),
object(object), capnpContext(capnpContext), capnpContextHandle(capnpContextHandle) { object(object), capnpContext(capnpContext), capnpContextHandle(capnpContextHandle) {}
localCapTypeCounts[schema]++;
}
~LocalCap() { ~LocalCap() {
// Call the object's close() method if it has one, so that it can react to the handle being // Call the object's close() method if it has one, so that it can react to the handle being
// dropped. // dropped.
auto iter = localCapTypeCounts.find(getSchema());
if (iter == localCapTypeCounts.end()) {
KJ_LOG(ERROR, "inconsistent type counts");
} else if (--iter->second == 0) {
localCapTypeCounts.erase(iter);
}
v8::HandleScope scope(v8::Isolate::GetCurrent()); v8::HandleScope scope(v8::Isolate::GetCurrent());
auto jsMethod = object->Get(newSymbol("close")); auto jsMethod = object->Get(newSymbol("close"));
@@ -2796,6 +2798,31 @@ void chacha20(const v8::FunctionCallbackInfo<v8::Value>& args) {
}); });
} }
namespace {
void crashHandler(int signo, siginfo_t* info, void* context) {
void* traceSpace[32];
// ignoreCount = 2 to ignore crashHandler() and signal trampoline.
auto trace = kj::getStackTrace(traceSpace, 2);
auto message = kj::str("*** Received signal #", signo, ": ", strsignal(signo),
"\nstack: ", stringifyStackTraceAddresses(trace), '\n');
kj::FdOutputStream(STDERR_FILENO).write(message.begin(), message.size());
_exit(1);
}
} // namespace
void catchSegfaults() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND;
action.sa_sigaction = &crashHandler;
KJ_SYSCALL(sigaction(SIGSEGV, &action, nullptr));
}
#endif // SANDSTORM_BUILD #endif // SANDSTORM_BUILD
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -2848,9 +2875,8 @@ void init(v8::Handle<v8::Object> exports) {
#ifdef SANDSTORM_BUILD #ifdef SANDSTORM_BUILD
mapFunction("matchPowerboxQuery", matchPowerboxQuery); mapFunction("matchPowerboxQuery", matchPowerboxQuery);
mapFunction("chacha20", chacha20); mapFunction("chacha20", chacha20);
catchSegfaults();
#endif #endif
mapFunction("dumpLocalCapTypeCounts", dumpLocalCapTypeCounts);
} }
} // namespace } // namespace
+13 -11
View File
@@ -192,16 +192,20 @@ function wrapLocalMethod(self, method) {
} }
function LocalCapWrapper(obj) { function LocalCapWrapper(obj) {
for (var name in obj) { var type = obj;
if (name === "close") { while (type && type !== Object.prototype) {
// Not an RPC method. Called when the capability has no clients. Object.getOwnPropertyNames(type).forEach(name => {
this.close = obj.close.bind(obj); if (name === "close") {
} else { // Not an RPC method. Called when the capability has no clients.
var method = obj[name]; this.close = obj.close.bind(obj);
if (typeof method === "function") { } else {
this[name] = wrapLocalMethod(obj, method); var method = obj[name];
if (typeof method === "function") {
this[name] = wrapLocalMethod(obj, method);
}
} }
} })
type = Object.getPrototypeOf(type);
} }
} }
@@ -298,5 +302,3 @@ exports.bytesToPreorder = function(schema, buf, options) {
if (v8capnp.matchPowerboxQuery) exports.matchPowerboxQuery = v8capnp.matchPowerboxQuery; if (v8capnp.matchPowerboxQuery) exports.matchPowerboxQuery = v8capnp.matchPowerboxQuery;
if (v8capnp.chacha20) exports.chacha20 = v8capnp.chacha20; if (v8capnp.chacha20) exports.chacha20 = v8capnp.chacha20;
exports.dumpLocalCapTypeCounts = v8capnp.dumpLocalCapTypeCounts;