/*
* tests/persist/cog-simple/SimpleServerHangUTest.cxxtest
*
* Test that server shutdown works correctly even when client connections
* are not properly closed (e.g., due to exceptions).
*
* Previously, this would hang because:
* 1. Client opens TCP connection to CogServer
* 2. An exception is thrown (connection not closed)
* 3. Server handler thread is blocked in recv() waiting for client data
* 4. In destructor, srvr->stop() is called
* 5. NetworkServer::stop() tries to join() handler threads
* 6. Handler thread blocked in recv() couldn't exit -> join() hung
*
* The fix: SocketManager::network_gone() now closes all sockets,
* causing handler threads to receive an error and exit their loops.
*
* Copyright (C) 2008, 2009, 2013, 2020, 2025 Linas Vepstas <linasvepstas@gmail.com>
* All Rights Reserved
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <cstdio>
#include <opencog/atoms/atom_types/atom_types.h>
#include <opencog/persist/cog-types/atom_types.h>
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atoms/value/VoidValue.h>
#include <opencog/persist/cog-simple/CogSimpleStorage.h>
#include <opencog/util/Logger.h>
#include "../TestCogServer.h"
using namespace opencog;
class ServerHangUTest : public CxxTest::TestSuite
{
private:
AtomSpacePtr _as;
std::string uri;
DECLARE_TEST_COGSERVER
public:
ServerHangUTest(void)
{
logger().set_level(Logger::INFO);
logger().set_print_to_stdout_flag(true);
uri = "cog://localhost:16314";
INIT_TEST_COGSERVER(16314);
printf("Started CogServer on port 16314\n");
}
~ServerHangUTest()
{
// This used to hang if _as still held an open connection.
// The fix in SocketManager::network_gone() now closes all
// sockets, allowing handler threads to exit.
printf("Stopping CogServer...\n");
STOP_TEST_COGSERVER
printf("Server stopped.\n");
logger().flush();
// erase the log file if no assertions failed
if (!CxxTest::TestTracker::tracker().suiteFailed())
std::remove(logger().get_filename().c_str());
}
void setUp(void)
{
_as = nullptr;
}
void tearDown(void)
{
// Clean up the atomspace. This closes the storage connection.
// Previously, if we forgot this (or an exception bypassed it),
// the destructor would hang. Now server shutdown handles it.
_as = nullptr;
}
void test_no_hang(void);
};
#define an _as->add_node
#define al _as->add_link
#define CONCEPT CONCEPT_NODE
// This test verifies that server shutdown works even when an exception
// is thrown and the client doesn't properly close its connection.
//
// The test:
// 1. Opens a connection to the CogServer
// 2. Triggers an exception (UniqueLink cannot be instantiated)
// 3. Catches the exception (simulating exception-safe code)
// 4. Does NOT explicitly close the connection
// 5. Server shutdown should still work (no hang)
//
void ServerHangUTest::test_no_hang(void)
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
_as = createAtomSpace();
Handle hsn = _as->add_node(COG_SIMPLE_STORAGE_NODE, std::string(uri));
StorageNodePtr store = StorageNodeCast(hsn);
store->setValue(_as->add_node(PREDICATE_NODE, "*-open-*"), createVoidValue());
TS_ASSERT(store->connected());
printf("Triggering an exception (UniqueLink cannot be instantiated)...\n");
// This will throw an exception. We catch it to continue the test.
bool caught_exception = false;
try
{
// All of these will throw, but only the first one gets a chance.
// The others are here just to look pretty.
al(UNIQUE_LINK, an(CONCEPT, "foo"), an(CONCEPT, "boink"));
al(DEFINE_LINK, an(CONCEPT, "foo"), an(CONCEPT, "bar"));
al(DEFINE_LINK, an(CONCEPT, "foo"), an(CONCEPT, "bummer"));
al(EQUAL_LINK, an(CONCEPT, "foo"), an(CONCEPT, "bar"));
al(IDENTICAL_LINK, an(CONCEPT, "foo"), an(CONCEPT, "bar"));
al(PRESENT_LINK, an(CONCEPT, "something"));
al(ABSENT_LINK, an(CONCEPT, "something"));
}
catch (const std::exception& e)
{
caught_exception = true;
printf("Caught expected exception: %s\n", e.what());
}
TS_ASSERT(caught_exception);
// Intentionally do NOT call store->close() here.
// The server shutdown should still work because network_gone()
// now closes all sockets.
printf("Test complete. Server shutdown will occur in destructor.\n");
logger().info("END TEST: %s", __FUNCTION__);
}
/* ============================= END OF FILE ================= */