Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
tls: remove cleartext input data queue
The TLS implementation previously kept a separate buffer for
incoming pieces of data, into which buffers were copied
before they were up for writing.

This removes this buffer, and replaces it with a simple list
of `uv_buf_t`s:

- The previous implementation copied all incoming data into
  that buffer, both allocating new storage and wasting time
  with copy operations. Node’s streams/net implementation
  already has to make sure that the allocated memory stays
  fresh until the write is finished, since that is what
  libuv streams rely on anyway.
- The fact that a separate kind of buffer, `crypto::NodeBIO`
  was used, was confusing: These `BIO` instances are
  only used to communicate with openssl’s streams system
  otherwise, whereas this one was purely for internal
  memory management.
- The name `clear_in_` was not very helpful.
  • Loading branch information
addaleax committed Jan 7, 2018
commit 4cbd571a0d280ffda98befd09798abdb9b821b1d
62 changes: 24 additions & 38 deletions src/tls_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ TLSWrap::TLSWrap(Environment* env,
stream_(stream),
enc_in_(nullptr),
enc_out_(nullptr),
clear_in_(nullptr),
write_size_(0),
started_(false),
established_(false),
Expand Down Expand Up @@ -95,8 +94,6 @@ TLSWrap::TLSWrap(Environment* env,
TLSWrap::~TLSWrap() {
enc_in_ = nullptr;
enc_out_ = nullptr;
delete clear_in_;
clear_in_ = nullptr;

sc_ = nullptr;

Expand All @@ -119,11 +116,6 @@ TLSWrap::~TLSWrap() {
}


void TLSWrap::MakePending() {
write_callback_scheduled_ = true;
}


bool TLSWrap::InvokeQueued(int status, const char* error_str) {
if (!write_callback_scheduled_)
return false;
Expand Down Expand Up @@ -183,10 +175,6 @@ void TLSWrap::InitSSL() {
// Unexpected
ABORT();
}

// Initialize ring for queud clear data
clear_in_ = new crypto::NodeBIO();
clear_in_->AssignEnvironment(env());
}


Expand Down Expand Up @@ -302,14 +290,14 @@ void TLSWrap::EncOut() {

// Split-off queue
if (established_ && current_write_ != nullptr)
MakePending();
write_callback_scheduled_ = true;

if (ssl_ == nullptr)
return;

// No data to write
if (BIO_pending(enc_out_) == 0) {
if (clear_in_->Length() == 0)
if (pending_cleartext_input_.empty())
InvokeQueued(0);
return;
}
Expand Down Expand Up @@ -496,21 +484,24 @@ bool TLSWrap::ClearIn() {
if (ssl_ == nullptr)
return false;

std::vector<uv_buf_t> buffers;
buffers.swap(pending_cleartext_input_);

crypto::MarkPopErrorOnReturn mark_pop_error_on_return;

size_t i;
int written = 0;
while (clear_in_->Length() > 0) {
size_t avail = 0;
char* data = clear_in_->Peek(&avail);
for (i = 0; i < buffers.size(); ++i) {
size_t avail = buffers[i].len;
char* data = buffers[i].base;
written = SSL_write(ssl_, data, avail);
CHECK(written == -1 || written == static_cast<int>(avail));
if (written == -1)
break;
clear_in_->Read(nullptr, avail);
}

// All written
if (clear_in_->Length() == 0) {
if (i == buffers.size()) {
CHECK_GE(written, 0);
return true;
}
Expand All @@ -520,9 +511,15 @@ bool TLSWrap::ClearIn() {
std::string error_str;
Local<Value> arg = GetSSLError(written, &err, &error_str);
if (!arg.IsEmpty()) {
MakePending();
write_callback_scheduled_ = true;
InvokeQueued(UV_EPROTO, error_str.c_str());
clear_in_->Reset();
} else {
// Push back the not-yet-written pending buffers into their queue.
// This can be skipped in the error case because no further writes
// would succeed anyway.
pending_cleartext_input_.insert(pending_cleartext_input_.end(),
&buffers[i],
&buffers[buffers.size()]);
}

return false;
Expand Down Expand Up @@ -615,14 +612,6 @@ int TLSWrap::DoWrite(WriteWrap* w,
return 0;
}

// Process enqueued data first
if (!ClearIn()) {
// If there're still data to process - enqueue current one
for (i = 0; i < count; i++)
clear_in_->Write(bufs[i].base, bufs[i].len);
return 0;
}

if (ssl_ == nullptr) {
ClearError();
error_ = "Write after DestroySSL";
Expand All @@ -645,9 +634,9 @@ int TLSWrap::DoWrite(WriteWrap* w,
if (!arg.IsEmpty())
return UV_EPROTO;

// No errors, queue rest
for (; i < count; i++)
clear_in_->Write(bufs[i].base, bufs[i].len);
pending_cleartext_input_.insert(pending_cleartext_input_.end(),
&bufs[i],
&bufs[count]);
}

// Try writing data immediately
Expand Down Expand Up @@ -817,17 +806,14 @@ void TLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args) {
TLSWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());

// Move all writes to pending
wrap->MakePending();
// If there is a write happening, mark it as finished.
wrap->write_callback_scheduled_ = true;

// And destroy
wrap->InvokeQueued(UV_ECANCELED, "Canceled because of SSL destruction");

// Destroy the SSL structure and friends
wrap->SSLWrap<TLSWrap>::DestroySSL();

delete wrap->clear_in_;
wrap->clear_in_ = nullptr;
}


Expand Down Expand Up @@ -927,7 +913,7 @@ void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo<Value>& info) {
TLSWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, info.This());

if (wrap->clear_in_ == nullptr) {
if (wrap->ssl_ == nullptr) {
info.GetReturnValue().Set(0);
return;
}
Expand Down
3 changes: 1 addition & 2 deletions src/tls_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ class TLSWrap : public AsyncWrap,
void EncOutAfterWrite(WriteWrap* req_wrap, int status);
bool ClearIn();
void ClearOut();
void MakePending();
bool InvokeQueued(int status, const char* error_str = nullptr);

inline void Cycle() {
Expand Down Expand Up @@ -158,7 +157,7 @@ class TLSWrap : public AsyncWrap,
StreamBase* stream_;
BIO* enc_in_;
BIO* enc_out_;
crypto::NodeBIO* clear_in_;
std::vector<uv_buf_t> pending_cleartext_input_;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a std::list be more appropriate here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimothyGu Why? uv_buf_t is a pretty small structure, so what I would guess is that the overhead of possibly having a small number of them being unused is more or less negligible compared to allocating a list node for every buffer?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on the variations I tried with http2, the performance difference is inconsequential. Using std::vector here is just fine.

size_t write_size_;
WriteWrap* current_write_ = nullptr;
bool write_callback_scheduled_ = false;
Expand Down
13 changes: 0 additions & 13 deletions src/util-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,6 @@ ListHead<T, M>::~ListHead() {
head_.next_->Remove();
}

template <typename T, ListNode<T> (T::*M)>
void ListHead<T, M>::MoveBack(ListHead* that) {
if (IsEmpty())
return;
ListNode<T>* to = &that->head_;
head_.next_->prev_ = to->prev_;
to->prev_->next_ = head_.next_;
head_.prev_->next_ = to;
to->prev_ = head_.prev_;
head_.prev_ = &head_;
head_.next_ = &head_;
}

template <typename T, ListNode<T> (T::*M)>
void ListHead<T, M>::PushBack(T* element) {
ListNode<T>* that = &(element->*M);
Expand Down
1 change: 0 additions & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ class ListHead {

inline ListHead() = default;
inline ~ListHead();
inline void MoveBack(ListHead* that);
inline void PushBack(T* element);
inline void PushFront(T* element);
inline bool IsEmpty() const;
Expand Down