[LTP] [PATCH] aio_cancel_5-1: Rewrite test

Martin Doucha mdoucha@suse.cz
Tue Jan 13 16:01:55 CET 2026


The test schedules multiple async writes into a file and then hopes that
at least one will block long enough that aio_cancel() will fail to cancel
it. Use a socket pair instead of a file to force async writes to block
indefinitely. Then wait for one of the writes to actually block on full
socket buffer. This fixes a race condition where aio_cancel() could be
called while no write is actually in progress (before the first write,
after the last one or between two writes) and the test would fail.

Also rewrite result checks to verify that all writes before the blocked
one actually completed and all writes after the blocked one were cancelled.

Add cleanup helper function that will flush socket buffers, free allocated
memory and close the sockets. Also make setup and cleanup simpler
by statically allocating the aiocb structure array.

Signed-off-by: Martin Doucha <mdoucha@suse.cz>
---

Race condition fix using the same socket approach as the earlier rewrite
of aio_cancel_7-1. Tested on kernel v4.4 and v6.12.

 .../conformance/interfaces/aio_cancel/5-1.c   | 185 +++++++++++-------
 1 file changed, 119 insertions(+), 66 deletions(-)

diff --git a/testcases/open_posix_testsuite/conformance/interfaces/aio_cancel/5-1.c b/testcases/open_posix_testsuite/conformance/interfaces/aio_cancel/5-1.c
index 5ed4f6d42..559625b13 100644
--- a/testcases/open_posix_testsuite/conformance/interfaces/aio_cancel/5-1.c
+++ b/testcases/open_posix_testsuite/conformance/interfaces/aio_cancel/5-1.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2004, Bull SA. All rights reserved.
+ * Copyright (c) 2025 SUSE LLC
  * Created by:  Laurent.Vivier@bull.net
  * This file is licensed under the GPL license.  For the full content
  * of this license, see the COPYING file at the top level of this
@@ -13,7 +14,7 @@
  *
  * method:
  *
- *	queue a some aio_write() to a file descriptor
+ *	queue some aio_write() to a file descriptor
  *	cancel all operations for this file descriptor
  *	for all operations not canceled at end of operations
  *	verify that values in aiocb is the good ones
@@ -28,117 +29,169 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include <sys/stat.h>
-#include <fcntl.h>
 #include <string.h>
 #include <errno.h>
 #include <stdlib.h>
+#include <time.h>
 #include <aio.h>
+#include <sys/socket.h>
 
 #include "posixtest.h"
-#include "tempfile.h"
 
 #define TNAME "aio_cancel/5-1.c"
 
