/* httperf -- a tool for measuring web server performance Copyright (C) 2000 Hewlett-Packard Company Contributed by David Mosberger-Tang This file is part of httperf, a web server performance measurment tool. 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; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* grrr, must come after sys/types.h for BSD */ #include #include #include #include #include #include #include #include #include #define HASH_TABLE_SIZE 1024 /* can't have more than this many servers */ #define MIN_IP_PORT IPPORT_RESERVED #define MAX_IP_PORT 65535 #define BITSPERLONG (8*sizeof (u_long)) static int running = 1; static int iteration; static u_long max_burst_len; static fd_set rdfds, wrfds; static int min_sd = 0x7fffffff, max_sd = 0, alloced_sd_to_conn = 0; static struct timeval select_timeout; static struct sockaddr_in myaddr; Conn **sd_to_conn; static u_long port_free_map[((MAX_IP_PORT - MIN_IP_PORT + BITSPERLONG) / BITSPERLONG)]; static char http10req[] = " HTTP/1.0\r\nUser-Agent: httperf/"VERSION"\r\nHost: "; static char http11req[] = " HTTP/1.1\r\nUser-Agent: httperf/"VERSION"\r\nHost: "; static char http10req_nohost[] = " HTTP/1.0\r\nUser-Agent: httperf/"VERSION"\r\n"; static char http11req_nohost[] = " HTTP/1.1\r\nUser-Agent: httperf/"VERSION"\r\n"; #ifndef SOL_TCP # define SOL_TCP 6 /* probably ought to do getprotlbyname () */ #endif #ifdef TIME_SYSCALLS # define SYSCALL(n,s) \ { \ Time start, stop; \ do \ { \ errno = 0; \ start = timer_now_forced (); \ s; /* execute the syscall */ \ stop = timer_now_forced (); \ syscall_time[SC_##n] += stop - start; \ ++syscall_count[SC_##n]; \ } \ while (errno == EINTR); \ } enum Syscalls { SC_BIND, SC_CONNECT, SC_READ, SC_SELECT, SC_SOCKET, SC_WRITEV, SC_SSL_READ, SC_SSL_WRITEV, SC_NUM_SYSCALLS }; static const char * const syscall_name[SC_NUM_SYSCALLS] = { "bind", "connct", "read", "select", "socket", "writev", "ssl_read", "ssl_writev" }; static Time syscall_time[SC_NUM_SYSCALLS]; static u_int syscall_count[SC_NUM_SYSCALLS]; #else # define SYSCALL(n,s) \ { \ do \ { \ errno = 0; \ s; \ } \ while (errno == EINTR); \ } #endif struct hash_entry { const char *hostname; int port; struct sockaddr_in sin; } hash_table[HASH_TABLE_SIZE]; static int hash_code (const char *server, size_t server_len, int port) { u_char *cp = (u_char *) server; u_long h = port; u_long g; int ch; /* Basically the ELF hash algorithm: */ while ((ch = *cp++) != '\0') { h = (h << 4) + ch; if ((g = (h & 0xf0000000)) != 0) { h ^= g >> 24; h &= ~g; } } return h; } static struct hash_entry* hash_enter (const char *server, size_t server_len, int port, struct sockaddr_in *sin) { struct hash_entry *he; int index = hash_code (server, server_len, port) % HASH_TABLE_SIZE; while (hash_table[index].hostname) { ++index; if (index >= HASH_TABLE_SIZE) index = 0; } he = hash_table + index; he->hostname = server; he->port = port; he->sin = *sin; return he; } static struct sockaddr_in* hash_lookup (const char *server, size_t server_len, int port) { int index, start_index; index = start_index = hash_code (server, server_len, port) % HASH_TABLE_SIZE; while (hash_table[index].hostname) { if (hash_table[index].port == port && strcmp (hash_table[index].hostname, server) == 0) return &hash_table[index].sin; ++index; if (index >= HASH_TABLE_SIZE) index = 0; if (index == start_index) break; } return 0; } static int lffs (long w) { int r; if (sizeof (w) == sizeof (int)) r = ffs (w); else { r = ffs (w); #if SIZEOF_LONG > 4 if (r == 0) { r = ffs (w >> (8*sizeof (int))); if (r > 0) r += 8*sizeof (int); } #endif } return r; } static void port_put (int port) { int i, bit; port -= MIN_IP_PORT; i = port / BITSPERLONG; bit = port % BITSPERLONG; port_free_map[i] |= (1UL << bit); } static int port_get (void) { static u_long mask = ~0UL; static int previous = 0; int port, bit, i; i = previous; if ((port_free_map[i] & mask) == 0) { do { ++i; if (i >= NELEMS (port_free_map)) i = 0; if (i == previous) { if (DBG > 0) fprintf (stderr, "%s.port_get: Yikes! I'm out of port numbers!\n", prog_name); return -1; } } while (port_free_map[i] == 0); mask = ~0UL; } previous = i; bit = lffs (port_free_map[i] & mask) - 1; if (bit >= BITSPERLONG - 1) mask = 0; else mask = ~((1UL << (bit + 1)) - 1); port_free_map[i] &= ~(1UL << bit); port = bit + i*BITSPERLONG + MIN_IP_PORT; return port; } static void conn_failure (Conn *s, int err) { Any_Type arg; arg.l = err; event_signal (EV_CONN_FAILED, (Object *) s, arg); core_close (s); } static void conn_timeout (Timer *t, Any_Type arg) { Conn *s = arg.vp; Time now; Call *c; assert (object_is_conn (s)); s->watchdog = 0; if (DBG > 0) { c = 0; if (s->sd >= 0) { now = timer_now (); if (FD_ISSET (s->sd, &rdfds) && s->recvq && now >= s->recvq->timeout) c = s->recvq; else if (FD_ISSET (s->sd, &wrfds) && s->sendq && now >= s->sendq->timeout) c = s->sendq; } if (DBG > 0) { fprintf (stderr, "connection_timeout"); if (c) fprintf (stderr, ".%lu", c->id); fprintf (stderr, ": t=%p, connection=%p\n", t, s); } } arg.l = 0; event_signal (EV_CONN_TIMEOUT, (Object *) s, arg); core_close (s); } static void set_active (Conn *s, fd_set *fdset) { int sd = s->sd; Any_Type arg; Time timeout; FD_SET (sd, fdset); if (sd < min_sd) min_sd = sd; if (sd >= max_sd) max_sd = sd; if (s->watchdog) return; timeout = 0.0; if (s->sendq) timeout = s->sendq->timeout; if (s->recvq && (timeout == 0.0 || timeout > s->recvq->timeout)) timeout = s->recvq->timeout; if (timeout > 0.0) { arg.vp = s; s->watchdog = timer_schedule (conn_timeout, arg, timeout - timer_now ()); } } static void do_send (Conn *conn) { int async_errno, len; struct iovec *iovp; int sd = conn->sd; ssize_t nsent = 0; Any_Type arg; Call *call; while (1) { call = conn->sendq; assert (call); arg.l = 0; event_signal (EV_CALL_SEND_RAW_DATA, (Object *) call, arg); #ifdef HAVE_SSL if (param.use_ssl) { extern ssize_t SSL_writev (SSL *, const struct iovec *, int); SYSCALL (SSL_WRITEV, nsent = SSL_writev(conn->ssl, call->req.iov + call->req.iov_index, (NELEMS (call->req.iov) - call->req.iov_index))); } else #endif { SYSCALL (WRITEV, nsent = writev (sd, call->req.iov + call->req.iov_index, (NELEMS (call->req.iov) - call->req.iov_index))); } if (DBG > 0) fprintf (stderr, "do_send.%lu: wrote %ld bytes on %p\n", call->id, (long) nsent, conn); if (nsent < 0) { if (errno == EAGAIN) return; len = sizeof (async_errno); if (getsockopt (sd, SOL_SOCKET, SO_ERROR, &async_errno, &len) == 0 && async_errno != 0) errno = async_errno; if (DBG > 0) fprintf (stderr, "%s.do_send: writev() failed: %s\n", prog_name, strerror (errno)); conn_failure (conn, errno); return; } call->req.size += nsent; iovp = call->req.iov + call->req.iov_index; while (iovp < call->req.iov + NELEMS (call->req.iov)) { if (nsent < iovp->iov_len) { iovp->iov_len -= nsent; iovp->iov_base = (caddr_t) ((char *) iovp->iov_base + nsent); break; } else { /* we're done with this fragment: */ nsent -= iovp->iov_len; *iovp = call->req.iov_saved; ++iovp; call->req.iov_saved = *iovp; } } call->req.iov_index = iovp - call->req.iov; if (call->req.iov_index < NELEMS (call->req.iov)) { /* there are more header bytes to write */ call->timeout = param.timeout ? timer_now () + param.timeout : 0.0; set_active (conn, &wrfds); return; } /* we're done with sending this request */ conn->sendq = call->sendq_next; if (!conn->sendq) { conn->sendq_tail = 0; FD_CLR (sd, &wrfds); } arg.l = 0; event_signal (EV_CALL_SEND_STOP, (Object *) call, arg); if (conn->state >= S_CLOSING) { call_dec_ref (call); return; } /* get ready to receive matching reply (note that we implicitly pass on the reference to the call from the sendq to the recvq): */ call->recvq_next = 0; if (!conn->recvq) conn->recvq = conn->recvq_tail = call; else { conn->recvq_tail->recvq_next = call; conn->recvq_tail = call; } call->timeout = param.timeout + param.think_timeout; if (call->timeout > 0.0) call->timeout += timer_now (); set_active (conn, &rdfds); if (conn->state < S_REPLY_STATUS) conn->state = S_REPLY_STATUS; /* expecting reply status */ if (!conn->sendq) return; arg.l = 0; event_signal (EV_CALL_SEND_START, (Object *) conn->sendq, arg); if (conn->state >= S_CLOSING) return; } } static void recv_done (Call *call) { Conn *conn = call->conn; Any_Type arg; conn->recvq = call->recvq_next; if (!conn->recvq) { FD_CLR (conn->sd, &rdfds); conn->recvq_tail = 0; } /* we're done with receiving this request */ arg.l = 0; event_signal (EV_CALL_RECV_STOP, (Object *) call, arg); call_dec_ref (call); } static void do_recv (Conn *s) { char *cp, buf[8193]; Call *c = s->recvq; int i, saved_errno; ssize_t nread = 0; size_t buf_len; assert (c); #ifdef HAVE_SSL if (param.use_ssl) { SYSCALL (SSL_READ, nread = SSL_read (s->ssl, buf, sizeof (buf) - 1)); } else #endif { SYSCALL (READ, nread = read (s->sd, buf, sizeof (buf) - 1)); } saved_errno = errno; if (nread <= 0) { if (DBG > 0) { fprintf (stderr, "do_recv.%lu: received %lu reply bytes on %p\n", c->id, (u_long) (c->reply.header_bytes + c->reply.content_bytes), s); if (nread < 0) fprintf (stderr, "%s.do_recv: read() failed: %s\n", prog_name, strerror (saved_errno)); } if (nread < 0) { if (saved_errno != EAGAIN) conn_failure (s, saved_errno); } else if (s->state != S_REPLY_DATA) conn_failure (s, ECONNRESET); else { if (s->state < S_CLOSING) s->state = S_REPLY_DONE; recv_done (c); } return; } buf[nread] = '\0'; /* ensure buffer is '\0' terminated */ if (DBG > 3) { /* dump received data in hex & ascii: */ fprintf (stderr, "do_recv.%lu: received reply data:\n", c->id); for (cp = buf; cp < buf + nread; ) { fprintf (stderr, " %04x:", (int) (c->reply.header_bytes + c->reply.content_bytes + (cp - buf))); for (i = 0; i < 16 && i < buf + nread - cp; ++i) fprintf (stderr, " %02x", cp[i] & 0xff); i *= 3; while (i++ < 50) fputc (' ', stderr); for (i = 0; i < 16 && cp < buf + nread; ++i, ++cp) fprintf (stderr, "%c", isprint (*cp) ? *cp : '.'); fprintf (stderr, "\n"); } } /* process the replies in this buffer: */ buf_len = nread; cp = buf; do { c = s->recvq; assert (c); http_process_reply_bytes (c, &cp, &buf_len); if (s->state == S_REPLY_DONE) { recv_done (c); if (s->state >= S_CLOSING) return; s->state = S_REPLY_STATUS; } } while (buf_len > 0); if (s->recvq) set_active (c->conn, &rdfds); } struct sockaddr_in* core_addr_intern (const char *server, size_t server_len, int port) { struct sockaddr_in sin; struct hash_entry *h; struct hostent *he; Any_Type arg; memset (&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_port = htons (port); arg.cvp = server; event_signal (EV_HOSTNAME_LOOKUP_START, 0, arg); he = gethostbyname (server); event_signal (EV_HOSTNAME_LOOKUP_STOP, 0, arg); if (he) { if (he->h_addrtype != AF_INET || he->h_length != sizeof (sin.sin_addr)) { fprintf (stderr, "%s: can't deal with addr family %d or size %d\n", prog_name, he->h_addrtype, he->h_length); exit (1); } memcpy (&sin.sin_addr, he->h_addr_list[0], sizeof (sin.sin_addr)); } else { if (!inet_aton (server, &sin.sin_addr)) { fprintf (stderr, "%s.core_addr_intern: invalid server address %s\n", prog_name, server); exit (1); } } h = hash_enter (server, server_len, port, &sin); if (!h) return 0; return &h->sin; } void core_init (void) { struct rlimit rlimit; memset (&hash_table, 0, sizeof (hash_table)); memset (&rdfds, 0, sizeof (rdfds)); memset (&wrfds, 0, sizeof (wrfds)); memset (&myaddr, 0, sizeof (myaddr)); memset (&port_free_map, 0xff, sizeof (port_free_map)); /* Don't disturb just because a TCP connection closed on us... */ signal (SIGPIPE, SIG_IGN); #ifdef DONT_POLL /* This causes select() to take several milliseconds on both Linux/x86 and HP-UX 10.20. */ select_timeout.tv_sec = (u_long) TIMER_INTERVAL; select_timeout.tv_usec = (u_long) (TIMER_INTERVAL * 1e6); #else /* This causes httperf to become a CPU hog as it polls for filedescriptors to become readable/writable. This is OK as long as httperf is the only (interesting) user-level process that executes on a machine. */ select_timeout.tv_sec = 0; select_timeout.tv_usec = 0; #endif /* boost open file limit to the max: */ if (getrlimit (RLIMIT_NOFILE, &rlimit) < 0) { fprintf (stderr, "%s: failed to get number of open file limit: %s", prog_name, strerror (errno)); exit (1); } if (rlimit.rlim_max > FD_SETSIZE) { fprintf (stderr, "%s: warning: open file limit > FD_SETSIZE; " "limiting max. # of open files to FD_SETSIZE\n", prog_name); rlimit.rlim_max = FD_SETSIZE; } rlimit.rlim_cur = rlimit.rlim_max; if (setrlimit (RLIMIT_NOFILE, &rlimit) < 0) { fprintf (stderr, "%s: failed to increase number of open file limit: %s", prog_name, strerror (errno)); exit (1); } if (verbose) printf ("%s: maximum number of open descriptors = %ld\n", prog_name, rlimit.rlim_max); if (param.server) core_addr_intern (param.server, strlen (param.server), param.port); } #ifdef HAVE_SSL void core_ssl_connect (Conn *s) { Any_Type arg; int ssl_err; if (DBG > 2) fprintf (stderr, "core_ssl_connect(conn=%p)\n", (void *) s); if (SSL_set_fd (s->ssl, s->sd) == 0) { ERR_print_errors_fp (stderr); exit (-1); } ssl_err = SSL_connect (s->ssl); if (ssl_err < 0) { int reason = SSL_get_error(s->ssl, ssl_err); if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) { if (DBG > 2) fprintf (stderr, "core_ssl_connect: want to %s more...\n", (reason == SSL_ERROR_WANT_READ) ? "read" : "write"); if (reason == SSL_ERROR_WANT_READ && !FD_ISSET (s->sd, &rdfds)) { FD_CLR (s->sd, &wrfds); set_active (s, &rdfds); } else if (reason == SSL_ERROR_WANT_WRITE && !FD_ISSET (s->sd, &wrfds)) { FD_CLR (s->sd, &rdfds); set_active (s, &wrfds); } return; } fprintf (stderr, "%s: failed to connect to SSL server (err=%d, reason=%d)\n", prog_name, ssl_err, reason); ERR_print_errors_fp (stderr); exit (-1); } s->state = S_CONNECTED; if (DBG > 0) fprintf (stderr, "core_ssl_connect: SSL is connected!\n"); if (DBG > 1) { SSL_CIPHER *ssl_cipher; ssl_cipher = SSL_get_current_cipher (s->ssl); if (!ssl_cipher) fprintf (stderr, "core_ssl_connect: server refused all client cipher " "suites!\n"); else fprintf (stderr, "core_ssl_connect: cipher=%s, valid=%d, id=%lu\n", ssl_cipher->name, ssl_cipher->valid, ssl_cipher->id); } arg.l = 0; event_signal (EV_CONN_CONNECTED, (Object *) s, arg); } #endif /* HAVE_SSL */ int core_connect (Conn *s) { int sd, result, len, async_errno; struct sockaddr_in *sin; struct linger linger; int myport, optval; Any_Type arg; static int prev_iteration = -1; static u_long burst_len; if (iteration == prev_iteration) ++burst_len; else { if (burst_len > max_burst_len) max_burst_len = burst_len; burst_len = 1; prev_iteration = iteration; } SYSCALL (SOCKET, sd = socket (AF_INET, SOCK_STREAM, 0)); if (sd < 0) { if (DBG > 0) fprintf (stderr, "%s.core_connect.socket: %s (max_sd=%d)\n", prog_name, strerror (errno), max_sd); goto failure; } if (fcntl (sd, F_SETFL, O_NONBLOCK) < 0) { fprintf (stderr, "%s.core_connect.fcntl: %s\n", prog_name, strerror (errno)); goto failure; } if (param.close_with_reset) { linger.l_onoff = 1; linger.l_linger = 0; if (setsockopt (sd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger)) < 0) { fprintf (stderr, "%s.core_connect.setsockopt(SO_LINGER): %s\n", prog_name, strerror (errno)); goto failure; } } /* Disable Nagle algorithm so we don't delay needlessly when pipelining requests. */ optval = 1; if (setsockopt (sd, SOL_TCP, TCP_NODELAY, &optval, sizeof (optval)) < 0) { fprintf (stderr, "%s.core_connect.setsockopt(SO_SNDBUF): %s\n", prog_name, strerror (errno)); goto failure; } optval = param.send_buffer_size; if (setsockopt (sd, SOL_SOCKET, SO_SNDBUF, &optval, sizeof (optval)) < 0) { fprintf (stderr, "%s.core_connect.setsockopt(SO_SNDBUF): %s\n", prog_name, strerror (errno)); goto failure; } optval = param.recv_buffer_size; if (setsockopt (sd, SOL_SOCKET, SO_RCVBUF, &optval, sizeof (optval)) < 0) { fprintf (stderr, "%s.core_connect.setsockopt(SO_SNDBUF): %s\n", prog_name, strerror (errno)); goto failure; } s->sd = sd; if (sd >= alloced_sd_to_conn) { size_t size, old_size; old_size = alloced_sd_to_conn * sizeof (sd_to_conn[0]); alloced_sd_to_conn += 2048; size = alloced_sd_to_conn * sizeof (sd_to_conn[0]); if (sd_to_conn) sd_to_conn = realloc (sd_to_conn, size); else sd_to_conn = malloc (size); if (!sd_to_conn) { if (DBG > 0) fprintf (stderr, "%s.core_connect.realloc: %s\n", prog_name, strerror (errno)); goto failure; } memset ((char *) sd_to_conn + old_size, 0, size - old_size); } assert (!sd_to_conn[sd]); sd_to_conn[sd] = s; sin = hash_lookup (s->hostname, s->hostname_len, s->port); if (!sin) { if (DBG > 0) fprintf (stderr, "%s.core_connect: unknown server/port %s:%d\n", prog_name, s->hostname, s->port); goto failure; } arg.l = 0; event_signal (EV_CONN_CONNECTING, (Object *) s, arg); if (s->state >= S_CLOSING) goto failure; if (param.hog) { while (1) { myport = port_get (); if (myport < 0) goto failure; myaddr.sin_port = htons (myport); SYSCALL (BIND, result = bind (sd, &myaddr, sizeof (myaddr))); if (result == 0) break; if (errno != EADDRINUSE && errno == EADDRNOTAVAIL) { if (DBG > 0) fprintf (stderr, "%s.core_connect.bind: %s\n", prog_name, strerror (errno)); goto failure; } } s->myport = myport; } SYSCALL (CONNECT, result = connect (sd, sin, sizeof (*sin))); if (result == 0) { #ifdef HAVE_SSL if (param.use_ssl) core_ssl_connect (s); else #endif { s->state = S_CONNECTED; arg.l = 0; event_signal (EV_CONN_CONNECTED, (Object *) s, arg); } } else if (errno == EINPROGRESS) { /* The socket becomes writable only after the connection has been established. Hence we wait for writability to detect connection establishment. */ s->state = S_CONNECTING; set_active (s, &wrfds); if (param.timeout > 0.0) { arg.vp = s; assert (!s->watchdog); s->watchdog = timer_schedule (conn_timeout, arg, param.timeout); } } else { len = sizeof (async_errno); if (getsockopt (sd, SOL_SOCKET, SO_ERROR, &async_errno, &len) == 0 && async_errno != 0) errno = async_errno; if (DBG > 0) fprintf (stderr, "%s.core_connect.connect: %s (max_sd=%d)\n", prog_name, strerror (errno), max_sd); goto failure; } return 0; failure: conn_failure (s, errno); return -1; } int core_send (Conn *conn, Call *call) { Any_Type arg; arg.l = 0; event_signal (EV_CALL_ISSUE, (Object *) call, arg); call->conn = conn; /* NO refcounting here (see call.h). */ if (param.no_host_hdr) { call->req.iov[IE_HOST].iov_base = (caddr_t) ""; call->req.iov[IE_HOST].iov_len = 0; } else if (!call->req.iov[IE_HOST].iov_base) { /* Default call's hostname to connection's hostname: */ call->req.iov[IE_HOST].iov_base = (caddr_t) conn->hostname; call->req.iov[IE_HOST].iov_len = conn->hostname_len; } /* NOTE: the protocol version indicates what the _client_ can understand. If we send HTTP/1.1, it doesn't mean that the server has to speak HTTP/1.1. In other words, sending an HTTP/1.1 header leaves it up to the server whether it wants to reply with a 1.0 or 1.1 reply. */ switch (call->req.version) { case 0x10000: if (param.no_host_hdr) { call->req.iov[IE_PROTL].iov_base = (caddr_t) http10req_nohost; call->req.iov[IE_PROTL].iov_len = sizeof (http10req_nohost) - 1; } else { call->req.iov[IE_PROTL].iov_base = (caddr_t) http10req; call->req.iov[IE_PROTL].iov_len = sizeof (http10req) - 1; } break; case 0x10001: if (param.no_host_hdr) { call->req.iov[IE_PROTL].iov_base = http11req_nohost; call->req.iov[IE_PROTL].iov_len = sizeof (http11req_nohost) - 1; } else { call->req.iov[IE_PROTL].iov_base = http11req; call->req.iov[IE_PROTL].iov_len = sizeof (http11req) - 1; } break; default: fprintf (stderr, "%s: unexpected version code %x\n", prog_name, call->req.version); exit (1); } call->req.iov_index = 0; call->req.iov_saved = call->req.iov[0]; /* insert call into connection's send queue: */ call_inc_ref (call); call->sendq_next = 0; if (!conn->sendq) { conn->sendq = conn->sendq_tail = call; arg.l = 0; event_signal (EV_CALL_SEND_START, (Object *) call, arg); if (conn->state >= S_CLOSING) return -1; call->timeout = param.timeout ? timer_now () + param.timeout : 0.0; set_active (conn, &wrfds); } else { conn->sendq_tail->sendq_next = call; conn->sendq_tail = call; } return 0; } void core_close (Conn *conn) { Call *call, *call_next; Any_Type arg; int sd; if (conn->state >= S_CLOSING) return; /* guard against recursive calls */ conn->state = S_CLOSING; if (DBG >= 10) fprintf (stderr, "%s.core_close(conn=%p)\n", prog_name, conn); if (conn->watchdog) { timer_cancel (conn->watchdog); conn->watchdog = 0; } /* first, get rid of all pending calls: */ for (call = conn->sendq; call; call = call_next) { call_next = call->sendq_next; call_dec_ref (call); } conn->sendq = 0; for (call = conn->recvq; call; call = call_next) { call_next = call->recvq_next; call_dec_ref (call); } conn->recvq = 0; sd = conn->sd; conn->sd = -1; arg.l = 0; event_signal (EV_CONN_CLOSE, (Object *) conn, arg); assert (conn->state == S_CLOSING); #ifdef HAVE_SSL if (param.use_ssl) SSL_shutdown (conn->ssl); #endif if (sd >= 0) { close (sd); sd_to_conn[sd] = 0; FD_CLR (sd, &wrfds); FD_CLR (sd, &rdfds); } if (conn->myport > 0) port_put (conn->myport); /* A connection that has been closed is not useful anymore, so we give up the reference obtained when creating the session. This normally initiates destruction of the connection. */ conn_dec_ref (conn); } void core_loop (void) { int is_readable, is_writable, n, sd, bit, min_i, max_i, i = 0; fd_set readable, writable; fd_mask mask; Any_Type arg; Conn *conn; while (running) { struct timeval tv = select_timeout; timer_tick (); readable = rdfds; writable = wrfds; min_i = min_sd / NFDBITS; max_i = max_sd / NFDBITS; SYSCALL (SELECT, n = select (max_sd + 1, &readable, &writable, 0, &tv)); ++iteration; if (n <= 0) { if (n < 0) { fprintf (stderr, "%s.core_loop: select failed: %s\n", prog_name, strerror (errno)); exit (1); } continue; } while (n > 0) { /* find the index of the fdmask that has something going on: */ do { ++i; if (i > max_i) i = min_i; assert (i <= max_i); mask = readable.fds_bits[i] | writable.fds_bits[i]; } while (!mask); bit = 0; sd = i*NFDBITS + bit; do { if (mask & 1) { --n; is_readable = (FD_ISSET (sd, &readable) && FD_ISSET (sd, &rdfds)); is_writable = (FD_ISSET (sd, &writable) && FD_ISSET (sd, &wrfds)); if (is_readable || is_writable) { /* only handle sockets that haven't timed out yet */ conn = sd_to_conn[sd]; conn_inc_ref (conn); if (conn->watchdog) { timer_cancel (conn->watchdog); conn->watchdog = 0; } if (conn->state == S_CONNECTING) { #ifdef HAVE_SSL if (param.use_ssl) core_ssl_connect (conn); else #endif if (is_writable) { FD_CLR (sd, &wrfds); conn->state = S_CONNECTED; arg.l = 0; event_signal (EV_CONN_CONNECTED, (Object *) conn, arg); } } else { if (is_writable && conn->sendq) do_send (conn); if (is_readable && conn->recvq) do_recv (conn); } conn_dec_ref (conn); if (n > 0) timer_tick (); } } mask = ((u_long) mask) >> 1; ++sd; } while (mask); } } } void core_exit (void) { running = 0; printf ("Maximum connect burst length: %lu\n", max_burst_len); #ifdef TIME_SYSCALLS { u_int count; Time time; int i; printf ("Average syscall execution times:\n"); for (i = 0; i < NELEMS (syscall_name); ++i) { count = syscall_count[i]; time = syscall_time[i]; printf ("\t%s:\t%.3f ms/call (%.3fs total, %u calls)\n", syscall_name[i], count > 0 ? 1e3*time/count : 0, time, count); } putchar ('\n'); } #endif }