Skip to content
Snippets Groups Projects
TimeoutTest.cpp 3.68 KiB
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright © 2021 Florian Fischer
#include <netinet/in.h>
#include <sys/eventfd.h>
#include <sys/socket.h>

#include <cerrno>
#include <cstdint>
#include <cstring>
#include <memory>

#include "Common.hpp"
#include "CountingPrivateSemaphore.hpp"
#include "Debug.hpp"
#include "Future.hpp"
#include "emper.hpp"
#include "fixtures/assert.hpp"
#include "io.hpp"
#include "lib/LinuxVersion.hpp"

using emper::io::ReadFuture;
using emper::io::TimeoutWrapper;

static void setupSockPair(int& sock1, int& sock2) {
	const int PORT = 4242;

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(PORT);

	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock == -1) DIE_MSG_ERRNO("creating listen socket failed");

	int enable = 1;
	if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1)
		DIE_MSG_ERRNO("setsockopt SO_REUSEADDR failed");
	if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1)
		DIE_MSG_ERRNO("setsockopt SO_REUSEPORT failed");

	if (bind(listen_sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
		DIE_MSG_ERRNO("bind failed");

	if (listen(listen_sock, 1) != 0) DIE_MSG_ERRNO("listen failed");

	sock2 = socket(AF_INET, SOCK_STREAM, 0);
	if (sock2 == -1) DIE_MSG_ERRNO("creating client socket failed");

	CPS cps;
	spawn(
			[&]() {
				if ((sock1 = emper::io::acceptAndWait(listen_sock, nullptr, nullptr)) == -1)
					DIE_MSG_ERRNO("accept failed");
			},
			cps);

	if (emper::io::connectAndWait(sock2, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) ==
			-1)
		DIE_MSG_ERRNO("connect failed");

	cps.wait();

	emper::io::closeAndForget(listen_sock);
}

void sockTest() {
	int sock1, sock2;
	DBG("setup sockets");
	setupSockPair(sock1, sock2);

	uint64_t recvBuf;
	DBG("submit recv");
	TimeoutWrapper::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
	ssize_t res = emper::io::recvAndTryWait(sock1, &recvBuf, sizeof(recvBuf), 0, ts);
	ASSERT(res == -1);
	ASSERT(errno == ECANCELED);

	// TODO: find a way to test sendAndTryWait
	// // allocate a huge buffer which is surely bigger then the sockets buffer and
	// // thus causing the send to block triggering the timeout
	// const ssize_t MEMB = 1 << 20;
	// auto* sendBuf = new char[MEMB];
	// DBG("submit send");
	// res = emper::io::sendAndTryWait(sock1, &sendBuf, MEMB, 0, ts, true);
	// ASSERT(res == -1);
	// ASSERT(errno == ECANCELED);
	// delete[] sendBuf;
	emper::io::closeAndForget(sock1);
	emper::io::closeAndForget(sock2);
}

void readTest() {
	int efd = eventfd(0, EFD_SEMAPHORE);
	if (efd == -1) {
		DIE_MSG_ERRNO("eventfd failed");
	}

	uint64_t readBuf;
	TimeoutWrapper::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
	ssize_t res = emper::io::readFileAndTryWait(efd, &readBuf, sizeof(readBuf), ts);
	ASSERT(res == -1);
	ASSERT(errno == ECANCELED);

	emper::io::closeAndForget(efd);
}

void writeTest() {
	int efd = eventfd(0, EFD_SEMAPHORE);
	if (efd == -1) {
		DIE_MSG_ERRNO("eventfd failed");
	}

	// fill up the eventfd
	uint64_t writeBuf = 0xfffffffffffffffe;
	if (emper::io::writeFileAndWait(efd, &writeBuf, sizeof(writeBuf)) == -1) {
		DIE_MSG("eventfd prep write failed");
	}

	writeBuf = 1;
	TimeoutWrapper::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
	ssize_t res = emper::io::writeFileAndTryWait(efd, &writeBuf, sizeof(writeBuf), ts);
	ASSERT(res == -1);
	// write requests can't be canceled when in execution so this
	// will return as interupted
	const int err = errno;
	ASSERT(err == EINTR || (EMPER_LINUX_GE("5.16.0") && err == ECANCELED));

	emper::io::closeAndForget(efd);
}

void emperTest() {
	sockTest();
	readTest();
	writeTest();
}