From 2122a77f5177a039b80403a3772fdd14323e158a Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Wed, 24 Sep 2014 13:42:33 +0400 Subject: [PATCH] crypto: lower RSS usage for TLSCallbacks Don't allocate any BIO buffers initially, do this on a first read from the TCP connection. Allocate different amount of data for initial read and for consequent reads: small buffer for hello+certificate, big buffer for better throughput. see #8416 --- src/node_crypto_bio.cc | 75 ++++++++++++++++++++++++++---------------- src/node_crypto_bio.h | 38 ++++++++++++++------- src/tls_wrap.cc | 9 +++-- src/tls_wrap.h | 6 ++++ 4 files changed, 87 insertions(+), 41 deletions(-) diff --git a/src/node_crypto_bio.cc b/src/node_crypto_bio.cc index 22f0f6e8c7..441e9b17f7 100644 --- a/src/node_crypto_bio.cc +++ b/src/node_crypto_bio.cc @@ -272,6 +272,8 @@ size_t NodeBIO::Read(char* out, size_t size) { void NodeBIO::FreeEmpty() { + if (write_head_ == NULL) + return; Buffer* child = write_head_->next_; if (child == write_head_ || child == read_head_) return; @@ -281,13 +283,6 @@ void NodeBIO::FreeEmpty() { Buffer* prev = child; while (cur != read_head_) { - // Skip embedded buffer, and continue deallocating again starting from it - if (cur == &head_) { - prev->next_ = cur; - prev = cur; - cur = head_.next_; - continue; - } assert(cur != write_head_); assert(cur->write_pos_ == cur->read_pos_); @@ -295,7 +290,6 @@ void NodeBIO::FreeEmpty() { delete cur; cur = next; } - assert(prev == child || prev == &head_); prev->next_ = cur; } @@ -330,7 +324,7 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) { } // Move to next buffer - if (current->read_pos_ + avail == kBufferLength) { + if (current->read_pos_ + avail == current->len_) { current = current->next_; } } @@ -343,10 +337,14 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) { void NodeBIO::Write(const char* data, size_t size) { size_t offset = 0; size_t left = size; + + // Allocate initial buffer if the ring is empty + TryAllocateForWrite(left); + while (left > 0) { size_t to_write = left; - assert(write_head_->write_pos_ <= kBufferLength); - size_t avail = kBufferLength - write_head_->write_pos_; + assert(write_head_->write_pos_ <= write_head_->len_); + size_t avail = write_head_->len_ - write_head_->write_pos_; if (to_write > avail) to_write = avail; @@ -361,12 +359,12 @@ void NodeBIO::Write(const char* data, size_t size) { offset += to_write; length_ += to_write; write_head_->write_pos_ += to_write; - assert(write_head_->write_pos_ <= kBufferLength); + assert(write_head_->write_pos_ <= write_head_->len_); // Go to next buffer if there still are some bytes to write if (left != 0) { - assert(write_head_->write_pos_ == kBufferLength); - TryAllocateForWrite(); + assert(write_head_->write_pos_ == write_head_->len_); + TryAllocateForWrite(left); write_head_ = write_head_->next_; // Additionally, since we're moved to the next buffer, read head @@ -379,7 +377,9 @@ void NodeBIO::Write(const char* data, size_t size) { char* NodeBIO::PeekWritable(size_t* size) { - size_t available = kBufferLength - write_head_->write_pos_; + TryAllocateForWrite(*size); + + size_t available = write_head_->len_ - write_head_->write_pos_; if (*size != 0 && available > *size) available = *size; else @@ -392,12 +392,12 @@ char* NodeBIO::PeekWritable(size_t* size) { void NodeBIO::Commit(size_t size) { write_head_->write_pos_ += size; length_ += size; - assert(write_head_->write_pos_ <= kBufferLength); + assert(write_head_->write_pos_ <= write_head_->len_); // Allocate new buffer if write head is full, // and there're no other place to go - TryAllocateForWrite(); - if (write_head_->write_pos_ == kBufferLength) { + TryAllocateForWrite(0); + if (write_head_->write_pos_ == write_head_->len_) { write_head_ = write_head_->next_; // Additionally, since we're moved to the next buffer, read head @@ -407,19 +407,35 @@ void NodeBIO::Commit(size_t size) { } -void NodeBIO::TryAllocateForWrite() { +void NodeBIO::TryAllocateForWrite(size_t hint) { + Buffer* w = write_head_; + Buffer* r = read_head_; // If write head is full, next buffer is either read head or not empty. - if (write_head_->write_pos_ == kBufferLength && - (write_head_->next_ == read_head_ || - write_head_->next_->write_pos_ != 0)) { - Buffer* next = new Buffer(); - next->next_ = write_head_->next_; - write_head_->next_ = next; + if (w == NULL || + (w->write_pos_ == w->len_ && + (w->next_ == r || w->next_->write_pos_ != 0))) { + size_t len = w == NULL ? initial_ : + kThroughputBufferLength; + if (len < hint) + len = hint; + Buffer* next = new Buffer(len); + + if (w == NULL) { + next->next_ = next; + write_head_ = next; + read_head_ = next; + } else { + next->next_ = w->next_; + w->next_ = next; + } } } void NodeBIO::Reset() { + if (read_head_ == NULL) + return; + while (read_head_->read_pos_ != read_head_->write_pos_) { assert(read_head_->write_pos_ > read_head_->read_pos_); @@ -435,12 +451,15 @@ void NodeBIO::Reset() { NodeBIO::~NodeBIO() { - Buffer* current = head_.next_; - while (current != &head_) { + if (read_head_ == NULL) + return; + + Buffer* current = read_head_; + do { Buffer* next = current->next_; delete current; current = next; - } + } while (current != read_head_); read_head_ = NULL; write_head_ = NULL; diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h index 0b9b3440c5..163a82124f 100644 --- a/src/node_crypto_bio.h +++ b/src/node_crypto_bio.h @@ -29,9 +29,10 @@ namespace node { class NodeBIO { public: - NodeBIO() : length_(0), read_head_(&head_), write_head_(&head_) { - // Loop head - head_.next_ = &head_; + NodeBIO() : initial_(kInitialBufferLength), + length_(0), + read_head_(NULL), + write_head_(NULL) { } ~NodeBIO(); @@ -42,7 +43,7 @@ class NodeBIO { void TryMoveReadHead(); // Allocate new buffer for write if needed - void TryAllocateForWrite(); + void TryAllocateForWrite(size_t hint); // Read `len` bytes maximum into `out`, return actual number of read bytes size_t Read(char* out, size_t size); @@ -76,11 +77,16 @@ class NodeBIO { // Commit reserved data void Commit(size_t size); + // Return size of buffer in bytes - size_t inline Length() { + inline size_t Length() const { return length_; } + inline void set_initial(size_t initial) { + initial_ = initial; + } + static inline NodeBIO* FromBIO(BIO* bio) { assert(bio->ptr != NULL); return static_cast(bio->ptr); @@ -95,24 +101,34 @@ class NodeBIO { static int Gets(BIO* bio, char* out, int size); static long Ctrl(BIO* bio, int cmd, long num, void* ptr); - // NOTE: Size is maximum TLS frame length, this is required if we want - // to fit whole ClientHello into one Buffer of NodeBIO. - static const size_t kBufferLength = 16 * 1024 + 5; + // Enough to handle the most of the client hellos + static const size_t kInitialBufferLength = 1024; + static const size_t kThroughputBufferLength = 16384; + static const BIO_METHOD method; class Buffer { public: - Buffer() : read_pos_(0), write_pos_(0), next_(NULL) { + explicit Buffer(size_t len) : read_pos_(0), + write_pos_(0), + len_(len), + next_(NULL) { + data_ = new char[len]; + } + + ~Buffer() { + delete[] data_; } size_t read_pos_; size_t write_pos_; + size_t len_; Buffer* next_; - char data_[kBufferLength]; + char* data_; }; + size_t initial_; size_t length_; - Buffer head_; Buffer* read_head_; Buffer* write_head_; }; diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index c65492cf31..84bc87e0cc 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -192,6 +192,8 @@ void TLSCallbacks::InitSSL() { if (is_server()) { SSL_set_accept_state(ssl_); } else if (is_client()) { + // Enough space for server response (hello, cert) + NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength); SSL_set_connect_state(ssl_); } else { // Unexpected @@ -254,6 +256,7 @@ void TLSCallbacks::Receive(const FunctionCallbackInfo& args) { wrap->DoAlloc(reinterpret_cast(stream), len, &buf); size_t copy = buf.len > len ? len : buf.len; memcpy(buf.base, data, copy); + buf.len = copy; wrap->DoRead(stream, buf.len, &buf, UV_UNKNOWN_HANDLE); data += copy; @@ -615,8 +618,9 @@ void TLSCallbacks::AfterWrite(WriteWrap* w) { void TLSCallbacks::DoAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&suggested_size); - buf->len = suggested_size; + size_t size = 0; + buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&size); + buf->len = size; } @@ -720,6 +724,7 @@ void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo& args) { TLSCallbacks* wrap = Unwrap(args.Holder()); + NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength); wrap->hello_parser_.Start(SSLWrap::OnClientHello, OnClientHelloParseEnd, wrap); diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 34ae3ccbf6..b12a6b6612 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -74,6 +74,12 @@ class TLSCallbacks : public crypto::SSLWrap, protected: static const int kClearOutChunkSize = 1024; + // Maximum number of bytes for hello parser + static const int kMaxHelloLength = 16384; + + // Usual ServerHello + Certificate size + static const int kInitialClientBufferLength = 4096; + // Maximum number of buffers passed to uv_write() static const int kSimultaneousBufferCount = 10;