Skip to content

Commit

Permalink
[clangd] Avoid reallocating buffers for each message read:
Browse files Browse the repository at this point in the history
 - reuse std::string we read messages into
 - when reading line-wise, use SmallVector<128> and read in chunks of 128
   (this affects headers, which are short, and tests, which don't matter)

Differential Revision: https://reviews.llvm.org/D93653
  • Loading branch information
sam-mccall committed Jan 13, 2021
1 parent 66d5994 commit 466acd6
Showing 1 changed file with 30 additions and 26 deletions.
56 changes: 30 additions & 26 deletions clang-tools-extra/clangd/JSONTransport.cpp
Expand Up @@ -10,6 +10,7 @@
#include "support/Cancellation.h"
#include "support/Logger.h"
#include "support/Shutdown.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/Error.h"
#include <system_error>
Expand Down Expand Up @@ -99,21 +100,22 @@ class JSONTransport : public Transport {
}

llvm::Error loop(MessageHandler &Handler) override {
std::string JSON; // Messages may be large, reuse same big buffer.
while (!feof(In)) {
if (shutdownRequested())
return error(std::make_error_code(std::errc::operation_canceled),
"Got signal, shutting down");
if (ferror(In))
return llvm::errorCodeToError(
std::error_code(errno, std::system_category()));
if (auto JSON = readRawMessage()) {
if (auto Doc = llvm::json::parse(*JSON)) {
if (readRawMessage(JSON)) {
if (auto Doc = llvm::json::parse(JSON)) {
vlog(Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
if (!handleMessage(std::move(*Doc), Handler))
return llvm::Error::success(); // we saw the "exit" notification.
} else {
// Parse error. Log the raw message.
vlog("<<< {0}\n", *JSON);
vlog("<<< {0}\n", JSON);
elog("JSON parse error: {0}", llvm::toString(Doc.takeError()));
}
}
Expand All @@ -136,12 +138,12 @@ class JSONTransport : public Transport {
}

// Read raw string messages from input stream.
llvm::Optional<std::string> readRawMessage() {
return Style == JSONStreamStyle::Delimited ? readDelimitedMessage()
: readStandardMessage();
bool readRawMessage(std::string &JSON) {
return Style == JSONStreamStyle::Delimited ? readDelimitedMessage(JSON)
: readStandardMessage(JSON);
}
llvm::Optional<std::string> readDelimitedMessage();
llvm::Optional<std::string> readStandardMessage();
bool readDelimitedMessage(std::string &JSON);
bool readStandardMessage(std::string &JSON);

llvm::SmallVector<char, 0> OutputBuffer;
std::FILE *In;
Expand Down Expand Up @@ -191,12 +193,14 @@ bool JSONTransport::handleMessage(llvm::json::Value Message,

// Tries to read a line up to and including \n.
// If failing, feof(), ferror(), or shutdownRequested() will be set.
bool readLine(std::FILE *In, std::string &Out) {
static constexpr int BufSize = 1024;
bool readLine(std::FILE *In, llvm::SmallVectorImpl<char> &Out) {
// Big enough to hold any reasonable header line. May not fit content lines
// in delimited mode, but performance doesn't matter for that mode.
static constexpr int BufSize = 128;
size_t Size = 0;
Out.clear();
for (;;) {
Out.resize(Size + BufSize);
Out.resize_for_overwrite(Size + BufSize);
// Handle EINTR which is sent when a debugger attaches on some platforms.
if (!retryAfterSignalUnlessShutdown(
nullptr, [&] { return std::fgets(&Out[Size], BufSize, In); }))
Expand All @@ -216,14 +220,14 @@ bool readLine(std::FILE *In, std::string &Out) {
// Returns None when:
// - ferror(), feof(), or shutdownRequested() are set.
// - Content-Length is missing or empty (protocol error)
llvm::Optional<std::string> JSONTransport::readStandardMessage() {
bool JSONTransport::readStandardMessage(std::string &JSON) {
// A Language Server Protocol message starts with a set of HTTP headers,
// delimited by \r\n, and terminated by an empty line (\r\n).
unsigned long long ContentLength = 0;
std::string Line;
llvm::SmallString<128> Line;
while (true) {
if (feof(In) || ferror(In) || !readLine(In, Line))
return llvm::None;
return false;
InMirror << Line;

llvm::StringRef LineRef(Line);
Expand Down Expand Up @@ -258,14 +262,14 @@ llvm::Optional<std::string> JSONTransport::readStandardMessage() {
elog("Refusing to read message with long Content-Length: {0}. "
"Expect protocol errors",
ContentLength);
return llvm::None;
return false;
}
if (ContentLength == 0) {
log("Warning: Missing Content-Length header, or zero-length message.");
return llvm::None;
return false;
}

std::string JSON(ContentLength, '\0');
JSON.resize(ContentLength);
for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
// Handle EINTR which is sent when a debugger attaches on some platforms.
Read = retryAfterSignalUnlessShutdown(0, [&]{
Expand All @@ -274,24 +278,24 @@ llvm::Optional<std::string> JSONTransport::readStandardMessage() {
if (Read == 0) {
elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
ContentLength);
return llvm::None;
return false;
}
InMirror << llvm::StringRef(&JSON[Pos], Read);
clearerr(In); // If we're done, the error was transient. If we're not done,
// either it was transient or we'll see it again on retry.
Pos += Read;
}
return std::move(JSON);
return true;
}

// For lit tests we support a simplified syntax:
// - messages are delimited by '---' on a line by itself
// - lines starting with # are ignored.
// This is a testing path, so favor simplicity over performance here.
// When returning None, feof(), ferror(), or shutdownRequested() will be set.
llvm::Optional<std::string> JSONTransport::readDelimitedMessage() {
std::string JSON;
std::string Line;
// When returning false: feof(), ferror(), or shutdownRequested() will be set.
bool JSONTransport::readDelimitedMessage(std::string &JSON) {
JSON.clear();
llvm::SmallString<128> Line;
while (readLine(In, Line)) {
InMirror << Line;
auto LineRef = llvm::StringRef(Line).trim();
Expand All @@ -306,12 +310,12 @@ llvm::Optional<std::string> JSONTransport::readDelimitedMessage() {
}

if (shutdownRequested())
return llvm::None;
return false;
if (ferror(In)) {
elog("Input error while reading message!");
return llvm::None;
return false;
}
return std::move(JSON); // Including at EOF
return true; // Including at EOF
}

} // namespace
Expand Down

0 comments on commit 466acd6

Please sign in to comment.