Return the correct status when a partially completed request is cancelled.

After the previous changes to fix requests on blocking sockets to complete
across multiple operations, an edge case exists where a request can be
cancelled after it has partially completed.  POSIX doesn't appear to
dictate exactly how to handle this case, but in general I feel that
aio_cancel() should arrange to cancel any request it can, but that any
partially completed requests should return a partial completion rather
than ECANCELED.  To that end, fix the socket AIO cancellation routine to
return a short read/write if a partially completed request is cancelled
rather than ECANCELED.

Sponsored by:	Chelsio Communications
This commit is contained in:
John Baldwin 2016-05-24 21:09:05 +00:00
parent cb05064e70
commit 778ce4f297
2 changed files with 75 additions and 1 deletions

View File

@ -721,6 +721,7 @@ soo_aio_cancel(struct kaiocb *job)
{
struct socket *so;
struct sockbuf *sb;
long done;
int opcode;
so = job->fd_file->f_data;
@ -739,7 +740,11 @@ soo_aio_cancel(struct kaiocb *job)
sb->sb_flags &= ~SB_AIO;
SOCKBUF_UNLOCK(sb);
aio_cancel(job);
done = job->uaiocb._aiocb_private.status;
if (done != 0)
aio_complete(job, done, 0);
else
aio_cancel(job);
}
static int

View File

@ -845,6 +845,74 @@ ATF_TC_BODY(aio_socket_blocking_short_write, tc)
close(s[0]);
}
/*
* This test verifies that cancelling a partially completed socket write
* returns a short write rather than ECANCELED.
*/
ATF_TC_WITHOUT_HEAD(aio_socket_short_write_cancel);
ATF_TC_BODY(aio_socket_short_write_cancel, tc)
{
struct aiocb iocb, *iocbp;
char *buffer[2];
ssize_t done;
int buffer_size, sb_size;
socklen_t len;
int s[2];
ATF_REQUIRE_KERNEL_MODULE("aio");
ATF_REQUIRE(socketpair(PF_UNIX, SOCK_STREAM, 0, s) != -1);
len = sizeof(sb_size);
ATF_REQUIRE(getsockopt(s[0], SOL_SOCKET, SO_RCVBUF, &sb_size, &len) !=
-1);
ATF_REQUIRE(len == sizeof(sb_size));
buffer_size = sb_size;
ATF_REQUIRE(getsockopt(s[1], SOL_SOCKET, SO_SNDBUF, &sb_size, &len) !=
-1);
ATF_REQUIRE(len == sizeof(sb_size));
if (sb_size > buffer_size)
buffer_size = sb_size;
/*
* Use three times the size of the MAX(receive buffer, send
* buffer) for the write to ensure that the write is split up
* into multiple writes internally. The recv() ensures that
* the write has partially completed, but a remaining size of
* two buffers should ensure that the write has not completed
* fully when it is cancelled.
*/
buffer[0] = malloc(buffer_size);
ATF_REQUIRE(buffer[0] != NULL);
buffer[1] = malloc(buffer_size * 3);
ATF_REQUIRE(buffer[1] != NULL);
srandomdev();
aio_fill_buffer(buffer[1], buffer_size * 3, random());
memset(&iocb, 0, sizeof(iocb));
iocb.aio_fildes = s[1];
iocb.aio_buf = buffer[1];
iocb.aio_nbytes = buffer_size * 3;
ATF_REQUIRE(aio_write(&iocb) == 0);
done = recv(s[0], buffer[0], buffer_size, MSG_WAITALL);
ATF_REQUIRE(done == buffer_size);
ATF_REQUIRE(aio_error(&iocb) == EINPROGRESS);
ATF_REQUIRE(aio_cancel(s[1], &iocb) == AIO_NOTCANCELED);
done = aio_waitcomplete(&iocbp, NULL);
ATF_REQUIRE(iocbp == &iocb);
ATF_REQUIRE(done >= buffer_size && done <= buffer_size * 2);
ATF_REQUIRE(memcmp(buffer[0], buffer[1], buffer_size) == 0);
close(s[1]);
close(s[0]);
}
ATF_TP_ADD_TCS(tp)
{
@ -857,6 +925,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, aio_large_read_test);
ATF_TP_ADD_TC(tp, aio_socket_two_reads);
ATF_TP_ADD_TC(tp, aio_socket_blocking_short_write);
ATF_TP_ADD_TC(tp, aio_socket_short_write_cancel);
return (atf_no_error());
}