diff options
author | Tobias Hunger <tobias.hunger@qt.io> | 2016-09-09 08:01:44 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2016-09-22 12:06:38 (GMT) |
commit | 1d601c6cb978a3b6b6143fdf64e284fb3a098d1e (patch) | |
tree | 1f274c04ba934cbc06cff9b8f9d33b7defbc7212 /Source | |
parent | 2c2ffd3874f749979d723d7a788d45e3830952d6 (diff) | |
download | CMake-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')
-rw-r--r-- | Source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Source/cmServer.cxx | 145 | ||||
-rw-r--r-- | Source/cmServer.h | 15 | ||||
-rw-r--r-- | Source/cmServerConnection.cxx | 307 | ||||
-rw-r--r-- | Source/cmServerConnection.h | 97 | ||||
-rw-r--r-- | Source/cmcmd.cxx | 42 |
6 files changed, 463 insertions, 144 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index a2dead6..5e136e4 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -789,6 +789,7 @@ target_link_libraries(cmake CMakeLib) if(CMake_HAVE_SERVER_MODE) add_library(CMakeServerLib cmServer.cxx cmServer.h + cmServerConnection.cxx cmServerConnection.h cmServerProtocol.cxx cmServerProtocol.h ) target_link_libraries(CMakeServerLib CMakeLib) diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx index be001a7..56cd7ba 100644 --- a/Source/cmServer.cxx +++ b/Source/cmServer.cxx @@ -13,6 +13,7 @@ #include "cmServer.h" +#include "cmServerConnection.h" #include "cmServerProtocol.h" #include "cmSystemTools.h" #include "cmVersionMacros.h" @@ -40,57 +41,6 @@ static const std::string kMESSAGE_TYPE = "message"; static const std::string kSTART_MAGIC = "[== CMake Server ==["; static const std::string kEND_MAGIC = "]== CMake Server ==]"; -typedef struct -{ - uv_write_t req; - uv_buf_t buf; -} write_req_t; - -void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) -{ - (void)handle; - *buf = uv_buf_init(static_cast<char*>(malloc(suggested_size)), - static_cast<unsigned int>(suggested_size)); -} - -void free_write_req(uv_write_t* req) -{ - write_req_t* wr = reinterpret_cast<write_req_t*>(req); - free(wr->buf.base); - free(wr); -} - -void on_stdout_write(uv_write_t* req, int status) -{ - (void)status; - auto server = reinterpret_cast<cmServer*>(req->data); - free_write_req(req); - server->PopOne(); -} - -void write_data(uv_stream_t* dest, std::string content, uv_write_cb cb) -{ - write_req_t* req = static_cast<write_req_t*>(malloc(sizeof(write_req_t))); - req->req.data = dest->data; - req->buf = uv_buf_init(static_cast<char*>(malloc(content.size())), - static_cast<unsigned int>(content.size())); - memcpy(req->buf.base, content.c_str(), content.size()); - uv_write(reinterpret_cast<uv_write_t*>(req), static_cast<uv_stream_t*>(dest), - &req->buf, 1, cb); -} - -void read_stdin(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) -{ - if (nread > 0) { - auto server = reinterpret_cast<cmServer*>(stream->data); - std::string result = std::string(buf->base, buf->base + nread); - server->handleData(result); - } - - if (buf->base) - free(buf->base); -} - class cmServer::DebugInfo { public: @@ -105,9 +55,11 @@ public: uint64_t StartTime; }; -cmServer::cmServer(bool supportExperimental) - : SupportExperimental(supportExperimental) +cmServer::cmServer(cmServerConnection* conn, bool supportExperimental) + : Connection(conn) + , SupportExperimental(supportExperimental) { + this->Connection->SetServer(this); // Register supported protocols: this->RegisterProtocol(new cmServerProtocol1_0); } @@ -118,18 +70,15 @@ cmServer::~cmServer() return; } - uv_close(reinterpret_cast<uv_handle_t*>(this->InputStream), NULL); - uv_close(reinterpret_cast<uv_handle_t*>(this->OutputStream), NULL); - uv_loop_close(this->Loop); - for (cmServerProtocol* p : this->SupportedProtocols) { delete p; } + + delete this->Connection; } void cmServer::PopOne() { - this->Writing = false; if (this->Queue.empty()) { return; } @@ -173,39 +122,6 @@ void cmServer::PopOne() } } -void cmServer::handleData(const std::string& data) -{ - this->DataBuffer += data; - - for (;;) { - auto needle = this->DataBuffer.find('\n'); - - if (needle == std::string::npos) { - return; - } - std::string line = this->DataBuffer.substr(0, needle); - const auto ls = line.size(); - if (ls > 1 && line.at(ls - 1) == '\r') - line.erase(ls - 1, 1); - this->DataBuffer.erase(this->DataBuffer.begin(), - this->DataBuffer.begin() + needle + 1); - if (line == kSTART_MAGIC) { - this->JsonData.clear(); - continue; - } - if (line == kEND_MAGIC) { - this->Queue.push_back(this->JsonData); - this->JsonData.clear(); - if (!this->Writing) { - this->PopOne(); - } - } else { - this->JsonData += line; - this->JsonData += "\n"; - } - } -} - void cmServer::RegisterProtocol(cmServerProtocol* protocol) { if (protocol->IsExperimental() && !this->SupportExperimental) { @@ -245,6 +161,12 @@ void cmServer::PrintHello() const this->WriteJsonObject(hello, nullptr); } +void cmServer::QueueRequest(const std::string& request) +{ + this->Queue.push_back(request); + this->PopOne(); +} + void cmServer::reportProgress(const char* msg, float progress, void* data) { const cmServerRequest* request = static_cast<const cmServerRequest*>(data); @@ -312,43 +234,16 @@ cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request) return request.Reply(Json::objectValue); } -bool cmServer::Serve() +bool cmServer::Serve(std::string* errorMessage) { if (this->SupportedProtocols.empty()) { + *errorMessage = + "No protocol versions defined. Maybe you need --experimental?"; return false; } assert(!this->Protocol); - this->Loop = uv_default_loop(); - - 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); - this->Input.tty.data = this; - InputStream = 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); - this->Output.tty.data = this; - OutputStream = 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); - this->Input.pipe.data = this; - InputStream = reinterpret_cast<uv_stream_t*>(&this->Input.pipe); - - uv_pipe_init(this->Loop, &this->Output.pipe, 0); - uv_pipe_open(&this->Output.pipe, 1); - this->Output.pipe.data = this; - OutputStream = reinterpret_cast<uv_stream_t*>(&this->Output.pipe); - } - - this->PrintHello(); - - uv_read_start(this->InputStream, alloc_buffer, read_stdin); - - uv_run(this->Loop, UV_RUN_DEFAULT); - return true; + return Connection->ProcessEvents(errorMessage); } void cmServer::WriteJsonObject(const Json::Value& jsonValue, @@ -385,10 +280,8 @@ void cmServer::WriteJsonObject(const Json::Value& jsonValue, } } - this->Writing = true; - write_data(this->OutputStream, std::string("\n") + kSTART_MAGIC + - std::string("\n") + result + kEND_MAGIC + std::string("\n"), - on_stdout_write); + Connection->WriteData(std::string("\n") + kSTART_MAGIC + std::string("\n") + + result + kEND_MAGIC + std::string("\n")); } cmServerProtocol* cmServer::FindMatchingProtocol( diff --git a/Source/cmServer.h b/Source/cmServer.h index 38a11bb..dde5333 100644 --- a/Source/cmServer.h +++ b/Source/cmServer.h @@ -24,6 +24,7 @@ #include <string> #include <vector> +class cmServerConnection; class cmServerProtocol; class cmServerRequest; class cmServerResponse; @@ -33,18 +34,18 @@ class cmServer public: class DebugInfo; - cmServer(bool supportExperimental); + cmServer(cmServerConnection* conn, bool supportExperimental); ~cmServer(); - bool Serve(); - - // for callbacks: - void PopOne(); - void handleData(std::string const& data); + bool Serve(std::string* errorMessage); private: void RegisterProtocol(cmServerProtocol* protocol); + // Callbacks from cmServerConnection: + void PopOne(); + void QueueRequest(const std::string& request); + static void reportProgress(const char* msg, float progress, void* data); static void reportMessage(const char* msg, const char* title, bool& cancel, void* data); @@ -69,6 +70,7 @@ private: static cmServerProtocol* FindMatchingProtocol( const std::vector<cmServerProtocol*>& protocols, int major, int minor); + cmServerConnection* Connection = nullptr; const bool SupportExperimental; cmServerProtocol* Protocol = nullptr; @@ -94,4 +96,5 @@ private: mutable bool Writing = false; friend class cmServerRequest; + friend class cmServerConnection; }; 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(); +} diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h new file mode 100644 index 0000000..fa86e71 --- /dev/null +++ b/Source/cmServerConnection.h @@ -0,0 +1,97 @@ +/*============================================================================ + 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. +============================================================================*/ + +#pragma once + +#include <string> +#include <vector> + +#if defined(CMAKE_BUILD_WITH_CMAKE) +#include "cm_uv.h" +#endif + +class cmServer; +class LoopGuard; + +class cmServerConnection +{ +public: + cmServerConnection(); + virtual ~cmServerConnection(); + + void SetServer(cmServer* s); + + bool ProcessEvents(std::string* errorMessage); + + void ReadData(const std::string& data); + void HandleEof(); + void WriteData(const std::string& data); + void ProcessNextRequest(); + + virtual void Connect(uv_stream_t* server) { (void)(server); } + +protected: + virtual bool DoSetup(std::string* errorMessage) = 0; + virtual void TearDown() = 0; + + void SendGreetings(); + + uv_loop_t* Loop() const { return mLoop; } + +protected: + std::string RawReadBuffer; + std::string RequestBuffer; + + uv_stream_t* ReadStream = nullptr; + uv_stream_t* WriteStream = nullptr; + +private: + uv_loop_t* mLoop = nullptr; + cmServer* Server = nullptr; + + friend class LoopGuard; +}; + +class cmServerStdIoConnection : public cmServerConnection +{ +public: + bool DoSetup(std::string* errorMessage) override; + + void TearDown() override; + +private: + typedef union + { + uv_tty_t tty; + uv_pipe_t pipe; + } InOutUnion; + + InOutUnion Input; + InOutUnion Output; +}; + +class cmServerPipeConnection : public cmServerConnection +{ +public: + cmServerPipeConnection(const std::string& name); + bool DoSetup(std::string* errorMessage) override; + + void TearDown() override; + + void Connect(uv_stream_t* server) override; + +private: + const std::string PipeName; + uv_pipe_t ServerPipe; + uv_pipe_t ClientPipe; +}; diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 38f00e6..9daed4b 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -25,6 +25,7 @@ #if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE #include "cmServer.h" +#include "cmServerConnection.h" #endif #if defined(CMAKE_BUILD_WITH_CMAKE) @@ -913,32 +914,49 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args) } return 0; } else if (args[1] == "server") { - if (args.size() > 3) { - cmSystemTools::Error("Too many arguments to start server mode"); - return 1; - } + const std::string pipePrefix = "--pipe="; bool supportExperimental = false; - if (args.size() == 3) { - if (args[2] == "--experimental") { + bool isDebug = false; + std::string pipe; + + for (size_t i = 2; i < args.size(); ++i) { + const std::string& a = args[i]; + + if (a == "--experimental") { supportExperimental = true; + } else if (a == "--debug") { + pipe.clear(); + isDebug = true; + } else if (a.substr(0, pipePrefix.size()) == pipePrefix) { + isDebug = false; + pipe = a.substr(pipePrefix.size()); + if (pipe.empty()) { + cmSystemTools::Error("No pipe given after --pipe="); + return 2; + } } else { cmSystemTools::Error("Unknown argument for server mode"); return 1; } } #if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE - cmServer server(supportExperimental); - if (server.Serve()) { + cmServerConnection* conn; + if (isDebug) { + conn = new cmServerStdIoConnection; + } else { + conn = new cmServerPipeConnection(pipe); + } + cmServer server(conn, supportExperimental); + std::string errorMessage; + if (server.Serve(&errorMessage)) { return 0; } else { - cmSystemTools::Error( - "CMake server could not find any supported protocol. " - "Try with \"--experimental\" to enable " - "experimental support."); + cmSystemTools::Error(errorMessage.c_str()); return 1; } #else static_cast<void>(supportExperimental); + static_cast<void>(isDebug); cmSystemTools::Error("CMake was not built with server mode enabled"); return 1; #endif |