Re: [PATCH] tcp: set SPLICE_F_NONBLOCK after first buffer has beenspliced

From: Max Kellermann
Date: Thu Nov 05 2009 - 05:12:03 EST


Hi,

here is a small test program for the bug. The last splice() blocks.
Interestingly, if you close the client socket before splice(), it does
not block, and the number of bytes transferred is smaller.

In my patch submission, I forgot the Signed-off-by line - please use
the attached patch file instead.

Max
/*
* This program demonstrates how a splice() from a TCP socket blocks,
* even though the destination pipe is empty.
*
* Copyright 2009 Content Management AG, Cologne, Germany
* Author: Max Kellermann <mk@xxxxxxxxxx>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; version 2 of the
* License.
*/

#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

static size_t fill_socket(int fd, size_t max_fill)
{
static char buffer[4096];
ssize_t nbytes;
size_t sent = 0;

do {
nbytes = send(fd, buffer, sizeof(buffer), MSG_DONTWAIT);
if (nbytes >= 0)
sent += (size_t)nbytes;
else if (errno == EAGAIN)
/* the socket buffer is full */
break;
else {
perror("send() failed");
exit(1);
}
} while (sent < max_fill);

printf("sent %zu bytes\n", sent);
return sent;
}

static void run(size_t max_fill, size_t max_splice, unsigned splice_flags,
bool early_close)
{
int i, listen_socket, client_socket, server_socket, pipefds[2];
struct sockaddr_in sa;
size_t sent;
ssize_t nbytes;

/* set up socket and pipe */

memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sa.sin_port = htons(1234);

listen_socket = socket(AF_INET, SOCK_STREAM, 0);
i = 1;
if (listen_socket < 0 ||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) ||
bind(listen_socket, (const struct sockaddr *)&sa, sizeof(sa)) < 0 ||
listen(listen_socket, 1) < 0) {
perror("failed to listen");
exit(1);
}

client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0 ||
connect(client_socket, (const struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("failed to connect");
exit(1);
}

server_socket = accept(listen_socket, NULL, 0);
if (server_socket < 0) {
perror("failed to accept");
exit(1);
}

close(listen_socket);

if (pipe(pipefds) < 0) {
perror("pipe() failed");
exit(1);
}

/* fill the socket buffer */

sent = fill_socket(client_socket, max_fill);

printf("%sclosing client socket\n", early_close ? "" : "not ");
if (early_close)
close(client_socket);

/* now splice from the socket into the pipe, as much as
possible */

if (max_splice == 0)
max_splice = sent;

printf("invoking splice(%zu, 0x%x)\n",
max_splice, splice_flags);

nbytes = splice(server_socket, NULL, pipefds[1], NULL,
max_splice, splice_flags);
if (nbytes >= 0)
printf("splice(%zu, 0x%x) = %zi\n",
max_splice, splice_flags, nbytes);
if (nbytes < 0)
printf("splice(%zu, 0x%x) failed: %s\n",
max_splice, splice_flags, strerror(errno));

if (!early_close)
close(client_socket);

close(server_socket);
close(pipefds[0]);
close(pipefds[1]);
}

int main(int argc, char **argv)
{
(void)argc; (void)argv;

run(4096, 0, 0, true);
run(262144, 4096, 0, true);
run(262144, 0, SPLICE_F_NONBLOCK, true);
/* when closing the client socket, this does not block! */
run(262144, 0, 0, true);

run(4096, 0, 0, false);
run(262144, 4096, 0, false);
run(262144, 0, SPLICE_F_NONBLOCK, false);

/* this will block (2.6.31, 2.6.32-rc6) */
run(262144, 0, 0, false);

return 0;
}