I've tested this a bit on Solaris and Linux, with both blocking and nonblocking sockets, with the expected results. I did notice one odd thing: when I set a socket to nonblocking, and then return it to blocking (using apr_socket_timeout_set(-1)), I don't seem to get blocking behaviour. Instead I get underwrites from writev(). However, when I leave a socket with its default of blocking, or explicitly set it once to blocking, I don't get an underwrite (all else is constant). This doesn't have anything to do with my patch -- it happens with both versions of apr_socket_sendv() -- but if anyone has an explanation, I'd like to hear it.
Another thing: the effect of this patch on Linux appears to be suboptimal. Based on my observations, Linux defines IOV_MAX (I've seen 16 and 1024), but ignores it in writev. I can send > IOV_MAX iovecs to writev() w/o an error return. Accordingly, the work done by this patch is unnecessary on Linux. However, beyond checking #ifdef IOV_MAX, I don't know of a good way to isolate that case. And this patch does solve a real problem on Solaris.
Lastly, I followed the convention in sendrecv.c of calling apr_wait_for_io_or_timeout() on an EAGAIN only if (socket->timeout != 0). Similarly, APR_INCOMPLETE_WRITE is only set if socket->timeout is non-zero. However, shouldn't the condition in both cases be (socket->timeout > 0)?
Thanks, Jim
--- network_io/unix/sendrecv.c.orig 2003-04-24 16:01:09.760030000 -0700 +++ network_io/unix/sendrecv.c 2003-04-24 18:32:08.890003000 -0700 @@ -65,6 +65,11 @@ #include <sys/sysctl.h> #endif
+#ifndef min
+#define min(x,y) ((x) <= (y) ? (x) : (y))
+#endif
+
+
apr_status_t apr_socket_send(apr_socket_t *sock, const char *buf,
apr_size_t *len)
{
@@ -222,45 +227,58 @@
apr_int32_t nvec, apr_size_t *len)
{
apr_ssize_t rv;
- apr_size_t requested_len = 0;
- apr_int32_t i;
+ apr_int32_t i, vecstosend, have_waited = 0;- for (i = 0; i < nvec; i++) {
- requested_len += vec[i].iov_len;
- }
+ *len = 0; if (sock->netmask & APR_INCOMPLETE_WRITE) {
sock->netmask &= ~APR_INCOMPLETE_WRITE;
goto do_select;
}- do {
- rv = writev(sock->socketdes, vec, nvec);
- } while (rv == -1 && errno == EINTR);
-
- if (rv == -1 && (errno == EAGAIN || errno == EWOULDBLOCK) &&
- sock->timeout != 0) {
- apr_status_t arv;
-do_select:
- arv = apr_wait_for_io_or_timeout(NULL, sock, 0);
- if (arv != APR_SUCCESS) {
- *len = 0;
- return arv;
- }
- else {
- do {
- rv = writev(sock->socketdes, vec, nvec);
- } while (rv == -1 && errno == EINTR);
+ /* loop repeatedly, sending vec in IOV_MAX sized chunks, until
+ * everything is sent, or we see an error, or we get EAGAIN more
+ * than once on a socket with timeout != 0 */
+ while(nvec > 0) {
+#ifdef IOV_MAX
+ vecstosend = min(nvec, IOV_MAX);
+#else
+ vecstosend = nvec;
+#endif
+ do {
+ rv = writev(sock->socketdes, vec, vecstosend);
+ } while (rv == -1 && errno == EINTR);
+
+ if (rv != -1) {
+ *len += rv;
+ /* did we send them all? */
+ for(i=0; i < vecstosend; i++)
+ rv -= vec[i].iov_len;
+ if (rv < 0) {
+ /* no */
+ break;
+ }
+ vec += vecstosend;
+ nvec -= vecstosend;
+ } else if ((errno == EAGAIN || errno == EWOULDBLOCK)
+ && sock->timeout != 0) {
+ if (have_waited) {
+ sock->netmask |= APR_INCOMPLETE_WRITE;
+ return errno;
+ }
+ do_select:
+ rv = apr_wait_for_io_or_timeout(NULL, sock, 0);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ have_waited = 1;
+ } else {
+ return errno;
}
}
- if (rv == -1) {
- *len = 0;
- return errno;
- }
- if (sock->timeout && rv < requested_len) {
- sock->netmask |= APR_INCOMPLETE_WRITE;
- }
- (*len) = rv;
+ if (nvec && sock->timeout)
+ sock->netmask |= APR_INCOMPLETE_WRITE;
+
return APR_SUCCESS;
}
#endif