diff options
author | 2018-01-16 19:29:26 -0600 | |
---|---|---|
committer | 2018-01-16 19:40:22 -0600 | |
commit | 0a3e435085046f535074f498a3de75a7704fb14c (patch) | |
tree | 24199cdbe1d5cba07ca9f70c0bcb83af1bac4a2a | |
parent | 4a8a71f1e78860262e189ba36a19afca956b81c2 (diff) | |
download | pseudo-0a3e435085046f535074f498a3de75a7704fb14c.tar.gz pseudo-0a3e435085046f535074f498a3de75a7704fb14c.tar.bz2 pseudo-0a3e435085046f535074f498a3de75a7704fb14c.zip |
Merge epoll support
Original commit message:
The idea came up here:
https://bugzilla.yoctoproject.org/show_bug.cgi?id=11309
and here:
http://lists.openembedded.org/pipermail/openembedded-core/2017-August/141491.html
Signed-off-by: Alexander Kanavin <alexander.kanavin@linux.intel.com>
I've adapted this to make epoll a configure-time option; you must
use --enable-epoll to get the new behavior. I've also confirmed that
it builds both ways and appears to run, and restored the SIGUSR2
functionality (except for the state check) for the epoll case.
Signed-off-by: Seebs <seebs@seebs.net>
-rw-r--r-- | ChangeLog.txt | 1 | ||||
-rw-r--r-- | Makefile.in | 3 | ||||
-rwxr-xr-x | configure | 19 | ||||
-rw-r--r-- | enums/exit_status.in | 3 | ||||
-rw-r--r-- | pseudo_server.c | 241 |
5 files changed, 240 insertions, 27 deletions
diff --git a/ChangeLog.txt b/ChangeLog.txt index 165faa8..1d98415 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -5,6 +5,7 @@ in getgrnam and similar functions. * (seebs) drop the diagnostic for a missing "real" function as it turns out to be counterproductive at best. + * (seebs) merge epoll support from <alexander.kanavin@linux.intel.com> 2017-12-22: * (seebs) handle the pathological case of LINKAT with diff --git a/Makefile.in b/Makefile.in index 76d0518..6e42021 100644 --- a/Makefile.in +++ b/Makefile.in @@ -25,6 +25,7 @@ SQLITE=@SQLITE@ SQLITE_LIB=@SQLITE_LIB@ SQLITE_MEMORY=@SQLITE_MEMORY@ FORCE_ASYNC=@FORCE_ASYNC@ +EPOLL=@EPOLL@ XATTR=@XATTR@ XATTRDB=@XATTRDB@ PROFILING=@PROFILING@ @@ -54,7 +55,7 @@ LOCALSTATEDIR=$(PREFIX)/$(LOCALSTATE) CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra CFLAGS_CODE=-fPIC -D_LARGEFILE64_SOURCE -D_ATFILE_SOURCE $(ARCH_FLAGS) -CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_BINDIR='"$(BIN)"' -DPSEUDO_LIBDIR='"$(LIB)"' -DPSEUDO_LOCALSTATEDIR='"$(LOCALSTATE)"' -DPSEUDO_VERSION='"$(VERSION)"' $(SQLITE_MEMORY) $(FORCE_ASYNC) -DPSEUDO_PASSWD_FALLBACK='$(PASSWD_FALLBACK)' $(OPTDEFS) +CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_BINDIR='"$(BIN)"' -DPSEUDO_LIBDIR='"$(LIB)"' -DPSEUDO_LOCALSTATEDIR='"$(LOCALSTATE)"' -DPSEUDO_VERSION='"$(VERSION)"' $(SQLITE_MEMORY) $(FORCE_ASYNC) -DPSEUDO_PASSWD_FALLBACK='$(PASSWD_FALLBACK)' $(OPTDEFS) $(EPOLL) CFLAGS_DEBUG=-O2 -g @DEFAULT_SQLITE@CFLAGS_SQL=-L$(SQLITE)/$(SQLITE_LIB) -I$(SQLITE)/include $(RPATH) CFLAGS_PSEUDO=$(CFLAGS_BASE) $(CFLAGS_CODE) $(CFLAGS_DEFS) \ @@ -25,6 +25,7 @@ opt_arch=x86 opt_bits= opt_sqlite=/usr opt_rpath= +opt_epoll= opt_memory= opt_async= opt_xattr= @@ -43,6 +44,7 @@ usage() echo >&2 " [--libdir=...]" echo >&2 " [--suffix=...]" echo >&2 " [--enable-memory-db]" + echo >&2 " [--enable-epoll]" echo >&2 " [--enable-xattr]" echo >&2 " [--enable-xattrdb]" echo >&2 " [--enable-profiling]" @@ -116,6 +118,12 @@ do --enable-profiling=yes | --enable-profiling) opt_profiling=true ;; + --enable-epoll=no) + opt_epoll=false + ;; + --enable-epoll=yes | --enable-epoll) + opt_epoll=true + ;; --enable-xattr=no) opt_xattr=false ;; @@ -255,6 +263,10 @@ if [ -z "$opt_async" ]; then opt_async=false fi +if [ -z "$opt_epoll" ]; then + opt_epoll=false +fi + if $opt_async; then FORCE_ASYNC="-DPSEUDO_FORCE_ASYNC" else @@ -283,6 +295,12 @@ if [ -z "$opt_xattr" ]; then fi fi +if $opt_epoll; then + EPOLL="-DPSEUDO_EPOLL" +else + EPOLL="" +fi + if $opt_xattr || $opt_xattrdb; then if ! $xattr_runs; then echo >&2 "WARNING: getfattr doesn't work, but xattr-related features requestd." @@ -322,6 +340,7 @@ touch func_deps.mk sed -e ' s,@PREFIX@,'"$opt_prefix"',g + s,@EPOLL@,'"$EPOLL"',g s,@XATTR@,'"$opt_xattr"',g s,@XATTRDB@,'"$opt_xattrdb"',g s,@PROFILING@,'"$opt_profiling"',g diff --git a/enums/exit_status.in b/enums/exit_status.in index 6be44d3..88f94cd 100644 --- a/enums/exit_status.in +++ b/enums/exit_status.in @@ -18,3 +18,6 @@ listen_fd, "server loop had no valid listen fd" pseudo_loaded, "server couldn't get out of pseudo environment" pseudo_prefix, "couldn't get valid pseudo prefix" pseudo_invocation, "invalid server command arguments" +epoll_create, "epoll_create() failed" +epoll_ctl, "epoll_ctl() failed" + diff --git a/pseudo_server.c b/pseudo_server.c index 68433b3..8c6805b 100644 --- a/pseudo_server.c +++ b/pseudo_server.c @@ -32,6 +32,9 @@ #include <sys/un.h> #include <sys/stat.h> #include <sys/wait.h> +#ifdef PSEUDO_EPOLL +#include <sys/epoll.h> +#endif #include <signal.h> #include "pseudo.h" @@ -59,6 +62,7 @@ static int active_clients = 0, highest_client = 0, max_clients = 0; #define LOOP_DELAY 2 #define DEFAULT_PSEUDO_SERVER_TIMEOUT 30 +#define EPOLL_MAX_EVENTS 10 int pseudo_server_timeout = DEFAULT_PSEUDO_SERVER_TIMEOUT; static int die_peacefully = 0; static int die_forcefully = 0; @@ -86,7 +90,11 @@ set_do_list_clients(int sig) { static int messages = 0, responses = 0; static struct timeval message_time = { .tv_sec = 0 }; +#ifdef PSEUDO_EPOLL +static void pseudo_server_loop_epoll(void); +#else static void pseudo_server_loop(void); +#endif /* helper function to make a directory, just like mkdir -p. * Can't use system() because the child shell would end up trying @@ -375,12 +383,16 @@ pseudo_server_start(int daemonize) { kill(ppid, SIGUSR1); } } +#ifdef PSEUDO_EPOLL + pseudo_server_loop_epoll(); +#else pseudo_server_loop(); +#endif return 0; } /* mess with internal tables as needed */ -static void +static unsigned int open_client(int fd) { pseudo_client_t *new_clients; int i; @@ -396,7 +408,7 @@ open_client(int fd) { ++active_clients; if (i > highest_client) highest_client = i; - return; + return i; } } @@ -420,9 +432,11 @@ open_client(int fd) { max_clients += 16; ++active_clients; + return max_clients - 16; } else { pseudo_diag("error allocating new client, fd %d\n", fd); close(fd); + return 0; } } @@ -468,7 +482,7 @@ serve_client(int i) { if (in) { char *response_path = 0; size_t response_pathlen; - int send_response = 1; + int send_response = 1; pseudo_debug(PDBGF_SERVER | PDBGF_VERBOSE, "got a message (%d): %s\n", in->type, (in->pathlen ? in->path : "<no path>")); /* handle incoming ping */ if (in->type == PSEUDO_MSG_PING && !clients[i].pid) { @@ -504,8 +518,8 @@ serve_client(int i) { * pseudo_server_response. */ if (in->type != PSEUDO_MSG_SHUTDOWN) { - if (in->type == PSEUDO_MSG_FASTOP) - send_response = 0; + if (in->type == PSEUDO_MSG_FASTOP) + send_response = 0; /* most messages don't need these, but xattr may */ response_path = 0; response_pathlen = -1; @@ -552,14 +566,14 @@ serve_client(int i) { die_peacefully = 1; } } - if (send_response) { - if ((rc = pseudo_msg_send(clients[i].fd, in, in->pathlen, response_path)) != 0) { - pseudo_debug(PDBGF_SERVER, "failed to send response to client %d [%d]: %d (%s)\n", - i, (int) clients[i].pid, rc, strerror(errno)); - } - } else { - rc = 1; - } + if (send_response) { + if ((rc = pseudo_msg_send(clients[i].fd, in, in->pathlen, response_path)) != 0) { + pseudo_debug(PDBGF_SERVER, "failed to send response to client %d [%d]: %d (%s)\n", + i, (int) clients[i].pid, rc, strerror(errno)); + } + } else { + rc = 1; + } free(response_path); return rc; } else { @@ -572,6 +586,180 @@ serve_client(int i) { } } +#ifdef PSEUDO_EPOLL +static void pseudo_server_loop_epoll(void) +{ + struct sockaddr_un client; + socklen_t len; + int i; + int rc; + int fd; + int timeout; + struct epoll_event ev, events[EPOLL_MAX_EVENTS]; + int loop_timeout = pseudo_server_timeout; + struct sigaction eat_usr2 = { + .sa_handler = set_do_list_clients + }; + + clients = malloc(16 * sizeof(*clients)); + + sigaction(SIGUSR2, &eat_usr2, NULL); + + clients[0].fd = listen_fd; + clients[0].pid = getpid(); + + for (i = 1; i < 16; ++i) { + clients[i].fd = -1; + clients[i].pid = 0; + clients[i].tag = NULL; + clients[i].program = NULL; + } + + active_clients = 1; + max_clients = 16; + highest_client = 0; + + pseudo_debug(PDBGF_SERVER, "server loop started.\n"); + if (listen_fd < 0) { + pseudo_diag("got into loop with no valid listen fd.\n"); + exit(PSEUDO_EXIT_LISTEN_FD); + } + + timeout = LOOP_DELAY * 1000; + + int epollfd = epoll_create1(0); + if (epollfd == -1) { + pseudo_diag("epoll_create1() failed.\n"); + exit(PSEUDO_EXIT_EPOLL_CREATE); + } + ev.events = EPOLLIN; + ev.data.u64 = 0; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clients[0].fd, &ev) == -1) { + pseudo_diag("epoll_ctl() failed with listening socket.\n"); + exit(PSEUDO_EXIT_EPOLL_CTL); + } + + pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server started (pid %d)", getpid()); + + for (;;) { + rc = epoll_wait(epollfd, events, EPOLL_MAX_EVENTS, timeout); + if (rc == 0 || (rc == -1 && errno == EINTR)) { + /* If there's no clients, start timing out. If there + * are active clients, never time out. + */ + if (active_clients == 1) { + loop_timeout -= LOOP_DELAY; + /* maybe flush database to disk */ + pdb_maybe_backup(); + if (loop_timeout <= 0) { + pseudo_debug(PDBGF_SERVER, "no more clients, got bored.\n"); + die_peacefully = 1; + } else { + /* display this if not exiting */ + pseudo_debug(PDBGF_SERVER | PDBGF_BENCHMARK, "%d messages handled in %.4f seconds, %d responses\n", + messages, + (double) message_time.tv_sec + + (double) message_time.tv_usec / 1000000.0, + responses); + } + } + } else if (rc > 0) { + loop_timeout = pseudo_server_timeout; + for (i = 0; i < rc; ++i) { + if (clients[events[i].data.u64].fd == listen_fd) { + if (!die_forcefully) { + len = sizeof(client); + if ((fd = accept(listen_fd, (struct sockaddr *) &client, &len)) != -1) { + /* Don't allow clients to end up on fd 2, because glibc's + * malloc debug uses that fd unconditionally. + */ + if (fd == 2) { + int newfd = fcntl(fd, F_DUPFD, 3); + close(fd); + fd = newfd; + } + pseudo_debug(PDBGF_SERVER, "new client fd %d\n", fd); + /* A new client implicitly cancels any + * previous shutdown request, or a + * shutdown for lack of clients. + */ + pseudo_server_timeout = DEFAULT_PSEUDO_SERVER_TIMEOUT; + die_peacefully = 0; + + ev.events = EPOLLIN; + ev.data.u64 = open_client(fd); + if (ev.data.u64 != 0 && epoll_ctl(epollfd, EPOLL_CTL_ADD, clients[ev.data.u64].fd, &ev) == -1) { + pseudo_diag("epoll_ctl() failed with accepted socket.\n"); + exit(PSEUDO_EXIT_EPOLL_CTL); + } + } else if (errno == EMFILE) { + // select() loop would drop a client here, we do nothing (for now) + pseudo_debug(PDBGF_SERVER, "Hit max open files.\n"); + } + } + } else { + struct timeval tv1, tv2; + int rc; + gettimeofday(&tv1, NULL); + rc = serve_client(events[i].data.u64); + gettimeofday(&tv2, NULL); + ++messages; + if (rc == 0) + ++responses; + message_time.tv_sec += (tv2.tv_sec - tv1.tv_sec); + message_time.tv_usec += (tv2.tv_usec - tv1.tv_usec); + if (message_time.tv_usec < 0) { + message_time.tv_usec += 1000000; + --message_time.tv_sec; + } else while (message_time.tv_usec > 1000000) { + message_time.tv_usec -= 1000000; + ++message_time.tv_sec; + } + } + if (die_forcefully) + break; + } + pseudo_debug(PDBGF_SERVER, "server loop complete [%d clients left]\n", active_clients); + } else { + pseudo_diag("epoll_wait failed: %s\n", strerror(errno)); + break; + } + if (do_list_clients) { + do_list_clients = 0; + pseudo_diag("listing clients [1 through %d]:\n", highest_client); + for (i = 1; i <= highest_client; ++i) { + if (clients[i].fd == -1) { + pseudo_diag("client %4d: inactive.\n", i); + continue; + } + pseudo_diag("client %4d: fd %4d, pid %5d, program %s\n", + i, clients[i].fd, clients[i].pid, + clients[i].program ? clients[i].program : "<unspecified>"); + } + pseudo_diag("done.\n"); + } + if (die_peacefully || die_forcefully) { + pseudo_debug(PDBGF_SERVER, "quitting.\n"); + pseudo_debug(PDBGF_SERVER | PDBGF_BENCHMARK, "server %d exiting: handled %d messages in %.4f seconds\n", + getpid(), messages, + (double) message_time.tv_sec + + (double) message_time.tv_usec / 1000000.0); + pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server %d exiting: handled %d messages in %.4f seconds", + getpid(), messages, + (double) message_time.tv_sec + + (double) message_time.tv_usec / 1000000.0); + /* and at this point, we'll start refusing connections */ + close(clients[0].fd); + /* This is a good place to insert a delay for + * debugging race conditions during startup. */ + /* usleep(300000); */ + exit(0); + } + } + +} +#else + /* get clients, handle messages, shut down. * This doesn't actually do any work, it just calls a ton of things which * do work. @@ -639,8 +827,8 @@ pseudo_server_loop(void) { */ if (active_clients == 1) { loop_timeout -= LOOP_DELAY; - /* maybe flush database to disk */ - pdb_maybe_backup(); + /* maybe flush database to disk */ + pdb_maybe_backup(); if (loop_timeout <= 0) { pseudo_debug(PDBGF_SERVER, "no more clients, got bored.\n"); die_peacefully = 1; @@ -650,7 +838,7 @@ pseudo_server_loop(void) { messages, (double) message_time.tv_sec + (double) message_time.tv_usec / 1000000.0, - responses); + responses); } } } else if (rc > 0) { @@ -663,13 +851,13 @@ pseudo_server_loop(void) { close_client(i); } else if (FD_ISSET(clients[i].fd, &reads)) { struct timeval tv1, tv2; - int rc; + int rc; gettimeofday(&tv1, NULL); rc = serve_client(i); gettimeofday(&tv2, NULL); ++messages; - if (rc == 0) - ++responses; + if (rc == 0) + ++responses; message_time.tv_sec += (tv2.tv_sec - tv1.tv_sec); message_time.tv_usec += (tv2.tv_usec - tv1.tv_usec); if (message_time.tv_usec < 0) { @@ -698,12 +886,12 @@ pseudo_server_loop(void) { } pseudo_debug(PDBGF_SERVER, "new client fd %d\n", fd); open_client(fd); - /* A new client implicitly cancels any - * previous shutdown request, or a - * shutdown for lack of clients. - */ - pseudo_server_timeout = DEFAULT_PSEUDO_SERVER_TIMEOUT; - die_peacefully = 0; + /* A new client implicitly cancels any + * previous shutdown request, or a + * shutdown for lack of clients. + */ + pseudo_server_timeout = DEFAULT_PSEUDO_SERVER_TIMEOUT; + die_peacefully = 0; } } pseudo_debug(PDBGF_SERVER, "server loop complete [%d clients left]\n", active_clients); @@ -718,7 +906,7 @@ pseudo_server_loop(void) { } pseudo_diag("client %4d: fd %4d, pid %5d, state %s, program %s\n", i, clients[i].fd, clients[i].pid, - FD_ISSET(clients[i].fd, &reads) ? "R" : "-", + FD_ISSET(clients[i].fd, &reads) ? "R" : "-", clients[i].program ? clients[i].program : "<unspecified>"); } pseudo_diag("done.\n"); @@ -770,3 +958,4 @@ pseudo_server_loop(void) { } pseudo_diag("select failed: %s\n", strerror(errno)); } +#endif /* this is the else of #ifdef PSEUDO_EPOLL */ |