X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fsysdep.c;h=c4aa2063c062db9727e26fb0b70bf8ad65b2a4f3;hb=c61307c63378edc18a9adbd628f5fbfc4a78025c;hp=761b225c746ca6620b3a8f3c6ac9029af952c5f5;hpb=01cc19a4c2da27b4db0e980ccd3ca54d834319c8;p=citadel.git diff --git a/citadel/sysdep.c b/citadel/sysdep.c index 761b225c7..c4aa2063c 100644 --- a/citadel/sysdep.c +++ b/citadel/sysdep.c @@ -2,7 +2,7 @@ * $Id$ * * Citadel "system dependent" stuff. - * See copyright.txt for copyright information. + * See COPYING for copyright information. * * Here's where we (hopefully) have most parts of the Citadel server that * would need to be altered to run the server in a non-POSIX environment. @@ -49,9 +49,7 @@ #include #include #include -#ifdef HAVE_PTHREAD_H -#include -#endif +#include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" @@ -60,9 +58,8 @@ #include "config.h" #include "database.h" #include "housekeeping.h" -#include "tools.h" -#include "serv_crypto.h" -#include "serv_fulltext.h" +#include "modules/crypto/serv_crypto.h" /* Needed for init_ssl, client_write_ssl, client_read_ssl, destruct_ssl */ +#include "ecrash.h" #ifdef HAVE_SYS_SELECT_H #include @@ -72,6 +69,11 @@ #include "snprintf.h" #endif +#include "ctdl_module.h" +#include "threads.h" +#include "user_ops.h" +#include "control.h" + #ifdef DEBUG_MEMORY_LEAKS struct igheap { @@ -85,34 +87,80 @@ struct igheap *igheap = NULL; #endif -pthread_mutex_t Critters[MAX_SEMAPHORES]; /* Things needing locking */ -pthread_key_t MyConKey; /* TSD key for MyContext() */ +citthread_key_t MyConKey; /* TSD key for MyContext() */ int verbosity = DEFAULT_VERBOSITY; /* Logging level */ struct CitContext masterCC; time_t last_purge = 0; /* Last dead session purge */ -static int num_threads = 0; /* Current number of threads */ int num_sessions = 0; /* Current number of sessions */ -pthread_t indexer_thread_tid; -pthread_t checkpoint_thread_tid; int syslog_facility = LOG_DAEMON; int enable_syslog = 0; -extern int running_as_daemon; -void DestroyWorkerList(void); + +/* Flag for single user mode */ +static int want_single_user = 0; + +/* Try to go single user */ + +int CtdlTrySingleUser(void) +{ + int can_do = 0; + + begin_critical_section(S_SINGLE_USER); + if (want_single_user) + can_do = 0; + else + { + can_do = 1; + want_single_user = 1; + } + end_critical_section(S_SINGLE_USER); + return can_do; +} + +void CtdlEndSingleUser(void) +{ + begin_critical_section(S_SINGLE_USER); + want_single_user = 0; + end_critical_section(S_SINGLE_USER); +} + + +int CtdlWantSingleUser(void) +{ + return want_single_user; +} + +int CtdlIsSingleUser(void) +{ + if (want_single_user) + { + /* check for only one context here */ + if (num_sessions == 1) + return TRUE; + } + return FALSE; +} + /* - * lprintf() ... Write logging information + * CtdlLogPrintf() ... Write logging information */ -void lprintf(enum LogLevel loglevel, const char *format, ...) { +void CtdlLogPrintf(enum LogLevel loglevel, const char *format, ...) { va_list arg_ptr; + va_start(arg_ptr, format); + vCtdlLogPrintf(loglevel, format, arg_ptr); + va_end(arg_ptr); +} + +void vCtdlLogPrintf(enum LogLevel loglevel, const char *format, va_list arg_ptr) +{ + char buf[SIZ], buf2[SIZ]; if (enable_syslog) { - va_start(arg_ptr, format); - vsyslog((syslog_facility | loglevel), format, arg_ptr); - va_end(arg_ptr); + vsyslog((syslog_facility | loglevel), format, arg_ptr); } /* stderr output code */ @@ -123,28 +171,29 @@ void lprintf(enum LogLevel loglevel, const char *format, ...) { struct timeval tv; struct tm tim; time_t unixtime; + struct CitContext *CCC = CC; gettimeofday(&tv, NULL); /* Promote to time_t; types differ on some OSes (like darwin) */ unixtime = tv.tv_sec; localtime_r(&unixtime, &tim); - if (CC->cs_pid != 0) { - fprintf(stderr, + if ((CCC != NULL) && (CCC->cs_pid != 0)) { + sprintf(buf, "%04d/%02d/%02d %2d:%02d:%02d.%06ld [%3d] ", tim.tm_year + 1900, tim.tm_mon + 1, tim.tm_mday, tim.tm_hour, tim.tm_min, tim.tm_sec, (long)tv.tv_usec, - CC->cs_pid); + CCC->cs_pid); } else { - fprintf(stderr, + sprintf(buf, "%04d/%02d/%02d %2d:%02d:%02d.%06ld ", tim.tm_year + 1900, tim.tm_mon + 1, tim.tm_mday, tim.tm_hour, tim.tm_min, tim.tm_sec, (long)tv.tv_usec); } - va_start(arg_ptr, format); - vfprintf(stderr, format, arg_ptr); - va_end(arg_ptr); + vsnprintf(buf2, SIZ, format, arg_ptr); + + fprintf(stderr, "%s%s", buf, buf2); fflush(stderr); } } @@ -155,20 +204,29 @@ void lprintf(enum LogLevel loglevel, const char *format, ...) { * Signal handler to shut down the server. */ -volatile int time_to_die = 0; +volatile int exit_signal = 0; volatile int shutdown_and_halt = 0; +volatile int restart_server = 0; +volatile int running_as_daemon = 0; static RETSIGTYPE signal_cleanup(int signum) { - lprintf(CTDL_DEBUG, "Caught signal %d; shutting down.\n", signum); - time_to_die = 1; - master_cleanup(signum); +#ifdef THREADS_USESIGNALS + if (CT) + CT->signal = signum; + else +#endif + { + CtdlLogPrintf(CTDL_DEBUG, "Caught signal %d; shutting down.\n", signum); + exit_signal = signum; + } } + + /* * Some initialization stuff... */ void init_sysdep(void) { - int i; sigset_t set; /* Avoid vulnerabilities related to FD_SETSIZE if we can. */ @@ -187,19 +245,14 @@ void init_sysdep(void) { init_ssl(); #endif - /* Set up a bunch of semaphores to be used for critical sections */ - for (i=0; iuser.fullname, "SYS_notauth"); + /* The new context will be created already in the CON_EXECUTING state * in order to prevent another thread from grabbing it while it's * being set up. */ me->state = CON_EXECUTING; - /* * Generate a unique session number and insert this context into * the list. @@ -469,14 +502,62 @@ struct CitContext *CreateNewContext(void) { } ++num_sessions; end_critical_section(S_SESSION_TABLE); - return(me); + return (me); +} + + +struct CitContext *CtdlGetContextArray(int *count) +{ + int nContexts, i; + struct CitContext *nptr, *cptr; + + nContexts = num_sessions; + nptr = malloc(sizeof(struct CitContext) * nContexts); + if (!nptr) + return NULL; + begin_critical_section(S_SESSION_TABLE); + for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) + memcpy(&nptr[i], cptr, sizeof (struct CitContext)); + end_critical_section (S_SESSION_TABLE); + + *count = i; + return nptr; } + +/** + * This function fills in a context and its user field correctly + * Then creates/loads that user + */ +void CtdlFillSystemContext(struct CitContext *context, char *name) +{ + char sysname[USERNAME_SIZE]; + + memset(context, 0, sizeof(struct CitContext)); + context->internal_pgm = 1; + context->cs_pid = 0; + strcpy (sysname, "SYS_"); + strcat (sysname, name); + /* internal_create_user has the side effect of loading the user regardless of wether they + * already existed or needed to be created + */ + internal_create_user (sysname, &(context->user), -1) ; + + /* Check to see if the system user needs upgrading */ + if (context->user.usernum == 0) + { /* old system user with number 0, upgrade it */ + context->user.usernum = get_new_user_number(); + CtdlLogPrintf(CTDL_DEBUG, "Upgrading system user \"%s\" from user number 0 to user number %d\n", context->user.fullname, context->user.usernum); + /* add user to the database */ + CtdlPutUser(&(context->user)); + cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1); + } +} + /* - * The following functions implement output buffering. If the kernel supplies - * native TCP buffering (Linux & *BSD), use that; otherwise, emulate it with - * user-space buffering. + * The following functions implement output buffering on operating systems which + * support it (such as Linux and various BSD flavors). */ #ifndef HAVE_DARWIN #ifdef TCP_CORK @@ -489,132 +570,112 @@ struct CitContext *CreateNewContext(void) { #endif /* TCP_CORK */ #endif /* HAVE_DARWIN */ -#ifdef HAVE_TCP_BUFFERING static unsigned on = 1, off = 0; -void buffer_output(void) { - struct CitContext *ctx = MyContext(); - setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4); - ctx->buffering = 1; -} - -void unbuffer_output(void) { - struct CitContext *ctx = MyContext(); - setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4); - ctx->buffering = 0; -} -void flush_output(void) { - struct CitContext *ctx = MyContext(); - setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4); - setsockopt(ctx->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4); -} -#elif HAVE_DARWIN -/* Stub functions for Darwin/OS X where TCP buffering isn't liked at all */ void buffer_output(void) { -CC->buffering = 0; +#ifdef HAVE_TCP_BUFFERING +#ifdef HAVE_OPENSSL + if (!CC->redirect_ssl) +#endif + setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4); +#endif } + void unbuffer_output(void) { -CC->buffering = 0; -} -void flush_output(void) { -} -#else -void buffer_output(void) { - if (CC->buffering == 0) { - CC->buffering = 1; - CC->buffer_len = 0; - CC->output_buffer = malloc(SIZ); - } +#ifdef HAVE_TCP_BUFFERING +#ifdef HAVE_OPENSSL + if (!CC->redirect_ssl) +#endif + setsockopt(CC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4); +#endif } void flush_output(void) { - if (CC->buffering == 1) { - client_write(CC->output_buffer, CC->buffer_len); - CC->buffer_len = 0; - } -} - -void unbuffer_output(void) { - if (CC->buffering == 1) { - CC->buffering = 0; - /* We don't call flush_output because we can't. */ - client_write(CC->output_buffer, CC->buffer_len); - CC->buffer_len = 0; - free(CC->output_buffer); - CC->output_buffer = NULL; - } -} +#ifdef HAVE_TCP_BUFFERING + struct CitContext *CCC = CC; + setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &off, 4); + setsockopt(CCC->client_socket, IPPROTO_TCP, TCP_CORK, &on, 4); #endif +} /* * client_write() ... Send binary data to the client. */ -void client_write(char *buf, int nbytes) +int client_write(char *buf, int nbytes) { int bytes_written = 0; int retval; #ifndef HAVE_TCP_BUFFERING int old_buffer_len = 0; #endif - - if (CC->redirect_buffer != NULL) { - if ((CC->redirect_len + nbytes + 2) >= CC->redirect_alloc) { - CC->redirect_alloc = (CC->redirect_alloc * 2) + nbytes; - CC->redirect_buffer = realloc(CC->redirect_buffer, - CC->redirect_alloc); + fd_set wset; + t_context *Ctx; + int fdflags; + + Ctx = CC; + if (Ctx->redirect_buffer != NULL) { + if ((Ctx->redirect_len + nbytes + 2) >= Ctx->redirect_alloc) { + Ctx->redirect_alloc = (Ctx->redirect_alloc * 2) + nbytes; + Ctx->redirect_buffer = realloc(Ctx->redirect_buffer, + Ctx->redirect_alloc); } - memcpy(&CC->redirect_buffer[CC->redirect_len], buf, nbytes); - CC->redirect_len += nbytes; - CC->redirect_buffer[CC->redirect_len] = 0; - return; + memcpy(&Ctx->redirect_buffer[Ctx->redirect_len], buf, nbytes); + Ctx->redirect_len += nbytes; + Ctx->redirect_buffer[Ctx->redirect_len] = 0; + return 0; } -#ifndef HAVE_TCP_BUFFERING - /* If we're buffering for later, do that now. */ - if (CC->buffering) { - old_buffer_len = CC->buffer_len; - CC->buffer_len += nbytes; - CC->output_buffer = realloc(CC->output_buffer, CC->buffer_len); - memcpy(&CC->output_buffer[old_buffer_len], buf, nbytes); - return; - } -#endif - - /* Ok, at this point we're not buffering. Go ahead and write. */ - #ifdef HAVE_OPENSSL - if (CC->redirect_ssl) { + if (Ctx->redirect_ssl) { client_write_ssl(buf, nbytes); - return; + return 0; } #endif + fdflags = fcntl(Ctx->client_socket, F_GETFL); + while (bytes_written < nbytes) { - retval = write(CC->client_socket, &buf[bytes_written], + if ((fdflags & O_NONBLOCK) == O_NONBLOCK) { + FD_ZERO(&wset); + FD_SET(Ctx->client_socket, &wset); + if (select(1, NULL, &wset, NULL, NULL) == -1) { + CtdlLogPrintf(CTDL_ERR, + "client_write(%d bytes) select failed: %s (%d)\n", + nbytes - bytes_written, + strerror(errno), errno); + cit_backtrace(); + Ctx->kill_me = 1; + return -1; + } + } + + retval = write(Ctx->client_socket, &buf[bytes_written], nbytes - bytes_written); if (retval < 1) { - lprintf(CTDL_ERR, + CtdlLogPrintf(CTDL_ERR, "client_write(%d bytes) failed: %s (%d)\n", nbytes - bytes_written, strerror(errno), errno); - CC->kill_me = 1; - return; + cit_backtrace(); + // CtdlLogPrintf(CTDL_DEBUG, "Tried to send: %s", &buf[bytes_written]); + Ctx->kill_me = 1; + return -1; } bytes_written = bytes_written + retval; } + return 0; } /* - * cprintf() ... Send formatted printable data to the client. It is - * implemented in terms of client_write() but remains in - * sysdep.c in case we port to somewhere without va_args... + * cprintf() Send formatted printable data to the client. + * Implemented in terms of client_write() so it's technically not sysdep... */ void cprintf(const char *format, ...) { va_list arg_ptr; - char buf[1024]; + char buf[1024]; va_start(arg_ptr, format); if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1) @@ -636,6 +697,7 @@ int client_read_to(char *buf, int bytes, int timeout) { int len,rlen; fd_set rfds; + int fd; struct timeval tv; int retval; @@ -645,20 +707,30 @@ int client_read_to(char *buf, int bytes, int timeout) } #endif len = 0; + fd = CC->client_socket; while(lenclient_socket, &rfds); + FD_SET(fd, &rfds); tv.tv_sec = timeout; tv.tv_usec = 0; - retval = select( (CC->client_socket)+1, - &rfds, NULL, NULL, &tv); + retval = select( (fd)+1, + &rfds, NULL, NULL, &tv); + if (retval < 0) + { + if (errno == EINTR) + { + CtdlLogPrintf(CTDL_DEBUG, "Interrupted select().\n"); + CC->kill_me = 1; + return (-1); + } + } - if (FD_ISSET(CC->client_socket, &rfds) == 0) { + if (FD_ISSET(fd, &rfds) == 0) { return(0); } - rlen = read(CC->client_socket, &buf[len], bytes-len); + rlen = read(fd, &buf[len], bytes-len); if (rlen<1) { /* The socket has been disconnected! */ CC->kill_me = 1; @@ -706,22 +778,56 @@ int client_getln(char *buf, int bufsize) /* Strip the trailing LF, and the trailing CR if present. */ buf[i] = 0; - while ( (strlen(buf) > 0) && ((buf[strlen(buf)-1]==10) || (buf[strlen(buf)-1] == 13)) ) { - buf[strlen(buf)-1] = 0; + while ( (i > 0) + && ( (buf[i - 1]==13) + || ( buf[i - 1]==10)) ) { + i--; + buf[i] = 0; } - if (retval < 0) safestrncpy(buf, "000", bufsize); + if (retval < 0) safestrncpy(&buf[i], "000", bufsize - i); return(retval); } - /* - * The system-dependent part of master_cleanup() - close the master socket. + * Cleanup any contexts that are left lying around */ -void sysdep_master_cleanup(void) { - struct ServiceFunctionHook *serviceptr; +void context_cleanup(void) +{ + struct CitContext *ptr = NULL; + struct CitContext *rem = NULL; -///// DestroyWorkerList(); + /* + * Clean up the contexts. + * There are no threads so no critical_section stuff is needed. + */ + ptr = ContextList; + + /* We need to update the ContextList because some modules may want to itterate it + * Question is should we NULL it before iterating here or should we just keep updating it + * as we remove items? + * + * Answer is to NULL it first to prevent modules from doing any actions on the list at all + */ + ContextList=NULL; + while (ptr != NULL){ + /* Remove the session from the active list */ + rem = ptr->next; + --num_sessions; + + CtdlLogPrintf(CTDL_DEBUG, "Purging session %d\n", ptr->cs_pid); + RemoveContext(ptr); + free (ptr); + ptr = rem; + } +} + + + +void close_masters (void) +{ + struct ServiceFunctionHook *serviceptr; + /* * close all protocol master sockets */ @@ -729,24 +835,38 @@ void sysdep_master_cleanup(void) { serviceptr = serviceptr->next ) { if (serviceptr->tcp_port > 0) - lprintf(CTDL_INFO, "Closing listener on port %d\n", + { + CtdlLogPrintf(CTDL_INFO, "Closing listener on port %d\n", serviceptr->tcp_port); - + serviceptr->tcp_port = 0; + } + if (serviceptr->sockpath != NULL) - lprintf(CTDL_INFO, "Closing listener on '%s'\n", + CtdlLogPrintf(CTDL_INFO, "Closing listener on '%s'\n", serviceptr->sockpath); close(serviceptr->msock); - /* If it's a Unix domain socket, remove the file. */ if (serviceptr->sockpath != NULL) { unlink(serviceptr->sockpath); + serviceptr->sockpath = NULL; } } +} + + +/* + * The system-dependent part of master_cleanup() - close the master socket. + */ +void sysdep_master_cleanup(void) { + + close_masters(); + + context_cleanup(); + #ifdef HAVE_OPENSSL destruct_ssl(); #endif - serv_calendar_destroy(); CtdlDestroyProtoHooks(); CtdlDestroyDeleteHooks(); CtdlDestroyXmsgHooks(); @@ -757,11 +877,14 @@ void sysdep_master_cleanup(void) { CtdlDestroyFixedOutputHooks(); CtdlDestroySessionHooks(); CtdlDestroyServiceHook(); + CtdlDestroyRoomHooks(); + #ifdef HAVE_BACKTRACE + eCrash_Uninit(); + #endif } - /* * Terminate another session. * (This could justifiably be moved out of sysdep.c because it @@ -786,6 +909,9 @@ void graceful_shutdown(int signum) { exit(0); } +int nFireUps = 0; +int nFireUpsNonRestart = 0; +pid_t ForkedPid = 1; /* * Start running as a daemon. @@ -836,36 +962,29 @@ void start_daemon(int unused) { else { fp = fopen(file_pid_file, "w"); if (fp != NULL) { - fprintf(fp, ""F_PID_T"\n", child); + fprintf(fp, ""F_PID_T"\n", getpid()); fclose(fp); } waitpid(current_child, &status, 0); } - do_restart = 0; + nFireUpsNonRestart = nFireUps; + + /* Exit code 0 means the watcher should exit */ + if (WIFEXITED(status) && (WEXITSTATUS(status) == CTDLEXIT_SHUTDOWN)) { + do_restart = 0; + } - /* Did the main process exit with an actual exit code? */ - if (WIFEXITED(status)) { - - /* Exit code 0 means the watcher should exit */ - if (WEXITSTATUS(status) == 0) { - do_restart = 0; - } - - /* Exit code 101-109 means the watcher should exit */ - else if ( (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109) ) { - do_restart = 0; - } - - /* Any other exit code means we should restart. */ - else { - do_restart = 1; - } + /* Exit code 101-109 means the watcher should exit */ + else if (WIFEXITED(status) && (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109)) { + do_restart = 0; } - /* Any other type of termination (signals, etc.) should also restart. */ + /* Any other exit code, or no exit code, means we should restart. */ else { do_restart = 1; + nFireUps++; + ForkedPid = current_child; } } while (do_restart); @@ -876,6 +995,32 @@ void start_daemon(int unused) { +void checkcrash(void) +{ + if (nFireUpsNonRestart != nFireUps) + { + StrBuf *CrashMail; + + CrashMail = NewStrBuf(); + CtdlLogPrintf(CTDL_ALERT, "Posting crash message\n"); + StrBufPrintf(CrashMail, + " \n" + " The Citadel server process (citserver) terminated unexpectedly." + "\n \n" + " This could be the result of a bug in the server program, or some external " + "factor. You can obtain more information about this by enabling core dumps. " + "For more information, please see: " + "http://citadel.org/doku.php/faq:mastering_your_os:gdb#how.do.i.make.my.system.produce.core-files" + "\n \n" + " If you have already done this, the core dump is likely to be found at %score.%d\n" + , + ctdl_run_dir, ForkedPid); + CtdlAideMessage(ChrPtr(CrashMail), "Citadel server process terminated unexpectedly"); + FreeStrBuf(&CrashMail); + } +} + + /* * Generic routine to convert a login name to a full name (gecos) * Returns nonzero if a conversion took place @@ -897,143 +1042,17 @@ int convert_login(char NameToConvert[]) { } } -struct worker_node *worker_list = NULL; - - -/* - * create a worker thread. this function must always be called from within - * an S_WORKER_LIST critical section! - */ -void create_worker(void) { - int ret; - struct worker_node *n; - pthread_attr_t attr; - - n = malloc(sizeof(struct worker_node)); - if (n == NULL) { - lprintf(CTDL_EMERG, "can't allocate worker_node, exiting\n"); - time_to_die = -1; - return; - } - - if ((ret = pthread_attr_init(&attr))) { - lprintf(CTDL_EMERG, "pthread_attr_init: %s\n", strerror(ret)); - time_to_die = -1; - return; - } - - /* Our per-thread stacks need to be bigger than the default size, - * otherwise the MIME parser crashes on FreeBSD, and the IMAP service - * crashes on 64-bit Linux. - */ - if ((ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE))) { - lprintf(CTDL_EMERG, "pthread_attr_setstacksize: %s\n", - strerror(ret)); - time_to_die = -1; - pthread_attr_destroy(&attr); - return; - } - - if ((ret = pthread_create(&n->tid, &attr, worker_thread, NULL) != 0)) - { - - lprintf(CTDL_ALERT, "Can't create worker thread: %s\n", - strerror(ret)); - } - - n->next = worker_list; - worker_list = n; - pthread_attr_destroy(&attr); -} - -void DestroyWorkerList(void) -{ - struct CitContext *ptr; /* general-purpose utility pointer */ - struct CitContext *rem = NULL; /* list of sessions to be destroyed */ - - begin_critical_section(S_SESSION_TABLE); - ptr = ContextList; - while (ptr != NULL){ - /* Remove the session from the active list */ - rem = ptr->next; - --num_sessions; - - lprintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); - end_critical_section(S_SESSION_TABLE); - RemoveContext(ptr); - begin_critical_section(S_SESSION_TABLE); - free (ptr); - ptr = rem; - } - end_critical_section(S_SESSION_TABLE); - - struct worker_node *cur, *p; - cur = worker_list; - while (cur != NULL) - { - p = cur->next; - free (cur); - cur = p; - } - worker_list = NULL; -} - -/* - * Create the indexer thread and begin its operation. - * Then create the checkpoint thread and begin its operation. - */ -void create_maintenance_threads(void) { - int ret; - pthread_attr_t attr; - - if ((ret = pthread_attr_init(&attr))) { - lprintf(CTDL_EMERG, "pthread_attr_init: %s\n", strerror(ret)); - time_to_die = -1; - return; - } - - /* Our per-thread stacks need to be bigger than the default size, - * otherwise the MIME parser crashes on FreeBSD, and the IMAP service - * crashes on 64-bit Linux. - */ - if ((ret = pthread_attr_setstacksize(&attr, THREADSTACKSIZE))) { - lprintf(CTDL_EMERG, "pthread_attr_setstacksize: %s\n", - strerror(ret)); - time_to_die = -1; - pthread_attr_destroy(&attr); - return; - } - - if ((ret = pthread_create(&indexer_thread_tid, &attr, indexer_thread, NULL) != 0)) { - lprintf(CTDL_ALERT, "Can't create thread: %s\n", strerror(ret)); - } - - if ((ret = pthread_create(&checkpoint_thread_tid, &attr, checkpoint_thread, NULL) != 0)) { - lprintf(CTDL_ALERT, "Can't create thread: %s\n", strerror(ret)); - } - - lprintf(CTDL_NOTICE, "Spawned indexer (%ld) and checkpoint (%ld) thread. \n", - indexer_thread_tid, checkpoint_thread_tid); - pthread_attr_destroy(&attr); -} - - - /* * Purge all sessions which have the 'kill_me' flag set. * This function has code to prevent it from running more than once every * few seconds, because running it after every single unbind would waste a lot * of CPU time and keep the context list locked too much. To force it to run * anyway, set "force" to nonzero. - * - * - * After that's done, we raise the size of the worker thread pool - * if such an action is appropriate. */ void dead_session_purge(int force) { - struct CitContext *ptr; /* general-purpose utility pointer */ + struct CitContext *ptr, *ptr2; /* general-purpose utility pointer */ struct CitContext *rem = NULL; /* list of sessions to be destroyed */ - + if (force == 0) { if ( (time(NULL) - last_purge) < 5 ) { return; /* Too soon, go away */ @@ -1041,27 +1060,30 @@ void dead_session_purge(int force) { } time(&last_purge); - begin_critical_section(S_SESSION_TABLE); - for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { - if ( (ptr->state == CON_IDLE) && (ptr->kill_me) ) { - + if (try_critical_section(S_SESSION_TABLE)) + return; + + ptr = ContextList; + while (ptr) { + ptr2 = ptr; + ptr = ptr->next; + + if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) { /* Remove the session from the active list */ - if (ptr->prev) { - ptr->prev->next = ptr->next; + if (ptr2->prev) { + ptr2->prev->next = ptr2->next; } else { - ContextList = ptr->next; + ContextList = ptr2->next; } - if (ptr->next) { - ptr->next->prev = ptr->prev; + if (ptr2->next) { + ptr2->next->prev = ptr2->prev; } --num_sessions; - /* And put it on our to-be-destroyed list */ - ptr->next = rem; - rem = ptr; - + ptr2->next = rem; + rem = ptr2; } } end_critical_section(S_SESSION_TABLE); @@ -1071,20 +1093,12 @@ void dead_session_purge(int force) { * is allocated privately on this thread's stack. */ while (rem != NULL) { - lprintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); + CtdlLogPrintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); RemoveContext(rem); ptr = rem; rem = rem->next; free(ptr); } - - /* Raise the size of the worker thread pool if necessary. */ - if ( (num_sessions > num_threads) - && (num_threads < config.c_max_workers) ) { - begin_critical_section(S_WORKER_LIST); - create_worker(); - end_critical_section(S_WORKER_LIST); - } } @@ -1104,20 +1118,43 @@ void InitializeMasterCC(void) { - - /* * Bind a thread to a context. (It's inline merely to speed things up.) */ INLINE void become_session(struct CitContext *which_con) { - pthread_setspecific(MyConKey, (void *)which_con ); + citthread_setspecific(MyConKey, (void *)which_con ); } /* * This loop just keeps going and going and going... - */ + */ +/* + * FIXME: + * This current implimentation of worker_thread creates a bottle neck in several situations + * The first thing to remember is that a single thread can handle more than one connection at a time. + * More threads mean less memory for the system to run in. + * So for efficiency we want every thread to be doing something useful or waiting in the main loop for + * something to happen anywhere. + * This current implimentation requires worker threads to wait in other locations, after it has + * been committed to a single connection which is very wasteful. + * As an extreme case consider this: + * A slow client connects and this slow client sends only one character each second. + * With this current implimentation a single worker thread is dispatched to handle that connection + * until such times as the client timeout expires, an error occurs on the socket or the client + * completes its transmission. + * THIS IS VERY BAD since that thread could have handled a read from many more clients in each one + * second interval between chars. + * + * It is my intention to re-write this code and the associated client_getln, client_read functions + * to allow any thread to read data on behalf of any connection (context). + * To do this I intend to have this main loop read chars into a buffer stored in the context. + * Once the correct criteria for a full buffer is met then we will dispatch a thread to + * process it. + * This worker thread loop also needs to be able to handle binary data. + */ + void *worker_thread(void *arg) { int i; int highest; @@ -1131,12 +1168,9 @@ void *worker_thread(void *arg) { struct timeval tv; int force_purge = 0; int m; + - num_threads++; - - cdb_allocate_tsd(); - - while (!time_to_die) { + while (!CtdlThreadCheckStop()) { /* make doubly sure we're not holding any stale db handles * which might cause a deadlock. @@ -1182,30 +1216,36 @@ do_select: force_purge = 0; } } - if (!time_to_die) { + if (!CtdlThreadCheckStop()) { tv.tv_sec = 1; /* wake up every second if no input */ tv.tv_usec = 0; - retval = select(highest + 1, &readfds, NULL, NULL, &tv); + retval = CtdlThreadSelect(highest + 1, &readfds, NULL, NULL, &tv); } - - if (time_to_die) return(NULL); + else + return NULL; /* Now figure out who made this select() unblock. * First, check for an error or exit condition. */ if (retval < 0) { if (errno == EBADF) { - lprintf(CTDL_NOTICE, "select() failed: (%s)\n", + CtdlLogPrintf(CTDL_NOTICE, "select() failed: (%s)\n", strerror(errno)); goto do_select; } if (errno != EINTR) { - lprintf(CTDL_EMERG, "Exiting (%s)\n", strerror(errno)); - time_to_die = 1; - } else if (!time_to_die) + CtdlLogPrintf(CTDL_EMERG, "Exiting (%s)\n", strerror(errno)); + CtdlThreadStopAll(); + } else { + CtdlLogPrintf(CTDL_DEBUG, "Interrupted CtdlThreadSelect.\n"); + if (CtdlThreadCheckStop()) return(NULL); goto do_select; + } + } + else if(retval == 0) { + if (CtdlThreadCheckStop()) return(NULL); + goto SKIP_SELECT; } - /* Next, check to see if it's a new client connecting * on a master socket. */ @@ -1215,7 +1255,7 @@ do_select: force_purge = 0; if (FD_ISSET(serviceptr->msock, &readfds)) { ssock = accept(serviceptr->msock, NULL, 0); if (ssock >= 0) { - lprintf(CTDL_DEBUG, + CtdlLogPrintf(CTDL_DEBUG, "New client socket %d\n", ssock); @@ -1224,7 +1264,7 @@ do_select: force_purge = 0; * operations barf on FreeBSD. Not a fatal error. */ if (fcntl(ssock, F_SETFL, 0) < 0) { - lprintf(CTDL_EMERG, + CtdlLogPrintf(CTDL_EMERG, "citserver: Can't set socket to blocking: %s\n", strerror(errno)); } @@ -1240,7 +1280,9 @@ do_select: force_purge = 0; serviceptr->h_command_function; con->h_async_function = serviceptr->h_async_function; - + con->ServiceName = + serviceptr->ServiceName; + /* Determine whether it's a local socket */ if (serviceptr->sockpath != NULL) con->is_local_socket = 1; @@ -1309,9 +1351,7 @@ SKIP_SELECT: dead_session_purge(force_purge); do_housekeeping(); - check_sched_shutdown(); } - if (con != NULL) free (con);//// TODO: could this harm other threads? /* If control reaches this point, the server is shutting down */ return(NULL); } @@ -1461,7 +1501,7 @@ void dump_heap(void) { struct igheap *thisheap; for (thisheap = igheap; thisheap != NULL; thisheap = thisheap->next) { - lprintf(CTDL_CRIT, "UNFREED: %30s : %d\n", + CtdlLogPrintf(CTDL_CRIT, "UNFREED: %30s : %d\n", thisheap->file, thisheap->line); } }