X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=daemon.c;h=0ae9af0ad4d6c58d8c7cc0b615d8f9a7bc75572c;hb=1cadb5a2719eef7dc4e707ee2f796c94661f4622;hp=c5a46b73f00cd467c7d867bbad6d2308a27c2fc6;hpb=7d80694af1a68ee1915e36bad9b26fa9fe054f06;p=git.git diff --git a/daemon.c b/daemon.c index c5a46b73..0ae9af0a 100644 --- a/daemon.c +++ b/daemon.c @@ -1,9 +1,12 @@ #include "cache.h" #include "pkt-line.h" +#include +#include #include #include +#include -static const char daemon_usage[] = "git-daemon [--port=n]"; +static const char daemon_usage[] = "git-daemon [--inetd | --port=n]"; static int upload(char *dir, int dirlen) { @@ -23,6 +26,12 @@ static int upload(char *dir, int dirlen) access("HEAD", R_OK)) return -1; + /* + * We'll ignore SIGTERM from now on, we have a + * good client. + */ + signal(SIGTERM, SIG_IGN); + /* git-upload-pack only ever reads stuff, so this is safe */ execlp("git-upload-pack", "git-upload-pack", ".", NULL); return -1; @@ -45,10 +54,145 @@ static int execute(void) return -1; } + +/* + * We count spawned/reaped separately, just to avoid any + * races when updating them from signals. The SIGCHLD handler + * will only update children_reaped, and the fork logic will + * only update children_spawned. + * + * MAX_CHILDREN should be a power-of-two to make the modulus + * operation cheap. It should also be at least twice + * the maximum number of connections we will ever allow. + */ +#define MAX_CHILDREN 128 + +static int max_connections = 25; + +/* These are updated by the signal handler */ +static volatile unsigned int children_reaped = 0; +pid_t dead_child[MAX_CHILDREN]; + +/* These are updated by the main loop */ +static unsigned int children_spawned = 0; +static unsigned int children_deleted = 0; + +struct child { + pid_t pid; + int addrlen; + struct sockaddr_in address; +} live_child[MAX_CHILDREN]; + +static void add_child(int idx, pid_t pid, struct sockaddr_in *addr, int addrlen) +{ + live_child[idx].pid = pid; + live_child[idx].addrlen = addrlen; + live_child[idx].address = *addr; +} + +/* + * Walk from "deleted" to "spawned", and remove child "pid". + * + * We move everything up by one, since the new "deleted" will + * be one higher. + */ +static void remove_child(pid_t pid, unsigned deleted, unsigned spawned) +{ + struct child n; + + deleted %= MAX_CHILDREN; + spawned %= MAX_CHILDREN; + if (live_child[deleted].pid == pid) { + live_child[deleted].pid = -1; + return; + } + n = live_child[deleted]; + for (;;) { + struct child m; + deleted = (deleted + 1) % MAX_CHILDREN; + if (deleted == spawned) + die("could not find dead child %d\n", pid); + m = live_child[deleted]; + live_child[deleted] = n; + if (m.pid == pid) + return; + n = m; + } +} + +/* + * This gets called if the number of connections grows + * past "max_connections". + * + * We _should_ start off by searching for connections + * from the same IP, and if there is some address wth + * multiple connections, we should kill that first. + * + * As it is, we just "randomly" kill 25% of the connections, + * and our pseudo-random generator sucks too. I have no + * shame. + * + * Really, this is just a place-holder for a _real_ algorithm. + */ +static void kill_some_children(int signo, unsigned start, unsigned stop) +{ + start %= MAX_CHILDREN; + stop %= MAX_CHILDREN; + while (start != stop) { + if (!(start & 3)) + kill(live_child[start].pid, signo); + start = (start + 1) % MAX_CHILDREN; + } +} + +static void check_max_connections(void) +{ + for (;;) { + int active; + unsigned spawned, reaped, deleted; + + spawned = children_spawned; + reaped = children_reaped; + deleted = children_deleted; + + while (deleted < reaped) { + pid_t pid = dead_child[deleted % MAX_CHILDREN]; + remove_child(pid, deleted, spawned); + deleted++; + } + children_deleted = deleted; + + active = spawned - deleted; + if (active <= max_connections) + break; + + /* Kill some unstarted connections with SIGTERM */ + kill_some_children(SIGTERM, deleted, spawned); + if (active <= max_connections << 1) + break; + + /* If the SIGTERM thing isn't helping use SIGKILL */ + kill_some_children(SIGKILL, deleted, spawned); + sleep(1); + } +} + static void handle(int incoming, struct sockaddr_in *addr, int addrlen) { - if (fork()) { + pid_t pid = fork(); + + if (pid) { + unsigned idx; + close(incoming); + if (pid < 0) + return; + + idx = children_spawned % MAX_CHILDREN; + children_spawned++; + add_child(idx, pid, addr, addrlen); + + check_max_connections(); return; } @@ -58,11 +202,27 @@ static void handle(int incoming, struct sockaddr_in *addr, int addrlen) exit(execute()); } +static void child_handler(int signo) +{ + for (;;) { + pid_t pid = waitpid(-1, NULL, WNOHANG); + + if (pid > 0) { + unsigned reaped = children_reaped; + dead_child[reaped % MAX_CHILDREN] = pid; + children_reaped = reaped + 1; + continue; + } + break; + } +} + static int serve(int port) { int sockfd; struct sockaddr_in addr; + signal(SIGCHLD, child_handler); sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); if (sockfd < 0) die("unable to open socket (%s)", strerror(errno)); @@ -96,6 +256,7 @@ static int serve(int port) int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; + int inetd_mode = 0; int i; for (i = 1; i < argc; i++) { @@ -110,8 +271,17 @@ int main(int argc, char **argv) continue; } } + + if (!strcmp(arg, "--inetd")) { + inetd_mode = 1; + continue; + } + usage(daemon_usage); } + if (inetd_mode) + return execute(); + return serve(port); }