Fix object lifetime and memory management problems in server code

This commit is contained in:
murgatroid99 2018-04-02 14:35:46 -07:00
parent e9a7f5ae07
commit 0210f160ed
5 changed files with 110 additions and 85 deletions

View File

@ -21,6 +21,7 @@
#include "call.h" #include "call.h"
#include "call_credentials.h" #include "call_credentials.h"
#include "channel_credentials.h" #include "channel_credentials.h"
#include "util.h"
#include "grpc/grpc.h" #include "grpc/grpc.h"
#include "grpc/grpc_security.h" #include "grpc/grpc_security.h"
#include "grpc/support/log.h" #include "grpc/support/log.h"
@ -120,33 +121,35 @@ NAN_METHOD(ChannelCredentials::New) {
} }
NAN_METHOD(ChannelCredentials::CreateSsl) { NAN_METHOD(ChannelCredentials::CreateSsl) {
char *root_certs = NULL; StringOrNull root_certs;
grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL}; StringOrNull private_key;
StringOrNull cert_chain;
if (::node::Buffer::HasInstance(info[0])) { if (::node::Buffer::HasInstance(info[0])) {
root_certs = ::node::Buffer::Data(info[0]); root_certs.assign(info[0]);
} else if (!(info[0]->IsNull() || info[0]->IsUndefined())) { } else if (!(info[0]->IsNull() || info[0]->IsUndefined())) {
return Nan::ThrowTypeError("createSsl's first argument must be a Buffer"); return Nan::ThrowTypeError("createSsl's first argument must be a Buffer");
} }
if (::node::Buffer::HasInstance(info[1])) { if (::node::Buffer::HasInstance(info[1])) {
key_cert_pair.private_key = ::node::Buffer::Data(info[1]); private_key.assign(info[1]);
} else if (!(info[1]->IsNull() || info[1]->IsUndefined())) { } else if (!(info[1]->IsNull() || info[1]->IsUndefined())) {
return Nan::ThrowTypeError( return Nan::ThrowTypeError(
"createSSl's second argument must be a Buffer if provided"); "createSSl's second argument must be a Buffer if provided");
} }
if (::node::Buffer::HasInstance(info[2])) { if (::node::Buffer::HasInstance(info[2])) {
key_cert_pair.cert_chain = ::node::Buffer::Data(info[2]); cert_chain.assign(info[2]);
} else if (!(info[2]->IsNull() || info[2]->IsUndefined())) { } else if (!(info[2]->IsNull() || info[2]->IsUndefined())) {
return Nan::ThrowTypeError( return Nan::ThrowTypeError(
"createSSl's third argument must be a Buffer if provided"); "createSSl's third argument must be a Buffer if provided");
} }
if ((key_cert_pair.private_key == NULL) != grpc_ssl_pem_key_cert_pair key_cert_pair = {private_key.get(),
(key_cert_pair.cert_chain == NULL)) { cert_chain.get()};
if (private_key.isAssigned() != cert_chain.isAssigned()) {
return Nan::ThrowError( return Nan::ThrowError(
"createSsl's second and third arguments must be" "createSsl's second and third arguments must be"
" provided or omitted together"); " provided or omitted together");
} }
grpc_channel_credentials *creds = grpc_ssl_credentials_create( grpc_channel_credentials *creds = grpc_ssl_credentials_create(
root_certs, key_cert_pair.private_key == NULL ? NULL : &key_cert_pair, root_certs.get(), private_key.isAssigned() ? &key_cert_pair : NULL,
NULL); NULL);
if (creds == NULL) { if (creds == NULL) {
info.GetReturnValue().SetNull(); info.GetReturnValue().SetNull();

View File

@ -62,28 +62,31 @@ using v8::Value;
Nan::Callback *Server::constructor; Nan::Callback *Server::constructor;
Persistent<FunctionTemplate> Server::fun_tpl; Persistent<FunctionTemplate> Server::fun_tpl;
static Callback *shutdown_callback = NULL; static Persistent<Function> shutdown_cb;
class ServerShutdownOp : public Op { class ServerShutdownOp : public Op {
public: public:
ServerShutdownOp(grpc_server *server) : server(server) {} ServerShutdownOp(Server *server) : server(server) {}
~ServerShutdownOp() {} ~ServerShutdownOp() {}
Local<Value> GetNodeValue() const { return Nan::Null(); } Local<Value> GetNodeValue() const {
EscapableHandleScope scope;
return scope.Escape(server->handle());
}
bool ParseOp(Local<Value> value, grpc_op *out) { return true; } bool ParseOp(Local<Value> value, grpc_op *out) { return true; }
bool IsFinalOp() { return false; } bool IsFinalOp() { return false; }
void OnComplete(bool success) { void OnComplete(bool success) {
/* Because cancel_all_calls was called, we assume that shutdown_and_notify if (success) {
completes successfully */ server->FinishShutdown();
grpc_server_destroy(server); }
} }
grpc_server *server; Server *server;
protected: protected:
std::string GetTypeString() const { return "shutdown"; } std::string GetTypeString() const { return "try_shutdown"; }
}; };
class NewCallOp : public Op { class NewCallOp : public Op {
@ -131,35 +134,17 @@ class NewCallOp : public Op {
std::string GetTypeString() const { return "new_call"; } std::string GetTypeString() const { return "new_call"; }
}; };
class TryShutdownOp : public Op { NAN_METHOD(ShutdownCallback) {
public: HandleScope scope;
TryShutdownOp(Server *server, Local<Value> server_value) : server(server) { if (!info[0]->IsNull()) {
server_persist.Reset(server_value); return Nan::ThrowError("forceShutdown failed somehow");
}
Local<Value> GetNodeValue() const {
EscapableHandleScope scope;
return scope.Escape(Nan::New(server_persist));
}
bool ParseOp(Local<Value> value, grpc_op *out) { return true; }
bool IsFinalOp() { return false; }
void OnComplete(bool success) {
if (success) {
server->DestroyWrappedServer();
}
} }
}
protected: Server::Server(grpc_server *server) :
std::string GetTypeString() const { return "try_shutdown"; } wrapped_server(server), is_shutdown(false) {}
private: Server::~Server() { grpc_server_destroy(this->wrapped_server); }
Server *server;
Nan::Persistent<v8::Value, Nan::CopyablePersistentTraits<v8::Value>>
server_persist;
};
Server::Server(grpc_server *server) : wrapped_server(server) {}
Server::~Server() { this->ShutdownServer(); }
void Server::Init(Local<Object> exports) { void Server::Init(Local<Object> exports) {
HandleScope scope; HandleScope scope;
@ -175,6 +160,9 @@ void Server::Init(Local<Object> exports) {
Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked(); Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
Nan::Set(exports, Nan::New("Server").ToLocalChecked(), ctr); Nan::Set(exports, Nan::New("Server").ToLocalChecked(), ctr);
constructor = new Callback(ctr); constructor = new Callback(ctr);
Local<FunctionTemplate> shutdown_tpl =
Nan::New<FunctionTemplate>(ShutdownCallback);
shutdown_cb.Reset(Nan::GetFunction(shutdown_tpl).ToLocalChecked());
} }
bool Server::HasInstance(Local<Value> val) { bool Server::HasInstance(Local<Value> val) {
@ -182,40 +170,25 @@ bool Server::HasInstance(Local<Value> val) {
return Nan::New(fun_tpl)->HasInstance(val); return Nan::New(fun_tpl)->HasInstance(val);
} }
void Server::DestroyWrappedServer() { void Server::FinishShutdown() {
if (this->wrapped_server != NULL) { is_shutdown = true;
grpc_server_destroy(this->wrapped_server); running_self_ref.Reset();
this->wrapped_server = NULL;
}
}
NAN_METHOD(ServerShutdownCallback) {
if (!info[0]->IsNull()) {
return Nan::ThrowError("forceShutdown failed somehow");
}
} }
void Server::ShutdownServer() { void Server::ShutdownServer() {
Nan::HandleScope scope; Nan::HandleScope scope;
if (this->wrapped_server != NULL) { if (!this->is_shutdown) {
if (shutdown_callback == NULL) { Callback *shutdown_callback =
Local<FunctionTemplate> callback_tpl = new Callback(Nan::New(shutdown_cb));
Nan::New<FunctionTemplate>(ServerShutdownCallback); ServerShutdownOp *op = new ServerShutdownOp(this);
shutdown_callback =
new Callback(Nan::GetFunction(callback_tpl).ToLocalChecked());
}
ServerShutdownOp *op = new ServerShutdownOp(this->wrapped_server);
unique_ptr<OpVec> ops(new OpVec()); unique_ptr<OpVec> ops(new OpVec());
ops->push_back(unique_ptr<Op>(op)); ops->push_back(unique_ptr<Op>(op));
grpc_server_shutdown_and_notify( grpc_server_shutdown_and_notify(
this->wrapped_server, GetCompletionQueue(), this->wrapped_server, GetCompletionQueue(),
new struct tag(new Callback(**shutdown_callback), ops.release(), NULL, new struct tag(shutdown_callback, ops.release(), NULL, Nan::Null()));
Nan::Null()));
grpc_server_cancel_all_calls(this->wrapped_server); grpc_server_cancel_all_calls(this->wrapped_server);
CompletionQueueNext(); CompletionQueueNext();
this->wrapped_server = NULL;
} }
} }
@ -304,6 +277,7 @@ NAN_METHOD(Server::Start) {
return Nan::ThrowTypeError("start can only be called on a Server"); return Nan::ThrowTypeError("start can only be called on a Server");
} }
Server *server = ObjectWrap::Unwrap<Server>(info.This()); Server *server = ObjectWrap::Unwrap<Server>(info.This());
server->running_self_ref.Reset(info.This());
grpc_server_start(server->wrapped_server); grpc_server_start(server->wrapped_server);
} }
@ -316,13 +290,7 @@ NAN_METHOD(Server::TryShutdown) {
return Nan::ThrowError("tryShutdown's argument must be a callback"); return Nan::ThrowError("tryShutdown's argument must be a callback");
} }
Server *server = ObjectWrap::Unwrap<Server>(info.This()); Server *server = ObjectWrap::Unwrap<Server>(info.This());
if (server->wrapped_server == NULL) { ServerShutdownOp *op = new ServerShutdownOp(server);
// Server is already shut down. Call callback immediately.
Nan::Callback callback(info[0].As<Function>());
callback.Call(0, {});
return;
}
TryShutdownOp *op = new TryShutdownOp(server, info.This());
unique_ptr<OpVec> ops(new OpVec()); unique_ptr<OpVec> ops(new OpVec());
ops->push_back(unique_ptr<Op>(op)); ops->push_back(unique_ptr<Op>(op));
grpc_server_shutdown_and_notify( grpc_server_shutdown_and_notify(

View File

@ -38,7 +38,7 @@ class Server : public Nan::ObjectWrap {
JavaScript constructor */ JavaScript constructor */
static bool HasInstance(v8::Local<v8::Value> val); static bool HasInstance(v8::Local<v8::Value> val);
void DestroyWrappedServer(); void FinishShutdown();
private: private:
explicit Server(grpc_server *server); explicit Server(grpc_server *server);
@ -58,9 +58,11 @@ class Server : public Nan::ObjectWrap {
static NAN_METHOD(ForceShutdown); static NAN_METHOD(ForceShutdown);
static Nan::Callback *constructor; static Nan::Callback *constructor;
static Nan::Persistent<v8::FunctionTemplate> fun_tpl; static Nan::Persistent<v8::FunctionTemplate> fun_tpl;
Nan::Persistent<v8::Value> running_self_ref;
grpc_server *wrapped_server; grpc_server *wrapped_server;
grpc_completion_queue *shutdown_queue; grpc_completion_queue *shutdown_queue;
bool is_shutdown;
}; };
} // namespace node } // namespace node

View File

@ -16,12 +16,14 @@
* *
*/ */
#include <nan.h>
#include <node.h> #include <node.h>
#include "grpc/grpc.h" #include "grpc/grpc.h"
#include "grpc/grpc_security.h" #include "grpc/grpc_security.h"
#include "grpc/support/log.h" #include "grpc/support/log.h"
#include "server_credentials.h" #include "server_credentials.h"
#include "util.h"
namespace grpc { namespace grpc {
namespace node { namespace node {
@ -119,9 +121,9 @@ NAN_METHOD(ServerCredentials::New) {
NAN_METHOD(ServerCredentials::CreateSsl) { NAN_METHOD(ServerCredentials::CreateSsl) {
Nan::HandleScope scope; Nan::HandleScope scope;
char *root_certs = NULL; StringOrNull root_certs;
if (::node::Buffer::HasInstance(info[0])) { if (::node::Buffer::HasInstance(info[0])) {
root_certs = ::node::Buffer::Data(info[0]); root_certs.assign(info[0]);
} else if (!(info[0]->IsNull() || info[0]->IsUndefined())) { } else if (!(info[0]->IsNull() || info[0]->IsUndefined())) {
return Nan::ThrowTypeError( return Nan::ThrowTypeError(
"createSSl's first argument must be a Buffer if provided"); "createSSl's first argument must be a Buffer if provided");
@ -145,36 +147,34 @@ NAN_METHOD(ServerCredentials::CreateSsl) {
} }
Local<Array> pair_list = Local<Array>::Cast(info[1]); Local<Array> pair_list = Local<Array>::Cast(info[1]);
uint32_t key_cert_pair_count = pair_list->Length(); uint32_t key_cert_pair_count = pair_list->Length();
grpc_ssl_pem_key_cert_pair *key_cert_pairs = grpc_ssl_pem_key_cert_pair key_cert_pairs[key_cert_pair_count];
new grpc_ssl_pem_key_cert_pair[key_cert_pair_count]; StringOrNull key_strings[key_cert_pair_count];
StringOrNull cert_strings[key_cert_pair_count];
Local<String> key_key = Nan::New("private_key").ToLocalChecked(); Local<String> key_key = Nan::New("private_key").ToLocalChecked();
Local<String> cert_key = Nan::New("cert_chain").ToLocalChecked(); Local<String> cert_key = Nan::New("cert_chain").ToLocalChecked();
for (uint32_t i = 0; i < key_cert_pair_count; i++) { for (uint32_t i = 0; i < key_cert_pair_count; i++) {
Local<Value> pair_val = Nan::Get(pair_list, i).ToLocalChecked(); Local<Value> pair_val = Nan::Get(pair_list, i).ToLocalChecked();
if (!pair_val->IsObject()) { if (!pair_val->IsObject()) {
delete[] key_cert_pairs;
return Nan::ThrowTypeError("Key/cert pairs must be objects"); return Nan::ThrowTypeError("Key/cert pairs must be objects");
} }
Local<Object> pair_obj = Nan::To<Object>(pair_val).ToLocalChecked(); Local<Object> pair_obj = Nan::To<Object>(pair_val).ToLocalChecked();
Local<Value> maybe_key = Nan::Get(pair_obj, key_key).ToLocalChecked(); Local<Value> maybe_key = Nan::Get(pair_obj, key_key).ToLocalChecked();
Local<Value> maybe_cert = Nan::Get(pair_obj, cert_key).ToLocalChecked(); Local<Value> maybe_cert = Nan::Get(pair_obj, cert_key).ToLocalChecked();
if (!::node::Buffer::HasInstance(maybe_key)) { if (!::node::Buffer::HasInstance(maybe_key)) {
delete[] key_cert_pairs;
return Nan::ThrowTypeError("private_key must be a Buffer"); return Nan::ThrowTypeError("private_key must be a Buffer");
} }
if (!::node::Buffer::HasInstance(maybe_cert)) { if (!::node::Buffer::HasInstance(maybe_cert)) {
delete[] key_cert_pairs;
return Nan::ThrowTypeError("cert_chain must be a Buffer"); return Nan::ThrowTypeError("cert_chain must be a Buffer");
} }
key_cert_pairs[i].private_key = ::node::Buffer::Data(maybe_key); key_strings[i].assign(maybe_key);
key_cert_pairs[i].cert_chain = ::node::Buffer::Data(maybe_cert); cert_strings[i].assign(maybe_cert);
key_cert_pairs[i].private_key = key_strings[i].get();
key_cert_pairs[i].cert_chain = cert_strings[i].get();
} }
grpc_server_credentials *creds = grpc_ssl_server_credentials_create_ex( grpc_server_credentials *creds = grpc_ssl_server_credentials_create_ex(
root_certs, key_cert_pairs, key_cert_pair_count, root_certs.get(), key_cert_pairs, key_cert_pair_count,
client_certificate_request, NULL); client_certificate_request, NULL);
delete[] key_cert_pairs;
if (creds == NULL) { if (creds == NULL) {
info.GetReturnValue().SetNull(); info.GetReturnValue().SetNull();
} else { } else {

View File

@ -0,0 +1,52 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_NODE_UTIL_H_
#define NET_GRPC_NODE_UTIL_H_
#include <node.h>
#include <nan.h>
#include <string>
namespace grpc {
namespace node {
class StringOrNull {
public:
StringOrNull() : assigned(false) { }
void assign(v8::Local<v8::Value> buffer) {
str_ = std::string(::node::Buffer::Data(buffer),
::node::Buffer::Length(buffer));
assigned = true;
}
const char * get() {
return assigned ? str_.c_str() : NULL;
}
bool isAssigned() {
return assigned;
}
private:
std::string str_;
bool assigned;
};
} // namespace node
} // namespace grpc
#endif // NET_GRPC_NODE_UTIL_H_