Skip to content
Snippets Groups Projects
TimeoutTest.cpp 3.68 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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 "Common.hpp"
    #include "CountingPrivateSemaphore.hpp"
    #include "Debug.hpp"
    #include "Future.hpp"
    #include "emper.hpp"
    
    #include "fixtures/assert.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);
    
    	// 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();
    }