diff --git a/tests/io/CancelFutureTest.cpp b/tests/io/CancelFutureTest.cpp index bdd3bc9058f9093611fafe1071d22cef2062fb47..822ae13c51c145a29f1689efa9afa75a6ad465b2 100644 --- a/tests/io/CancelFutureTest.cpp +++ b/tests/io/CancelFutureTest.cpp @@ -4,11 +4,18 @@ #include <cerrno> // for ECANCELED, ETIME #include <cstdint> // for uint64_t, int32_t +#include <memory> #include "Common.hpp" +#include "CountingPrivateSemaphore.hpp" #include "Emper.hpp" +#include "Runtime.hpp" +#include "Semaphore.hpp" +#include "emper.hpp" #include "fixtures/assert.hpp" -#include "io/Future.hpp" // for ReadFuture, WriteFuture +#include "io.hpp" +#include "io/Future.hpp" +#include "lib/LinuxVersion.hpp" using emper::io::ReadFuture; using emper::io::WriteFuture; @@ -75,6 +82,69 @@ static void cancelPartialCompletedChain() { ASSERT(readFuture.wait() == sizeof(write_buf) && read_buf == write_buf); } +/** + * @brief Cancel a lot of read futures hopefully going through scheduleOn + * + * The test uses one fiber per worker. Each fiber creates an eventfd, prepares + * and submits a ReadFuture. Then each Fiber yields and creates a second ReadFuture. + * After all Fibers are done with this preparation each will issue a write to the + * next eventfd. + * Each fiber yields a last time to change the worker and exit resulting in + * cancellation of two Futures one possible completed and one definitely outstanding. + */ +static void massCancelOnDifferentWorker() { + const unsigned fiberCount = Runtime::getRuntime()->getWorkerCount() * 5; + int* evfds = new int[fiberCount]; + emper::Semaphore readySem; + emper::Semaphore startSem; + + CPS cps; + for (unsigned i = 0; i < fiberCount; ++i) { + spawn( + [&, i = i] { + uint64_t rbuf = 0; + const uint64_t wbuf = i; + + evfds[i] = eventfd(0, 0); + + ReadFuture rf(evfds[i], &rbuf, sizeof(rbuf), 0); + rf.submit(); + + emper::yield(); + + ReadFuture rf2(evfds[i], &rbuf, sizeof(rbuf), 0); + rf2.submit(); + + readySem.release(); + startSem.acquire(); + + WriteFuture wf(evfds[(i + 1) % fiberCount], &wbuf, sizeof(wbuf), 0); + wf.submitAndWait(); + + emper::yield(); + rf.cancel(); + rf2.cancel(); + }, + cps); + } + + for (unsigned i = 0; i < fiberCount; ++i) { + readySem.acquire(); + } + + for (unsigned i = 0; i < fiberCount; ++i) { + startSem.release(); + } + cps.wait(); + + CPS closeCps; + for (unsigned i = 0; i < fiberCount; ++i) { + spawn([&, i = i] { emper::io::closeAndForget(evfds[i]); }, closeCps); + } + closeCps.wait(); + delete[] evfds; +} + void emperTest() { efd = eventfd(0, 0); if (efd == -1) { @@ -87,4 +157,12 @@ void emperTest() { cancelCompleted(); cancelNotCompletedChain(); cancelPartialCompletedChain(); + + // Async cancelation is racy before linux 5.17. + // Work may be not found at all or only the work is cancelled but + // not the armed poll. + // https://lore.kernel.org/io-uring/20220119024241.609233-1-axboe@kernel.dk/T/#t + if (EMPER_LINUX_GE("5.17.0")) { + massCancelOnDifferentWorker(); + } }