summaryrefslogtreecommitdiffstats
path: root/Source/cmServerConnection.cxx
diff options
context:
space:
mode:
authorTobias Hunger <tobias.hunger@qt.io>2016-09-09 08:01:44 (GMT)
committerBrad King <brad.king@kitware.com>2016-09-22 12:06:38 (GMT)
commit1d601c6cb978a3b6b6143fdf64e284fb3a098d1e (patch)
tree1f274c04ba934cbc06cff9b8f9d33b7defbc7212 /Source/cmServerConnection.cxx
parent2c2ffd3874f749979d723d7a788d45e3830952d6 (diff)
downloadCMake-1d601c6cb978a3b6b6143fdf64e284fb3a098d1e.zip
CMake-1d601c6cb978a3b6b6143fdf64e284fb3a098d1e.tar.gz
CMake-1d601c6cb978a3b6b6143fdf64e284fb3a098d1e.tar.bz2
server-mode: Introduce cmServerConnection
Use it to split pipe and stdin/out handling out of cmServer itself. The server will shut down when it looses its connection to the client. This has the nice property that a crashing client will cause the server to terminate as the OS will close the connection on behave of the client.
Diffstat (limited to 'Source/cmServerConnection.cxx')
-rw-r--r--Source/cmServerConnection.cxx307
1 files changed, 307 insertions, 0 deletions
diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx
new file mode 100644
index 0000000..398e250
--- /dev/null
+++ b/Source/cmServerConnection.cxx
@@ -0,0 +1,307 @@
+/*============================================================================
+ CMake - Cross Platform Makefile Generator
+ Copyright 2015 Stephen Kelly <steveire@gmail.com>
+ Copyright 2016 Tobias Hunger <tobias.hunger@qt.io>
+
+ Distributed under the OSI-approved BSD License (the "License");
+ see accompanying file Copyright.txt for details.
+
+ This software is distributed WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the License for more information.
+============================================================================*/
+
+#include "cmServerConnection.h"
+
+#include <cmServer.h>
+
+#include <assert.h>
+
+namespace {
+
+static const std::string kSTART_MAGIC = "[== CMake Server ==[";
+static const std::string kEND_MAGIC = "]== CMake Server ==]";
+
+struct write_req_t
+{
+ uv_write_t req;
+ uv_buf_t buf;
+};
+
+void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
+{
+ (void)(handle);
+ char* rawBuffer = new char[suggested_size];
+ *buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
+}
+
+void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
+{
+ auto conn = reinterpret_cast<cmServerConnection*>(stream->data);
+ if (nread >= 0) {
+ conn->ReadData(std::string(buf->base, buf->base + nread));
+ } else {
+ conn->HandleEof();
+ }
+
+ delete[](buf->base);
+}
+
+void on_write(uv_write_t* req, int status)
+{
+ (void)(status);
+ auto conn = reinterpret_cast<cmServerConnection*>(req->data);
+
+ // Free req and buffer
+ write_req_t* wr = reinterpret_cast<write_req_t*>(req);
+ delete[](wr->buf.base);
+ delete wr;
+
+ conn->ProcessNextRequest();
+}
+
+void on_new_connection(uv_stream_t* stream, int status)
+{
+ (void)(status);
+ auto conn = reinterpret_cast<cmServerConnection*>(stream->data);
+ conn->Connect(stream);
+}
+
+} // namespace
+
+class LoopGuard
+{
+public:
+ LoopGuard(cmServerConnection* connection)
+ : Connection(connection)
+ {
+ Connection->mLoop = uv_default_loop();
+ }
+
+ ~LoopGuard()
+ {
+ uv_loop_close(Connection->mLoop);
+ Connection->mLoop = nullptr;
+ }
+
+private:
+ cmServerConnection* Connection;
+};
+
+cmServerConnection::cmServerConnection()
+{
+}
+
+cmServerConnection::~cmServerConnection()
+{
+}
+
+void cmServerConnection::SetServer(cmServer* s)
+{
+ this->Server = s;
+}
+
+bool cmServerConnection::ProcessEvents(std::string* errorMessage)
+{
+ assert(this->Server);
+ errorMessage->clear();
+
+ this->RawReadBuffer.clear();
+ this->RequestBuffer.clear();
+
+ LoopGuard guard(this);
+ (void)(guard);
+ if (!this->mLoop) {
+ *errorMessage = "Internal Error: Failed to create event loop.";
+ return false;
+ }
+
+ if (!DoSetup(errorMessage)) {
+ return false;
+ }
+
+ if (uv_run(this->mLoop, UV_RUN_DEFAULT) != 0) {
+ *errorMessage = "Internal Error: Event loop stopped in unclean state.";
+ return false;
+ }
+
+ // These need to be cleaned up by now:
+ assert(!this->ReadStream);
+ assert(!this->WriteStream);
+
+ this->RawReadBuffer.clear();
+ this->RequestBuffer.clear();
+
+ return true;
+}
+
+void cmServerConnection::ReadData(const std::string& data)
+{
+ this->RawReadBuffer += data;
+
+ for (;;) {
+ auto needle = this->RawReadBuffer.find('\n');
+
+ if (needle == std::string::npos) {
+ return;
+ }
+ std::string line = this->RawReadBuffer.substr(0, needle);
+ const auto ls = line.size();
+ if (ls > 1 && line.at(ls - 1) == '\r')
+ line.erase(ls - 1, 1);
+ this->RawReadBuffer.erase(this->RawReadBuffer.begin(),
+ this->RawReadBuffer.begin() +
+ static_cast<long>(needle) + 1);
+ if (line == kSTART_MAGIC) {
+ this->RequestBuffer.clear();
+ continue;
+ }
+ if (line == kEND_MAGIC) {
+ this->Server->QueueRequest(this->RequestBuffer);
+ this->RequestBuffer.clear();
+ } else {
+ this->RequestBuffer += line;
+ this->RequestBuffer += "\n";
+ }
+ }
+}
+
+void cmServerConnection::HandleEof()
+{
+ this->TearDown();
+}
+
+void cmServerConnection::WriteData(const std::string& data)
+{
+ assert(this->WriteStream);
+
+ auto ds = data.size();
+
+ write_req_t* req = new write_req_t;
+ req->req.data = this;
+ req->buf = uv_buf_init(new char[ds], static_cast<unsigned int>(ds));
+ memcpy(req->buf.base, data.c_str(), ds);
+
+ uv_write(reinterpret_cast<uv_write_t*>(req),
+ static_cast<uv_stream_t*>(this->WriteStream), &req->buf, 1,
+ on_write);
+}
+
+void cmServerConnection::ProcessNextRequest()
+{
+ Server->PopOne();
+}
+
+void cmServerConnection::SendGreetings()
+{
+ Server->PrintHello();
+}
+
+bool cmServerStdIoConnection::DoSetup(std::string* errorMessage)
+{
+ (void)(errorMessage);
+
+ if (uv_guess_handle(1) == UV_TTY) {
+ uv_tty_init(this->Loop(), &this->Input.tty, 0, 1);
+ uv_tty_set_mode(&this->Input.tty, UV_TTY_MODE_NORMAL);
+ Input.tty.data = this;
+ this->ReadStream = reinterpret_cast<uv_stream_t*>(&this->Input.tty);
+
+ uv_tty_init(this->Loop(), &this->Output.tty, 1, 0);
+ uv_tty_set_mode(&this->Output.tty, UV_TTY_MODE_NORMAL);
+ Output.tty.data = this;
+ this->WriteStream = reinterpret_cast<uv_stream_t*>(&this->Output.tty);
+ } else {
+ uv_pipe_init(this->Loop(), &this->Input.pipe, 0);
+ uv_pipe_open(&this->Input.pipe, 0);
+ Input.pipe.data = this;
+ this->ReadStream = reinterpret_cast<uv_stream_t*>(&this->Input.pipe);
+
+ uv_pipe_init(this->Loop(), &this->Output.pipe, 0);
+ uv_pipe_open(&this->Output.pipe, 1);
+ Output.pipe.data = this;
+ this->WriteStream = reinterpret_cast<uv_stream_t*>(&this->Output.pipe);
+ }
+
+ SendGreetings();
+ uv_read_start(this->ReadStream, on_alloc_buffer, on_read);
+
+ return true;
+}
+
+void cmServerStdIoConnection::TearDown()
+{
+ uv_close(reinterpret_cast<uv_handle_t*>(this->ReadStream), nullptr);
+ this->ReadStream = nullptr;
+ uv_close(reinterpret_cast<uv_handle_t*>(this->WriteStream), nullptr);
+ this->WriteStream = nullptr;
+}
+
+cmServerPipeConnection::cmServerPipeConnection(const std::string& name)
+ : PipeName(name)
+{
+ this->ServerPipe.data = nullptr;
+ this->ClientPipe.data = nullptr;
+}
+
+bool cmServerPipeConnection::DoSetup(std::string* errorMessage)
+{
+ uv_pipe_init(this->Loop(), &this->ServerPipe, 0);
+ this->ServerPipe.data = this;
+
+ int r;
+ if ((r = uv_pipe_bind(&this->ServerPipe, this->PipeName.c_str())) != 0) {
+ *errorMessage = std::string("Internal Error with ") + this->PipeName +
+ ": " + uv_err_name(r);
+ return false;
+ }
+ auto serverStream = reinterpret_cast<uv_stream_t*>(&this->ServerPipe);
+ serverStream->data = this;
+ if ((r = uv_listen(serverStream, 1, on_new_connection)) != 0) {
+ *errorMessage = std::string("Internal Error with ") + this->PipeName +
+ ": " + uv_err_name(r);
+ return false;
+ }
+
+ return true;
+}
+
+void cmServerPipeConnection::TearDown()
+{
+ if (this->WriteStream->data) {
+ uv_close(reinterpret_cast<uv_handle_t*>(this->WriteStream), nullptr);
+ this->WriteStream->data = nullptr;
+ }
+ uv_close(reinterpret_cast<uv_handle_t*>(&this->ServerPipe), nullptr);
+
+ this->WriteStream = nullptr;
+ this->ReadStream = nullptr;
+}
+
+void cmServerPipeConnection::Connect(uv_stream_t* server)
+{
+ if (this->ClientPipe.data == this) {
+ // Accept and close all pipes but the first:
+ uv_pipe_t rejectPipe;
+
+ uv_pipe_init(this->Loop(), &rejectPipe, 0);
+ auto rejecter = reinterpret_cast<uv_stream_t*>(&rejectPipe);
+ uv_accept(server, rejecter);
+ uv_close(reinterpret_cast<uv_handle_t*>(rejecter), nullptr);
+ return;
+ }
+
+ uv_pipe_init(this->Loop(), &this->ClientPipe, 0);
+ this->ClientPipe.data = this;
+ auto client = reinterpret_cast<uv_stream_t*>(&this->ClientPipe);
+ if (uv_accept(server, client) != 0) {
+ uv_close(reinterpret_cast<uv_handle_t*>(client), nullptr);
+ return;
+ }
+ this->ReadStream = client;
+ this->WriteStream = client;
+
+ uv_read_start(this->ReadStream, on_alloc_buffer, on_read);
+
+ this->SendGreetings();
+}