diff options
Diffstat (limited to 'src/mod_fastcgi.c')
-rw-r--r-- | src/mod_fastcgi.c | 927 |
1 files changed, 562 insertions, 365 deletions
diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c index 890062c..8b1be0a 100644 --- a/src/mod_fastcgi.c +++ b/src/mod_fastcgi.c @@ -69,20 +69,24 @@ typedef struct fcgi_proc { size_t requests; /* see max_requests */ struct fcgi_proc *prev, *next; /* see first */ - time_t disable_ts; /* replace by host->something */ + time_t disabled_until; /* this proc is disabled until, use something else until than */ int is_local; - enum { PROC_STATE_UNSET, /* init-phase */ - PROC_STATE_RUNNING, /* alive */ - PROC_STATE_DIED_WAIT_FOR_PID, - PROC_STATE_KILLED, /* was killed as we don't have the load anymore */ - PROC_STATE_DIED, /* marked as dead, should be restarted */ - PROC_STATE_DISABLED /* proc disabled as it resulted in an error */ + enum { + PROC_STATE_UNSET, /* init-phase */ + PROC_STATE_RUNNING, /* alive */ + PROC_STATE_DIED_WAIT_FOR_PID, + PROC_STATE_KILLED, /* was killed as we don't have the load anymore */ + PROC_STATE_DIED, /* marked as dead, should be restarted */ + PROC_STATE_DISABLED /* proc disabled as it resulted in an error */ } state; } fcgi_proc; typedef struct { + /* the key that is used to reference this value */ + buffer *id; + /* list of processes handling this extension * sorted by lowest load * @@ -300,6 +304,8 @@ typedef struct { buffer *path; buffer *parse_response; + + buffer *statuskey; plugin_config **config_storage; @@ -307,13 +313,19 @@ typedef struct { } plugin_data; /* connection specific data */ -typedef enum { FCGI_STATE_INIT, FCGI_STATE_CONNECT, FCGI_STATE_PREPARE_WRITE, - FCGI_STATE_WRITE, FCGI_STATE_READ +typedef enum { + FCGI_STATE_UNSET, + FCGI_STATE_INIT, + FCGI_STATE_CONNECT_DELAYED, + FCGI_STATE_PREPARE_WRITE, + FCGI_STATE_WRITE, + FCGI_STATE_READ } fcgi_connection_state_t; typedef struct { fcgi_proc *proc; fcgi_extension_host *host; + fcgi_extension *ext; fcgi_connection_state_t state; time_t state_timestamp; @@ -325,8 +337,6 @@ typedef struct { buffer *response_header; - int delayed; /* flag to mark that the connect() is delayed */ - size_t request_id; int fd; /* fd to the fastcgi process */ int fde_ndx; /* index into the fd-event buffer */ @@ -348,7 +358,74 @@ static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents); int fcgi_proclist_sort_down(server *srv, fcgi_extension_host *host, fcgi_proc *proc); +data_integer *status_counter_get_counter(server *srv, const char *s, size_t len) { + data_integer *di; + + if (NULL == (di = (data_integer *)array_get_element(srv->status, s))) { + /* not found, create it */ + + if (NULL == (di = (data_integer *)array_get_unused_element(srv->status, TYPE_INTEGER))) { + di = data_integer_init(); + } + buffer_copy_string_len(di->key, s, len); + di->value = 0; + + array_insert_unique(srv->status, (data_unset *)di); + } + return di; +} + +/* dummies of the statistic framework functions + * they will be moved to a statistics.c later */ +int status_counter_inc(server *srv, const char *s, size_t len) { + data_integer *di = status_counter_get_counter(srv, s, len); + + di->value++; + + return 0; +} + +int status_counter_dec(server *srv, const char *s, size_t len) { + data_integer *di = status_counter_get_counter(srv, s, len); + + if (di->value > 0) di->value--; + + return 0; +} + +int status_counter_set(server *srv, const char *s, size_t len, int val) { + data_integer *di = status_counter_get_counter(srv, s, len); + + di->value = val; + return 0; +} + +int fastcgi_status_copy_procname(buffer *b, fcgi_extension_host *host, fcgi_proc *proc) { + buffer_copy_string(b, "fastcgi.backend."); + buffer_append_string_buffer(b, host->id); + buffer_append_string(b, "."); + buffer_append_long(b, proc->id); + + return 0; +} + +int fastcgi_status_init(server *srv, buffer *b, fcgi_extension_host *host, fcgi_proc *proc) { +#define CLEAN(x) \ + fastcgi_status_copy_procname(b, host, proc); \ + buffer_append_string(b, x); \ + status_counter_set(srv, CONST_BUF_LEN(b), 0); + + CLEAN(".disabled"); + CLEAN(".died"); + CLEAN(".overloaded"); + CLEAN(".connected"); + CLEAN(".load"); + +#undef CLEAN + + return 0; +} static handler_ctx * handler_ctx_init() { handler_ctx * hctx; @@ -366,8 +443,6 @@ static handler_ctx * handler_ctx_init() { hctx->fd = -1; - hctx->delayed = 0; - hctx->reconnects = 0; hctx->send_content_body = 1; @@ -413,6 +488,7 @@ fcgi_extension_host *fastcgi_host_init() { f = calloc(1, sizeof(*f)); + f->id = buffer_init(); f->host = buffer_init(); f->unixsocket = buffer_init(); f->docroot = buffer_init(); @@ -427,6 +503,7 @@ fcgi_extension_host *fastcgi_host_init() { void fastcgi_host_free(fcgi_extension_host *h) { if (!h) return; + buffer_free(h->id); buffer_free(h->host); buffer_free(h->unixsocket); buffer_free(h->docroot); @@ -540,6 +617,8 @@ INIT_FUNC(mod_fastcgi_init) { p->path = buffer_init(); p->parse_response = buffer_init(); + + p->statuskey = buffer_init(); return p; } @@ -556,6 +635,7 @@ FREE_FUNC(mod_fastcgi_free) { buffer_free(p->fcgi_env); buffer_free(p->path); buffer_free(p->parse_response); + buffer_free(p->statuskey); if (p->config_storage) { size_t i, j, n; @@ -824,6 +904,7 @@ static int fcgi_spawn_connection(server *srv, switch ((child = fork())) { case 0: { size_t i = 0; + char *c; char_array env; char_array arg; @@ -888,6 +969,20 @@ static int fcgi_spawn_connection(server *srv, parse_binpath(&arg, host->bin_path); + /* chdir into the base of the bin-path, + * search for the last / */ + if (NULL != (c = strrchr(arg.ptr[0], '/'))) { + *c = '\0'; + + /* change to the physical directory */ + if (-1 == chdir(arg.ptr[0])) { + *c = '/'; + log_error_write(srv, __FILE__, __LINE__, "sss", "chdir failed:", strerror(errno), arg.ptr[0]); + } + *c = '/'; + } + + /* exec the cgi */ execve(arg.ptr[0], arg.ptr, env.ptr); @@ -1058,7 +1153,7 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { for (n = 0; n < da_ext->value->used; n++) { data_array *da_host = (data_array *)da_ext->value->data[n]; - fcgi_extension_host *df; + fcgi_extension_host *host; config_values_t fcv[] = { { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ @@ -1094,54 +1189,56 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { return HANDLER_ERROR; } - df = fastcgi_host_init(); + host = fastcgi_host_init(); - df->check_local = 1; - df->min_procs = 4; - df->max_procs = 4; - df->max_load_per_proc = 1; - df->idle_timeout = 60; - df->mode = FCGI_RESPONDER; - df->disable_time = 60; - df->break_scriptfilename_for_php = 0; - df->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */ + buffer_copy_string_buffer(host->id, da_host->key); + + host->check_local = 1; + host->min_procs = 4; + host->max_procs = 4; + host->max_load_per_proc = 1; + host->idle_timeout = 60; + host->mode = FCGI_RESPONDER; + host->disable_time = 60; + host->break_scriptfilename_for_php = 0; + host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */ - fcv[0].destination = df->host; - fcv[1].destination = df->docroot; + fcv[0].destination = host->host; + fcv[1].destination = host->docroot; fcv[2].destination = fcgi_mode; - fcv[3].destination = df->unixsocket; - fcv[4].destination = df->bin_path; + fcv[3].destination = host->unixsocket; + fcv[4].destination = host->bin_path; - fcv[5].destination = &(df->check_local); - fcv[6].destination = &(df->port); - fcv[7].destination = &(df->min_procs); - fcv[8].destination = &(df->max_procs); - fcv[9].destination = &(df->max_load_per_proc); - fcv[10].destination = &(df->idle_timeout); - fcv[11].destination = &(df->disable_time); + fcv[5].destination = &(host->check_local); + fcv[6].destination = &(host->port); + fcv[7].destination = &(host->min_procs); + fcv[8].destination = &(host->max_procs); + fcv[9].destination = &(host->max_load_per_proc); + fcv[10].destination = &(host->idle_timeout); + fcv[11].destination = &(host->disable_time); - fcv[12].destination = df->bin_env; - fcv[13].destination = df->bin_env_copy; - fcv[14].destination = &(df->break_scriptfilename_for_php); - fcv[15].destination = &(df->allow_xsendfile); - fcv[16].destination = df->strip_request_uri; + fcv[12].destination = host->bin_env; + fcv[13].destination = host->bin_env_copy; + fcv[14].destination = &(host->break_scriptfilename_for_php); + fcv[15].destination = &(host->allow_xsendfile); + fcv[16].destination = host->strip_request_uri; if (0 != config_insert_values_internal(srv, da_host->value, fcv)) { return HANDLER_ERROR; } - if ((!buffer_is_empty(df->host) || df->port) && - !buffer_is_empty(df->unixsocket)) { + if ((!buffer_is_empty(host->host) || host->port) && + !buffer_is_empty(host->unixsocket)) { log_error_write(srv, __FILE__, __LINE__, "s", "either host+port or socket"); return HANDLER_ERROR; } - if (!buffer_is_empty(df->unixsocket)) { + if (!buffer_is_empty(host->unixsocket)) { /* unix domain socket */ - if (df->unixsocket->used > UNIX_PATH_MAX - 2) { + if (host->unixsocket->used > UNIX_PATH_MAX - 2) { log_error_write(srv, __FILE__, __LINE__, "s", "path of the unixdomain socket is too large"); return HANDLER_ERROR; @@ -1149,8 +1246,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { } else { /* tcp/ip */ - if (buffer_is_empty(df->host) && - buffer_is_empty(df->bin_path)) { + if (buffer_is_empty(host->host) && + buffer_is_empty(host->bin_path)) { log_error_write(srv, __FILE__, __LINE__, "sbbbs", "missing key (string):", da->key, @@ -1159,7 +1256,7 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { "host"); return HANDLER_ERROR; - } else if (df->port == 0) { + } else if (host->port == 0) { log_error_write(srv, __FILE__, __LINE__, "sbbbs", "missing key (short):", da->key, @@ -1170,37 +1267,37 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { } } - if (!buffer_is_empty(df->bin_path)) { + if (!buffer_is_empty(host->bin_path)) { /* a local socket + self spawning */ size_t pno; /* HACK: just to make sure the adaptive spawing is disabled */ - df->min_procs = df->max_procs; + host->min_procs = host->max_procs; - if (df->min_procs > df->max_procs) df->max_procs = df->min_procs; - if (df->max_load_per_proc < 1) df->max_load_per_proc = 0; + if (host->min_procs > host->max_procs) host->max_procs = host->min_procs; + if (host->max_load_per_proc < 1) host->max_load_per_proc = 0; if (s->debug) { log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd", "--- fastcgi spawning local", - "\n\tproc:", df->bin_path, - "\n\tport:", df->port, - "\n\tsocket", df->unixsocket, - "\n\tmin-procs:", df->min_procs, - "\n\tmax-procs:", df->max_procs); + "\n\tproc:", host->bin_path, + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tmin-procs:", host->min_procs, + "\n\tmax-procs:", host->max_procs); } - for (pno = 0; pno < df->min_procs; pno++) { + for (pno = 0; pno < host->min_procs; pno++) { fcgi_proc *proc; proc = fastcgi_process_init(); - proc->id = df->num_procs++; - df->max_id++; + proc->id = host->num_procs++; + host->max_id++; - if (buffer_is_empty(df->unixsocket)) { - proc->port = df->port + pno; + if (buffer_is_empty(host->unixsocket)) { + proc->port = host->port + pno; } else { - buffer_copy_string_buffer(proc->socket, df->unixsocket); + buffer_copy_string_buffer(proc->socket, host->unixsocket); buffer_append_string(proc->socket, "-"); buffer_append_long(proc->socket, pno); } @@ -1208,49 +1305,53 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { if (s->debug) { log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd", "--- fastcgi spawning", - "\n\tport:", df->port, - "\n\tsocket", df->unixsocket, - "\n\tcurrent:", pno, "/", df->min_procs); + "\n\tport:", host->port, + "\n\tsocket", host->unixsocket, + "\n\tcurrent:", pno, "/", host->min_procs); } - if (fcgi_spawn_connection(srv, p, df, proc)) { + if (fcgi_spawn_connection(srv, p, host, proc)) { log_error_write(srv, __FILE__, __LINE__, "s", "[ERROR]: spawning fcgi failed."); return HANDLER_ERROR; } + + fastcgi_status_init(srv, p->statuskey, host, proc); - proc->next = df->first; - if (df->first) df->first->prev = proc; + proc->next = host->first; + if (host->first) host->first->prev = proc; - df->first = proc; + host->first = proc; } } else { - fcgi_proc *fp; + fcgi_proc *proc; - fp = fastcgi_process_init(); - fp->id = df->num_procs++; - df->max_id++; - df->active_procs++; - fp->state = PROC_STATE_RUNNING; + proc = fastcgi_process_init(); + proc->id = host->num_procs++; + host->max_id++; + host->active_procs++; + proc->state = PROC_STATE_RUNNING; - if (buffer_is_empty(df->unixsocket)) { - fp->port = df->port; + if (buffer_is_empty(host->unixsocket)) { + proc->port = host->port; } else { - buffer_copy_string_buffer(fp->socket, df->unixsocket); + buffer_copy_string_buffer(proc->socket, host->unixsocket); } - df->first = fp; + fastcgi_status_init(srv, p->statuskey, host, proc); + + host->first = proc; - df->min_procs = 1; - df->max_procs = 1; + host->min_procs = 1; + host->max_procs = 1; } if (!buffer_is_empty(fcgi_mode)) { if (strcmp(fcgi_mode->ptr, "responder") == 0) { - df->mode = FCGI_RESPONDER; + host->mode = FCGI_RESPONDER; } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) { - df->mode = FCGI_AUTHORIZER; - if (buffer_is_empty(df->docroot)) { + host->mode = FCGI_AUTHORIZER; + if (buffer_is_empty(host->docroot)) { log_error_write(srv, __FILE__, __LINE__, "s", "ERROR: docroot is required for authorizer mode."); return HANDLER_ERROR; @@ -1261,9 +1362,9 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { fcgi_mode, "(ignored, mode set to responder)"); } } - + /* if extension already exists, take it */ - fastcgi_extension_insert(s->exts, da_ext->key, df); + fastcgi_extension_insert(s->exts, da_ext->key, host); } } } @@ -1327,7 +1428,6 @@ static int fcgi_requestid_del(server *srv, plugin_data *p, size_t request_id) { return 0; } - void fcgi_connection_close(server *srv, handler_ctx *hctx) { plugin_data *p; connection *con; @@ -1360,6 +1460,13 @@ void fcgi_connection_close(server *srv, handler_ctx *hctx) { /* after the connect the process gets a load */ hctx->proc->load--; + status_counter_dec(srv, CONST_STR_LEN("fastcgi.active-requests")); + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".load"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), hctx->proc->load); + if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sddb", "release proc:", @@ -1397,11 +1504,13 @@ static int fcgi_reconnect(server *srv, handler_ctx *hctx) { * we have a connection but the child died by some other reason * */ - - fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); - fdevent_unregister(srv->ev, hctx->fd); - close(hctx->fd); - srv->cur_fds--; + + if (hctx->fd != -1) { + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + fdevent_unregister(srv->ev, hctx->fd); + close(hctx->fd); + srv->cur_fds--; + } fcgi_requestid_del(srv, p, hctx->request_id); @@ -1416,9 +1525,14 @@ static int fcgi_reconnect(server *srv, handler_ctx *hctx) { hctx->fd, hctx->proc->pid, hctx->proc->socket); } - - hctx->proc->load--; - fcgi_proclist_sort_down(srv, hctx->host, hctx->proc); + + if (hctx->proc) { + hctx->proc->load--; + fcgi_proclist_sort_down(srv, hctx->host, hctx->proc); + } + + /* perhaps another host gives us more luck */ + hctx->host = NULL; return 0; } @@ -1491,7 +1605,15 @@ static int fcgi_header(FCGI_Header * header, unsigned char type, size_t request_ * 1 not connected yet */ -static int fcgi_establish_connection(server *srv, handler_ctx *hctx) { +typedef enum { + CONNECTION_UNSET, + CONNECTION_OK, + CONNECTION_DELAYED, /* retry after event, take same host */ + CONNECTION_OVERLOADED, /* disable for 1 seconds, take another backend */ + CONNECTION_DEAD /* disable for 60 seconds, take another backend */ +} connection_result_t; + +static connection_result_t fcgi_establish_connection(server *srv, handler_ctx *hctx) { struct sockaddr *fcgi_addr; struct sockaddr_in fcgi_addr_in; #ifdef HAVE_SYS_UN_H @@ -1540,57 +1662,37 @@ static int fcgi_establish_connection(server *srv, handler_ctx *hctx) { errno == EALREADY || errno == EINTR) { if (hctx->conf.debug) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "connect delayed, will continue later:", fcgi_fd); + log_error_write(srv, __FILE__, __LINE__, "sb", + "connect delayed, will continue later:", host->host); } - return 1; + return CONNECTION_DELAYED; } else if (errno == EAGAIN) { -#if 0 - if(hctx->delayed == 0) { - log_error_write(srv, __FILE__, __LINE__, "sdsdsdb", - "need reconnect, will continue later:", fcgi_fd, - "reconnects:", hctx->reconnects, - "load:", host->load, - host->unixsocket); + if (hctx->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sd", + "This means that the you have more incoming requests than your fastcgi-backend can handle in parallel. " + "Perhaps it helps to spawn more fastcgi backend or php-children, if not decrease server.max-connections." + "The load for this fastcgi backend is:", proc->load); } -#endif - hctx->reconnects++; - return -1; + + return CONNECTION_OVERLOADED; } else { - log_error_write(srv, __FILE__, __LINE__, "sdsddb", - "connect failed:", fcgi_fd, - strerror(errno), errno, + log_error_write(srv, __FILE__, __LINE__, "ssdb", + "connect failed:", + strerror(errno), proc->port, proc->socket); -#if 0 - if (errno == EAGAIN) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "This means that the you have more incoming requests than your fastcgi-backend can handle in parallel. " - "Perhaps it helps to spawn more fastcgi backend or php-children, if not decrease server.max-connections." - "The load for this fastcgi backend is:", proc->load); - } -#endif - - return -1; + return CONNECTION_DEAD; } } -#if 0 - if(hctx->delayed == 1) { - log_error_write(srv, __FILE__, __LINE__, "sdsdsdb", - "reconnected:", fcgi_fd, - "reconnects:", hctx->reconnects, - "load:", host->load, - host->unixsocket); - } -#endif + hctx->reconnects = 0; if (hctx->conf.debug > 1) { log_error_write(srv, __FILE__, __LINE__, "sd", "connect succeeded: ", fcgi_fd); } - return 0; + return CONNECTION_OK; } static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_data *p) { @@ -1639,9 +1741,15 @@ static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_dat buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); for (j = 0; j < ds->key->used - 1; j++) { - srv->tmp_buf->ptr[srv->tmp_buf->used++] = - isalpha((unsigned char)ds->key->ptr[j]) ? - toupper((unsigned char)ds->key->ptr[j]) : '_'; + char c = '_'; + if (light_isalpha(ds->key->ptr[j])) { + /* upper-case */ + c = ds->key->ptr[j] & ~32; + } else if (light_isdigit(ds->key->ptr[j])) { + /* copy */ + c = ds->key->ptr[j]; + } + srv->tmp_buf->ptr[srv->tmp_buf->used++] = c; } srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; @@ -2156,7 +2264,7 @@ static int fastcgi_get_packet(server *srv, handler_ctx *hctx, fastcgi_response_p /* no header */ buffer_free(packet->b); - log_error_write(srv, __FILE__, __LINE__, "s", "FastCGI: header to small"); + log_error_write(srv, __FILE__, __LINE__, "s", "FastCGI: header too small"); return -1; } @@ -2260,11 +2368,12 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { b->used = r + 1; /* one extra for the fake \0 */ b->ptr[b->used - 1] = '\0'; } else { - log_error_write(srv, __FILE__, __LINE__, "ssdsdsd", + log_error_write(srv, __FILE__, __LINE__, "ssdsbsbsd", "unexpected end-of-file (perhaps the fastcgi process died):", "pid:", proc->pid, - "fcgi-fd:", fcgi_fd, - "remote-fd:", con->fd); + "socket:", proc->socket, + "host:", host->host, + "port:", proc->port); return -1; } @@ -2279,9 +2388,6 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { /* check if we have at least one packet */ if (0 != fastcgi_get_packet(srv, hctx, &packet)) { /* no full packet */ - - hctx->delayed = 1; - break; } @@ -2546,7 +2652,7 @@ static int fcgi_restart_dead_procs(server *srv, plugin_data *p, fcgi_extension_h proc->pid); } - if (0 == proc->is_local) { + if (!proc->is_local) { /* * external servers might get disabled * @@ -2554,10 +2660,15 @@ static int fcgi_restart_dead_procs(server *srv, plugin_data *p, fcgi_extension_h */ if ((proc->state == PROC_STATE_DISABLED) && - (srv->cur_ts - proc->disable_ts > host->disable_time)) { + (srv->cur_ts > proc->disabled_until)) { proc->state = PROC_STATE_RUNNING; host->active_procs++; + fastcgi_status_copy_procname(p->statuskey, host, proc); + buffer_append_string(p->statuskey, ".disabled"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 0); + log_error_write(srv, __FILE__, __LINE__, "sbdb", "fcgi-server re-enabled:", host->host, host->port, @@ -2626,7 +2737,6 @@ static int fcgi_restart_dead_procs(server *srv, plugin_data *p, fcgi_extension_h return 0; } - static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { plugin_data *p = hctx->plugin_data; fcgi_extension_host *host= hctx->host; @@ -2645,10 +2755,60 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { host->unixsocket->used); return HANDLER_ERROR; } + + /* we can't handle this in the switch as we have to fall through in it */ + if (hctx->state == FCGI_STATE_CONNECT_DELAYED) { + int socket_error; + socklen_t socket_error_len = sizeof(socket_error); + + /* try to finish the connect() */ + if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) { + log_error_write(srv, __FILE__, __LINE__, "ss", + "getsockopt failed:", strerror(errno)); + + return HANDLER_ERROR; + } + if (socket_error != 0) { + if (!hctx->proc->is_local || p->conf.debug) { + /* local procs get restarted */ + + log_error_write(srv, __FILE__, __LINE__, "sssd", + "establishing connection failed:", strerror(socket_error), + "port:", hctx->proc->port); + } + hctx->proc->disabled_until = srv->cur_ts + 10; + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".died"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + return HANDLER_ERROR; + } + /* go on with preparing the request */ + hctx->state = FCGI_STATE_PREPARE_WRITE; + } + switch(hctx->state) { + case FCGI_STATE_CONNECT_DELAYED: + /* should never happen */ + break; case FCGI_STATE_INIT: + /* do we have a running process for this host (max-procs) ? */ + + for (hctx->proc = hctx->host->first; + hctx->proc && hctx->proc->state != PROC_STATE_RUNNING; + hctx->proc = hctx->proc->next); + + /* all childs are dead */ + if (hctx->proc == NULL) { + hctx->fde_ndx = -1; + + return HANDLER_ERROR; + } + ret = host->unixsocket->used ? AF_UNIX : AF_INET; if (-1 == (hctx->fd = socket(ret, SOCK_STREAM, 0))) { @@ -2672,80 +2832,88 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", - "fcntl failed: ", strerror(errno)); + "fcntl failed:", strerror(errno)); return HANDLER_ERROR; } - - /* fall through */ - case FCGI_STATE_CONNECT: - if (hctx->state == FCGI_STATE_INIT || hctx->delayed == 1) { - for (hctx->proc = hctx->host->first; - hctx->proc && hctx->proc->state != PROC_STATE_RUNNING; - hctx->proc = hctx->proc->next); - - /* all childs are dead */ - if (hctx->proc == NULL) { - hctx->fde_ndx = -1; - - return HANDLER_ERROR; - } - if (hctx->proc->is_local) { - hctx->pid = hctx->proc->pid; - } + if (hctx->proc->is_local) { + hctx->pid = hctx->proc->pid; + } - switch (fcgi_establish_connection(srv, hctx)) { - case 1: - fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT); - - /* connection is in progress, wait for an event and call getsockopt() below */ - - fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); - - hctx->delayed = 0; - return HANDLER_WAIT_FOR_EVENT; - case -1: - /* if ECONNREFUSED/EAGAIN re-try connect() */ - - fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT); - hctx->delayed = 1; - return HANDLER_WAIT_FOR_EVENT; - default: - /* everything is ok, go on */ - break; - } + switch (fcgi_establish_connection(srv, hctx)) { + case CONNECTION_DELAYED: + /* connection is in progress, wait for an event and call getsockopt() below */ + + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + + fcgi_set_state(srv, hctx, FCGI_STATE_CONNECT_DELAYED); + return HANDLER_WAIT_FOR_EVENT; + case CONNECTION_OVERLOADED: + /* cool down the backend, it is overloaded + * -> EAGAIN */ - } else { - int socket_error; - socklen_t socket_error_len = sizeof(socket_error); + log_error_write(srv, __FILE__, __LINE__, "ssdsdb", + "backend is overloaded, we disable it for a 2 seconds and send the request to another backend instead:" + "reconnects:", hctx->reconnects, + "load:", host->load, + host->unixsocket); + + + hctx->proc->disabled_until = srv->cur_ts + 2; + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".overloaded"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); - /* try to finish the connect() */ - if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) { - log_error_write(srv, __FILE__, __LINE__, "ss", - "getsockopt failed:", strerror(errno)); - - return HANDLER_ERROR; - } - if (socket_error != 0) { - if (!hctx->proc->is_local || p->conf.debug) { - /* local procs get restarted */ - - log_error_write(srv, __FILE__, __LINE__, "ss", - "establishing connection failed:", strerror(socket_error), - "port:", hctx->proc->port); - } - - return HANDLER_ERROR; - } + return HANDLER_ERROR; + case CONNECTION_DEAD: + /* we got a hard error from the backend like + * - ECONNREFUSED for tcp-ip sockets + * - ENOENT for unix-domain-sockets + * + * for check if the host is back in 10 seconds + * */ + + hctx->proc->disabled_until = srv->cur_ts + 10; + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".died"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + return HANDLER_ERROR; + case CONNECTION_OK: + /* everything is ok, go on */ + + fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE); + + break; + case CONNECTION_UNSET: + break; } + case FCGI_STATE_PREPARE_WRITE: /* ok, we have the connection */ hctx->proc->load++; hctx->proc->last_used = srv->cur_ts; hctx->got_proc = 1; - + + status_counter_inc(srv, CONST_STR_LEN("fastcgi.requests")); + status_counter_inc(srv, CONST_STR_LEN("fastcgi.active-requests")); + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".connected"); + + status_counter_inc(srv, CONST_BUF_LEN(p->statuskey)); + + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".load"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), hctx->proc->load); + if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sddbdd", "got proc:", @@ -2766,9 +2934,7 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { "fcgi-request is already in use:", hctx->request_id); } - fcgi_set_state(srv, hctx, FCGI_STATE_PREPARE_WRITE); /* fall through */ - case FCGI_STATE_PREPARE_WRITE: fcgi_create_env(srv, hctx, hctx->request_id); fcgi_set_state(srv, hctx, FCGI_STATE_WRITE); @@ -2847,6 +3013,9 @@ static handler_t fcgi_write_request(server *srv, handler_ctx *hctx) { return HANDLER_WAIT_FOR_EVENT; } + +/* might be called on fdevent after a connect() is delay too + * */ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { plugin_data *p = p_d; @@ -2858,83 +3027,118 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { /* not my job */ if (con->mode != p->id) return HANDLER_GO_ON; + + /* we don't have a host yet, choose one + * -> this happens in the first round + * and when the host died and we have to select a new one */ + if (hctx->host == NULL) { + size_t k; + int ndx, used = -1; + + /* get best server */ + for (k = 0, ndx = -1; k < hctx->ext->used; k++) { + host = hctx->ext->hosts[k]; + + /* we should have at least one proc that can do somthing */ + if (host->active_procs == 0) continue; + + if (used == -1 || host->load < used) { + used = host->load; + + ndx = k; + } + } + /* found a server */ + if (ndx == -1) { + /* all hosts are down */ + + fcgi_connection_close(srv, hctx); + + con->http_status = 500; + con->mode = DIRECT; + + return HANDLER_FINISHED; + } + + host = hctx->ext->hosts[ndx]; + + /* + * if check-local is disabled, use the uri.path handler + * + */ + + /* init handler-context */ + hctx->host = host; + hctx->proc = NULL; + } else { + host = hctx->host; + } + /* ok, create the request */ switch(fcgi_write_request(srv, hctx)) { case HANDLER_ERROR: proc = hctx->proc; host = hctx->host; -#if 0 - if (proc && - 0 == proc->is_local && - proc->state != PROC_STATE_DISABLED) { - /* only disable remote servers as we don't manage them*/ - - log_error_write(srv, __FILE__, __LINE__, "sbdb", "fcgi-server disabled:", - host->host, - proc->port, - proc->socket); - - /* disable this server */ - proc->disable_ts = srv->cur_ts; - proc->state = PROC_STATE_DISABLED; - host->active_procs--; - } -#endif - if (hctx->state == FCGI_STATE_INIT || - hctx->state == FCGI_STATE_CONNECT) { + hctx->state == FCGI_STATE_CONNECT_DELAYED) { /* connect() or getsockopt() failed, * restart the request-handling */ - if (proc && proc->is_local) { + if (proc) { + if (proc->is_local) { - if (p->conf.debug) { - log_error_write(srv, __FILE__, __LINE__, "sbdb", "connect() to fastcgi failed, restarting the request-handling:", + if (p->conf.debug) { + log_error_write(srv, __FILE__, __LINE__, "sbdb", + "connect() to fastcgi failed, restarting the request-handling:", host->host, proc->port, proc->socket); - } + } - /* - * several hctx might reference the same proc - * - * Only one of them should mark the proc as dead all the other - * ones should just take a new one. - * - * If a new proc was started with the old struct this might lead - * the mark a perfect proc as dead otherwise - * - */ - if (proc->state == PROC_STATE_RUNNING && - hctx->pid == proc->pid) { - proc->state = PROC_STATE_DIED_WAIT_FOR_PID; + /* + * several hctx might reference the same proc + * + * Only one of them should mark the proc as dead all the other + * ones should just take a new one. + * + * If a new proc was started with the old struct this might lead + * the mark a perfect proc as dead otherwise + * + */ + if (proc->state == PROC_STATE_RUNNING && + hctx->pid == proc->pid) { + proc->state = PROC_STATE_DIED_WAIT_FOR_PID; + } + } else { + proc->state = PROC_STATE_DISABLED; } + host->active_procs--; + fastcgi_status_copy_procname(p->statuskey, hctx->host, hctx->proc); + buffer_append_string(p->statuskey, ".disabled"); + + status_counter_set(srv, CONST_BUF_LEN(p->statuskey), 1); } + fcgi_restart_dead_procs(srv, p, host); - - fcgi_connection_close(srv, hctx); - - buffer_reset(con->physical.path); - con->mode = DIRECT; - joblist_append(srv, con); /* really ? */ - /* mis-using HANDLER_WAIT_FOR_FD to break out of the loop - * and hope that the childs will be restarted - * - */ - - /* we might get into a LOOP here - * - * but how to handle this ? - * - * if we enter a endless loop, we will burn the CPU - * - * let this handle by the loop-detection - */ + /* cleanup this request and let the request handler start this request again */ + if (hctx->reconnects < 5) { + fcgi_reconnect(srv, hctx); + joblist_append(srv, con); /* in case we come from the event-handler */ + + return HANDLER_WAIT_FOR_FD; + } else { + fcgi_connection_close(srv, hctx); - return HANDLER_WAIT_FOR_FD; + buffer_reset(con->physical.path); + con->mode = DIRECT; + con->http_status = 500; + joblist_append(srv, con); /* in case we come from the event-handler */ + + return HANDLER_FINISHED; + } } else { fcgi_connection_close(srv, hctx); @@ -3054,7 +3258,7 @@ static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) { fcgi_reconnect(srv, hctx); log_error_write(srv, __FILE__, __LINE__, "ssdsd", - "response not sent, request not sent, reconnection.", + "response not received, request not sent, reconnecting.", "connection-fd:", con->fd, "fcgi-fd:", hctx->fd); @@ -3062,7 +3266,7 @@ static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) { } log_error_write(srv, __FILE__, __LINE__, "sosdsd", - "response not sent, request sent:", hctx->wb->bytes_out, + "response not received, request sent:", hctx->wb->bytes_out, "connection-fd:", con->fd, "fcgi-fd:", hctx->fd); @@ -3093,7 +3297,7 @@ static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) { } if (revents & FDEVENT_OUT) { - if (hctx->state == FCGI_STATE_CONNECT || + if (hctx->state == FCGI_STATE_CONNECT_DELAYED || hctx->state == FCGI_STATE_WRITE) { /* we are allowed to send something out * @@ -3110,7 +3314,7 @@ static handler_t fcgi_handle_fdevent(void *s, void *ctx, int revents) { /* perhaps this issue is already handled */ if (revents & FDEVENT_HUP) { - if (hctx->state == FCGI_STATE_CONNECT) { + if (hctx->state == FCGI_STATE_CONNECT_DELAYED) { /* getoptsock will catch this one (right ?) * * if we are in connect we might get a EINPROGRESS @@ -3193,16 +3397,15 @@ static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p) { static handler_t fcgi_check_extension(server *srv, connection *con, void *p_d, int uri_path_handler) { plugin_data *p = p_d; size_t s_len; - int used = -1; - int ndx; size_t k; buffer *fn; fcgi_extension *extension = NULL; + fcgi_extension_host *host = NULL; /* Possibly, we processed already this request */ if (con->file_started == 1) return HANDLER_GO_ON; - - fn = con->uri.path; + + fn = uri_path_handler ? con->uri.path : con->physical.path; if (fn->used == 0) { return HANDLER_ERROR; @@ -3237,124 +3440,118 @@ static handler_t fcgi_check_extension(server *srv, connection *con, void *p_d, i if (k == p->conf.exts->used) { return HANDLER_GO_ON; } - + /* get best server */ - for (k = 0, ndx = -1; k < extension->used; k++) { - fcgi_extension_host *host = extension->hosts[k]; - + for (k = 0; k < extension->used; k++) { + host = extension->hosts[k]; + /* we should have at least one proc that can do somthing */ - if (host->active_procs == 0) continue; + if (host->active_procs == 0) { + host = NULL; - if (used == -1 || host->load < used) { - used = host->load; - - ndx = k; + continue; } + + /* we found one host that is alive */ + break; } - /* found a server */ - if (ndx != -1) { - fcgi_extension_host *host = extension->hosts[ndx]; + if (!host) { + /* sorry, we don't have a server alive for this ext */ + buffer_reset(con->physical.path); + con->http_status = 500; - /* - * if check-local is disabled, use the uri.path handler - * - */ + log_error_write(srv, __FILE__, __LINE__, "sb", + "no fcgi-handler found for:", + fn); - /* init handler-context */ - if (uri_path_handler) { - if (host->check_local == 0) { - handler_ctx *hctx; - char *pathinfo; - - hctx = handler_ctx_init(); - - hctx->remote_conn = con; - hctx->plugin_data = p; - hctx->host = host; - hctx->proc = NULL; + return HANDLER_FINISHED; + } - hctx->conf.exts = p->conf.exts; - hctx->conf.debug = p->conf.debug; - - con->plugin_ctx[p->id] = hctx; - - host->load++; - - con->mode = p->id; - - if (con->conf.log_request_handling) { - log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi"); - } - - /* the prefix is the SCRIPT_NAME, - * everthing from start to the next slash - * this is important for check-local = "disable" - * - * if prefix = /admin.fcgi - * - * /admin.fcgi/foo/bar - * - * SCRIPT_NAME = /admin.fcgi - * PATH_INFO = /foo/bar - * - * if prefix = /fcgi-bin/ - * - * /fcgi-bin/foo/bar - * - * SCRIPT_NAME = /fcgi-bin/foo - * PATH_INFO = /bar - * - */ - - /* the rewrite is only done for /prefix/? matches */ - if (extension->key->ptr[0] == '/' && - con->uri.path->used > extension->key->used && - NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) { - /* rewrite uri.path and pathinfo */ - - buffer_copy_string(con->request.pathinfo, pathinfo); - - con->uri.path->used -= con->request.pathinfo->used - 1; - con->uri.path->ptr[con->uri.path->used - 1] = '\0'; - } - } - return HANDLER_GO_ON; - } else { + /* + * if check-local is disabled, use the uri.path handler + * + */ + + /* init handler-context */ + if (uri_path_handler) { + if (host->check_local == 0) { handler_ctx *hctx; + char *pathinfo; + hctx = handler_ctx_init(); hctx->remote_conn = con; hctx->plugin_data = p; - hctx->host = host; - hctx->proc = NULL; - + hctx->proc = NULL; + hctx->ext = extension; + + hctx->conf.exts = p->conf.exts; hctx->conf.debug = p->conf.debug; - + con->plugin_ctx[p->id] = hctx; - - host->load++; - + con->mode = p->id; - + if (con->conf.log_request_handling) { - log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi"); + log_error_write(srv, __FILE__, __LINE__, "s", + "handling it in mod_fastcgi"); } + + /* the prefix is the SCRIPT_NAME, + * everthing from start to the next slash + * this is important for check-local = "disable" + * + * if prefix = /admin.fcgi + * + * /admin.fcgi/foo/bar + * + * SCRIPT_NAME = /admin.fcgi + * PATH_INFO = /foo/bar + * + * if prefix = /fcgi-bin/ + * + * /fcgi-bin/foo/bar + * + * SCRIPT_NAME = /fcgi-bin/foo + * PATH_INFO = /bar + * + */ - return HANDLER_GO_ON; + /* the rewrite is only done for /prefix/? matches */ + if (extension->key->ptr[0] == '/' && + con->uri.path->used > extension->key->used && + NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) { + /* rewrite uri.path and pathinfo */ + + buffer_copy_string(con->request.pathinfo, pathinfo); + + con->uri.path->used -= con->request.pathinfo->used - 1; + con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + } } } else { - /* no handler found */ - buffer_reset(con->physical.path); - con->http_status = 500; + handler_ctx *hctx; + hctx = handler_ctx_init(); - log_error_write(srv, __FILE__, __LINE__, "sb", - "no fcgi-handler found for:", - fn); + hctx->remote_conn = con; + hctx->plugin_data = p; + hctx->proc = NULL; + hctx->ext = extension; - return HANDLER_FINISHED; + hctx->conf.exts = p->conf.exts; + hctx->conf.debug = p->conf.debug; + + con->plugin_ctx[p->id] = hctx; + + con->mode = p->id; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_fastcgi"); + } } + return HANDLER_GO_ON; } @@ -3380,7 +3577,7 @@ JOBLIST_FUNC(mod_fastcgi_handle_joblist) { fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); break; - case FCGI_STATE_CONNECT: + case FCGI_STATE_CONNECT_DELAYED: case FCGI_STATE_WRITE: fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); @@ -3598,7 +3795,7 @@ TRIGGER_FUNC(mod_fastcgi_handle_trigger) { int mod_fastcgi_plugin_init(plugin *p) { - p->version = LIGHTTPD_VERSION_ID; + p->version = LIGHTTPD_VERSION_ID; p->name = buffer_init_string("fastcgi"); p->init = mod_fastcgi_init; |