/* Write to a socket using the grpc_tcp API, then drain it directly. Note that if the write does not complete immediately we need to drain the socket in parallel with the read. */ static void write_test(ssize_t num_bytes, ssize_t slice_size) { int sv[2]; grpc_endpoint *ep; struct write_socket_state state; ssize_t read_bytes; size_t num_blocks; gpr_slice *slices; int current_data = 0; gpr_slice_buffer outgoing; grpc_iomgr_closure write_done_closure; gpr_timespec deadline = GRPC_TIMEOUT_SECONDS_TO_DEADLINE(20); gpr_log(GPR_INFO, "Start write test with %d bytes, slice size %d", num_bytes, slice_size); create_sockets(sv); ep = grpc_tcp_create(grpc_fd_create(sv[1], "write_test"), GRPC_TCP_DEFAULT_READ_SLICE_SIZE, "test"); grpc_endpoint_add_to_pollset(ep, &g_pollset); state.ep = ep; state.write_done = 0; slices = allocate_blocks(num_bytes, slice_size, &num_blocks, ¤t_data); gpr_slice_buffer_init(&outgoing); gpr_slice_buffer_addn(&outgoing, slices, num_blocks); grpc_iomgr_closure_init(&write_done_closure, write_done, &state); switch (grpc_endpoint_write(ep, &outgoing, &write_done_closure)) { case GRPC_ENDPOINT_DONE: /* Write completed immediately */ read_bytes = drain_socket(sv[0]); GPR_ASSERT(read_bytes == num_bytes); break; case GRPC_ENDPOINT_PENDING: drain_socket_blocking(sv[0], num_bytes, num_bytes); gpr_mu_lock(GRPC_POLLSET_MU(&g_pollset)); for (;;) { grpc_pollset_worker worker; if (state.write_done) { break; } grpc_pollset_work(&g_pollset, &worker, gpr_now(GPR_CLOCK_MONOTONIC), deadline); } gpr_mu_unlock(GRPC_POLLSET_MU(&g_pollset)); break; case GRPC_ENDPOINT_ERROR: gpr_log(GPR_ERROR, "endpoint got error"); abort(); } gpr_slice_buffer_destroy(&outgoing); grpc_endpoint_destroy(ep); gpr_free(slices); }
static ssize_t default_sys_recvfile(int fromfd, int tofd, off_t offset, size_t count) { int saved_errno = 0; size_t total = 0; size_t bufsize = MIN(TRANSFER_BUF_SIZE,count); size_t total_written = 0; char buffer[bufsize]; DEBUG(10,("default_sys_recvfile: from = %d, to = %d, " "offset=%.0f, count = %lu\n", fromfd, tofd, (double)offset, (unsigned long)count)); if (count == 0) { return 0; } if (tofd != -1 && offset != (off_t)-1) { if (lseek(tofd, offset, SEEK_SET) == -1) { if (errno != ESPIPE) { return -1; } } } while (total < count) { size_t num_written = 0; ssize_t read_ret; size_t toread = MIN(bufsize,count - total); /* * Read from socket - ignore EINTR. * Can't use sys_read() as that also * ignores EAGAIN and EWOULDBLOCK. */ do { read_ret = read(fromfd, buffer, toread); } while (read_ret == -1 && errno == EINTR); #if defined(EWOULDBLOCK) if (read_ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { #else if (read_ret == -1 && (errno == EAGAIN)) { #endif /* * fromfd socket is in non-blocking mode. * If we already read some and wrote * it successfully, return that. * Only return -1 if this is the first read * attempt. Caller will handle both cases. */ if (total_written != 0) { return total_written; } return -1; } if (read_ret <= 0) { /* EOF or socket error. */ return -1; } num_written = 0; /* Don't write any more after a write error. */ while (tofd != -1 && (num_written < read_ret)) { ssize_t write_ret; /* Write to file - ignore EINTR. */ write_ret = sys_write(tofd, buffer + num_written, read_ret - num_written); if (write_ret <= 0) { /* write error - stop writing. */ tofd = -1; if (total_written == 0) { /* Ensure we return -1 if the first write failed. */ total_written = -1; } saved_errno = errno; break; } num_written += (size_t)write_ret; total_written += (size_t)write_ret; } total += read_ret; } if (saved_errno) { /* Return the correct write error. */ errno = saved_errno; } return (ssize_t)total_written; } #if defined(HAVE_LINUX_SPLICE) /* * Try and use the Linux system call to do this. * Remember we only return -1 if the socket read * failed. Else we return the number of bytes * actually written. We always read count bytes * from the network in the case of return != -1. */ ssize_t sys_recvfile(int fromfd, int tofd, off_t offset, size_t count) { static int pipefd[2] = { -1, -1 }; static bool try_splice_call = false; size_t total_written = 0; loff_t splice_offset = offset; DEBUG(10,("sys_recvfile: from = %d, to = %d, " "offset=%.0f, count = %lu\n", fromfd, tofd, (double)offset, (unsigned long)count)); if (count == 0) { return 0; } /* * Older Linux kernels have splice for sendfile, * but it fails for recvfile. Ensure we only try * this once and always fall back to the userspace * implementation if recvfile splice fails. JRA. */ if (!try_splice_call) { return default_sys_recvfile(fromfd, tofd, offset, count); } if ((pipefd[0] == -1) && (pipe(pipefd) == -1)) { try_splice_call = false; return default_sys_recvfile(fromfd, tofd, offset, count); } while (count > 0) { int nread, to_write; nread = splice(fromfd, NULL, pipefd[1], NULL, MIN(count, 16384), SPLICE_F_MOVE); if (nread == -1) { if (errno == EINTR) { continue; } if (total_written == 0 && (errno == EBADF || errno == EINVAL)) { try_splice_call = false; return default_sys_recvfile(fromfd, tofd, offset, count); } #if defined(EWOULDBLOCK) if (errno == EAGAIN || errno == EWOULDBLOCK) { #else if (errno == EAGAIN) { #endif /* * fromfd socket is in non-blocking mode. * If we already read some and wrote * it successfully, return that. * Only return -1 if this is the first read * attempt. Caller will handle both cases. */ if (total_written != 0) { return total_written; } return -1; } break; } to_write = nread; while (to_write > 0) { int thistime; thistime = splice(pipefd[0], NULL, tofd, &splice_offset, to_write, SPLICE_F_MOVE); if (thistime == -1) { goto done; } to_write -= thistime; } total_written += nread; count -= nread; } done: if (count) { int saved_errno = errno; if (drain_socket(fromfd, count) != count) { /* socket is dead. */ return -1; } errno = saved_errno; } return total_written; } #else /***************************************************************** No recvfile system call - use the default 128 chunk implementation. *****************************************************************/ ssize_t sys_recvfile(int fromfd, int tofd, off_t offset, size_t count) { return default_sys_recvfile(fromfd, tofd, offset, count); } #endif /***************************************************************** Throw away "count" bytes from the client socket. Returns count or -1 on error. Must only operate on a blocking socket. *****************************************************************/ ssize_t drain_socket(int sockfd, size_t count) { size_t total = 0; size_t bufsize = MIN(TRANSFER_BUF_SIZE,count); char buffer[bufsize]; int old_flags = 0; if (count == 0) { return 0; } old_flags = fcntl(sockfd, F_GETFL, 0); if (set_blocking(sockfd, true) == -1) { return -1; } while (total < count) { ssize_t read_ret; size_t toread = MIN(bufsize,count - total); /* Read from socket - ignore EINTR. */ read_ret = sys_read(sockfd, buffer, toread); if (read_ret <= 0) { /* EOF or socket error. */ count = (size_t)-1; goto out; } total += read_ret; } out: if (fcntl(sockfd, F_SETFL, old_flags) == -1) { return -1; } return count; }
ssize_t sys_recvfile(int fromfd, int tofd, off_t offset, size_t count) { static int pipefd[2] = { -1, -1 }; static bool try_splice_call = false; size_t total_written = 0; loff_t splice_offset = offset; DEBUG(10,("sys_recvfile: from = %d, to = %d, " "offset=%.0f, count = %lu\n", fromfd, tofd, (double)offset, (unsigned long)count)); if (count == 0) { return 0; } /* * Older Linux kernels have splice for sendfile, * but it fails for recvfile. Ensure we only try * this once and always fall back to the userspace * implementation if recvfile splice fails. JRA. */ if (!try_splice_call) { return default_sys_recvfile(fromfd, tofd, offset, count); } if ((pipefd[0] == -1) && (pipe(pipefd) == -1)) { try_splice_call = false; return default_sys_recvfile(fromfd, tofd, offset, count); } while (count > 0) { int nread, to_write; nread = splice(fromfd, NULL, pipefd[1], NULL, MIN(count, 16384), SPLICE_F_MOVE); if (nread == -1) { if (errno == EINTR) { continue; } if (total_written == 0 && (errno == EBADF || errno == EINVAL)) { try_splice_call = false; return default_sys_recvfile(fromfd, tofd, offset, count); } break; } to_write = nread; while (to_write > 0) { int thistime; thistime = splice(pipefd[0], NULL, tofd, &splice_offset, to_write, SPLICE_F_MOVE); if (thistime == -1) { goto done; } to_write -= thistime; } total_written += nread; count -= nread; } done: if (count) { int saved_errno = errno; if (drain_socket(fromfd, count) != count) { /* socket is dead. */ return -1; } errno = saved_errno; } return total_written; }