OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace-cog / tests / persist / cog-simple / SimpleServerHangUTest.cxxtest

149 lines · cxxtest ref:
/*
 * 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 ================= */