# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: rousskov@measurement-factory.com-20100614212201-\ # o66t5l0kki2ri5ii # target_branch: http://www.squid-cache.org/bzr/squid3/trunk # testament_sha1: 2e4d2114dab96b38690dc2a5ad30f653087078fa # timestamp: 2010-06-30 17:59:56 -0600 # base_revision_id: squid3@treenet.co.nz-20100303093849-\ # ndhnc129fhg9xuie # # Begin patch === modified file 'configure.in' --- configure.in 2010-02-03 12:36:21 +0000 +++ configure.in 2010-03-30 21:54:40 +0000 @@ -4,7 +4,7 @@ dnl dnl dnl -AC_INIT([Squid Web Proxy],[3.HEAD-BZR],[http://www.squid-cache.org/bugs/],[squid]) +AC_INIT([Squid Web Proxy],[3.HEAD-SMP-BZR],[http://www.squid-cache.org/bugs/],[squid]) AC_PREREQ(2.61) AC_CONFIG_HEADERS([include/autoconf.h]) AC_CONFIG_AUX_DIR(cfgaux) @@ -4233,6 +4233,7 @@ src/ident/Makefile \ src/ip/Makefile \ src/log/Makefile \ + src/ipc/Makefile \ contrib/Makefile \ snmplib/Makefile \ icons/Makefile \ === modified file 'src/Makefile.am' --- src/Makefile.am 2010-01-12 21:16:29 +0000 +++ src/Makefile.am 2010-03-30 21:54:40 +0000 @@ -34,7 +34,7 @@ LoadableModules.h \ LoadableModules.cc -SUBDIRS = base comm eui acl fs repl auth ip icmp ident log +SUBDIRS = base comm eui acl fs repl auth ip icmp ident log ipc if USE_ADAPTATION SUBDIRS += adaptation @@ -164,7 +164,8 @@ auth/libauth.la \ acl/libapi.la \ ip/libip.la \ - fs/libfs.la + fs/libfs.la \ + ipc/libipc.la EXTRA_PROGRAMS = \ DiskIO/DiskDaemon/diskd \ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2010-02-06 00:13:14 +0000 +++ src/cache_cf.cc 2010-03-30 21:54:40 +0000 @@ -69,6 +69,7 @@ #include "StoreFileSystem.h" #include "SwapDir.h" #include "wordlist.h" +#include "ipc/Kids.h" #if HAVE_GLOB_H #include @@ -258,6 +259,110 @@ return error_count; } +static void +ReplaceSubstr(char*& str, int& len, unsigned substrIdx, unsigned substrLen, const char* newSubstr) +{ + assert(str != NULL); + assert(newSubstr != NULL); + + unsigned newSubstrLen = strlen(newSubstr); + if (newSubstrLen > substrLen) + str = (char*)realloc(str, len - substrLen + newSubstrLen + 1); + + // move tail part including zero + memmove(str + substrIdx + newSubstrLen, str + substrIdx + substrLen, len - substrIdx - substrLen + 1); + // copy new substring in place + memcpy(str + substrIdx, newSubstr, newSubstrLen); + + len = strlen(str); +} + +static void +SubstituteMacro(char*& line, int& len, const char* macroName, const char* substStr) +{ + assert(line != NULL); + assert(macroName != NULL); + assert(substStr != NULL); + unsigned macroNameLen = strlen(macroName); + while (const char* macroPos = strstr(line, macroName)) // we would replace all occurrences + ReplaceSubstr(line, len, macroPos - line, macroNameLen, substStr); +} + +static void +ProcessMacros(char*& line, int& len) +{ + SubstituteMacro(line, len, "${process_name}", KidName); + SubstituteMacro(line, len, "${process_number}", xitoa(KidIdentifier)); +} + +static void +trim_trailing_ws(char* str) +{ + assert(str != NULL); + unsigned i = strlen(str); + while ((i > 0) && xisspace(str[i - 1])) + --i; + str[i] = '\0'; +} + +static const char* +FindStatement(const char* line, const char* statement) +{ + assert(line != NULL); + assert(statement != NULL); + + const char* str = skip_ws(line); + unsigned len = strlen(statement); + if (strncmp(str, statement, len) == 0) { + str += len; + if (*str == '\0') + return str; + else if (xisspace(*str)) + return skip_ws(str); + } + + return NULL; +} + +static bool +StrToInt(const char* str, long& number) +{ + assert(str != NULL); + + char* end; + number = strtol(str, &end, 0); + + return (end != str) && (*end == '\0'); // returns true if string contains nothing except number +} + +static bool +EvalBoolExpr(const char* expr) +{ + assert(expr != NULL); + if (strcmp(expr, "true") == 0) { + return true; + } else if (strcmp(expr, "false") == 0) { + return false; + } else if (const char* equation = strchr(expr, '=')) { + const char* rvalue = skip_ws(equation + 1); + char* lvalue = (char*)xmalloc(equation - expr + 1); + xstrncpy(lvalue, expr, equation - expr + 1); + trim_trailing_ws(lvalue); + + long number1; + if (!StrToInt(lvalue, number1)) + fatalf("String is not a integer number: '%s'\n", lvalue); + long number2; + if (!StrToInt(rvalue, number2)) + fatalf("String is not a integer number: '%s'\n", rvalue); + + xfree(lvalue); + return number1 == number2; + } + fatalf("Unable to evaluate expression '%s'\n", expr); + return false; // this place cannot be reached +} + static int parseOneConfigFile(const char *file_name, unsigned int depth) { @@ -298,6 +403,7 @@ config_lineno = 0; + Vector if_states; while (fgets(config_input_line, BUFSIZ, fp)) { config_lineno++; @@ -357,20 +463,38 @@ continue; } + trim_trailing_ws(tmp_line); + ProcessMacros(tmp_line, tmp_line_len); debugs(3, 5, "Processing: '" << tmp_line << "'"); - /* Handle includes here */ - if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) { - err_count += parseManyConfigFiles(tmp_line + 8, depth + 1); - } else if (!parse_line(tmp_line)) { - debugs(3, 0, HERE << cfg_filename << ":" << config_lineno << " unrecognized: '" << tmp_line << "'"); - err_count++; + if (const char* expr = FindStatement(tmp_line, "if")) { + if_states.push_back(EvalBoolExpr(expr)); // store last if-statement meaning + } else if (FindStatement(tmp_line, "endif")) { + if (!if_states.empty()) + if_states.pop_back(); // remove last if-statement meaning + else + fatalf("'endif' without 'if'\n"); + } else if (FindStatement(tmp_line, "else")) { + if (!if_states.empty()) + if_states.back() = !if_states.back(); + else + fatalf("'else' without 'if'\n"); + } else if (if_states.empty() || if_states.back()) { // test last if-statement meaning if present + /* Handle includes here */ + if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) { + err_count += parseManyConfigFiles(tmp_line + 8, depth + 1); + } else if (!parse_line(tmp_line)) { + debugs(3, 0, HERE << cfg_filename << ":" << config_lineno << " unrecognized: '" << tmp_line << "'"); + err_count++; + } } safe_free(tmp_line); tmp_line_len = 0; } + if (!if_states.empty()) + fatalf("if-statement without 'endif'\n"); if (is_pipe) { int ret = pclose(fp); === modified file 'src/cf.data.pre' --- src/cf.data.pre 2010-03-03 09:38:49 +0000 +++ src/cf.data.pre 2010-03-26 13:52:36 +0000 @@ -57,6 +57,43 @@ This arbitrary restriction is to prevent recursive include references from causing Squid entering an infinite loop whilst trying to load configuration files. + + + Conditional configuration + + If-statements can be used to make configuration directives + depend on conditions: + + if + ... regular configuration directives ... + [else + ... regular configuration directives ...] + endif + + The else part is optional. The keywords "if", "else", and "endif" + must be typed on their own lines, as if they were regular + configuration directives. + + These individual conditions types are supported: + + true + Always evaluates to true. + false + Always evaluates to false. + = + Equality comparison of two integer numbers. + + + SMP-Related Macros + + The following SMP-related preprocessor macros can be used. + + ${process_name} expands to the current Squid process "name" + (e.g., squid1, squid2, or cache1). + + ${process_number} expands to the current Squid process + identifier, which is an integer number (e.g., 1, 2, 3) unique + across all Squid processes. COMMENT_END COMMENT_START @@ -6807,4 +6844,15 @@ Whether to lookup the EUI or MAC address of a connected client. DOC_END +NAME: main_processes +TYPE: int +LOC: Config.main_processes +DEFAULT: 1 +DOC_START + Number of main Squid processes to fork. + 0: "no daemon" mode, like running "squid -N ..." + 1: "no SMP" mode, start one main Squid process daemon (default) + N: start N main Squid process daemons +DOC_END + EOF === modified file 'src/client_side.cc' --- src/client_side.cc 2010-01-29 01:13:09 +0000 +++ src/client_side.cc 2010-05-24 20:09:03 +0000 @@ -102,6 +102,7 @@ #include "ident/Config.h" #include "ident/Ident.h" #include "ip/IpIntercept.h" +#include "ipc/StartListening.h" #include "MemBuf.h" #include "MemObject.h" #include "ProtoPort.h" @@ -113,6 +114,33 @@ #define comm_close comm_lingering_close #endif +/// dials clientHttpConnectionOpened or clientHttpsConnectionOpened call +class ListeningStartedDialer: public CallDialer, public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo, http_port_list *portCfg); + ListeningStartedDialer(Handler aHandler, http_port_list *aPortCfg): + handler(aHandler), portCfg(aPortCfg) {} + + virtual void print(std::ostream &os) const { startPrint(os) << + ", port=" << (void*)portCfg << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo, portCfg); } + +public: + Handler handler; + +private: + http_port_list *portCfg; ///< from Config.Sockaddr.http +}; + + +static void clientHttpConnectionOpened(int fd, int errNo, http_port_list *s); +#if USE_SSL +static void clientHttpsConnectionOpened(int fd, int errNo, http_port_list *s); +#endif + /* our socket-related context */ @@ -3329,12 +3357,39 @@ #endif /* USE_SSL */ +/// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed +static bool +OpenedHttpSocket(int fd, const char *msgIfFail) +{ + if (fd < 0) { + Must(NHttpSockets > 0); // we tried to open some + --NHttpSockets; // there will be fewer sockets than planned + Must(HttpSockets[NHttpSockets] < 0); // no extra fds received + + if (!NHttpSockets) // we could not open any listen sockets at all + fatal(msgIfFail); + + return false; + } + return true; +} + +/// find any unused HttpSockets[] slot and store fd there or return false +static bool +AddOpenedHttpSocket(int fd) +{ + bool found = false; + for (int i = 0; i < NHttpSockets && !found; i++) { + if ((found = HttpSockets[i] < 0)) + HttpSockets[i] = fd; + } + return found; +} static void clientHttpConnectionsOpen(void) { http_port_list *s = NULL; - int fd = -1; #if USE_SSL int bumpCount = 0; // counts http_ports with sslBump option #endif @@ -3358,18 +3413,34 @@ /* AYJ: 2009-12-27: bit bumpy. new ListenStateData(...) should be doing all the Comm:: stuff ... */ - enter_suid(); - - if (s->spoof_client_ip) { - fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, s->s, (COMM_NONBLOCKING|COMM_TRANSPARENT), "HTTP Socket"); - } else { - fd = comm_open_listener(SOCK_STREAM, IPPROTO_TCP, s->s, COMM_NONBLOCKING, "HTTP Socket"); - } - - leave_suid(); - - if (fd < 0) - continue; + const int openFlags = COMM_NONBLOCKING | + (s->spoof_client_ip ? COMM_TRANSPARENT : 0); + + AsyncCall::Pointer callback = asyncCall(33,2, + "clientHttpConnectionOpened", + ListeningStartedDialer(&clientHttpConnectionOpened, s)); + Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->s, openFlags, + Ipc::fdnHttpSocket, callback); + + HttpSockets[NHttpSockets++] = -1; // set in clientHttpConnectionOpened + } + +#if USE_SSL + if (bumpCount && !Config.accessList.ssl_bump) + debugs(33, 1, "WARNING: http_port(s) with SslBump found, but no " << + std::endl << "\tssl_bump ACL configured. No requests will be " << + "bumped."); +#endif +} + +/// process clientHttpConnectionsOpen result +static void +clientHttpConnectionOpened(int fd, int, http_port_list *s) +{ + if (!OpenedHttpSocket(fd, "Cannot open HTTP Port")) + return; + + Must(s); AsyncCall::Pointer call = commCbCall(5,5, "SomeCommAcceptHandler(httpAccept)", CommAcceptCbPtrFun(httpAccept, s)); @@ -3384,15 +3455,7 @@ << " HTTP connections at " << s->s << ", FD " << fd << "." ); - HttpSockets[NHttpSockets++] = fd; - } - -#if USE_SSL - if (bumpCount && !Config.accessList.ssl_bump) - debugs(33, 1, "WARNING: http_port(s) with SslBump found, but no " << - std::endl << "\tssl_bump ACL configured. No requests will be " << - "bumped."); -#endif + Must(AddOpenedHttpSocket(fd)); // otherwise, we have received a fd we did not ask for } #if USE_SSL @@ -3400,7 +3463,6 @@ clientHttpsConnectionsOpen(void) { https_port_list *s; - int fd; for (s = Config.Sockaddr.https; s; s = (https_port_list *)s->http.next) { if (MAXHTTPPORTS == NHttpSockets) { @@ -3415,25 +3477,33 @@ continue; } - enter_suid(); - fd = comm_open_listener(SOCK_STREAM, - IPPROTO_TCP, - s->http.s, - COMM_NONBLOCKING, "HTTPS Socket"); - leave_suid(); - - if (fd < 0) - continue; + AsyncCall::Pointer call = asyncCall(33, 2, "clientHttpsConnectionOpened", + ListeningStartedDialer(&clientHttpsConnectionOpened, &s->http)); + + Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->http.s, COMM_NONBLOCKING, + Ipc::fdnHttpsSocket, call); + + HttpSockets[NHttpSockets++] = -1; + } +} + +/// process clientHttpsConnectionsOpen result +static void +clientHttpsConnectionOpened(int fd, int, http_port_list *s) +{ + if (!OpenedHttpSocket(fd, "Cannot open HTTPS Port")) + return; + + Must(s); AsyncCall::Pointer call = commCbCall(5,5, "SomeCommAcceptHandler(httpsAccept)", CommAcceptCbPtrFun(httpsAccept, s)); s->listener = new Comm::ListenStateData(fd, call, true); - debugs(1, 1, "Accepting HTTPS connections at " << s->http.s << ", FD " << fd << "."); + debugs(1, 1, "Accepting HTTPS connections at " << s->s << ", FD " << fd << "."); - HttpSockets[NHttpSockets++] = fd; - } + Must(AddOpenedHttpSocket(fd)); // otherwise, we have received a fd we did not ask for } #endif @@ -3447,7 +3517,7 @@ #endif if (NHttpSockets < 1) - fatal("Cannot open HTTP Port"); + fatal("No HTTP or HTTPS ports configured"); // defaults prohibit this? } void === modified file 'src/comm.cc' --- src/comm.cc 2010-01-02 10:17:00 +0000 +++ src/comm.cc 2010-05-04 15:34:46 +0000 @@ -71,6 +71,8 @@ static void commStopHalfClosedMonitor(int fd); static IOCB commHalfClosedReader; +static void comm_init_opened(int new_socket, IpAddress &addr, unsigned char TOS, const char *note, struct addrinfo *AI); +static int comm_apply_flags(int new_socket, IpAddress &addr, int flags, struct addrinfo *AI); struct comm_io_callback_t { @@ -687,7 +689,6 @@ const char *note) { int new_socket; - fde *F = NULL; int tos = 0; struct addrinfo *AI = NULL; @@ -743,6 +744,29 @@ #endif + comm_init_opened(new_socket, addr, TOS, note, AI); + new_socket = comm_apply_flags(new_socket, addr, flags, AI); + + addr.FreeAddrInfo(AI); + + PROF_stop(comm_open); + + return new_socket; +} + +/// update FD tables after a local or remote (IPC) comm_openex(); +void +comm_init_opened(int new_socket, + IpAddress &addr, + unsigned char TOS, + const char *note, + struct addrinfo *AI) +{ + assert(new_socket >= 0); + assert(AI); + + fde *F = NULL; + /* update fdstat */ debugs(5, 5, "comm_open: FD " << new_socket << " is a new socket"); @@ -760,6 +784,19 @@ F->tos = TOS; F->sock_family = AI->ai_family; +} + +/// apply flags after a local comm_open*() call; +/// returns new_socket or -1 on error +static int +comm_apply_flags(int new_socket, + IpAddress &addr, + int flags, + struct addrinfo *AI) +{ + assert(new_socket >= 0); + assert(AI); + const int sock_type = AI->ai_socktype; if (!(flags & COMM_NOCLOEXEC)) commSetCloseOnExec(new_socket); @@ -790,18 +827,14 @@ if (commBind(new_socket, *AI) != COMM_OK) { comm_close(new_socket); - addr.FreeAddrInfo(AI); return -1; - PROF_stop(comm_open); } } - addr.FreeAddrInfo(AI); - if (flags & COMM_NONBLOCKING) if (commSetNonBlocking(new_socket) == COMM_ERROR) { + comm_close(new_socket); return -1; - PROF_stop(comm_open); } #ifdef TCP_NODELAY @@ -813,11 +846,50 @@ if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM) commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz); - PROF_stop(comm_open); - return new_socket; } +void +comm_import_opened(int fd, + IpAddress &addr, + int flags, + const char *note, + struct addrinfo *AI) +{ + debugs(5, 2, HERE << " FD " << fd << " at " << addr); + assert(fd >= 0); + assert(AI); + + comm_init_opened(fd, addr, 0, note, AI); + + if (!(flags & COMM_NOCLOEXEC)) + fd_table[fd].flags.close_on_exec = 1; + + if (addr.GetPort() > (u_short) 0) { +#ifdef _SQUID_MSWIN_ + if (sock_type != SOCK_DGRAM) +#endif + fd_table[fd].flags.nolinger = 1; + } + + if ((flags & COMM_TRANSPARENT)) + fd_table[fd].flags.transparent = 1; + + if (flags & COMM_NONBLOCKING) + fd_table[fd].flags.nonblocking = 1; + +#ifdef TCP_NODELAY + if (AI->ai_socktype == SOCK_STREAM) + fd_table[fd].flags.nodelay = 1; +#endif + + /* no fd_table[fd].flags. updates needed for these conditions: + * if ((flags & COMM_REUSEADDR)) ... + * if ((flags & COMM_DOBIND) ...) ... + */ +} + + CBDATA_CLASS_INIT(ConnectStateData); void * @@ -2409,3 +2481,97 @@ return EVENT_ERROR; }; } + +/// Create a unix-domain socket (UDS) that only supports FD_MSGHDR I/O. +int +comm_open_uds(int sock_type, + int proto, + struct sockaddr_un* addr, + int flags) +{ + // TODO: merge with comm_openex() when IpAddress becomes NetAddress + + int new_socket; + + PROF_start(comm_open); + /* Create socket for accepting new connections. */ + statCounter.syscalls.sock.sockets++; + + /* Setup the socket addrinfo details for use */ + struct addrinfo AI; + AI.ai_flags = 0; + AI.ai_family = PF_UNIX; + AI.ai_socktype = sock_type; + AI.ai_protocol = proto; + AI.ai_addrlen = SUN_LEN(addr); + AI.ai_addr = (sockaddr*)addr; + AI.ai_canonname = NULL; + AI.ai_next = NULL; + + debugs(50, 3, HERE << "Attempt open socket for: " << addr->sun_path); + + if ((new_socket = socket(AI.ai_family, AI.ai_socktype, AI.ai_protocol)) < 0) { + /* Increase the number of reserved fd's if calls to socket() + * are failing because the open file table is full. This + * limits the number of simultaneous clients */ + + if (limitError(errno)) { + debugs(50, DBG_IMPORTANT, HERE << "socket failure: " << xstrerror()); + fdAdjustReserved(); + } else { + debugs(50, DBG_CRITICAL, HERE << "socket failure: " << xstrerror()); + } + + PROF_stop(comm_open); + return -1; + } + + debugs(50, 3, HERE "Opened UDS FD " << new_socket << " : family=" << AI.ai_family << ", type=" << AI.ai_socktype << ", protocol=" << AI.ai_protocol); + + /* update fdstat */ + debugs(50, 5, HERE << "FD " << new_socket << " is a new socket"); + + assert(!isOpen(new_socket)); + fd_open(new_socket, FD_MSGHDR, NULL); + + fdd_table[new_socket].close_file = NULL; + + fdd_table[new_socket].close_line = 0; + + fd_table[new_socket].sock_family = AI.ai_family; + + if (!(flags & COMM_NOCLOEXEC)) + commSetCloseOnExec(new_socket); + + if (flags & COMM_REUSEADDR) + commSetReuseAddr(new_socket); + + if (flags & COMM_NONBLOCKING) { + if (commSetNonBlocking(new_socket) != COMM_OK) { + comm_close(new_socket); + PROF_stop(comm_open); + return -1; + } + } + + if (flags & COMM_DOBIND) { + if (commBind(new_socket, AI) != COMM_OK) { + comm_close(new_socket); + PROF_stop(comm_open); + return -1; + } + } + +#ifdef TCP_NODELAY + if (sock_type == SOCK_STREAM) + commSetTcpNoDelay(new_socket); + +#endif + + if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM) + commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz); + + PROF_stop(comm_open); + + return new_socket; +} === modified file 'src/comm.h' --- src/comm.h 2009-12-31 02:35:01 +0000 +++ src/comm.h 2010-05-02 01:42:47 +0000 @@ -55,6 +55,9 @@ SQUIDCEXTERN void comm_exit(void); SQUIDCEXTERN int comm_open(int, int, IpAddress &, int, const char *note); +SQUIDCEXTERN int comm_open_uds(int sock_type, int proto, struct sockaddr_un* addr, int flags); +/// update Comm state after getting a comm_open() FD from another process +SQUIDCEXTERN void comm_import_opened(int fd, IpAddress &addr, int flags, const char *note, struct addrinfo *AI); /** * Open a port specially bound for listening or sending through a specific port. === modified file 'src/debug.cc' --- src/debug.cc 2009-11-22 20:37:27 +0000 +++ src/debug.cc 2010-04-26 07:09:03 +0000 @@ -36,6 +36,7 @@ #include "Debug.h" #include "SquidTime.h" #include "util.h" +#include "ipc/Kids.h" /* for shutting_down flag in xassert() */ #include "globals.h" @@ -52,6 +53,7 @@ static char *debug_log_file = NULL; static int Ctx_Lock = 0; static const char *debugLogTime(void); +static const char *debugLogKid(void); static void ctx_print(void); #if HAVE_SYSLOG #ifdef LOG_LOCAL4 @@ -117,8 +119,9 @@ va_start(args2, format); va_start(args3, format); - snprintf(f, BUFSIZ, "%s| %s", + snprintf(f, BUFSIZ, "%s%s| %s", debugLogTime(), + debugLogKid(), format); _db_print_file(f, args1); @@ -543,6 +546,18 @@ return buf; } +static const char * +debugLogKid(void) +{ + if (KidIdentifier != 0) { + static char buf[16]; + snprintf(buf, sizeof(buf), " kid%d", KidIdentifier); + return buf; + } + + return ""; +} + void xassert(const char *msg, const char *file, int line) { === modified file 'src/enums.h' --- src/enums.h 2010-01-01 21:16:57 +0000 +++ src/enums.h 2010-04-29 20:12:03 +0000 @@ -69,6 +69,7 @@ FD_FILE, FD_SOCKET, FD_PIPE, + FD_MSGHDR, FD_UNKNOWN }; === modified file 'src/fd.cc' --- src/fd.cc 2009-03-06 13:26:57 +0000 +++ src/fd.cc 2010-04-29 20:12:03 +0000 @@ -45,6 +45,9 @@ int socket_write_method(int, const char *, int); int file_read_method(int, char *, int); int file_write_method(int, const char *, int); +#else +int msghdr_read_method(int, char *, int); +int msghdr_write_method(int, const char *, int); #endif const char *fdTypeStr[] = { @@ -53,6 +56,7 @@ "File", "Socket", "Pipe", + "MsgHdr", "Unknown" }; @@ -169,6 +173,24 @@ return i; } +int +msghdr_read_method(int fd, char *buf, int len) +{ + PROF_start(read); + const int i = recvmsg(fd, reinterpret_cast(buf), MSG_DONTWAIT); + PROF_stop(read); + return i; +} + +int +msghdr_write_method(int fd, const char *buf, int len) +{ + PROF_start(write); + const int i = sendmsg(fd, reinterpret_cast(buf), MSG_NOSIGNAL); + PROF_stop(write); + return i > 0 ? len : i; // len is imprecise but the caller expects a match +} + #endif void @@ -213,9 +235,18 @@ } #else - F->read_method = &default_read_method; - - F->write_method = &default_write_method; + switch (type) { + + case FD_MSGHDR: + F->read_method = &msghdr_read_method; + F->write_method = &msghdr_write_method; + break; + + default: + F->read_method = &default_read_method; + F->write_method = &default_write_method; + break; + } #endif === modified file 'src/htcp.cc' --- src/htcp.cc 2010-02-11 23:48:51 +0000 +++ src/htcp.cc 2010-06-14 21:22:01 +0000 @@ -46,6 +46,24 @@ #include "http.h" #include "icmp/net_db.h" #include "AccessLogEntry.h" +#include "ipc/StartListening.h" + +/// dials htcpIncomingConnectionOpened call +class HtcpListeningStartedDialer: public CallDialer, + public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo); + HtcpListeningStartedDialer(Handler aHandler): handler(aHandler) {} + + virtual void print(std::ostream &os) const { startPrint(os) << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo); } + +public: + Handler handler; +}; typedef struct _Countstr Countstr; @@ -225,6 +243,8 @@ RR_RESPONSE }; +static void htcpIncomingConnectionOpened(int fd, int errNo); + static u_int32_t msg_id_counter = 0; static int htcpInSocket = -1; static int htcpOutSocket = -1; @@ -1483,20 +1503,15 @@ IpAddress incomingAddr = Config.Addrs.udp_incoming; incomingAddr.SetPort(Config.Port.htcp); - enter_suid(); - htcpInSocket = comm_open_listener(SOCK_DGRAM, + AsyncCall::Pointer call = asyncCall(31, 2, + "htcpIncomingConnectionOpened", + HtcpListeningStartedDialer(&htcpIncomingConnectionOpened)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, incomingAddr, COMM_NONBLOCKING, - "HTCP Socket"); - leave_suid(); - - if (htcpInSocket < 0) - fatal("Cannot open HTCP Socket"); - - commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0); - - debugs(31, 1, "Accepting HTCP messages on port " << Config.Port.htcp << ", FD " << htcpInSocket << "."); + Ipc::fdnInHtcpSocket, call); if (!Config.Addrs.udp_outgoing.IsNoAddr()) { IpAddress outgoingAddr = Config.Addrs.udp_outgoing; @@ -1518,8 +1533,6 @@ debugs(31, 1, "Outgoing HTCP messages on port " << Config.Port.htcp << ", FD " << htcpOutSocket << "."); fd_note(htcpInSocket, "Incoming HTCP socket"); - } else { - htcpOutSocket = htcpInSocket; } if (!htcpDetailPool) { @@ -1527,6 +1540,22 @@ } } +static void +htcpIncomingConnectionOpened(int fd, int errNo) +{ + htcpInSocket = fd; + + if (htcpInSocket < 0) + fatal("Cannot open HTCP Socket"); + + commSetSelect(htcpInSocket, COMM_SELECT_READ, htcpRecv, NULL, 0); + + debugs(31, 1, "Accepting HTCP messages on port " << Config.Port.htcp << ", FD " << htcpInSocket << "."); + + if (Config.Addrs.udp_outgoing.IsNoAddr()) + htcpOutSocket = htcpInSocket; +} + int htcpQuery(StoreEntry * e, HttpRequest * req, peer * p) { === modified file 'src/icp_v2.cc' --- src/icp_v2.cc 2009-11-09 11:25:11 +0000 +++ src/icp_v2.cc 2010-06-14 21:22:01 +0000 @@ -48,8 +48,31 @@ #include "SwapDir.h" #include "icmp/net_db.h" #include "ip/IpAddress.h" +#include "ipc/StartListening.h" #include "rfc1738.h" +/// dials icpIncomingConnectionOpened call +class IcpListeningStartedDialer: public CallDialer, + public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo, IpAddress& addr); + IcpListeningStartedDialer(Handler aHandler, IpAddress& anAddr): + handler(aHandler), addr(anAddr) {} + + virtual void print(std::ostream &os) const { startPrint(os) << + ", address=" << addr << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo, addr); } + +public: + Handler handler; + IpAddress addr; +}; + +static void icpIncomingConnectionOpened(int fd, int errNo, IpAddress& addr); + /// \ingroup ServerProtocolICPInternal2 static void icpLogIcp(const IpAddress &, log_type, int, const char *, int); @@ -656,35 +679,22 @@ struct addrinfo *xai = NULL; int x; - wordlist *s; if ((port = Config.Port.icp) <= 0) return; - enter_suid(); - addr = Config.Addrs.udp_incoming; addr.SetPort(port); - theInIcpConnection = comm_open_listener(SOCK_DGRAM, + + AsyncCall::Pointer call = asyncCall(12, 2, + "icpIncomingConnectionOpened", + IcpListeningStartedDialer(&icpIncomingConnectionOpened, addr)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, addr, COMM_NONBLOCKING, - "ICP Socket"); - leave_suid(); - - if (theInIcpConnection < 0) - fatal("Cannot open ICP Port"); - - commSetSelect(theInIcpConnection, - COMM_SELECT_READ, - icpHandleUdp, - NULL, - 0); - - for (s = Config.mcast_group_list; s; s = s->next) - ipcache_nbgethostbyname(s->key, mcastJoinGroups, NULL); - - debugs(12, 1, "Accepting ICP messages at " << addr << ", FD " << theInIcpConnection << "."); + Ipc::fdnInIcpSocket, call); addr.SetEmpty(); // clear for next use. addr = Config.Addrs.udp_outgoing; @@ -710,10 +720,6 @@ debugs(12, 1, "Outgoing ICP messages on port " << addr.GetPort() << ", FD " << theOutIcpConnection << "."); fd_note(theOutIcpConnection, "Outgoing ICP socket"); - - fd_note(theInIcpConnection, "Incoming ICP socket"); - } else { - theOutIcpConnection = theInIcpConnection; } theOutICPAddr.SetEmpty(); @@ -730,6 +736,31 @@ theOutICPAddr.FreeAddrInfo(xai); } +static void +icpIncomingConnectionOpened(int fd, int errNo, IpAddress& addr) +{ + theInIcpConnection = fd; + + if (theInIcpConnection < 0) + fatal("Cannot open ICP Port"); + + commSetSelect(theInIcpConnection, + COMM_SELECT_READ, + icpHandleUdp, + NULL, + 0); + + for (const wordlist *s = Config.mcast_group_list; s; s = s->next) + ipcache_nbgethostbyname(s->key, mcastJoinGroups, NULL); + + debugs(12, 1, "Accepting ICP messages at " << addr << ", FD " << theInIcpConnection << "."); + + fd_note(theInIcpConnection, "Incoming ICP socket"); + + if (Config.Addrs.udp_outgoing.IsNoAddr()) + theOutIcpConnection = theInIcpConnection; +} + /** * icpConnectionShutdown only closes the 'in' socket if it is * different than the 'out' socket. === modified file 'src/ip/IpAddress.cc' --- src/ip/IpAddress.cc 2010-03-01 01:13:17 +0000 +++ src/ip/IpAddress.cc 2010-05-02 01:20:05 +0000 @@ -879,6 +879,11 @@ return 0; } +int IpAddress::compareWhole(const IpAddress &rhs) const +{ + return memcmp(this, &rhs, sizeof(*this)); +} + bool IpAddress::operator ==(const IpAddress &s) const { return (0 == matchIPAddr(s)); @@ -1041,9 +1046,9 @@ p += ToHostname(p, blen); - if (m_SocketAddr.sin6_port > 0 && p < (buf+blen-6) ) { - /* 6 is max length of expected ':port' (short int) */ - snprintf(p, 6,":%d", GetPort() ); + if (m_SocketAddr.sin6_port > 0 && p <= (buf+blen-7) ) { + // ':port' (short int) needs at most 6 bytes plus 1 for 0-terminator + snprintf(p, 7, ":%d", GetPort() ); } // force a null-terminated string === modified file 'src/ip/IpAddress.h' --- src/ip/IpAddress.h 2010-02-28 22:04:23 +0000 +++ src/ip/IpAddress.h 2010-05-02 01:20:05 +0000 @@ -326,6 +326,13 @@ */ int matchIPAddr(const IpAddress &rhs) const; + /** Compare taking IP, port, protocol, etc. into account. Returns an + integer less than, equal to, or greater than zero if the object + is found, respectively, to be less than, to match, or to be greater + than rhs. The exact ordering algorithm is not specified and may change. + */ + int compareWhole(const IpAddress &rhs) const; + /** * Get RFC 3493 addrinfo structure from the IpAddress data * for protocol-neutral socket operations. === added directory 'src/ipc' === added file 'src/ipc/Coordinator.cc' --- src/ipc/Coordinator.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Coordinator.cc 2010-05-02 18:49:25 +0000 @@ -0,0 +1,172 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "comm.h" +#include "ipc/Coordinator.h" +#include "ipc/FdNotes.h" +#include "ipc/SharedListen.h" + + +CBDATA_NAMESPACED_CLASS_INIT(Ipc, Coordinator); +Ipc::Coordinator* Ipc::Coordinator::TheInstance = NULL; + + +Ipc::Coordinator::Coordinator(): + Port(coordinatorAddr) +{ +} + +void Ipc::Coordinator::start() +{ + Port::start(); +} + +Ipc::StrandCoord* Ipc::Coordinator::findStrand(int kidId) +{ + typedef Strands::iterator SI; + for (SI iter = strands.begin(); iter != strands.end(); ++iter) { + if (iter->kidId == kidId) + return &(*iter); + } + return NULL; +} + +void Ipc::Coordinator::registerStrand(const StrandCoord& strand) +{ + if (StrandCoord* found = findStrand(strand.kidId)) + *found = strand; + else + strands.push_back(strand); +} + +void Ipc::Coordinator::receive(const TypedMsgHdr& message) +{ + switch (message.type()) { + case mtRegistration: + debugs(54, 6, HERE << "Registration request"); + handleRegistrationRequest(StrandCoord(message)); + break; + + case mtSharedListenRequest: + debugs(54, 6, HERE << "Shared listen request"); + handleSharedListenRequest(SharedListenRequest(message)); + break; + + case mtDescriptorGet: + debugs(54, 6, HERE << "Descriptor get request"); + handleDescriptorGet(Descriptor(message)); + break; + + default: + debugs(54, 1, HERE << "Unhandled message type: " << message.type()); + break; + } +} + +void Ipc::Coordinator::handleRegistrationRequest(const StrandCoord& strand) +{ + registerStrand(strand); + + // send back an acknowledgement; TODO: remove as not needed? + TypedMsgHdr message; + strand.pack(message); + SendMessage(MakeAddr(strandAddrPfx, strand.kidId), message); +} + +void +Ipc::Coordinator::handleSharedListenRequest(const SharedListenRequest& request) +{ + debugs(54, 4, HERE << "kid" << request.requestorId << + " needs shared listen FD for " << request.params.addr); + Listeners::const_iterator i = listeners.find(request.params); + int errNo = 0; + const int sock = (i != listeners.end()) ? + i->second : openListenSocket(request, errNo); + + debugs(54, 3, HERE << "sending shared listen FD " << sock << " for " << + request.params.addr << " to kid" << request.requestorId << + " mapId=" << request.mapId); + + SharedListenResponse response(sock, errNo, request.mapId); + TypedMsgHdr message; + response.pack(message); + SendMessage(MakeAddr(strandAddrPfx, request.requestorId), message); +} + +int +Ipc::Coordinator::openListenSocket(const SharedListenRequest& request, + int &errNo) +{ + const OpenListenerParams &p = request.params; + + debugs(54, 6, HERE << "opening listen FD at " << p.addr << " for kid" << + request.requestorId); + + IpAddress addr = p.addr; // comm_open_listener may modify it + + enter_suid(); + const int sock = comm_open_listener(p.sock_type, p.proto, addr, p.flags, + FdNote(p.fdNote)); + errNo = (sock >= 0) ? 0 : errno; + leave_suid(); + + // cache positive results + if (sock >= 0) + listeners[request.params] = sock; + + return sock; +} + +void Ipc::Coordinator::handleDescriptorGet(const Descriptor& request) +{ + // XXX: hack: create descriptor here + char buffer[64]; + snprintf(buffer, sizeof(buffer), "/tmp/squid_shared_file.txt"); + static int fd = -1; + if (fd < 0) { + fd = open(buffer, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + int n = snprintf(buffer, sizeof(buffer), "coord: created %d\n", fd); + ssize_t bytes = write(fd, buffer, n); + Must(bytes == n); + debugs(54, 6, "Created FD " << fd << " for kid" << request.fromKid); + } else { + int n = snprintf(buffer, sizeof(buffer), "coord: updated %d\n", fd); + ssize_t bytes = write(fd, buffer, n); + Must(bytes == n); + } + + debugs(54, 6, "Sending FD " << fd << " to kid" << request.fromKid); + + Descriptor response(-1, fd); + TypedMsgHdr message; + response.pack(message); + SendMessage(MakeAddr(strandAddrPfx, request.fromKid), message); + + // XXX: close(fd); fd should be opened until the message has not reached rec iver +} + +void Ipc::Coordinator::broadcastSignal(int sig) const +{ + typedef Strands::const_iterator SCI; + for (SCI iter = strands.begin(); iter != strands.end(); ++iter) { + debugs(54, 5, HERE << "signal " << sig << " to kid" << iter->kidId << + ", PID=" << iter->pid); + kill(iter->pid, sig); + } +} + +Ipc::Coordinator* Ipc::Coordinator::Instance() +{ + if (!TheInstance) + TheInstance = new Coordinator; + // XXX: if the Coordinator job quits, this pointer will become invalid + // we could make Coordinator death fatal, except during exit, but since + // Strands do not re-register, even process death would be pointless. + return TheInstance; +} === added file 'src/ipc/Coordinator.h' --- src/ipc/Coordinator.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Coordinator.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,66 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_COORDINATOR_H +#define SQUID_IPC_COORDINATOR_H + + +#include "Array.h" +#include +#include "ipc/Port.h" +#include "ipc/Messages.h" +#include "ipc/SharedListen.h" + +namespace Ipc +{ + +/// Coordinates shared activities of Strands (Squid processes or threads) +class Coordinator: public Port +{ +public: + static Coordinator* Instance(); + +public: + Coordinator(); + + void broadcastSignal(int sig) const; ///< send sig to registered strands + +protected: + virtual void start(); // Port (AsyncJob) API + virtual void receive(const TypedMsgHdr& message); // Port API + + StrandCoord* findStrand(int kidId); ///< registered strand or NULL + void registerStrand(const StrandCoord &); ///< adds or updates existing + void handleRegistrationRequest(const StrandCoord &); ///< register,ACK + + /// returns cached socket or calls openListenSocket() + void handleSharedListenRequest(const SharedListenRequest& request); + void handleDescriptorGet(const Descriptor& request); + + /// calls comm_open_listener() + int openListenSocket(const SharedListenRequest& request, int &errNo); + +private: + typedef Vector Strands; ///< unsorted strands + Strands strands; ///< registered processes and threads + + typedef std::map Listeners; ///< params:fd map + Listeners listeners; ///< cached comm_open_listener() results + + static Coordinator* TheInstance; ///< the only class instance in existence + + CBDATA_CLASS2(Coordinator); + +private: + Coordinator(const Coordinator&); // not implemented + Coordinator& operator =(const Coordinator&); // not implemented +}; + + +} // namespace Ipc + +#endif /* SQUID_IPC_COORDINATOR_H */ === added file 'src/ipc/FdNotes.cc' --- src/ipc/FdNotes.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/FdNotes.cc 2010-06-14 21:22:01 +0000 @@ -0,0 +1,31 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "Debug.h" +#include "ipc/FdNotes.h" + + +const char * +Ipc::FdNote(int fdNoteId) +{ + static const char *FdNotes[Ipc::fdnEnd] = { + "None", // fdnNone + "HTTP Socket", // fdnHttpSocket + "HTTPS Socket", // fdnHttpsSocket + "Incoming SNMP Socket", // fdnInSnmpSocket + "Outgoing SNMP Socket", // fdnOutSnmpSocket + "Incoming ICP Socket", // fdnInIcpSocket + "Incoming HTCP Socket" // fdnInHtcpSocket + }; + + if (fdnNone < fdNoteId && fdNoteId < fdnEnd) + return FdNotes[fdNoteId]; + + debugs(54, 1, HERE << "salvaged bug: wrong fd_note ID: " << fdNoteId); + return FdNotes[fdnNone]; +} === added file 'src/ipc/FdNotes.h' --- src/ipc/FdNotes.h 1970-01-01 00:00:00 +0000 +++ src/ipc/FdNotes.h 2010-06-14 21:22:01 +0000 @@ -0,0 +1,26 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_FD_NOTES_H +#define SQUID_IPC_FD_NOTES_H + +namespace Ipc +{ + +/// We cannot send char* FD notes to other processes. Pass int IDs and convert. + +/// fd_note() label ID +typedef enum { fdnNone, fdnHttpSocket, fdnHttpsSocket, + fdnInSnmpSocket, fdnOutSnmpSocket, + fdnInIcpSocket, fdnInHtcpSocket, fdnEnd } FdNoteId; + +extern const char *FdNote(int fdNodeId); ///< converts FdNoteId into a string + +} // namespace Ipc; + + +#endif /* SQUID_IPC_FD_NOTES_H */ === added file 'src/ipc/Kid.cc' --- src/ipc/Kid.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Kid.cc 2010-03-30 21:54:40 +0000 @@ -0,0 +1,122 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "ipc/Kid.h" + +Kid::Kid(): + badFailures(0), + pid(-1), + startTime(0), + isRunning(false) +{ +} + +Kid::Kid(const String& kid_name): + theName(kid_name), + badFailures(0), + pid(-1), + startTime(0), + isRunning(false) +{ +} + +/// called when this kid got started, records PID +void Kid::start(pid_t cpid) +{ + assert(!running()); + assert(cpid > 0); + + isRunning = true; + pid = cpid; + time(&startTime); +} + +/// called when kid terminates, sets exiting status +void Kid::stop(status_type exitStatus) +{ + assert(running()); + assert(startTime != 0); + + isRunning = false; + + time_t stop_time; + time(&stop_time); + if ((stop_time - startTime) < fastFailureTimeLimit) + badFailures++; + else + badFailures = 0; // the failures are not "frequent" [any more] + + status = exitStatus; +} + +/// returns true if tracking of kid is stopped +bool Kid::running() const +{ + return isRunning; +} + +/// returns current pid for a running kid and last pid for a stopped kid +pid_t Kid::getPid() const +{ + assert(pid > 0); + return pid; +} + +/// whether the failures are "repeated and frequent" +bool Kid::hopeless() const +{ + return badFailures > badFailureLimit; +} + +/// returns true if the process terminated normally +bool Kid::calledExit() const +{ + return (pid > 0) && !running() && WIFEXITED(status); +} + +/// returns the exit status of the process +int Kid::exitStatus() const +{ + return WEXITSTATUS(status); +} + +/// whether the process exited with a given exit status code +bool Kid::calledExit(int code) const +{ + return calledExit() && (exitStatus() == code); +} + +/// whether the process exited with code 0 +bool Kid::exitedHappy() const +{ + return calledExit(0); +} + +/// returns true if the kid was terminated by a signal +bool Kid::signaled() const +{ + return (pid > 0) && !running() && WIFSIGNALED(status); +} + +/// returns the number of the signal that caused the kid to terminate +int Kid::termSignal() const +{ + return WTERMSIG(status); +} + +/// whether the process was terminated by a given signal +bool Kid::signaled(int sgnl) const +{ + return signaled() && (termSignal() == sgnl); +} + +/// returns kid name +const String& Kid::name() const +{ + return theName; +} === added file 'src/ipc/Kid.h' --- src/ipc/Kid.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Kid.h 2010-03-30 21:54:40 +0000 @@ -0,0 +1,85 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_IPC_KID_H +#define SQUID_IPC_KID_H + +#include "SquidString.h" + + +/// Squid child, including current forked process info and +/// info persistent across restarts +class Kid +{ +public: +#ifdef _SQUID_NEXT_ + typedef union wait status_type; +#else + typedef int status_type; +#endif + + /// keep restarting until the number of bad failures exceed this limit + enum { badFailureLimit = 4 }; + + /// slower start failures are not "frequent enough" to be counted as "bad" + enum { fastFailureTimeLimit = 10 }; // seconds + +public: + Kid(); + + Kid(const String& kid_name); + + /// called when this kid got started, records PID + void start(pid_t cpid); + + /// called when kid terminates, sets exiting status + void stop(status_type exitStatus); + + /// returns true if tracking of kid is stopped + bool running() const; + + /// returns current pid for a running kid and last pid for a stopped kid + pid_t getPid() const; + + /// whether the failures are "repeated and frequent" + bool hopeless() const; + + /// returns true if the process terminated normally + bool calledExit() const; + + /// returns the exit status of the process + int exitStatus() const; + + /// whether the process exited with a given exit status code + bool calledExit(int code) const; + + /// whether the process exited with code 0 + bool exitedHappy() const; + + /// returns true if the kid was terminated by a signal + bool signaled() const; + + /// returns the number of the signal that caused the kid to terminate + int termSignal() const; + + /// whether the process was terminated by a given signal + bool signaled(int sgnl) const; + + /// returns kid name + const String& name() const; + +private: + // Information preserved across restarts + String theName; ///< process name + int badFailures; ///< number of "repeated frequent" failures + + // Information specific to a running or stopped kid + pid_t pid; ///< current (for a running kid) or last (for stopped kid) PID + time_t startTime; ///< last start time + bool isRunning; ///< whether the kid is assumed to be alive + status_type status; ///< exit status of a stopped kid +}; + +#endif /* SQUID_IPC_KID_H */ === added file 'src/ipc/Kids.cc' --- src/ipc/Kids.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Kids.cc 2010-04-26 07:09:03 +0000 @@ -0,0 +1,98 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "ipc/Kids.h" + +Kids TheKids; +char KidName[NAME_MAX]; +int KidIdentifier; + +Kids::Kids() +{ +} + +/// maintain n kids +void Kids::init(size_t n) +{ + assert(n > 0); + + if (storage.size() > 0) + storage.clean(); + + storage.reserve(n); + + char kid_name[32]; + + // add Kid records for all n main strands + for (size_t i = 1; i <= n; ++i) { + snprintf(kid_name, sizeof(kid_name), "(squid-%d)", (int)i); + storage.push_back(Kid(kid_name)); + } + + // if coordination is needed, add a Kid record for Coordinator + if (n > 1) { + snprintf(kid_name, sizeof(kid_name), "(squid-coord-%d)", (int)(n + 1)); + storage.push_back(Kid(kid_name)); + } +} + +/// returns kid by pid +Kid* Kids::find(pid_t pid) +{ + assert(pid > 0); + assert(count() > 0); + + for (size_t i = 0; i < storage.size(); ++i) { + if (storage[i].getPid() == pid) + return &storage[i]; + } + return NULL; +} + +/// returns the kid by index, useful for kids iteration +Kid& Kids::get(size_t i) +{ + assert(i >= 0 && i < count()); + return storage[i]; +} + +/// whether all kids are hopeless +bool Kids::allHopeless() const +{ + for (size_t i = 0; i < storage.size(); ++i) { + if (!storage[i].hopeless()) + return false; + } + return true; +} + +/// whether all kids called exited happy +bool Kids::allExitedHappy() const +{ + for (size_t i = 0; i < storage.size(); ++i) { + if (!storage[i].exitedHappy()) + return false; + } + return true; +} + +/// whether all kids died from a given signal +bool Kids::allSignaled(int sgnl) const +{ + for (size_t i = 0; i < storage.size(); ++i) { + if (!storage[i].signaled(sgnl)) + return false; + } + return true; +} + +/// returns the number of kids +size_t Kids::count() const +{ + return storage.size(); +} === added file 'src/ipc/Kids.h' --- src/ipc/Kids.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Kids.h 2010-03-30 21:54:40 +0000 @@ -0,0 +1,55 @@ +/* + * $Id$ + * + */ + +#ifndef SQUID_IPC_KIDS_H +#define SQUID_IPC_KIDS_H + +#include "Array.h" +#include "ipc/Kid.h" + + +/// a collection of kids +class Kids +{ +public: + Kids (); + +private: + Kids (const Kids&); ///< not implemented + Kids& operator= (const Kids&); ///< not implemented + +public: + /// maintain n kids + void init(size_t n); + + /// returns kid by pid + Kid* find(pid_t pid); + + /// returns the kid by index, useful for kids iteration + Kid& get(size_t i); + + /// whether all kids are hopeless + bool allHopeless() const; + + /// whether all kids called exited happy + bool allExitedHappy() const; + + /// whether all kids died from a given signal + bool allSignaled(int sgnl) const; + + /// returns the number of kids + size_t count() const; + +private: + Vector storage; +}; + +extern Kids TheKids; ///< All kids being maintained + +extern char KidName[NAME_MAX]; ///< current Squid process name (e.g., squid2) +extern int KidIdentifier; ///< current Squid process number (e.g., 4) + + +#endif /* SQUID_IPC_KIDS_H */ === added file 'src/ipc/Makefile.am' --- src/ipc/Makefile.am 1970-01-01 00:00:00 +0000 +++ src/ipc/Makefile.am 2010-05-02 18:49:25 +0000 @@ -0,0 +1,30 @@ +include $(top_srcdir)/src/Common.am +include $(top_srcdir)/src/TestHeaders.am + +noinst_LTLIBRARIES = libipc.la + +libipc_la_SOURCES = \ + FdNotes.cc \ + FdNotes.h \ + Kid.cc \ + Kid.h \ + Kids.cc \ + Kids.h \ + Messages.cc \ + Messages.h \ + StartListening.cc \ + StartListening.h \ + SharedListen.cc \ + SharedListen.h \ + TypedMsgHdr.cc \ + TypedMsgHdr.h \ + Coordinator.cc \ + Coordinator.h \ + UdsOp.cc \ + UdsOp.h \ + Port.cc \ + Port.h \ + Strand.cc \ + Strand.h + +DEFS += -DDEFAULT_PREFIX=\"$(prefix)\" === added file 'src/ipc/Messages.cc' --- src/ipc/Messages.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Messages.cc 2010-04-30 18:01:29 +0000 @@ -0,0 +1,61 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "comm.h" +#include "ipc/Messages.h" +#include "ipc/TypedMsgHdr.h" + + +Ipc::StrandCoord::StrandCoord(): kidId(-1), pid(0) +{ +} + +Ipc::StrandCoord::StrandCoord(int aKidId, pid_t aPid): kidId(aKidId), pid(aPid) +{ +} + +Ipc::StrandCoord::StrandCoord(const TypedMsgHdr &hdrMsg): kidId(-1), pid(0) +{ + hdrMsg.getData(mtRegistration, this, sizeof(*this)); +} + +void Ipc::StrandCoord::pack(TypedMsgHdr &hdrMsg) const +{ + hdrMsg.putData(mtRegistration, this, sizeof(*this)); +} + + +Ipc::Descriptor::Descriptor(): fromKid(-1), fd(-1) +{ +} + +Ipc::Descriptor::Descriptor(int aFromKid, int aFd): fromKid(aFromKid), fd(aFd) +{ +} + +Ipc::Descriptor::Descriptor(const TypedMsgHdr &hdrMsg): fromKid(-1), fd(-1) +{ + if (hdrMsg.type() == mtDescriptorGet) { + hdrMsg.getData(mtDescriptorGet, this, sizeof(*this)); + fd = -1; + } else { + hdrMsg.getData(mtDescriptorPut, this, sizeof(*this)); + fd = hdrMsg.getFd(); + } +} + +void Ipc::Descriptor::pack(TypedMsgHdr &hdrMsg) const +{ + if (fd >= 0) { + hdrMsg.putData(mtDescriptorPut, this, sizeof(*this)); + hdrMsg.putFd(fd); + } else { + hdrMsg.putData(mtDescriptorGet, this, sizeof(*this)); + } +} === added file 'src/ipc/Messages.h' --- src/ipc/Messages.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Messages.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,57 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_MESSAGES_H +#define SQUID_IPC_MESSAGES_H + +#include +#include + +/// Declare IPC messages. These classes translate between high-level +/// information and low-level TypedMsgHdr (i.e., struct msghdr) buffers. + +namespace Ipc +{ + +class TypedMsgHdr; + +typedef enum { mtNone = 0, mtRegistration, + mtSharedListenRequest, mtSharedListenResponse, + mtDescriptorGet, mtDescriptorPut } MessageType; + +/// Strand location details +class StrandCoord { +public: + StrandCoord(); ///< unknown location + StrandCoord(int akidId, pid_t aPid); ///< from registrant + explicit StrandCoord(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int kidId; ///< internal Squid process number + pid_t pid; ///< OS process or thread identifier +}; + +/// a [socket] descriptor information +class Descriptor +{ +public: + Descriptor(); ///< unknown descriptor + Descriptor(int fromKid, int fd); ///< from descriptor sender or requestor + explicit Descriptor(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int fromKid; /// the source of this message + int fd; ///< raw descriptor value +}; + + +} // namespace Ipc; + + +#endif /* SQUID_IPC_MESSAGES_H */ === added file 'src/ipc/Port.cc' --- src/ipc/Port.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Port.cc 2010-04-29 22:35:11 +0000 @@ -0,0 +1,65 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "CommCalls.h" +#include "ipc/Port.h" + +const char Ipc::coordinatorAddr[] = DEFAULT_PREFIX "/var/run/coordinator.ipc"; +const char Ipc::strandAddrPfx[] = DEFAULT_PREFIX "/var/run/squid"; + + +Ipc::Port::Port(const String& aListenAddr): + UdsOp(aListenAddr) +{ + setOptions(COMM_NONBLOCKING | COMM_DOBIND); +} + +void Ipc::Port::start() +{ + UdsOp::start(); + listen(); +} + +void Ipc::Port::listen() +{ + debugs(54, 6, HERE); + buf.prepForReading(); + AsyncCall::Pointer readHandler = asyncCall(54, 6, "Ipc::Port::noteRead", + CommCbMemFunT(this, &Port::noteRead)); + comm_read(fd(), buf.raw(), buf.size(), readHandler); +} + +bool Ipc::Port::doneAll() const +{ + return false; // listen forever +} + +String Ipc::Port::MakeAddr(const char* pathAddr, int id) +{ + assert(id >= 0); + String addr = pathAddr; + addr.append('-'); + addr.append(xitoa(id)); + addr.append(".ipc"); + return addr; +} + +void Ipc::Port::noteRead(const CommIoCbParams& params) +{ + debugs(54, 6, HERE << "FD " << params.fd << " flag " << params.flag << + " [" << this << ']'); + if (params.flag == COMM_OK) { + assert(params.buf == buf.raw()); + receive(buf); + } + // TODO: if there was a fatal error on our socket, close the socket before + // trying to listen again and print a level-1 error message. + + listen(); +} === added file 'src/ipc/Port.h' --- src/ipc/Port.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Port.h 2010-04-29 20:12:03 +0000 @@ -0,0 +1,53 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_PORT_H +#define SQUID_IPC_PORT_H + + +#include "SquidString.h" +#include "ipc/UdsOp.h" + + +namespace Ipc +{ + + +/// Waits for and receives incoming IPC messages; kids handle the messages +class Port: public UdsOp +{ +public: + Port(const String &aListenAddr); + +protected: + /// calculates IPC message address for strand #id at path + static String MakeAddr(const char *path, int id); + + virtual void start() = 0; // UdsOp (AsyncJob) API; has body + virtual bool doneAll() const; // UdsOp (AsyncJob) API + + /// read the next incoming message + void listen(); + + /// handle IPC message just read + virtual void receive(const TypedMsgHdr& message) = 0; + +private: + void noteRead(const CommIoCbParams ¶ms); // Comm callback API + +private: + TypedMsgHdr buf; ///< msghdr struct filled by Comm +}; + + +extern const char coordinatorAddr[]; ///< where coordinator listens +extern const char strandAddrPfx[]; ///< strand's listening address prefix + +} // namespace Ipc + + +#endif /* SQUID_IPC_PORT_H */ === added file 'src/ipc/SharedListen.cc' --- src/ipc/SharedListen.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/SharedListen.cc 2010-05-02 18:49:25 +0000 @@ -0,0 +1,149 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include +#include "comm.h" +#include "ipc/Port.h" +#include "ipc/Messages.h" +#include "ipc/Kids.h" +#include "ipc/TypedMsgHdr.h" +#include "ipc/StartListening.h" +#include "ipc/SharedListen.h" + + +/// holds information necessary to handle JoinListen response +class PendingOpenRequest +{ +public: + Ipc::OpenListenerParams params; ///< actual comm_open_sharedListen() parameters + AsyncCall::Pointer callback; // who to notify +}; + +/// maps ID assigned at request time to the response callback +typedef std::map SharedListenRequestMap; +static SharedListenRequestMap TheSharedListenRequestMap; + +static int +AddToMap(const PendingOpenRequest &por) +{ + // find unused ID using linear seach; there should not be many entries + for (int id = 0; true; ++id) { + if (TheSharedListenRequestMap.find(id) == TheSharedListenRequestMap.end()) { + TheSharedListenRequestMap[id] = por; + return id; + } + } + assert(false); // not reached + return -1; +} + +Ipc::OpenListenerParams::OpenListenerParams() +{ + xmemset(this, 0, sizeof(*this)); +} + +bool +Ipc::OpenListenerParams::operator <(const OpenListenerParams &p) const +{ + if (sock_type != p.sock_type) + return sock_type < p.sock_type; + + if (proto != p.proto) + return proto < p.proto; + + // ignore flags and fdNote differences because they do not affect binding + + return addr.compareWhole(p.addr) < 0; +} + + + +Ipc::SharedListenRequest::SharedListenRequest(): requestorId(-1), mapId(-1) +{ + // caller will then set public data members +} + +Ipc::SharedListenRequest::SharedListenRequest(const TypedMsgHdr &hdrMsg) +{ + hdrMsg.getData(mtSharedListenRequest, this, sizeof(*this)); +} + +void Ipc::SharedListenRequest::pack(TypedMsgHdr &hdrMsg) const +{ + hdrMsg.putData(mtSharedListenRequest, this, sizeof(*this)); +} + + +Ipc::SharedListenResponse::SharedListenResponse(int aFd, int anErrNo, int aMapId): + fd(aFd), errNo(anErrNo), mapId(aMapId) +{ +} + +Ipc::SharedListenResponse::SharedListenResponse(const TypedMsgHdr &hdrMsg): + fd(-1), errNo(0), mapId(-1) +{ + hdrMsg.getData(mtSharedListenResponse, this, sizeof(*this)); + fd = hdrMsg.getFd(); +} + +void Ipc::SharedListenResponse::pack(TypedMsgHdr &hdrMsg) const +{ + hdrMsg.putData(mtSharedListenResponse, this, sizeof(*this)); + hdrMsg.putFd(fd); +} + + +void Ipc::JoinSharedListen(const OpenListenerParams ¶ms, + AsyncCall::Pointer &callback) +{ + PendingOpenRequest por; + por.params = params; + por.callback = callback; + + SharedListenRequest request; + request.requestorId = KidIdentifier; + request.params = por.params; + request.mapId = AddToMap(por); + + debugs(54, 3, HERE << "getting listening FD for " << request.params.addr << + " mapId=" << request.mapId); + + TypedMsgHdr message; + request.pack(message); + SendMessage(coordinatorAddr, message); +} + +void Ipc::SharedListenJoined(const SharedListenResponse &response) +{ + const int fd = response.fd; + + debugs(54, 3, HERE << "got listening FD " << fd << " errNo=" << + response.errNo << " mapId=" << response.mapId); + + Must(TheSharedListenRequestMap.find(response.mapId) != TheSharedListenRequestMap.end()); + PendingOpenRequest por = TheSharedListenRequestMap[response.mapId]; + Must(por.callback != NULL); + TheSharedListenRequestMap.erase(response.mapId); + + if (fd >= 0) { + OpenListenerParams &p = por.params; + struct addrinfo *AI = NULL; + p.addr.GetAddrInfo(AI); + AI->ai_socktype = p.sock_type; + AI->ai_protocol = p.proto; + comm_import_opened(fd, p.addr, p.flags, FdNote(p.fdNote), AI); + p.addr.FreeAddrInfo(AI); + } + + StartListeningCb *cbd = + dynamic_cast(por.callback->getDialer()); + Must(cbd); + cbd->fd = fd; + cbd->errNo = response.errNo; + ScheduleCallHere(por.callback); +} === added file 'src/ipc/SharedListen.h' --- src/ipc/SharedListen.h 1970-01-01 00:00:00 +0000 +++ src/ipc/SharedListen.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,74 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_SHARED_LISTEN_H +#define SQUID_IPC_SHARED_LISTEN_H + +#include "base/AsyncCall.h" + +namespace Ipc +{ + +/// "shared listen" is when concurrent processes are listening on the same fd + +/// comm_open_listener() parameters holder +class OpenListenerParams +{ +public: + OpenListenerParams(); + + bool operator <(const OpenListenerParams &p) const; ///< useful for map<> + + int sock_type; + int proto; + IpAddress addr; ///< will be memset and memcopied + int flags; + int fdNote; ///< index into fd_note() comment strings +}; + +class TypedMsgHdr; + +/// a request for a listen socket with given parameters +class SharedListenRequest +{ +public: + SharedListenRequest(); ///< from OpenSharedListen() which then sets public data + explicit SharedListenRequest(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int requestorId; ///< kidId of the requestor + + OpenListenerParams params; ///< actual comm_open_sharedListen() parameters + + int mapId; ///< to map future response to the requestor's callback +}; + +/// a response to SharedListenRequest +class SharedListenResponse +{ +public: + SharedListenResponse(int fd, int errNo, int mapId); + explicit SharedListenResponse(const TypedMsgHdr &hdrMsg); ///< from recvmsg() + void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() + +public: + int fd; ///< opened listening socket or -1 + int errNo; ///< errno value from comm_open_sharedListen() call + int mapId; ///< to map future response to the requestor's callback +}; + +/// prepare and send SharedListenRequest to Coordinator +extern void JoinSharedListen(const OpenListenerParams &, AsyncCall::Pointer &); + +/// process Coordinator response to SharedListenRequest +extern void SharedListenJoined(const SharedListenResponse &response); + +} // namespace Ipc; + + +#endif /* SQUID_IPC_SHARED_LISTEN_H */ === added file 'src/ipc/StartListening.cc' --- src/ipc/StartListening.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/StartListening.cc 2010-06-14 20:45:53 +0000 @@ -0,0 +1,58 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "comm.h" +#include "TextException.h" +#include "ipc/SharedListen.h" +#include "ipc/StartListening.h" + + +Ipc::StartListeningCb::StartListeningCb(): fd(-1), errNo(0) +{ +} + +Ipc::StartListeningCb::~StartListeningCb() +{ +} + +std::ostream &Ipc::StartListeningCb::startPrint(std::ostream &os) const +{ + return os << "(FD " << fd << ", err=" << errNo; +} + + +void Ipc::StartListening(int sock_type, int proto, IpAddress &addr, + int flags, FdNoteId fdNote, AsyncCall::Pointer &callback) +{ + OpenListenerParams p; + p.sock_type = sock_type; + p.proto = proto; + p.addr = addr; + p.flags = flags; + p.fdNote = fdNote; + + if (UsingSmp()) { // if SMP is on, share + Ipc::JoinSharedListen(p, callback); + return; // wait for the call back + } + + enter_suid(); + const int sock = comm_open_listener(p.sock_type, p.proto, p.addr, p.flags, + FdNote(p.fdNote)); + const int errNo = (sock >= 0) ? 0 : errno; + leave_suid(); + + debugs(54, 3, HERE << "opened listen FD " << sock << " for " << p.addr); + + StartListeningCb *cbd = + dynamic_cast(callback->getDialer()); + Must(cbd); + cbd->fd = sock; + cbd->errNo = errNo; + ScheduleCallHere(callback); +} === added file 'src/ipc/StartListening.h' --- src/ipc/StartListening.h 1970-01-01 00:00:00 +0000 +++ src/ipc/StartListening.h 2010-05-02 18:49:25 +0000 @@ -0,0 +1,43 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_START_LISTENING_H +#define SQUID_IPC_START_LISTENING_H + +#include +#include "ipc/FdNotes.h" +#include "base/AsyncCall.h" + +class IpAddress; + +namespace Ipc +{ + +/// common API for all StartListening() callbacks +class StartListeningCb +{ +public: + StartListeningCb(); + virtual ~StartListeningCb(); + + /// starts printing arguments, return os + std::ostream &startPrint(std::ostream &os) const; + +public: + int fd; ///< opened listening socket or -1 + int errNo; ///< errno value from the comm_open_listener() call +}; + +/// Depending on whether SMP is on, either ask Coordinator to send us +/// the listening FD or call comm_open_listener() directly. +extern void StartListening(int sock_type, int proto, IpAddress &addr, + int flags, FdNoteId fdNote, AsyncCall::Pointer &callback); + +} // namespace Ipc; + + +#endif /* SQUID_IPC_START_LISTENING_H */ === added file 'src/ipc/Strand.cc' --- src/ipc/Strand.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/Strand.cc 2010-05-02 18:49:25 +0000 @@ -0,0 +1,97 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#include "config.h" +#include "ipc/Strand.h" +#include "ipc/Messages.h" +#include "ipc/SharedListen.h" +#include "ipc/Kids.h" + + +CBDATA_NAMESPACED_CLASS_INIT(Ipc, Strand); + + +Ipc::Strand::Strand(): + Port(MakeAddr(strandAddrPfx, KidIdentifier)), + isRegistered(false) +{ +} + +void Ipc::Strand::start() +{ + Port::start(); + registerSelf(); +} + +void Ipc::Strand::registerSelf() +{ + debugs(54, 6, HERE); + Must(!isRegistered); + TypedMsgHdr message; + StrandCoord(KidIdentifier, getpid()).pack(message); + SendMessage(coordinatorAddr, message); + setTimeout(6, "Ipc::Strand::timeoutHandler"); // TODO: make 6 configurable? +} + +void Ipc::Strand::receive(const TypedMsgHdr &message) +{ + debugs(54, 6, HERE << message.type()); + switch (message.type()) { + + case mtRegistration: + handleRegistrationResponse(StrandCoord(message)); + break; + + case mtSharedListenResponse: + SharedListenJoined(SharedListenResponse(message)); + break; + + case mtDescriptorPut: + putDescriptor(Descriptor(message)); + break; + + default: + debugs(54, 1, HERE << "Unhandled message type: " << message.type()); + break; + } +} + +void Ipc::Strand::handleRegistrationResponse(const StrandCoord &strand) +{ + // handle registration response from the coordinator; it could be stale + if (strand.kidId == KidIdentifier && strand.pid == getpid()) { + debugs(54, 6, "kid" << KidIdentifier << " registered"); + clearTimeout(); // we are done + + debugs(54, 6, HERE << "requesting FD"); + Descriptor request(KidIdentifier, -1); + TypedMsgHdr message; + request.pack(message); + SendMessage(coordinatorAddr, message); + } else { + // could be an ACK to the registration message of our dead predecessor + debugs(54, 6, "kid" << KidIdentifier << " is not yet registered"); + // keep listening, with a timeout + } +} + +/// receive descriptor we asked for +void Ipc::Strand::putDescriptor(const Descriptor &message) +{ + debugs(54, 6, HERE << "got FD " << message.fd); + char buffer[64]; + const int n = snprintf(buffer, sizeof(buffer), "strand: kid%d wrote using FD %d\n", KidIdentifier, message.fd); + ssize_t bytes = write(message.fd, buffer, n); + Must(bytes == n); +} + +void Ipc::Strand::timedout() +{ + debugs(54, 6, HERE << isRegistered); + if (!isRegistered) + fatalf("kid%d registration timed out", KidIdentifier); +} === added file 'src/ipc/Strand.h' --- src/ipc/Strand.h 1970-01-01 00:00:00 +0000 +++ src/ipc/Strand.h 2010-04-29 22:35:11 +0000 @@ -0,0 +1,51 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_STRAND_H +#define SQUID_IPC_STRAND_H + +#include "ipc/Port.h" + + +namespace Ipc +{ + +class StrandCoord; +class Descriptor; + +/// Receives coordination messages on behalf of its process or thread +class Strand: public Port +{ +public: + Strand(); + + virtual void start(); // Port (AsyncJob) API + +protected: + virtual void timedout(); // Port (UsdOp) API + virtual void receive(const TypedMsgHdr &message); // Port API + +private: + void registerSelf(); /// let Coordinator know this strand exists + void handleRegistrationResponse(const StrandCoord &strand); + void putDescriptor(const Descriptor &message); + +private: + bool isRegistered; ///< whether Coordinator ACKed registration (unused) + + CBDATA_CLASS2(Strand); + +private: + Strand(const Strand&); // not implemented + Strand& operator =(const Strand&); // not implemented +}; + + +} + + +#endif /* SQUID_IPC_STRAND_H */ === added file 'src/ipc/TypedMsgHdr.cc' --- src/ipc/TypedMsgHdr.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/TypedMsgHdr.cc 2010-05-01 23:47:46 +0000 @@ -0,0 +1,167 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include +#include "TextException.h" +#include "ipc/TypedMsgHdr.h" + +Ipc::TypedMsgHdr::TypedMsgHdr() +{ + xmemset(this, 0, sizeof(*this)); + sync(); +} + +Ipc::TypedMsgHdr::TypedMsgHdr(const TypedMsgHdr &tmh) +{ + xmemcpy(this, &tmh, sizeof(*this)); + sync(); +} + +Ipc::TypedMsgHdr &Ipc::TypedMsgHdr::operator =(const TypedMsgHdr &tmh) +{ + if (this != &tmh) { // skip assignment to self + xmemcpy(this, &tmh, sizeof(*this)); + sync(); + } + return *this; +} + +// update msghdr and ios pointers based on msghdr counters +void Ipc::TypedMsgHdr::sync() +{ + if (msg_name) { // we have a name + msg_name = &name; + } else { + Must(!msg_namelen && !msg_name); + } + + if (msg_iov) { // we have a data component + Must(msg_iovlen == 1); + msg_iov = ios; + ios[0].iov_base = &data; + Must(ios[0].iov_len == sizeof(data)); + } else { + Must(!msg_iovlen && !msg_iov); + } + + if (msg_control) { // we have a control component + Must(msg_controllen > 0); + msg_control = &ctrl; + } else { + Must(!msg_controllen && !msg_control); + } +} + + + +int +Ipc::TypedMsgHdr::type() const +{ + Must(msg_iovlen == 1); + return data.type_; +} + +void +Ipc::TypedMsgHdr::address(const struct sockaddr_un& addr) +{ + allocName(); + name = addr; + msg_name = &name; + msg_namelen = SUN_LEN(&name); +} + +void +Ipc::TypedMsgHdr::getData(int destType, void *raw, size_t size) const +{ + Must(type() == destType); + Must(size == data.size); + xmemcpy(raw, data.raw, size); +} + +void +Ipc::TypedMsgHdr::putData(int aType, const void *raw, size_t size) +{ + Must(size <= sizeof(data.raw)); + allocData(); + data.type_ = aType; + data.size = size; + xmemcpy(data.raw, raw, size); +} + +void +Ipc::TypedMsgHdr::putFd(int fd) +{ + Must(fd >= 0); + allocControl(); + + const int fdCount = 1; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(this); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fdCount); + + int *fdStore = reinterpret_cast(CMSG_DATA(cmsg)); + xmemcpy(fdStore, &fd, fdCount * sizeof(int)); + msg_controllen = cmsg->cmsg_len; +} + +int +Ipc::TypedMsgHdr::getFd() const +{ + Must(msg_control && msg_controllen); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(this); + Must(cmsg->cmsg_level == SOL_SOCKET); + Must(cmsg->cmsg_type == SCM_RIGHTS); + + const int fdCount = 1; + const int *fdStore = reinterpret_cast(CMSG_DATA(cmsg)); + int fd = -1; + xmemcpy(&fd, fdStore, fdCount * sizeof(int)); + return fd; +} + +void +Ipc::TypedMsgHdr::prepForReading() +{ + xmemset(this, 0, sizeof(*this)); + allocName(); + allocData(); + allocControl(); +} + +/// initialize io vector with one io record +void +Ipc::TypedMsgHdr::allocData() +{ + Must(!msg_iovlen && !msg_iov); + msg_iovlen = 1; + msg_iov = ios; + ios[0].iov_base = &data; + ios[0].iov_len = sizeof(data); + data.type_ = 0; + data.size = 0; +} + +void +Ipc::TypedMsgHdr::allocName() +{ + Must(!msg_name && !msg_namelen); + msg_name = &name; + msg_namelen = sizeof(name); // is that the right size? +} + +void +Ipc::TypedMsgHdr::allocControl() +{ + Must(!msg_control && !msg_controllen); + msg_control = &ctrl; + msg_controllen = sizeof(ctrl); +} === added file 'src/ipc/TypedMsgHdr.h' --- src/ipc/TypedMsgHdr.h 1970-01-01 00:00:00 +0000 +++ src/ipc/TypedMsgHdr.h 2010-05-02 17:58:46 +0000 @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_TYPED_MSG_HDR_H +#define SQUID_IPC_TYPED_MSG_HDR_H + +#include "config.h" +#include +#include +#if HAVE_SYS_UN_H +#include +#endif + +namespace Ipc +{ + +/// struct msghdr with a known type, fixed-size I/O and control buffers +class TypedMsgHdr: public msghdr +{ +public: + TypedMsgHdr(); + TypedMsgHdr(const TypedMsgHdr &tmh); + TypedMsgHdr &operator =(const TypedMsgHdr &tmh); + + // type-safe access to message details + int type() const; ///< returns stored type or zero if none + void address(const struct sockaddr_un& addr); ///< sets [dest.] address + void getData(int ofType, void *raw, size_t size) const; ///< checks type + void putData(int aType, const void *raw, size_t size); ///< stores type + void putFd(int aFd); ///< stores descriptor + int getFd() const; ///< returns descriptor + + /// raw, type-independent access for I/O + void prepForReading(); ///< reset and provide all buffers + char *raw() { return reinterpret_cast(this); } + const char *raw() const { return reinterpret_cast(this); } + size_t size() const { return sizeof(*this); } ///< not true message size + +private: + void sync(); + void allocData(); + void allocName(); + void allocControl(); + +private: + struct sockaddr_un name; ///< same as .msg_name + + struct iovec ios[1]; ///< same as .msg_iov[] + + struct DataBuffer { + int type_; ///< Message kind, uses MessageType values + size_t size; ///< actual raw data size (for sanity checks) + char raw[250]; ///< buffer with type-specific data + } data; ///< same as .msg_iov[0].iov_base + + struct CtrlBuffer { + char raw[CMSG_SPACE(sizeof(int))]; ///< control buffer space for one fd + } ctrl; ///< same as .msg_control +}; + +} // namespace Ipc + +#endif /* SQUID_IPC_TYPED_MSG_HDR_H */ === added file 'src/ipc/UdsOp.cc' --- src/ipc/UdsOp.cc 1970-01-01 00:00:00 +0000 +++ src/ipc/UdsOp.cc 2010-04-29 20:12:03 +0000 @@ -0,0 +1,132 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + + +#include "config.h" +#include "comm.h" +#include "CommCalls.h" +#include "ipc/UdsOp.h" + + +Ipc::UdsOp::UdsOp(const String& pathAddr): + AsyncJob("Ipc::UdsOp"), + address(PathToAddress(pathAddr)), + options(COMM_NONBLOCKING), + fd_(-1) +{ + debugs(54, 5, HERE << '[' << this << "] pathAddr=" << pathAddr); +} + +Ipc::UdsOp::~UdsOp() +{ + debugs(54, 5, HERE << '[' << this << ']'); + if (fd_ >= 0) + comm_close(fd_); +} + +void Ipc::UdsOp::setOptions(int newOptions) +{ + options = newOptions; +} + +int Ipc::UdsOp::fd() +{ + if (fd_ < 0) { + if (options & COMM_DOBIND) + unlink(address.sun_path); + fd_ = comm_open_uds(SOCK_DGRAM, 0, &address, options); + Must(fd_ >= 0); + } + return fd_; +} + +void Ipc::UdsOp::setTimeout(int seconds, const char *handlerName) +{ + AsyncCall::Pointer handler = asyncCall(54,5, handlerName, + CommCbMemFunT(this, + &UdsOp::noteTimeout)); + commSetTimeout(fd(), seconds, handler); +} + +void Ipc::UdsOp::clearTimeout() +{ + commSetTimeout(fd(), -1, NULL, NULL); // TODO: add Comm::ClearTimeout(fd) +} + +void Ipc::UdsOp::noteTimeout(const CommTimeoutCbParams &) +{ + timedout(); // our kid handles communication timeout +} + + +struct sockaddr_un +Ipc::PathToAddress(const String& pathAddr) +{ + assert(pathAddr.size() != 0); + struct sockaddr_un unixAddr; + memset(&unixAddr, 0, sizeof(unixAddr)); + unixAddr.sun_family = AF_LOCAL; + xstrncpy(unixAddr.sun_path, pathAddr.termedBuf(), sizeof(unixAddr.sun_path)); + return unixAddr; +} + + +CBDATA_NAMESPACED_CLASS_INIT(Ipc, UdsSender); + +Ipc::UdsSender::UdsSender(const String& pathAddr, const TypedMsgHdr& aMessage): + UdsOp(pathAddr), + message(aMessage), + retries(10), // TODO: make configurable? + timeout(10), // TODO: make configurable? + writing(false) +{ + message.address(address); +} + +void Ipc::UdsSender::start() +{ + UdsOp::start(); + write(); + if (timeout > 0) + setTimeout(timeout, "Ipc::UdsSender::noteTimeout"); +} + +bool Ipc::UdsSender::doneAll() const +{ + return !writing && UdsOp::doneAll(); +} + +void Ipc::UdsSender::write() +{ + debugs(54, 5, HERE); + AsyncCall::Pointer writeHandler = asyncCall(54, 5, "Ipc::UdsSender::wrote", + CommCbMemFunT(this, &UdsSender::wrote)); + comm_write(fd(), message.raw(), message.size(), writeHandler); + writing = true; +} + +void Ipc::UdsSender::wrote(const CommIoCbParams& params) +{ + debugs(54, 5, HERE << "FD " << params.fd << " flag " << params.flag << " [" << this << ']'); + writing = false; + if (params.flag != COMM_OK && retries-- > 0) { + sleep(1); // do not spend all tries at once; XXX: use an async timed event instead of blocking here; store the time when we started writing so that we do not sleep if not needed? + write(); // XXX: should we close on error so that fd() reopens? + } +} + +void Ipc::UdsSender::timedout() +{ + debugs(54, 5, HERE); + mustStop("timedout"); +} + + +void Ipc::SendMessage(const String& toAddress, const TypedMsgHdr &message) +{ + AsyncJob::AsyncStart(new UdsSender(toAddress, message)); +} === added file 'src/ipc/UdsOp.h' --- src/ipc/UdsOp.h 1970-01-01 00:00:00 +0000 +++ src/ipc/UdsOp.h 2010-04-29 20:12:03 +0000 @@ -0,0 +1,97 @@ +/* + * $Id$ + * + * DEBUG: section 54 Interprocess Communication + * + */ + +#ifndef SQUID_IPC_ASYNCUDSOP_H +#define SQUID_IPC_ASYNCUDSOP_H + + +#include "SquidString.h" +#include "base/AsyncJob.h" +#include "ipc/TypedMsgHdr.h" + +class CommTimeoutCbParams; +class CommIoCbParams; + +namespace Ipc +{ + +/// code shared by unix-domain socket senders (e.g., UdsSender or Coordinator) +/// and receivers (e.g. Port or Coordinator) +class UdsOp: public AsyncJob +{ +public: + UdsOp(const String &pathAddr); + virtual ~UdsOp(); + +public: + struct sockaddr_un address; ///< UDS address from path; treat as read-only + +protected: + virtual void timedout() {} ///< called after setTimeout() if timed out + + int fd(); ///< creates if needed and returns raw UDS socket descriptor + + /// call timedout() if no UDS messages in a given number of seconds + void setTimeout(int seconds, const char *handlerName); + void clearTimeout(); ///< remove previously set timeout, if any + + void setOptions(int newOptions); ///< changes socket options + +private: + /// Comm timeout callback; calls timedout() + void noteTimeout(const CommTimeoutCbParams &p); + +private: + int options; ///< UDS options + int fd_; ///< UDS descriptor + +private: + UdsOp(const UdsOp &); // not implemented + UdsOp &operator= (const UdsOp &); // not implemented +}; + +/// converts human-readable filename path into UDS address +extern struct sockaddr_un PathToAddress(const String &pathAddr); + + + +// XXX: move UdsSender code to UdsSender.{cc,h} +/// attempts to send an IPC message a few times, with a timeout +class UdsSender: public UdsOp +{ +public: + UdsSender(const String& pathAddr, const TypedMsgHdr& aMessage); + +protected: + virtual void start(); // UdsOp (AsyncJob) API + virtual bool doneAll() const; // UdsOp (AsyncJob) API + virtual void timedout(); // UdsOp API + +private: + void write(); ///< schedule writing + void wrote(const CommIoCbParams& params); ///< done writing or error + +private: + TypedMsgHdr message; ///< what to send + int retries; ///< how many times to try after a write error + int timeout; ///< total time to send the message + bool writing; ///< whether Comm started and did not finish writing + + CBDATA_CLASS2(UdsSender); + +private: + UdsSender(const UdsSender&); // not implemented + UdsSender& operator= (const UdsSender&); // not implemented +}; + + +void SendMessage(const String& toAddress, const TypedMsgHdr& message); + + +} + +#endif /* SQUID_IPC_ASYNCUDSOP_H */ === modified file 'src/main.cc' --- src/main.cc 2009-12-21 12:00:15 +0000 +++ src/main.cc 2010-06-05 19:08:44 +0000 @@ -55,6 +55,9 @@ #include "StoreFileSystem.h" #include "DiskIO/DiskIOModule.h" #include "comm.h" +#include "ipc/Kids.h" +#include "ipc/Coordinator.h" +#include "ipc/Strand.h" #if USE_EPOLL #include "comm_epoll.h" #endif @@ -124,6 +127,10 @@ static volatile int do_shutdown = 0; static volatile int shutdown_status = 0; +static int RotateSignal = -1; +static int ReconfigureSignal = -1; +static int ShutdownSignal = -1; + static void mainRotate(void); static void mainReconfigureStart(void); static void mainReconfigureFinish(void*); @@ -195,6 +202,10 @@ doShutdown(do_shutdown > 0 ? (int) Config.shutdownLifetime : 0); do_shutdown = 0; } + BroadcastSignalIfAny(DebugSignal); + BroadcastSignalIfAny(RotateSignal); + BroadcastSignalIfAny(ReconfigureSignal); + BroadcastSignalIfAny(ShutdownSignal); PROF_stop(SignalEngine_checkEvents); return EVENT_IDLE; @@ -553,6 +564,7 @@ rotate_logs(int sig) { do_rotate = 1; + RotateSignal = sig; #ifndef _SQUID_MSWIN_ #if !HAVE_SIGACTION @@ -566,6 +578,7 @@ reconfigure(int sig) { do_reconfigure = 1; + ReconfigureSignal = sig; #ifndef _SQUID_MSWIN_ #if !HAVE_SIGACTION @@ -578,6 +591,7 @@ shut_down(int sig) { do_shutdown = sig == SIGINT ? -1 : 1; + ShutdownSignal = sig; #ifdef SIGTTIN if (SIGTTIN == sig) @@ -607,6 +621,19 @@ static void serverConnectionsOpen(void) { + if (IamPrimaryProcess()) { +#if USE_WCCP + + wccpConnectionOpen(); +#endif + +#if USE_WCCPv2 + + wccp2ConnectionOpen(); +#endif + } + // Coordinator does not start proxying services + if (!IamCoordinatorProcess()) { clientOpenListenSockets(); icpConnectionsOpen(); #if USE_HTCP @@ -617,15 +644,6 @@ snmpConnectionOpen(); #endif -#if USE_WCCP - - wccpConnectionOpen(); -#endif - -#if USE_WCCPv2 - - wccp2ConnectionOpen(); -#endif clientdbInit(); icmpEngine.Open(); @@ -637,12 +655,25 @@ carpInit(); peerUserHashInit(); peerSourceHashInit(); + } } static void serverConnectionsClose(void) { assert(shutting_down || reconfiguring); + + if (IamPrimaryProcess()) { +#if USE_WCCP + + wccpConnectionClose(); +#endif +#if USE_WCCPv2 + + wccp2ConnectionClose(); +#endif + } + if (!IamCoordinatorProcess()) { clientHttpConnectionsClose(); icpConnectionShutdown(); #if USE_HTCP @@ -655,16 +686,9 @@ snmpConnectionShutdown(); #endif -#if USE_WCCP - - wccpConnectionClose(); -#endif -#if USE_WCCPv2 - - wccp2ConnectionClose(); -#endif asnFreeMemory(); + } } static void @@ -721,10 +745,17 @@ Config2.onoff.enable_purge = 2; // parse the config returns a count of errors encountered. + const int oldMainProcesses = Config.main_processes; if ( parseConfigFile(ConfigFile) != 0) { // for now any errors are a fatal condition... self_destruct(); } + if (oldMainProcesses != Config.main_processes) { + debugs(1, DBG_CRITICAL, "WARNING: Changing 'main_processes' (from " << + oldMainProcesses << " to " << Config.main_processes << + ") is not supported and ignored"); + Config.main_processes = oldMainProcesses; + } setUmask(Config.umask); Mem::Report(); @@ -753,6 +784,8 @@ redirectInit(); authenticateInit(&Config.authConfiguration); externalAclInit(); + + if (IamPrimaryProcess()) { #if USE_WCCP wccpInit(); @@ -761,6 +794,7 @@ wccp2Init(); #endif + } serverConnectionsOpen(); @@ -1015,6 +1049,7 @@ // moved to PconnModule::PconnModule() } + if (IamPrimaryProcess()) { #if USE_WCCP wccpInit(); @@ -1024,6 +1059,7 @@ wccp2Init(); #endif + } serverConnectionsOpen(); @@ -1149,9 +1185,28 @@ return -1; // not reached } +/// computes name and ID for the current kid process +static void +ConfigureCurrentKid(const char *processName) +{ + // kids are marked with parenthesis around their process names + if (processName && processName[0] == '(') { + if (const char *idStart = strrchr(processName, '-')) { + KidIdentifier = atoi(idStart + 1); + const int nameLen = idStart - (processName + 1); + xstrncpy(KidName, processName + 1, nameLen + 1); + } + } else { + xstrncpy(KidName, APP_SHORTNAME, sizeof(KidName)); + KidIdentifier = 0; + } +} + int SquidMain(int argc, char **argv) { + ConfigureCurrentKid(argv[0]); + #ifdef _SQUID_WIN32_ int WIN32_init_err; @@ -1320,7 +1375,7 @@ return 0; } - if (!opt_no_daemon) + if (!opt_no_daemon && Config.main_processes > 0) watch_child(argv); setMaxFD(); @@ -1376,6 +1431,11 @@ mainLoop.setTimeService(&time_engine); + if (IamCoordinatorProcess()) + AsyncJob::AsyncStart(Ipc::Coordinator::Instance()); + else if (UsingSmp() && IamWorkerProcess()) + AsyncJob::AsyncStart(new Ipc::Strand); + /* at this point we are finished the synchronous startup. */ starting_up = 0; @@ -1477,11 +1537,11 @@ do { #ifdef _SQUID_NEXT_ union wait status; - rpid = wait3(&status, 0, NULL); + rpid = wait4(cpid, &status, 0, NULL); #else int status; - rpid = waitpid(-1, &status, 0); + rpid = waitpid(cpid, &status, 0); #endif } while (rpid != cpid); @@ -1493,6 +1553,10 @@ static int checkRunningPid(void) { + // master process must start alone, but its kids processes may co-exist + if (!IamMasterProcess()) + return 0; + pid_t pid; if (!debug_log) @@ -1516,9 +1580,6 @@ { #ifndef _SQUID_MSWIN_ char *prog; - int failcount = 0; - time_t start; - time_t stop; #ifdef _SQUID_NEXT_ union wait status; @@ -1535,7 +1596,7 @@ int nullfd; - if (*(argv[0]) == '(') + if (!IamMasterProcess()) return; openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); @@ -1577,25 +1638,39 @@ dup2(nullfd, 2); } + if (Config.main_processes > 128) { + syslog(LOG_ALERT, "Suspiciously high main_processes value: %d", + Config.main_processes); + // but we keep going in hope that user knows best + } + TheKids.init(Config.main_processes); + + // keep [re]starting kids until it is time to quit for (;;) { mainStartScript(argv[0]); - if ((pid = fork()) == 0) { - /* child */ - openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); - prog = xstrdup(argv[0]); - argv[0] = xstrdup("(squid)"); - execvp(prog, argv); - syslog(LOG_ALERT, "execvp failed: %s", xstrerror()); + // start each kid that needs to be [re]started; once + for (int i = TheKids.count() - 1; i >= 0; --i) { + Kid& kid = TheKids.get(i); + if (kid.hopeless() || kid.exitedHappy() || kid.running()) + continue; + + if ((pid = fork()) == 0) { + /* child */ + openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); + prog = xstrdup(argv[0]); /* XXX: leak */ + argv[0] = const_cast(kid.name().termedBuf()); + execvp(prog, argv); + syslog(LOG_ALERT, "execvp failed: %s", xstrerror()); + } + + kid.start(pid); + syslog(LOG_NOTICE, "Squid Parent: child process %d started", pid); } /* parent */ openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_LOCAL4); - syslog(LOG_NOTICE, "Squid Parent: child process %d started", pid); - - time(&start); - squid_signal(SIGINT, SIG_IGN, SA_RESTART); #ifdef _SQUID_NEXT_ @@ -1607,51 +1682,48 @@ pid = waitpid(-1, &status, 0); #endif - - time(&stop); - - if (WIFEXITED(status)) { - syslog(LOG_NOTICE, - "Squid Parent: child process %d exited with status %d", - pid, WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - syslog(LOG_NOTICE, - "Squid Parent: child process %d exited due to signal %d with status %d", - pid, WTERMSIG(status), WEXITSTATUS(status)); - } else { - syslog(LOG_NOTICE, "Squid Parent: child process %d exited", pid); + // Loop to collect all stopped kids before we go to sleep below. + do + { + Kid* kid = TheKids.find(pid); + if (kid) { + kid->stop(status); + if (kid->calledExit()) { + syslog(LOG_NOTICE, + "Squid Parent: child process %d exited with status %d", + kid->getPid(), kid->exitStatus()); + } else if (kid->signaled()) { + syslog(LOG_NOTICE, + "Squid Parent: child process %d exited due to signal %d with status %d", + kid->getPid(), kid->termSignal(), kid->exitStatus()); + } else { + syslog(LOG_NOTICE, "Squid Parent: child process %d exited", kid->getPid()); + } + } else { + syslog(LOG_NOTICE, "Squid Parent: unknown child process %d exited", pid); + } +#ifdef _SQUID_NEXT_ + } while ((pid = wait3(&status, WNOHANG, NULL)) > 0); +#else + } while ((pid = waitpid(-1, &status, WNOHANG)) > 0); +#endif + + if (TheKids.allExitedHappy()) { + exit(0); } - if (stop - start < 10) - failcount++; - else - failcount = 0; - - if (failcount == 5) { + if (TheKids.allHopeless()) { syslog(LOG_ALERT, "Exiting due to repeated, frequent failures"); exit(1); } - if (WIFEXITED(status)) - if (WEXITSTATUS(status) == 0) - exit(0); - - if (WIFSIGNALED(status)) { - switch (WTERMSIG(status)) { - - case SIGKILL: - exit(0); - break; - - case SIGINT: - case SIGTERM: - syslog(LOG_ALERT, "Exiting due to unexpected forced shutdown"); - exit(1); - break; - - default: - break; - } + if (TheKids.allSignaled(SIGKILL)) { + exit(0); + } + + if (TheKids.allSignaled(SIGINT) || TheKids.allSignaled(SIGTERM)) { + syslog(LOG_ALERT, "Exiting due to unexpected forced shutdown"); + exit(1); } squid_signal(SIGINT, SIG_DFL, SA_RESTART); @@ -1789,10 +1861,12 @@ #endif - if (Config.pidFilename && strcmp(Config.pidFilename, "none") != 0) { - enter_suid(); - safeunlink(Config.pidFilename, 0); - leave_suid(); + if (IamPrimaryProcess()) { + if (Config.pidFilename && strcmp(Config.pidFilename, "none") != 0) { + enter_suid(); + safeunlink(Config.pidFilename, 0); + leave_suid(); + } } debugs(1, 1, "Squid Cache (Version " << version_string << "): Exiting normally."); === modified file 'src/protos.h' --- src/protos.h 2010-01-14 09:18:00 +0000 +++ src/protos.h 2010-06-05 19:08:44 +0000 @@ -575,6 +575,22 @@ SQUIDCEXTERN pid_t readPidFile(void); SQUIDCEXTERN void keepCapabilities(void); +SQUIDCEXTERN void BroadcastSignalIfAny(int& sig); +/// whether the current process is the parent of all other Squid processes +SQUIDCEXTERN bool IamMasterProcess(); +/** + whether the current process is dedicated to doing things that only + a single process should do, such as PID file maintenance and WCCP +*/ +SQUIDCEXTERN bool IamPrimaryProcess(); +/// whether the current process coordinates worker processes +SQUIDCEXTERN bool IamCoordinatorProcess(); +/// whether the current process handles HTTP transactions and such +SQUIDCEXTERN bool IamWorkerProcess(); +/// Whether there should be more than one worker process running +SQUIDCEXTERN bool UsingSmp(); // try using specific Iam*() checks above first +SQUIDCEXTERN int DebugSignal; + /* AYJ debugs function to show locations being reset with memset() */ SQUIDCEXTERN void *xmemset(void *dst, int, size_t); === modified file 'src/snmp_core.cc' --- src/snmp_core.cc 2010-01-14 09:18:00 +0000 +++ src/snmp_core.cc 2010-06-05 18:32:22 +0000 @@ -34,10 +34,30 @@ #include "cache_snmp.h" #include "acl/FilledChecklist.h" #include "ip/IpAddress.h" +#include "ipc/StartListening.h" #define SNMP_REQUEST_SIZE 4096 #define MAX_PROTOSTAT 5 + +/// dials snmpConnectionOpened call +class SnmpListeningStartedDialer: public CallDialer, + public Ipc::StartListeningCb +{ +public: + typedef void (*Handler)(int fd, int errNo); + SnmpListeningStartedDialer(Handler aHandler): handler(aHandler) {} + + virtual void print(std::ostream &os) const { startPrint(os) << ')'; } + + virtual bool canDial(AsyncCall &) const { return true; } + virtual void dial(AsyncCall &) { (handler)(fd, errNo); } + +public: + Handler handler; +}; + + IpAddress theOutSNMPAddr; typedef struct _mib_tree_entry mib_tree_entry; @@ -58,6 +78,9 @@ mib_tree_entry *mib_tree_head; mib_tree_entry *mib_tree_last; +static void snmpIncomingConnectionOpened(int fd, int errNo); +static void snmpOutgoingConnectionOpened(int fd, int errNo); + static mib_tree_entry * snmpAddNodeStr(const char *base_str, int o, oid_ParseFn * parsefunction, instance_Fn * instancefunction); static mib_tree_entry *snmpAddNode(oid * name, int len, oid_ParseFn * parsefunction, instance_Fn * instancefunction, int children,...); static oid *snmpCreateOid(int length,...); @@ -278,54 +301,71 @@ void snmpConnectionOpen(void) { - struct addrinfo *xaddr = NULL; - int x; - debugs(49, 5, "snmpConnectionOpen: Called"); if (Config.Port.snmp > 0) { Config.Addrs.snmp_incoming.SetPort(Config.Port.snmp); - enter_suid(); - theInSnmpConnection = comm_open_listener(SOCK_DGRAM, + + AsyncCall::Pointer call = asyncCall(49, 2, + "snmpIncomingConnectionOpened", + SnmpListeningStartedDialer(&snmpIncomingConnectionOpened)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, Config.Addrs.snmp_incoming, COMM_NONBLOCKING, - "SNMP Port"); - leave_suid(); - - if (theInSnmpConnection < 0) - fatal("Cannot open SNMP Port"); - - commSetSelect(theInSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, 0); - - debugs(1, 1, "Accepting SNMP messages on " << Config.Addrs.snmp_incoming << ", FD " << theInSnmpConnection << "."); + Ipc::fdnInSnmpSocket, call); if (!Config.Addrs.snmp_outgoing.IsNoAddr()) { Config.Addrs.snmp_outgoing.SetPort(Config.Port.snmp); - enter_suid(); - theOutSnmpConnection = comm_open_listener(SOCK_DGRAM, + + AsyncCall::Pointer call = asyncCall(49, 2, + "snmpOutgoingConnectionOpened", + SnmpListeningStartedDialer(&snmpOutgoingConnectionOpened)); + + Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, Config.Addrs.snmp_outgoing, COMM_NONBLOCKING, - "SNMP Port"); - leave_suid(); - - if (theOutSnmpConnection < 0) - fatal("Cannot open Outgoing SNMP Port"); - - commSetSelect(theOutSnmpConnection, - COMM_SELECT_READ, - snmpHandleUdp, - NULL, 0); - - debugs(1, 1, "Outgoing SNMP messages on " << Config.Addrs.snmp_outgoing << ", FD " << theOutSnmpConnection << "."); - - fd_note(theOutSnmpConnection, "Outgoing SNMP socket"); - - fd_note(theInSnmpConnection, "Incoming SNMP socket"); - } else { - theOutSnmpConnection = theInSnmpConnection; + Ipc::fdnOutSnmpSocket, call); } + } +} + +static void +snmpIncomingConnectionOpened(int fd, int errNo) +{ + theInSnmpConnection = fd; + if (theInSnmpConnection < 0) + fatal("Cannot open Incoming SNMP Port"); + + commSetSelect(theInSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, + 0); + + debugs(1, 1, "Accepting SNMP messages on " << Config.Addrs.snmp_incoming << + ", FD " << theInSnmpConnection << "."); + + if (Config.Addrs.snmp_outgoing.IsNoAddr()) + theOutSnmpConnection = theInSnmpConnection; +} + +static void +snmpOutgoingConnectionOpened(int fd, int errNo) +{ + theOutSnmpConnection = fd; + if (theOutSnmpConnection < 0) + fatal("Cannot open Outgoing SNMP Port"); + + commSetSelect(theOutSnmpConnection, COMM_SELECT_READ, snmpHandleUdp, NULL, + 0); + + debugs(1, 1, "Outgoing SNMP messages on " << Config.Addrs.snmp_outgoing << + ", FD " << theOutSnmpConnection << "."); + + { + struct addrinfo *xaddr = NULL; + int x; + theOutSNMPAddr.SetEmpty(); === modified file 'src/structs.h' --- src/structs.h 2010-03-03 09:38:49 +0000 +++ src/structs.h 2010-03-04 06:25:18 +0000 @@ -617,6 +617,7 @@ char *accept_filter; int umask; + int main_processes; #if USE_LOADABLE_MODULES wordlist *loadable_module_names; === modified file 'src/tools.cc' --- src/tools.cc 2009-12-02 22:39:11 +0000 +++ src/tools.cc 2010-06-05 19:08:44 +0000 @@ -41,6 +41,8 @@ #include "SquidMath.h" #include "SquidTime.h" #include "ip/IpIntercept.h" +#include "ipc/Kids.h" +#include "ipc/Coordinator.h" #if HAVE_SYS_PRCTL_H #include @@ -64,6 +66,7 @@ extern void log_trace_init(char *); #endif static void restoreCapabilities(int keep); +int DebugSignal = -1; #ifdef _SQUID_LINUX_ /* Workaround for crappy glic header files */ @@ -397,6 +400,15 @@ abort(); } +void +BroadcastSignalIfAny(int& sig) +{ + if (sig > 0) { + if (IamCoordinatorProcess()) + Ipc::Coordinator::Instance()->broadcastSignal(sig); + sig = -1; + } +} void sigusr2_handle(int sig) @@ -404,6 +416,8 @@ static int state = 0; /* no debugs() here; bad things happen if the signal is delivered during _db_print() */ + DebugSignal = sig; + if (state == 0) { #ifndef MEM_GEN_TRACE Debug::parseOptions("ALL,7"); @@ -790,6 +804,50 @@ #endif } +bool +IamMasterProcess() +{ + return KidIdentifier == 0; +} + +bool +IamWorkerProcess() +{ + // when there is only one process, it has to be the worker + if (opt_no_daemon || Config.main_processes == 0) + return true; + + return 0 < KidIdentifier && KidIdentifier <= Config.main_processes; +} + +bool +UsingSmp() +{ + return !opt_no_daemon && Config.main_processes > 1; +} + +bool +IamCoordinatorProcess() +{ + return UsingSmp() && KidIdentifier == Config.main_processes + 1; +} + +bool +IamPrimaryProcess() +{ + // when there is only one process, it has to be primary + if (opt_no_daemon || Config.main_processes == 0) + return true; + + // when there is a master and worker process, the master delegates + // primary functions to its only kid + if (Config.main_processes == 1) + return IamWorkerProcess(); + + // in SMP mode, multiple kids delegate primary functions to the coordinator + return IamCoordinatorProcess(); +} + void writePidFile(void) { @@ -798,6 +856,9 @@ mode_t old_umask; char buf[32]; + if (!IamPrimaryProcess()) + return; + if ((f = Config.pidFilename) == NULL) return; === modified file 'test-suite/testheaders.sh' --- test-suite/testheaders.sh 2009-11-19 23:32:21 +0000 +++ test-suite/testheaders.sh 2010-05-02 18:09:21 +0000 @@ -16,6 +16,8 @@ dir="${2}" fi +exitCode=0 + for f in `cd ${dir} && ls -1 *.h 2>/dev/null`; do echo -n "Testing ${dir}/${f} ..." hdr=`echo "${f}" | sed s/.h//` @@ -32,11 +34,14 @@ fi if [ ! -f testHeaderDeps_${hdr}.o ]; then rm testHeaders - exit 1 - fi + exitCode=1 + else echo "OK." # unit-tests require an app to run. # our most-recent object suits this purpose. # let's link or some tests will fail ${cc} ./testHeaderDeps_${hdr}.o -o ./testHeaders + fi done + +exit $exitCode # Begin bundle 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 Mcq6FAwd5lwQuESxSgCn7Vf/8XckU4UJBR9SApA=