Initial changes to move tmux to libevent.

This moves the client-side loops are pretty much fully over to event-based only
(tmux.c and client.c) but server-side (server.c and friends) treats libevent as
a sort of clever poll, waking up after every event to run various things.

Moving the server stuff over to bufferevents and timers and so on will come
later.
This commit is contained in:
Nicholas Marriott
2009-11-04 20:50:11 +00:00
parent f575e39b0a
commit abf3a5d50e
13 changed files with 601 additions and 632 deletions

601
server.c
View File

@ -24,6 +24,7 @@
#include <sys/wait.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
@ -45,112 +46,28 @@
struct clients clients;
struct clients dead_clients;
/* Mapping of a pollfd to an fd independent of its position in the array. */
struct poll_item {
int fd;
int events;
void (*fn)(int, int, void *);
void *data;
RB_ENTRY(poll_item) entry;
};
RB_HEAD(poll_items, poll_item) poll_items;
int server_poll_cmp(struct poll_item *, struct poll_item *);
struct poll_item*server_poll_lookup(int);
struct pollfd *server_poll_flatten(int *);
void server_poll_dispatch(struct pollfd *, int);
void server_poll_reset(void);
RB_PROTOTYPE(poll_items, poll_item, entry, server_poll_cmp);
RB_GENERATE(poll_items, poll_item, entry, server_poll_cmp);
int server_fd;
int server_shutdown;
struct event server_ev_accept;
struct event server_ev_sigterm;
struct event server_ev_sigusr1;
struct event server_ev_sigchld;
struct event server_ev_second;
int server_create_socket(void);
void server_callback(int, int, void *);
int server_main(int);
void server_shutdown(void);
void server_loop(void);
int server_should_shutdown(void);
void server_child_signal(void);
void server_send_shutdown(void);
void server_clean_dead(void);
void server_second_timers(void);
int server_update_socket(void);
void server_accept_callback(int, short, void *);
void server_signal_callback(int, short, void *);
void server_child_signal(void);
void server_child_exited(pid_t, int);
void server_child_stopped(pid_t, int);
void server_second_callback(int, short, void *);
void server_lock_server(void);
void server_lock_sessions(void);
int server_update_socket(void);
int
server_poll_cmp(struct poll_item *pitem1, struct poll_item *pitem2)
{
return (pitem1->fd - pitem2->fd);
}
void
server_poll_add(int fd, int events, void (*fn)(int, int, void *), void *data)
{
struct poll_item *pitem;
pitem = xmalloc(sizeof *pitem);
pitem->fd = fd;
pitem->events = events;
pitem->fn = fn;
pitem->data = data;
RB_INSERT(poll_items, &poll_items, pitem);
}
struct poll_item *
server_poll_lookup(int fd)
{
struct poll_item pitem;
pitem.fd = fd;
return (RB_FIND(poll_items, &poll_items, &pitem));
}
struct pollfd *
server_poll_flatten(int *nfds)
{
struct poll_item *pitem;
struct pollfd *pfds;
pfds = NULL;
*nfds = 0;
RB_FOREACH(pitem, poll_items, &poll_items) {
pfds = xrealloc(pfds, (*nfds) + 1, sizeof *pfds);
pfds[*nfds].fd = pitem->fd;
pfds[*nfds].events = pitem->events;
(*nfds)++;
}
return (pfds);
}
void
server_poll_dispatch(struct pollfd *pfds, int nfds)
{
struct poll_item *pitem;
struct pollfd *pfd;
while (nfds > 0) {
pfd = &pfds[--nfds];
if (pfd->revents != 0) {
pitem = server_poll_lookup(pfd->fd);
pitem->fn(pitem->fd, pfd->revents, pitem->data);
}
}
xfree(pfds);
}
void
server_poll_reset(void)
{
struct poll_item *pitem;
while (!RB_EMPTY(&poll_items)) {
pitem = RB_ROOT(&poll_items);
RB_REMOVE(poll_items, &poll_items, pitem);
xfree(pitem);
}
}
/* Create server socket. */
int
@ -191,40 +108,14 @@ server_create_socket(void)
return (fd);
}
/* Callback for server socket. */
void
server_callback(int fd, int events, unused void *data)
{
struct sockaddr_storage sa;
socklen_t slen = sizeof sa;
int newfd;
if (events & (POLLERR|POLLNVAL|POLLHUP))
fatalx("lost server socket");
if (!(events & POLLIN))
return;
newfd = accept(fd, (struct sockaddr *) &sa, &slen);
if (newfd == -1) {
if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED)
return;
fatal("accept failed");
}
if (sigterm) {
close(newfd);
return;
}
server_client_create(newfd);
}
/* Fork new server. */
int
server_start(char *path)
{
struct client *c;
int pair[2], srv_fd;
char *cause;
char rpathbuf[MAXPATHLEN];
int pair[2];
char *cause, rpathbuf[MAXPATHLEN];
struct timeval tv;
/* The first client is special and gets a socketpair; create it. */
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0)
@ -269,7 +160,7 @@ server_start(char *path)
log_debug("socket path %s", socket_path);
setproctitle("server (%s)", rpathbuf);
srv_fd = server_create_socket();
server_fd = server_create_socket();
server_client_create(pair[1]);
if (access(SYSTEM_CFG, R_OK) == 0) {
@ -282,7 +173,20 @@ server_start(char *path)
if (cfg_file != NULL && load_cfg(cfg_file, NULL, &cause) != 0)
goto error;
exit(server_main(srv_fd));
event_init();
event_set(&server_ev_accept,
server_fd, EV_READ|EV_PERSIST, server_accept_callback, NULL);
event_add(&server_ev_accept, NULL);
memset(&tv, 0, sizeof tv);
tv.tv_sec = 1;
evtimer_set(&server_ev_second, server_second_callback, NULL);
evtimer_add(&server_ev_second, &tv);
server_signal_set();
server_loop();
exit(0);
error:
/* Write the error and shutdown the server. */
@ -291,150 +195,39 @@ error:
server_write_error(c, cause);
xfree(cause);
sigterm = 1;
server_shutdown();
server_shutdown = 1;
exit(server_main(srv_fd));
server_signal_set();
server_loop();
exit(1);
}
/* Main server loop. */
int
server_main(int srv_fd)
void
server_loop(void)
{
struct pollfd *pfds;
int nfds, xtimeout;
u_int i;
time_t now, last;
struct timeval tv;
siginit();
log_debug("server socket is %d", srv_fd);
memset(&tv, 0, sizeof tv);
tv.tv_usec = POLL_TIMEOUT * 1000;
last = time(NULL);
while (!server_should_shutdown()) {
server_update_socket();
pfds = NULL;
for (;;) {
/* If sigterm, kill all windows and clients. */
if (sigterm)
server_shutdown();
/* Stop if no sessions or clients left. */
if (server_should_shutdown())
break;
/* Handle child exit. */
if (sigchld) {
sigchld = 0;
server_child_signal();
continue;
}
/* Recreate socket on SIGUSR1. */
if (sigusr1) {
sigusr1 = 0;
close(srv_fd);
srv_fd = server_create_socket();
continue;
}
/* Initialise pollfd array and add server socket. */
server_poll_reset();
server_poll_add(srv_fd, POLLIN, server_callback, NULL);
/* Fill window and client sockets. */
server_job_prepare();
server_window_prepare();
server_client_prepare();
/* Update socket permissions. */
xtimeout = INFTIM;
if (server_update_socket() != 0)
xtimeout = POLL_TIMEOUT;
/* Do the poll. */
pfds = server_poll_flatten(&nfds);
if (poll(pfds, nfds, xtimeout) == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
fatal("poll failed");
}
server_poll_dispatch(pfds, nfds);
event_loopexit(&tv);
event_loop(EVLOOP_ONCE);
/* Call second-based timers. */
now = time(NULL);
if (now != last) {
last = now;
server_second_timers();
}
/* Run once-per-loop events. */
server_job_loop();
server_window_loop();
server_client_loop();
/* Collect any unset key bindings. */
key_bindings_clean();
/* Collect dead clients and sessions. */
server_clean_dead();
}
server_poll_reset();
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
if (ARRAY_ITEM(&sessions, i) != NULL)
session_destroy(ARRAY_ITEM(&sessions, i));
}
ARRAY_FREE(&sessions);
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
if (ARRAY_ITEM(&clients, i) != NULL)
server_client_lost(ARRAY_ITEM(&clients, i));
}
ARRAY_FREE(&clients);
mode_key_free_trees();
key_bindings_free();
close(srv_fd);
unlink(socket_path);
xfree(socket_path);
options_free(&global_s_options);
options_free(&global_w_options);
return (0);
}
/* Kill all clients. */
void
server_shutdown(void)
{
struct session *s;
struct client *c;
u_int i, j;
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
c = ARRAY_ITEM(&clients, i);
if (c != NULL) {
if (c->flags & (CLIENT_BAD|CLIENT_SUSPENDED))
server_client_lost(c);
else
server_write_client(c, MSG_SHUTDOWN, NULL, 0);
}
}
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
s = ARRAY_ITEM(&sessions, i);
for (j = 0; j < ARRAY_LENGTH(&clients); j++) {
c = ARRAY_ITEM(&clients, j);
if (c != NULL && c->session == s) {
s = NULL;
break;
}
}
if (s != NULL)
session_destroy(s);
}
}
}
/* Check if the server should be shutting down (no more clients or windows). */
@ -454,49 +247,28 @@ server_should_shutdown(void)
return (1);
}
/* Handle SIGCHLD. */
/* Shutdown the server by killing all clients and windows. */
void
server_child_signal(void)
server_send_shutdown(void)
{
struct window *w;
struct window_pane *wp;
struct job *job;
int status;
pid_t pid;
u_int i;
struct client *c;
struct session *s;
u_int i;
for (;;) {
switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
case -1:
if (errno == ECHILD)
return;
fatal("waitpid failed");
case 0:
return;
for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
c = ARRAY_ITEM(&clients, i);
if (c != NULL) {
if (c->flags & (CLIENT_BAD|CLIENT_SUSPENDED))
server_client_lost(c);
else
server_write_client(c, MSG_SHUTDOWN, NULL, 0);
c->session = NULL;
}
if (!WIFSTOPPED(status)) {
SLIST_FOREACH(job, &all_jobs, lentry) {
if (pid == job->pid) {
job->pid = -1;
job->status = status;
}
}
continue;
}
if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
continue;
}
for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
w = ARRAY_ITEM(&windows, i);
if (w == NULL)
continue;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (wp->pid == pid) {
if (killpg(pid, SIGCONT) != 0)
kill(pid, SIGCONT);
}
}
}
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
if ((s = ARRAY_ITEM(&sessions, i)) != NULL)
session_destroy(s);
}
}
@ -525,12 +297,215 @@ server_clean_dead(void)
}
}
/* Call any once-per-second timers. */
/* Update socket execute permissions based on whether sessions are attached. */
int
server_update_socket(void)
{
struct session *s;
u_int i;
static int last = -1;
int n;
n = 0;
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
s = ARRAY_ITEM(&sessions, i);
if (s != NULL && !(s->flags & SESSION_UNATTACHED)) {
n++;
break;
}
}
if (n != last) {
last = n;
if (n != 0)
chmod(socket_path, S_IRWXU);
else
chmod(socket_path, S_IRUSR|S_IWUSR);
}
return (n);
}
/* Callback for server socket. */
void
server_second_timers(void)
server_accept_callback(int fd, short events, unused void *data)
{
struct sockaddr_storage sa;
socklen_t slen = sizeof sa;
int newfd;
if (!(events & EV_READ))
return;
newfd = accept(fd, (struct sockaddr *) &sa, &slen);
if (newfd == -1) {
if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED)
return;
fatal("accept failed");
}
if (server_shutdown) {
close(newfd);
return;
}
server_client_create(newfd);
}
/* Set up server signal handling. */
void
server_signal_set(void)
{
struct sigaction sigact;
memset(&sigact, 0, sizeof sigact);
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_RESTART;
sigact.sa_handler = SIG_IGN;
if (sigaction(SIGINT, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGPIPE, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR2, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTSTP, &sigact, NULL) != 0)
fatal("sigaction failed");
signal_set(&server_ev_sigchld, SIGCHLD, server_signal_callback, NULL);
signal_add(&server_ev_sigchld, NULL);
signal_set(&server_ev_sigterm, SIGTERM, server_signal_callback, NULL);
signal_add(&server_ev_sigterm, NULL);
signal_set(&server_ev_sigusr1, SIGUSR1, server_signal_callback, NULL);
signal_add(&server_ev_sigusr1, NULL);
}
/* Destroy server signal events. */
void
server_signal_clear(void)
{
struct sigaction sigact;
memset(&sigact, 0, sizeof sigact);
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_RESTART;
sigact.sa_handler = SIG_DFL;
if (sigaction(SIGINT, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGPIPE, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR2, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTSTP, &sigact, NULL) != 0)
fatal("sigaction failed");
signal_del(&server_ev_sigchld);
signal_del(&server_ev_sigterm);
signal_del(&server_ev_sigusr1);
}
/* Signal handler. */
void
server_signal_callback(int sig, unused short events, unused void *data)
{
switch (sig) {
case SIGTERM:
server_shutdown = 1;
server_send_shutdown();
break;
case SIGCHLD:
server_child_signal();
break;
case SIGUSR1:
event_del(&server_ev_accept);
close(server_fd);
server_fd = server_create_socket();
event_set(&server_ev_accept, server_fd,
EV_READ|EV_PERSIST, server_accept_callback, NULL);
event_add(&server_ev_accept, NULL);
break;
}
}
/* Handle SIGCHLD. */
void
server_child_signal(void)
{
int status;
pid_t pid;
for (;;) {
switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
case -1:
if (errno == ECHILD)
return;
fatal("waitpid failed");
case 0:
return;
}
if (WIFSTOPPED(status))
server_child_stopped(pid, status);
else if (WIFEXITED(status))
server_child_exited(pid, status);
}
}
/* Handle exited children. */
void
server_child_exited(pid_t pid, int status)
{
struct window *w;
struct window_pane *wp;
struct job *job;
u_int i;
for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
if ((w = ARRAY_ITEM(&windows, i)) == NULL)
continue;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (wp->pid == pid) {
close(wp->fd);
wp->fd = -1;
}
}
}
SLIST_FOREACH(job, &all_jobs, lentry) {
if (pid == job->pid) {
job->pid = -1;
job->status = status;
}
}
}
/* Handle stopped children. */
void
server_child_stopped(pid_t pid, int status)
{
struct window *w;
struct window_pane *wp;
u_int i;
if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
return;
for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
if ((w = ARRAY_ITEM(&windows, i)) == NULL)
continue;
TAILQ_FOREACH(wp, &w->panes, entry) {
if (wp->pid == pid) {
if (killpg(pid, SIGCONT) != 0)
kill(pid, SIGCONT);
}
}
}
}
/* Handle once-per-second timer events. */
void
server_second_callback(unused int fd, unused short events, unused void *arg)
{
struct window *w;
struct window_pane *wp;
struct timeval tv;
u_int i;
if (options_get_number(&global_s_options, "lock-server"))
@ -548,6 +523,11 @@ server_second_timers(void)
wp->mode->timer(wp);
}
}
evtimer_del(&server_ev_second);
memset(&tv, 0, sizeof tv);
tv.tv_sec = 1;
evtimer_add(&server_ev_second, &tv);
}
/* Lock the server if ALL sessions have hit the time limit. */
@ -606,32 +586,3 @@ server_lock_sessions(void)
}
}
}
/* Update socket execute permissions based on whether sessions are attached. */
int
server_update_socket(void)
{
struct session *s;
u_int i;
static int last = -1;
int n;
n = 0;
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
s = ARRAY_ITEM(&sessions, i);
if (s != NULL && !(s->flags & SESSION_UNATTACHED)) {
n++;
break;
}
}
if (n != last) {
last = n;
if (n != 0)
chmod(socket_path, S_IRWXU);
else
chmod(socket_path, S_IRUSR|S_IWUSR);
}
return (n);
}