-#define BUF_NB		128
-#define BUF_SIZE	1024
+#define BUF_NB		8
+#define BLOCKED_TASK	2
+
+static int fds[2];
+static struct aiocb aiocb[BUF_NB];
+
+static void cleanup(void)
+{
+	int i, ret;
+
+	for (i = 0; i < BUF_NB; i++) {
+		if (!aiocb[i].aio_buf)
+			break;
+
+		ret = aio_error(&aiocb[i]);
+
+		/* flush written data from the socket */
+		if (ret == 0 || ret == EINPROGRESS) {
+			read(fds[1], (void *)aiocb[i].aio_buf,
+				aiocb[i].aio_nbytes);
+		}
+
+		free((void *)aiocb[i].aio_buf);
+	}
+
+	close(fds[0]);
+	close(fds[1]);
+}
 
 int main(void)
 {
-	char tmpfname[PATH_MAX];
-	int fd;
-	struct aiocb *aiocb[BUF_NB];
 	char *buf[BUF_NB];
 	int i;
-	int in_progress;
-	static int check_one;
+	int ret;
+	int bufsize;
+	int exp_ret;
+	socklen_t argsize = sizeof(bufsize);
+	const struct timespec sleep_ts = { .tv_sec = 0, .tv_nsec = 10000000 };
 
 	if (sysconf(_SC_ASYNCHRONOUS_IO) < 200112L)
 		return PTS_UNSUPPORTED;
 
-	PTS_GET_TMP_FILENAME(tmpfname, "pts_aio_cancel_5_1");
-	unlink(tmpfname);
-	fd = open(tmpfname, O_CREAT | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
-	if (fd == -1) {
-		printf(TNAME " Error at open(): %s\n", strerror(errno));
+	ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, fds);
+	if (ret == -1) {
+		printf(TNAME " Error creating sockets(): %s\n",
+			strerror(errno));
+		return PTS_UNRESOLVED;
+	}
+
+	ret = getsockopt(fds[0], SOL_SOCKET, SO_SNDBUF, &bufsize, &argsize);
+	if (ret == -1) {
+		printf(TNAME " Error reading socket buffer size: %s\n",
+			strerror(errno));
+		cleanup();
 		return PTS_UNRESOLVED;
 	}
 
-	unlink(tmpfname);
+	/* Socket buffer size is twice the maximum message size */
+	bufsize /= 2;
 
 	/* create AIO req */
-
 	for (i = 0; i < BUF_NB; i++) {
-		aiocb[i] = calloc(1, sizeof(struct aiocb));
-		if (aiocb[i] == NULL) {
-			printf(TNAME " Error at malloc(): %s\n",
-			       strerror(errno));
-			return PTS_UNRESOLVED;
-		}
-		buf[i] = malloc(BUF_SIZE);
+		buf[i] = malloc(bufsize);
 		if (buf[i] == NULL) {
 			printf(TNAME " Error at malloc(): %s\n",
-			       strerror(errno));
+				strerror(errno));
+			cleanup();
 			return PTS_UNRESOLVED;
 		}
-		aiocb[i]->aio_fildes = fd;
-		aiocb[i]->aio_buf = buf[i];
-		aiocb[i]->aio_nbytes = BUF_SIZE;
-		aiocb[i]->aio_offset = 0;
-		aiocb[i]->aio_sigevent.sigev_notify = SIGEV_NONE;
+		aiocb[i].aio_fildes = fds[0];
+		aiocb[i].aio_buf = buf[i];
+		aiocb[i].aio_nbytes = bufsize;
+		aiocb[i].aio_offset = 0;
+		aiocb[i].aio_sigevent.sigev_notify = SIGEV_NONE;
 
-		if (aio_write(aiocb[i]) == -1) {
+		if (aio_write(&aiocb[i]) == -1) {
 			printf(TNAME " loop %d: Error at aio_write(): %s\n",
-			       i, strerror(errno));
+				i, strerror(errno));
+			cleanup();
 			return PTS_FAIL;
 		}
 	}
 
-	/* try to cancel all
-	 * we hope to have enough time to cancel at least one
-	 */
+	/* wait for write #2 to start and get blocked by full socket buffer */
+	for (i = 0; i < 1000; i++) {
+		ret = aio_error(&aiocb[BLOCKED_TASK - 1]);
+		nanosleep(&sleep_ts, NULL);
 
-	if (aio_cancel(fd, NULL) == -1) {
+		if (ret <= 0)
+			break;
+	}
+
+	if (ret) {
+		printf(TNAME " Task #%d failed to complete: %s\n",
+			BLOCKED_TASK - 1, strerror(ret == -1 ? errno : ret));
+		cleanup();
+		return PTS_FAIL;
+	}
+
+	/* try to cancel all */
+	ret = aio_cancel(fds[0], NULL);
+	if (ret == -1) {
 		printf(TNAME " Error at aio_cancel(): %s\n", strerror(errno));
+		cleanup();
 		return PTS_FAIL;
 	}
 
-	close(fd);
+	if (ret != AIO_NOTCANCELED) {
+		printf(TNAME " Unexpected aio_cancel() return value: %s\n",
+			strerror(ret));
+		cleanup();
+		return PTS_FAIL;
+	}
 
-	check_one = 0;
-	do {
-		in_progress = 0;
-		for (i = 0; i < BUF_NB; i++) {
-			int ret;
+	for (i = 0, exp_ret = 0; i < BUF_NB; i++) {
+		ret = (aio_error(&aiocb[i]));
 
-			ret = (aio_error(aiocb[i]));
+		if (i == BLOCKED_TASK) {
+			if (ret != EINPROGRESS) {
+				printf(TNAME " Bad task #%d result: %s "
+					"(expected EINPROGRESS)\n",
+					i, strerror(ret));
+				cleanup();
+				return PTS_FAIL;
+			}
 
-			if (ret == -1) {
-				printf(TNAME " Error at aio_error(): %s\n",
-				       strerror(errno));
+			/* check iocb is not modified */
+			if ((aiocb[i].aio_fildes != fds[0]) ||
+				(aiocb[i].aio_buf != buf[i]) ||
+				(aiocb[i].aio_nbytes != (size_t)bufsize) ||
+				(aiocb[i].aio_offset != 0) ||
+				(aiocb[i].aio_sigevent.sigev_notify !=
+				SIGEV_NONE)) {
+
+				printf(TNAME " aiocbp modified\n");
+				cleanup();
 				return PTS_FAIL;
-			} else if ((ret == EINPROGRESS) || (ret == 0)) {
-				if (ret == EINPROGRESS)
-					in_progress = 1;
-
-				check_one = 1;
-
-				/* check iocb is not modified */
-
-				if ((aiocb[i]->aio_fildes != fd) ||
-				    (aiocb[i]->aio_buf != buf[i]) ||
-				    (aiocb[i]->aio_nbytes != BUF_SIZE) ||
-				    (aiocb[i]->aio_offset != 0) ||
-				    (aiocb[i]->aio_sigevent.sigev_notify !=
-				     SIGEV_NONE)) {
-					printf(TNAME " aiocbp modified\n");
-					return PTS_FAIL;
-				}
 			}
+
+			exp_ret = ECANCELED;
+			continue;
 		}
-	} while (in_progress);
 
-	if (!check_one)
-		return PTS_UNRESOLVED;
+		if (ret != exp_ret) {
+			printf(TNAME " Bad task #%d result: %s (expected %s)\n",
+				i, strerror(ret), strerror(exp_ret));
+			cleanup();
+			return PTS_FAIL;
+		}
+	}
 
+	cleanup();
+	printf("Test PASSED\n");
 	return PTS_PASS;
 }
-- 
2.51.0



More information about the ltp mailing list