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/src/XmlRpcServer.cpp b/utilities/xmlrpcpp/src/XmlRpcServer.cpp index 2515142ce5..5a86aa4832 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; iname()] = method; } // Remove a command from the RPC server -void +void XmlRpcServer::removeMethod(XmlRpcServerMethod* method) { MethodMap::iterator i = _methods.find(method->name()); @@ -48,7 +72,7 @@ XmlRpcServer::removeMethod(XmlRpcServerMethod* method) } // Remove a command from the RPC server by name -void +void XmlRpcServer::removeMethod(const std::string& methodName) { MethodMap::iterator i = _methods.find(methodName); @@ -58,7 +82,7 @@ XmlRpcServer::removeMethod(const std::string& methodName) // Look up a method by name -XmlRpcServerMethod* +XmlRpcServerMethod* XmlRpcServer::findMethod(const std::string& name) const { MethodMap::const_iterator i = _methods.find(name); @@ -70,7 +94,7 @@ XmlRpcServer::findMethod(const std::string& name) const // Create a socket, bind to the specified port, and // set it in listen mode to make it available for clients. -bool +bool XmlRpcServer::bindAndListen(int port, int backlog /*= 5*/) { int fd = XmlRpcSocket::socket(); @@ -126,10 +150,13 @@ XmlRpcServer::bindAndListen(int port, int backlog /*= 5*/) // Process client requests for the specified time -void +void XmlRpcServer::work(double msTime) { XmlRpcUtil::log(2, "XmlRpcServer::work: waiting for a connection"); + if(_accept_error && _disp.getTime() > _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..ee331e9c31 --- /dev/null +++ b/utilities/xmlrpcpp/test/test_dispatch_live.cpp @@ -0,0 +1,180 @@ +/* + * 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..02487bda75 --- /dev/null +++ b/utilities/xmlrpcpp/test/test_fixtures.cpp @@ -0,0 +1,69 @@ +/* + * 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..a74802c1af --- /dev/null +++ b/utilities/xmlrpcpp/test/test_fixtures.h @@ -0,0 +1,62 @@ +/* + * 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..6ff621e3aa --- /dev/null +++ b/utilities/xmlrpcpp/test/test_ulimit.cpp @@ -0,0 +1,77 @@ +/* + * 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(); +}