// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2010, Matthew E. Cross <matt.cross@gmail.com>
 *
 * Code to reproduce an aio lockup.
 *
 * Make a test file that is at least 4MB long.  Something like this:
 * 'dd if=/dev/zero of=/tmp/testfile bs=1M count=10'
 *
 * Run this test as './aio_test 0 100 /tmp/testfile' to induce the
 * failure.
 *
 * Run this test as './aio_test 1 100 /tmp/testfile' to demonstrate an
 * incomplete workaround (close fd, then wait for all io to complete
 * on an io context before calling io_destroy()).  This still induces
 * the failure.
 *
 * This test was written several years ago by Matt Cross, and he has
 * graciously allowed me to post it for inclusion in xfstests.
 *
 * Changelog
 * - reduce output and make it consistent for integration into xfstests (JEM)
 * - run for fixed amount of time instead of indefinitely (JEM)
 * - change coding style to meet xfstests standards (JEM)
 * - get rid of unused code (workaround 2 documented above) (JEM)
 * - use posix_memalign (JEM)
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* to get definition of O_DIRECT flag. */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <pthread.h>
#include <unistd.h>
#include <libaio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sched.h>

#undef DEBUG
#ifdef DEBUG
#define dprintf(fmt, args...) printf(fmt, ##args)
#else
#define dprintf(fmt, args...)
#endif

char *filename;
int wait_for_events = 0;

pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
unsigned long total_loop_count = 0;

#define NUM_IOS 16
#define IOSIZE (1024 * 64)

pid_t
gettid(void)
{
	return (pid_t)syscall(SYS_gettid);
}

void *
aio_test_thread(void *data)
{
	int fd = -1;
	io_context_t ioctx;
	int ioctx_initted;
	int ios_submitted;
	struct iocb iocbs[NUM_IOS];
	int i;
	static unsigned char *buffer;
	int ret;
	long mycpu = (long)data;
	pid_t mytid = gettid();
	cpu_set_t cpuset;

	dprintf("setting thread %d to run on cpu %ld\n", mytid, mycpu);

	/*
	 * Problems have been easier to trigger when spreading the
	 * workload over the available CPUs.
	 */
	CPU_ZERO(&cpuset);
	CPU_SET(mycpu, &cpuset);
	if (sched_setaffinity(mytid, sizeof(cpuset), &cpuset)) {
		printf("FAILED to set thread %d to run on cpu %ld\n",
		       mytid, mycpu);
	}

	ioctx_initted = 0;
	ios_submitted = 0;

	ret = posix_memalign((void **)&buffer, getpagesize(), IOSIZE * NUM_IOS);
	if (ret != 0) {
		printf("%lu: Failed to allocate buffer for IO: %d\n",
		       pthread_self(), ret);
		goto done;
	}

	while (1) {
		fd = open(filename, O_RDONLY | O_DIRECT);
		if (fd < 0) {
			printf("%lu: Failed to open file '%s'\n",
			       pthread_self(), filename);
			goto done;
		}

		memset(&ioctx, 0, sizeof(ioctx));
		if (io_setup(NUM_IOS, &ioctx)) {
			printf("%lu: Failed to setup io context\n",
			       pthread_self());
			goto done;
		}
		ioctx_initted = 1;

		if (mycpu != 0) {
			for (i = 0; i < NUM_IOS; i++) {
				struct iocb *iocb = &iocbs[i];

				memset(iocb, 0, sizeof(*iocb));
				io_prep_pread(iocb, fd, (buffer + i * IOSIZE),
					      IOSIZE, i * IOSIZE);
				if (io_submit(ioctx, 1, &iocb) != 1) {
					printf("%lu: failed to submit io #%d\n",
						pthread_self(), i+1);
				}
			}
			ios_submitted = 1;
		}

done:
		if (fd >= 0)
			close(fd);

		if (wait_for_events && ios_submitted) {
			struct io_event io_events[NUM_IOS];

			if (io_getevents(ioctx, NUM_IOS, NUM_IOS,
					 io_events, NULL) != NUM_IOS)
				printf("io_getevents failed to wait for all IO\n");
		}

		if (ioctx_initted) {
			io_destroy(ioctx);
			ioctx_initted = 0;
		}

		if (ios_submitted) {
			pthread_mutex_lock(&count_mutex);
			total_loop_count++;
			pthread_mutex_unlock(&count_mutex);

			ios_submitted = 0;
		}
	}
}

int
main(int argc, char **argv)
{
	unsigned num_threads;
	unsigned i;
	int fd;
	pthread_t *threads;
	long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
	struct timeval start, now, delta = { 0, 0 };

	if (argc != 4) {
		printf("Usage: aio_test [wait for events?] [# of threads] "
		       "[filename]\n");
		return -1;
	}

	wait_for_events = strtoul(argv[1], NULL, 0);
	num_threads = strtoul(argv[2], NULL, 0);
	filename = argv[3];

	printf("wait_for_events: %d\n", wait_for_events);
	printf("num_threads: %u\n", num_threads);
	printf("filename: '%s'\n", basename(filename));

	if (num_threads < 1) {
		printf("Number of threads is invalid, must be at least 1\n");
		return -1;
	}

	fd = open(filename, O_RDONLY|O_DIRECT);
	if (fd < 0) {
		printf("Failed to open filename '%s' for reading\n", filename);
		return -1;
	}
	close(fd);

	threads = malloc(sizeof(pthread_t) * num_threads);
	if (threads == NULL) {
		printf("Failed to allocate thread id storage\n");
		return -1;
	}

	for (i = 0; i < num_threads; i++) {
		if (pthread_create(&threads[i], NULL,
				   aio_test_thread, (void *)(i % ncpus))) {
			printf("Failed to create thread #%u\n", i+1);
			threads[i] = (pthread_t)-1;
		}
	}

	printf("All threads spawned\n");
	gettimeofday(&start, NULL);

	while (delta.tv_sec < 60) {
		sleep(1);
		gettimeofday(&now, NULL);
		timersub(&now, &start, &delta);
		dprintf("%lu loops completed in %ld seconds\n",
			total_loop_count, delta.tv_sec);
	}

	return 0;
}
