aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/liburing/test/linked-defer-close.c
blob: 600887398fbdb7287492bb81a8654c5943347cc8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#include "../config-host.h"
/* SPDX-License-Identifier: MIT */
/*
 * Test that the final close of a file does indeed get it closed, if the
 * ring is setup with DEFER_TASKRUN and the task is waiting in cqring_wait()
 * during. Also see:
 *
 * https://github.com/axboe/liburing/issues/1235
 *
 * for a bug report, and the zig code on which this test program is based.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>
#include <pthread.h>

#include "liburing.h"
#include "helpers.h"

enum {
	IS_ACCEPT = 0,
	IS_SEND = 0x100,
	IS_SEND2 = 0x101,
	IS_SEND3 = 0x102,
	IS_CLOSE = 0x200,
};

struct thread_data {
	int parent_pid;
};

static void *thread_fn(void *__data)
{
	struct thread_data *data = __data;
	struct sockaddr_in saddr;
	int sockfd, ret;
	char msg[64];

	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(9999);
	inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr);

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		perror("socket");
		goto done;
	}

	ret = connect(sockfd, (struct sockaddr *) &saddr, sizeof(saddr));
	if (ret < 0) {
		perror("connect");
		close(sockfd);
		goto done;
	}

	do {
		memset(msg, 0, sizeof(msg));
		ret = recv(sockfd, msg, sizeof(msg), 0);
	} while (ret > 0);

	close(sockfd);
done:
	kill(data->parent_pid, SIGUSR1);
	return NULL;
}

/* we got SIGUSR1, exit normally */
static void sig_usr1(int sig)
{
	exit(T_EXIT_PASS);
}

/* timed out, failure */
static void sig_timeout(int sig)
{
	exit(T_EXIT_FAIL);
}

int main(int argc, char *argv[])
{
	struct io_uring ring;
	struct io_uring_sqe *sqe;
	struct io_uring_cqe *cqe;
	struct sockaddr_in saddr;
	char *msg1 = "message number 1\n";
	char *msg2 = "message number 2\n";
	char *msg3 = "message number 3\n";
	int val, send_fd, ret, sockfd;
	struct sigaction act[2] = { };
	struct thread_data td;
	pthread_t thread;

	if (argc > 1)
		return T_EXIT_SKIP;

	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	saddr.sin_port = htons(9999);

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		perror("socket");
		return T_EXIT_FAIL;
	}

	val = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

	ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
	if (ret < 0) {
		perror("bind");
		close(sockfd);
		return T_EXIT_FAIL;
	}

	ret = listen(sockfd, 1);
	if (ret < 0) {
		perror("listen");
		close(sockfd);
		return T_EXIT_FAIL;
	}

	ret = io_uring_queue_init(8, &ring, IORING_SETUP_SINGLE_ISSUER |
					    IORING_SETUP_DEFER_TASKRUN);
	if (ret == -EINVAL) {
		close(sockfd);
		return T_EXIT_SKIP;
	}

	sqe = io_uring_get_sqe(&ring);
	io_uring_prep_multishot_accept(sqe, sockfd, NULL, NULL, 0);
	sqe->user_data = IS_ACCEPT;
	io_uring_submit(&ring);

	/* check for no multishot accept */
	ret = io_uring_peek_cqe(&ring, &cqe);
	if (!ret && cqe->res == -EINVAL) {
		close(sockfd);
		return T_EXIT_SKIP;
	}

	/* expected exit */
	act[0].sa_handler = sig_usr1;
	sigaction(SIGUSR1, &act[0], NULL);

	/* if this hits, we have failed */
	act[1].sa_handler = sig_timeout;
	sigaction(SIGALRM, &act[1], NULL);
	alarm(5);
	
	/* start receiver */
	td.parent_pid = getpid();
	pthread_create(&thread, NULL, thread_fn, &td);

	do {
		ret = io_uring_submit_and_wait(&ring, 1);
		if (ret < 0) {
			fprintf(stderr, "submit: %d\n", ret);
			return T_EXIT_FAIL;
		}
		ret = io_uring_peek_cqe(&ring, &cqe);
		if (ret) {
			fprintf(stderr, "peek: %d\n", ret);
			return T_EXIT_FAIL;
		}

		switch (cqe->user_data) {
		case IS_ACCEPT:
			send_fd = cqe->res;
			io_uring_cqe_seen(&ring, cqe);

			/*
			 * prep two sends, with the 2nd linked to a close
			 * operation. Once the close has been completed, that
			 * will terminate the receiving thread and that will
			 * in turn send this task a SIGUSR1 signal. If the
			 * kernel is buggy, then we never get SIGUSR1 and we
			 * will sit forever waiting and be timed out.
			 */
			sqe = io_uring_get_sqe(&ring);
			io_uring_prep_send(sqe, send_fd, msg1, strlen(msg1), 0);
			sqe->user_data = IS_SEND;
			sqe->flags = IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_LINK;

			sqe = io_uring_get_sqe(&ring);
			io_uring_prep_send(sqe, send_fd, msg2, strlen(msg2), 0);
			sqe->user_data = IS_SEND2;
			sqe->flags = IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_LINK;

			sqe = io_uring_get_sqe(&ring);
			io_uring_prep_send(sqe, send_fd, msg3, strlen(msg3), 0);
			sqe->user_data = IS_SEND3;
			sqe->flags = IOSQE_CQE_SKIP_SUCCESS | IOSQE_IO_LINK;

			sqe = io_uring_get_sqe(&ring);
			io_uring_prep_close(sqe, send_fd);
			sqe->user_data = IS_CLOSE;
			sqe->flags = IOSQE_CQE_SKIP_SUCCESS;
			break;
		case IS_SEND:
		case IS_SEND2:
			fprintf(stderr, "Should not see send response\n");
			io_uring_cqe_seen(&ring, cqe);
			return T_EXIT_FAIL;
		case IS_CLOSE:
			fprintf(stderr, "Should not see close response\n");
			io_uring_cqe_seen(&ring, cqe);
			return T_EXIT_FAIL;
		default:
			fprintf(stderr, "got unknown cqe\n");
			return T_EXIT_FAIL;
		}
	} while (1);

	/* will never get here */
	return T_EXIT_FAIL;
}