From 6cc4fff538e052696cd9c34a301712e29fa1ffd0 Mon Sep 17 00:00:00 2001 From: Austin Date: Tue, 19 Dec 2017 01:03:24 -0800 Subject: [PATCH] Tests and bug fixes for XmlRpcServer (#1243) * Tests and bug fixes for XmlRpcServer Add integration tests for the XmlRpcServer interacting with an XmlRpcClient in the same process, particularly around the behavior when the server process runs out of available file handles. Update the XmlRpcServer with two different mitigations for file handle exhaustion (Fixes #914, replaces #960 and #977): * Measure the number of free file handles and reject incoming connections if the pool of free file descriptors is too small. This actively rejects incoming clients instead of waiting for complete file handle exhaustion, which would leave clients in a pending state until a file descriptor becomes free. * If accept fails due to complete file handle exhaustion, temporarily stop calling accept on this socket. This prevents a busy-loop where poll() believes the listening socket is readable, but accept() fails to allocate a file descriptor and leaves the socket in a readable state. * Add test depend on boost * Run astyle --- .../xmlrpcpp/include/xmlrpcpp/XmlRpcServer.h | 19 +- utilities/xmlrpcpp/package.xml | 2 + utilities/xmlrpcpp/src/XmlRpcServer.cpp | 92 +++++- utilities/xmlrpcpp/test/CMakeLists.txt | 16 + utilities/xmlrpcpp/test/HelloTest.cpp | 276 ++++++++++++++++++ .../xmlrpcpp/test/test_dispatch_live.cpp | 189 ++++++++++++ utilities/xmlrpcpp/test/test_fixtures.cpp | 75 +++++ utilities/xmlrpcpp/test/test_fixtures.h | 64 ++++ utilities/xmlrpcpp/test/test_ulimit.cpp | 78 +++++ 9 files changed, 803 insertions(+), 8 deletions(-) create mode 100644 utilities/xmlrpcpp/test/HelloTest.cpp create mode 100644 utilities/xmlrpcpp/test/test_dispatch_live.cpp create mode 100644 utilities/xmlrpcpp/test/test_fixtures.cpp create mode 100644 utilities/xmlrpcpp/test/test_fixtures.h create mode 100644 utilities/xmlrpcpp/test/test_ulimit.cpp diff --git a/utilities/xmlrpcpp/include/xmlrpcpp/XmlRpcServer.h b/utilities/xmlrpcpp/include/xmlrpcpp/XmlRpcServer.h index ac16daebcb..1069755aac 100644 --- a/utilities/xmlrpcpp/include/xmlrpcpp/XmlRpcServer.h +++ b/utilities/xmlrpcpp/include/xmlrpcpp/XmlRpcServer.h @@ -14,6 +14,8 @@ #ifndef MAKEDEPEND # include # include +# include +# include #endif #include "xmlrpcpp/XmlRpcDispatch.h" @@ -87,11 +89,14 @@ namespace XmlRpc { protected: //! Accept a client connection request - virtual void acceptConnection(); + virtual unsigned acceptConnection(); //! Create a new connection object for processing requests from a specific client. virtual XmlRpcServerConnection* createConnection(int socket); + //! Count number of free file descriptors + int countFreeFDs(); + // Whether the introspection API is supported by this server bool _introspectionEnabled; @@ -108,6 +113,18 @@ namespace XmlRpc { int _port; + // Flag indicating that accept had an error and needs to be retried. + bool _accept_error; + // If we cannot accept(), retry after this many seconds. Hopefully there + // will be more free file descriptors later. + static const double ACCEPT_RETRY_INTERVAL_SEC; + // Retry time for accept. + double _accept_retry_time_sec; + + // Minimum number of free file descriptors before rejecting clients. + static const int FREE_FD_BUFFER; + // List of all file descriptors, used for counting open files. + std::vector pollfds; }; } // namespace XmlRpc diff --git a/utilities/xmlrpcpp/package.xml b/utilities/xmlrpcpp/package.xml index d2e6748e9b..85190bf30b 100644 --- a/utilities/xmlrpcpp/package.xml +++ b/utilities/xmlrpcpp/package.xml @@ -22,6 +22,8 @@ cpp_common + boost + diff --git a/utilities/xmlrpcpp/src/XmlRpcServer.cpp b/utilities/xmlrpcpp/src/XmlRpcServer.cpp index 2515142ce5..99154efacb 100644 --- a/utilities/xmlrpcpp/src/XmlRpcServer.cpp +++ b/utilities/xmlrpcpp/src/XmlRpcServer.cpp @@ -9,16 +9,40 @@ #include "xmlrpcpp/XmlRpcUtil.h" #include "xmlrpcpp/XmlRpcException.h" +#include +#include +#include using namespace XmlRpc; +const int XmlRpcServer::FREE_FD_BUFFER = 32; +const double XmlRpcServer::ACCEPT_RETRY_INTERVAL_SEC = 1.0; XmlRpcServer::XmlRpcServer() + : _introspectionEnabled(false), + _listMethods(0), + _methodHelp(0), + _port(0), + _accept_error(false), + _accept_retry_time_sec(0.0) { - _introspectionEnabled = false; - _listMethods = 0; - _methodHelp = 0; - _port = 0; + struct rlimit limit = { .rlim_cur = 0, .rlim_max = 0 }; + int max_files = 1024; + + if(getrlimit(RLIMIT_NOFILE, &limit) == 0) { + max_files = limit.rlim_max; + } else { + XmlRpcUtil::error("Could not get open file limit: %s", strerror(errno)); + } + pollfds.resize(max_files); + for(int i=0; i _accept_retry_time_sec) { + _disp.addSource(this, XmlRpcDispatch::ReadableEvent); + } _disp.work(msTime); } @@ -140,14 +167,13 @@ XmlRpcServer::work(double msTime) unsigned XmlRpcServer::handleEvent(unsigned) { - acceptConnection(); - return XmlRpcDispatch::ReadableEvent; // Continue to monitor this fd + return acceptConnection(); } // Accept a client connection request and create a connection to // handle method calls from the client. -void +unsigned XmlRpcServer::acceptConnection() { int s = XmlRpcSocket::accept(this->getfd()); @@ -156,6 +182,16 @@ XmlRpcServer::acceptConnection() { //this->close(); XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not accept connection (%s).", XmlRpcSocket::getErrorMsg().c_str()); + + // Note that there was an accept error; retry in 1 second + _accept_error = true; + _accept_retry_time_sec = _disp.getTime() + ACCEPT_RETRY_INTERVAL_SEC; + return 0; // Stop monitoring this FD + } + else if( countFreeFDs() < FREE_FD_BUFFER ) + { + XmlRpcSocket::close(s); + XmlRpcUtil::error("XmlRpcServer::acceptConnection: Rejecting client, not enough free file descriptors"); } else if ( ! XmlRpcSocket::setNonBlocking(s)) { @@ -167,6 +203,48 @@ XmlRpcServer::acceptConnection() XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: creating a connection"); _disp.addSource(this->createConnection(s), XmlRpcDispatch::ReadableEvent); } + return XmlRpcDispatch::ReadableEvent; // Continue to monitor this fd +} + +int XmlRpcServer::countFreeFDs() { + // NOTE(austin): this function is not free, but in a few small tests it only + // takes about 1.2mS when querying 50k file descriptors. + // + // If the underlying system calls here fail, this will print an error and + // return 0 + int free_fds = 0; + + struct rlimit limit = { .rlim_cur = 0, .rlim_max = 0 }; + + // Get the current soft limit on the number of file descriptors. + if(getrlimit(RLIMIT_NOFILE, &limit) == 0) { + + // Poll the available file descriptors. + // The POSIX specification guarantees that rlim_cur will always be less or + // equal to the process's initial rlim_max, so we don't need an additonal + // bounds check here. + if(poll(&pollfds[0], limit.rlim_cur, 1) >= 0) { + for(rlim_t i=0; i + * Loosely based on HelloServer.cpp and HelloClient.cpp by Chris Morley + * + */ + +#include "xmlrpcpp/XmlRpc.h" +#include "xmlrpcpp/XmlRpcClient.h" +#include "xmlrpcpp/XmlRpcServer.h" +#include "xmlrpcpp/XmlRpcServerMethod.h" + +#include +#include +#include +#include +#include + +#include + +using XmlRpc::XmlRpcServerMethod; +using XmlRpc::XmlRpcServer; +using XmlRpc::XmlRpcClient; +using XmlRpc::XmlRpcValue; + +// No arguments, result is "Hello". +class Hello : public XmlRpcServerMethod +{ +public: + Hello(XmlRpcServer* s) : XmlRpcServerMethod("Hello", s) {} + + void execute(XmlRpcValue& params, XmlRpcValue& result) + { + (void)params; + result = "Hello"; + } + + std::string help() + { + return std::string("Say hello"); + } +}; + +// One argument is passed, result is "Hello, " + arg. +class HelloName : public XmlRpcServerMethod +{ +public: + HelloName(XmlRpcServer* s) : XmlRpcServerMethod("HelloName", s) {} + + void execute(XmlRpcValue& params, XmlRpcValue& result) + { + std::string resultString = "Hello, "; + resultString += std::string(params[0]); + result = resultString; + } +}; + +// A variable number of arguments are passed, all doubles, result is their sum. +class Sum : public XmlRpcServerMethod +{ +public: + Sum(XmlRpcServer* s) : XmlRpcServerMethod("Sum", s) {} + + void execute(XmlRpcValue& params, XmlRpcValue& result) + { + int nArgs = params.size(); + double sum = 0.0; + for (int i = 0; i < nArgs; ++i) + sum += double(params[i]); + result = sum; + } +}; + +class XmlRpcTest : public ::testing::Test +{ +protected: + XmlRpcTest() : hello(&s), helloName(&s), sum(&s), port(0), done(false) {} + + void work() + { + while (!done) + { + s.work(0.1); // run the worker queue for 100ms + } + } + + virtual void SetUp() + { + // XmlRpc::setVerbosity(5); + + // Create the server socket. Passing 0 for the port number requests that + // the OS randomly select an available port. + s.bindAndListen(0); + // Retrieve the assigned port number. + port = s.get_port(); + + // Enable introspection. + s.enableIntrospection(true); + + // Start the worker thread. + server_thread = boost::thread(boost::mem_fn(&XmlRpcTest::work), this); + } + + virtual void TearDown() + { + // TODO(austin): determine if we need to do anything here to avoid + // leaking resources + done = true; + server_thread.join(); + s.shutdown(); + } + + // The server and its methods + XmlRpcServer s; + Hello hello; + HelloName helloName; + Sum sum; + + // Server port number (for clients) + int port; + + // Server thread + bool done; + boost::thread server_thread; +}; + +TEST_F(XmlRpcTest, Introspection) +{ + XmlRpcClient c("localhost", port); + + // Use introspection API to look up the supported methods + XmlRpcValue noArgs, result; + + ASSERT_TRUE(c.execute("system.listMethods", noArgs, result)); + + XmlRpcValue methods; + methods[0] = "Hello"; + methods[1] = "HelloName"; + methods[2] = "Sum"; + methods[3] = "system.listMethods"; + methods[4] = "system.methodHelp"; + methods[5] = "system.multicall"; + EXPECT_EQ(result, methods); + + // Use introspection API to get the help string for the Hello method + XmlRpcValue oneArg; + oneArg[0] = "Hello"; + + ASSERT_TRUE(c.execute("system.methodHelp", oneArg, result)); + + EXPECT_EQ(result, XmlRpcValue("Say hello")); + + // Use introspection API to get the help string for the HelloName method + // This should be the default help string, ie empty string. + oneArg[0] = "HelloName"; + + ASSERT_TRUE(c.execute("system.methodHelp", oneArg, result)); + + EXPECT_EQ(result, XmlRpcValue("")); +} + +TEST_F(XmlRpcTest, Hello) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + // Call the Hello method + ASSERT_TRUE(c.execute("Hello", noArgs, result)); + + EXPECT_EQ(result, XmlRpcValue("Hello")); +} + +TEST_F(XmlRpcTest, HelloURI) +{ + XmlRpcClient c("localhost", port, "/"); + XmlRpcValue noArgs, result; + + // Call the Hello method + ASSERT_TRUE(c.execute("Hello", noArgs, result)); + + EXPECT_EQ(result, XmlRpcValue("Hello")); +} + +TEST_F(XmlRpcTest, HelloName) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue oneArg, result; + + // Call the HelloName method + oneArg[0] = "Chris"; + ASSERT_TRUE(c.execute("HelloName", oneArg, result)); + + EXPECT_EQ(result, XmlRpcValue("Hello, Chris")); +} + +TEST_F(XmlRpcTest, Sum) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue result; + + // Add up an array of numbers + XmlRpcValue numbers; + numbers[0] = 33.33; + numbers[1] = 112.57; + numbers[2] = 76.1; + EXPECT_EQ(numbers.size(), 3); + + ASSERT_TRUE(c.execute("Sum", numbers, result)); + EXPECT_DOUBLE_EQ(double(result), 222.0); + + // Test the "no such method" fault + ASSERT_TRUE(c.execute("NoSuchMethod", numbers, result)); + EXPECT_TRUE(c.isFault()); + + XmlRpcValue fault; + fault["faultCode"] = -1; + fault["faultString"] = "NoSuchMethod: unknown method name"; + EXPECT_EQ(result, fault); +} + +TEST_F(XmlRpcTest, Multicall) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue result; + + // Test the multicall method. It accepts one arg, an array of structs + XmlRpcValue multicall, expected_result; + multicall[0][0]["methodName"] = "Sum"; + multicall[0][0]["params"][0] = 5.0; + multicall[0][0]["params"][1] = 9.0; + expected_result[0][0] = 14.0; + + multicall[0][1]["methodName"] = "NoSuchMethod"; + multicall[0][1]["params"][0] = ""; + expected_result[1]["faultCode"] = -1; + expected_result[1]["faultString"] = "NoSuchMethod: unknown method name"; + + multicall[0][2]["methodName"] = "Sum"; + // Missing params + expected_result[2]["faultCode"] = -1; + expected_result[2]["faultString"] = "system.multicall: Invalid argument " + "(expected a struct with members " + "methodName and params)"; + + multicall[0][3]["methodName"] = "Sum"; + multicall[0][3]["params"][0] = 10.5; + multicall[0][3]["params"][1] = 12.5; + expected_result[3][0] = 23.0; + + ASSERT_TRUE(c.execute("system.multicall", multicall, result)); + EXPECT_EQ(result, expected_result); + EXPECT_EQ(result.toXml(), expected_result.toXml()); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/utilities/xmlrpcpp/test/test_dispatch_live.cpp b/utilities/xmlrpcpp/test/test_dispatch_live.cpp new file mode 100644 index 0000000000..2cd23fda85 --- /dev/null +++ b/utilities/xmlrpcpp/test/test_dispatch_live.cpp @@ -0,0 +1,189 @@ +/* + * Unit tests for XmlRpc++ + * + * Copyright (C) 2017, Zoox Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Austin Hendrix + * Loosley based on HelloServer.cpp and HelloClient.cpp by Chris Morley + * + */ + +#include "xmlrpcpp/XmlRpc.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "test_fixtures.h" + +using namespace XmlRpc; + +TEST_F(XmlRpcTest, Hello) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + // Call the Hello method + ASSERT_TRUE(c.execute("Hello", noArgs, result)); + + EXPECT_FALSE(c.isFault()); + XmlRpcValue hello("Hello"); + EXPECT_EQ(result, hello); +} + +TEST_F(XmlRpcTest, HelloNonBlock) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + // Call the Hello method, non-blocking + ASSERT_TRUE(c.executeNonBlock("Hello", noArgs)); + + bool done = false; + for (int i = 0; i < 30; i++) + { + done = c.executeCheckDone(result); + if (done) + break; + // run the client's dispatch loop to service the respond when it comes back + c._disp.work(0.1); + } + + ASSERT_TRUE(done); + + XmlRpcValue hello("Hello"); + EXPECT_EQ(result, hello); +} + +TEST_F(XmlRpcTest, HelloNonBlock2) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + // Lock the hello mutex so that the service call cannot return immediately + hello.hello_mutex.lock(); + + // Call the Hello method, non-blocking + ASSERT_TRUE(c.executeNonBlock("Hello", noArgs)); + + bool done = false; + for (int i = 0; i < 100; i++) + { + done = c.executeCheckDone(result); + if (done) + break; + // run the client's dispatch loop to service the respond when it comes back + c._disp.work(0.1); + + // unlock the hello mutex after 10 cycles + if (i == 10) + hello.hello_mutex.unlock(); + } + + ASSERT_TRUE(done); + + XmlRpcValue hello("Hello"); + EXPECT_EQ(result, hello); +} + +TEST_F(XmlRpcTest, ClientDisconnect) +{ + XmlRpcClient* c = new XmlRpcClient("localhost", port); + XmlRpcValue noArgs, result; + + // Lock the hello mutex so that the service call cannot return immediately + hello.hello_mutex.lock(); + + // Call the Hello method, non-blocking + ASSERT_TRUE(c->executeNonBlock("Hello", noArgs)); + + // Destroy the client before the server can answer + delete c; + + // Unlock the mutex so the server can finish + hello.hello_mutex.unlock(); +} + +TEST_F(XmlRpcTest, ServerDisconnect) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + XmlRpc::setVerbosity(3); + + // Stop calling the work method on the server + server_done = true; + server_thread.join(); + + // Call the Hello method, non-blocking + ASSERT_TRUE(c.executeNonBlock("Hello", noArgs)); + + // Destroy the server before it can answer + s.shutdown(); + + // Run the client to completion + bool done = false; + for (int i = 0; i < 100; i++) + { + done = c.executeCheckDone(result); + if (done) + break; + // run the client's dispatch loop to service the respond when it comes back + c._disp.work(0.1); + } + + // The client should return true because the request is done, even though it + // timed out and wasn't able to complete. + EXPECT_TRUE(done); + EXPECT_EQ(-1, c.getfd()); + + EXPECT_EQ(result, XmlRpcValue()); // Expect empty result +} + +TEST_F(XmlRpcTest, ServerDisconnect2) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + // Stop calling the work method on the server + server_done = true; + server_thread.join(); + // Close the server socket to reads (ie incoming connections) + shutdown(s.getfd(), SHUT_RD); + + // Call the Hello method. Expect failure since the server socket is not + // accepting new connections. + ASSERT_FALSE(c.execute("Hello", noArgs, result)); + + XmlRpcValue hello; // Expect empty result + EXPECT_EQ(result, hello); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/utilities/xmlrpcpp/test/test_fixtures.cpp b/utilities/xmlrpcpp/test/test_fixtures.cpp new file mode 100644 index 0000000000..ad4ca87597 --- /dev/null +++ b/utilities/xmlrpcpp/test/test_fixtures.cpp @@ -0,0 +1,75 @@ +/* + * Unit tests for XmlRpc++ + * + * Copyright (C) 2017, Zoox Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Austin Hendrix + * Loosley based on HelloServer.cpp by Chris Morley + * + */ + +#include "test_fixtures.h" +// No arguments, result is "Hello". + +using namespace XmlRpc; + +void Hello::execute(XmlRpcValue& params, XmlRpcValue& result) +{ + (void)params; + boost::unique_lock lock(hello_mutex); + result = "Hello"; +} + +XmlRpcTest::XmlRpcTest() : hello(&s), port(0), server_done(false) {} + +void XmlRpcTest::work() +{ + while (!server_done) + { + s.work(0.1); // run the worker queue for 100ms + } +} + +void XmlRpcTest::SetUp() +{ + // XmlRpc::setVerbosity(5); + + // Create the server socket on the specified port + s.bindAndListen(0); + port = s.get_port(); + + // Enable introspection + s.enableIntrospection(true); + + // Start the worker thread + server_thread = boost::thread(boost::mem_fn(&XmlRpcTest::work), this); +} + +void XmlRpcTest::TearDown() +{ + // TODO(austin): determine if we need to do anything here to avoid + // leaking resources + server_done = true; + if (server_thread.joinable()) + { + server_thread.join(); + } + s.shutdown(); + + // Reset verbosity in case a test raises the verbosity. + XmlRpc::setVerbosity(0); +} diff --git a/utilities/xmlrpcpp/test/test_fixtures.h b/utilities/xmlrpcpp/test/test_fixtures.h new file mode 100644 index 0000000000..13d0a36059 --- /dev/null +++ b/utilities/xmlrpcpp/test/test_fixtures.h @@ -0,0 +1,64 @@ +/* + * Unit tests for XmlRpc++ + * + * Copyright (C) 2017, Zoox Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Austin Hendrix + * + */ + +#include "xmlrpcpp/XmlRpc.h" + +#include +#include +#include + +// No arguments, result is "Hello". +class Hello : public XmlRpc::XmlRpcServerMethod +{ +public: + Hello(XmlRpc::XmlRpcServer* s) : XmlRpc::XmlRpcServerMethod("Hello", s) {} + + virtual ~Hello() {} + + void execute(XmlRpc::XmlRpcValue& params, XmlRpc::XmlRpcValue& result); + + boost::mutex hello_mutex; +}; + +class XmlRpcTest : public ::testing::Test +{ +protected: + XmlRpcTest(); + + void work(); + + virtual void SetUp(); + + virtual void TearDown(); + + // The server and its methods + XmlRpc::XmlRpcServer s; + Hello hello; + + // Server port number (for clients) + int port; + + // Server thread + bool server_done; + boost::thread server_thread; +}; diff --git a/utilities/xmlrpcpp/test/test_ulimit.cpp b/utilities/xmlrpcpp/test/test_ulimit.cpp new file mode 100644 index 0000000000..845cc4dee4 --- /dev/null +++ b/utilities/xmlrpcpp/test/test_ulimit.cpp @@ -0,0 +1,78 @@ +/* + * Unit tests for XmlRpc++ + * + * Copyright (C) 2017, Zoox Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Austin Hendrix + * + */ + +#include "test_fixtures.h" + +#include + +using namespace XmlRpc; + +TEST_F(XmlRpcTest, Ulimit) +{ + XmlRpcClient c("localhost", port); + XmlRpcValue noArgs, result; + + // Call the Hello method + ASSERT_TRUE(c.execute("Hello", noArgs, result)); + + EXPECT_FALSE(c.isFault()); + XmlRpcValue hello("Hello"); + EXPECT_EQ(result, hello); + + c.close(); + result.clear(); + + // Get the current open file limits and check that we have a reasonable + // margin. We need to reduce the limit to 8 open files to starve the server + // side, so we would need 9 or 10 open files for it to work correctly + // Ensuring that we have a hard limit of at least 64 file descriptors gives + // a very wide margin above that. + struct rlimit limit = {.rlim_cur = 0, .rlim_max = 0}; + ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &limit)); + ASSERT_LT(64, limit.rlim_max); + ASSERT_LT(64, limit.rlim_cur); + + // Reduce the number of open file descriptors so that we can create a client + // but can't accept the connection on the server side. 32 is more than the + // number of currently open files, but less than minimum unused file + // descriptors. We expect the server to be able to accept the connection and + // then immediately reject it without servicing it. + limit.rlim_cur = 32; + ASSERT_EQ(0, setrlimit(RLIMIT_NOFILE, &limit)); + + XmlRpcClient c2("127.0.0.1", port); + EXPECT_FALSE(c2.execute("Hello", noArgs, result)); + + // Raise the limit and verify that clients can connect again + limit.rlim_cur = limit.rlim_max; + ASSERT_EQ(0, setrlimit(RLIMIT_NOFILE, &limit)); + c2.close(); + EXPECT_TRUE(c2.execute("Hello", noArgs, result)); + EXPECT_EQ(result, hello); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}