/* * this function enables proxies when there are enough free sessions, * or stops them when the table is full. It is designed to be called from the * select_loop(). It adjusts the date of next expiration event during stop * time if appropriate. */ void maintain_proxies(int *next) { struct proxy *p; struct listener *l; unsigned int wait; p = proxy; /* if there are enough free sessions, we'll activate proxies */ if (actconn < global.maxconn) { for (; p; p = p->next) { /* check the various reasons we may find to block the frontend */ if (p->feconn >= p->maxconn) goto do_block; if (p->fe_sps_lim && (wait = next_event_delay(&p->fe_sess_per_sec, p->fe_sps_lim, 1))) { /* we're blocking because a limit was reached on the number of * requests/s on the frontend. We want to re-check ASAP, which * means in 1 ms before estimated expiration date, because the * timer will have settled down. Note that we may already be in * IDLE state here. */ *next = tick_first(*next, tick_add(now_ms, wait)); goto do_block; } /* OK we have no reason to block, so let's unblock if we were blocking */ if (p->state == PR_STIDLE) { for (l = p->listen; l != NULL; l = l->next) enable_listener(l); p->state = PR_STRUN; } continue; do_block: if (p->state == PR_STRUN) { for (l = p->listen; l != NULL; l = l->next) disable_listener(l); p->state = PR_STIDLE; } } } else { /* block all proxies */ while (p) { if (p->state == PR_STRUN) { for (l = p->listen; l != NULL; l = l->next) disable_listener(l); p->state = PR_STIDLE; } p = p->next; } } if (stopping) { p = proxy; while (p) { if (p->state != PR_STSTOPPED) { int t; t = tick_remain(now_ms, p->stop_time); if (t == 0) { Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", p->id, p->cum_feconn, p->cum_beconn); send_log(p, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", p->id, p->cum_feconn, p->cum_beconn); stop_proxy(p); /* try to free more memory */ pool_gc2(); } else { *next = tick_first(*next, p->stop_time); } } p = p->next; } } return; }
/* This function is called on a read event from a listening socket, corresponding * to an accept. It tries to accept as many connections as possible, and for each * calls the listener's accept handler (generally the frontend's accept handler). */ int stream_sock_accept(int fd) { struct listener *l = fdtab[fd].owner; struct proxy *p = l->frontend; int max_accept = global.tune.maxaccept; int cfd; int ret; if (unlikely(l->nbconn >= l->maxconn)) { listener_full(l); return 0; } if (global.cps_lim && !(l->options & LI_O_UNLIMITED)) { int max = freq_ctr_remain(&global.conn_per_sec, global.cps_lim, 0); if (unlikely(!max)) { /* frontend accept rate limit was reached */ limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0))); return 0; } if (max_accept > max) max_accept = max; } if (p && p->fe_sps_lim) { int max = freq_ctr_remain(&p->fe_sess_per_sec, p->fe_sps_lim, 0); if (unlikely(!max)) { /* frontend accept rate limit was reached */ limit_listener(l, &p->listener_queue); task_schedule(p->task, tick_add(now_ms, next_event_delay(&p->fe_sess_per_sec, p->fe_sps_lim, 0))); return 0; } if (max_accept > max) max_accept = max; } /* Note: if we fail to allocate a connection because of configured * limits, we'll schedule a new attempt worst 1 second later in the * worst case. If we fail due to system limits or temporary resource * shortage, we try again 100ms later in the worst case. */ while (max_accept--) { struct sockaddr_storage addr; socklen_t laddr = sizeof(addr); if (unlikely(actconn >= global.maxconn) && !(l->options & LI_O_UNLIMITED)) { limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, 1000)); /* try again in 1 second */ return 0; } if (unlikely(p && p->feconn >= p->maxconn)) { limit_listener(l, &p->listener_queue); return 0; } cfd = accept(fd, (struct sockaddr *)&addr, &laddr); if (unlikely(cfd == -1)) { switch (errno) { case EAGAIN: case EINTR: case ECONNABORTED: return 0; /* nothing more to accept */ case ENFILE: if (p) send_log(p, LOG_EMERG, "Proxy %s reached system FD limit at %d. Please check system tunables.\n", p->id, maxfd); limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, 100)); /* try again in 100 ms */ return 0; case EMFILE: if (p) send_log(p, LOG_EMERG, "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n", p->id, maxfd); limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, 100)); /* try again in 100 ms */ return 0; case ENOBUFS: case ENOMEM: if (p) send_log(p, LOG_EMERG, "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n", p->id, maxfd); limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, 100)); /* try again in 100 ms */ return 0; default: return 0; } } if (unlikely(cfd >= global.maxsock)) { send_log(p, LOG_EMERG, "Proxy %s reached the configured maximum connection limit. Please check the global 'maxconn' value.\n", p->id); close(cfd); limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, 1000)); /* try again in 1 second */ return 0; } /* increase the per-process number of cumulated connections */ if (!(l->options & LI_O_UNLIMITED)) { update_freq_ctr(&global.conn_per_sec, 1); if (global.conn_per_sec.curr_ctr > global.cps_max) global.cps_max = global.conn_per_sec.curr_ctr; actconn++; } jobs++; totalconn++; l->nbconn++; if (l->counters) { if (l->nbconn > l->counters->conn_max) l->counters->conn_max = l->nbconn; } ret = l->accept(l, cfd, &addr); if (unlikely(ret <= 0)) { /* The connection was closed by session_accept(). Either * we just have to ignore it (ret == 0) or it's a critical * error due to a resource shortage, and we must stop the * listener (ret < 0). */ if (!(l->options & LI_O_UNLIMITED)) actconn--; jobs--; l->nbconn--; if (ret == 0) /* successful termination */ continue; limit_listener(l, &global_listener_queue); task_schedule(global_listener_queue_task, tick_add(now_ms, 100)); /* try again in 100 ms */ return 0; } if (l->nbconn >= l->maxconn) { listener_full(l); return 0; } } /* end of while (p->feconn < p->maxconn) */ return 0; }
/* * This is the proxy management task. It enables proxies when there are enough * free sessions, or stops them when the table is full. It is designed to be * called as a task which is woken up upon stopping or when rate limiting must * be enforced. */ struct task *manage_proxy(struct task *t) { struct proxy *p = t->context; int next = TICK_ETERNITY; unsigned int wait; /* We should periodically try to enable listeners waiting for a * global resource here. */ /* first, let's check if we need to stop the proxy */ if (unlikely(stopping && p->state != PR_STSTOPPED)) { int t; t = tick_remain(now_ms, p->stop_time); if (t == 0) { Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn); send_log(p, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n", p->id, p->fe_counters.cum_conn, p->be_counters.cum_conn); stop_proxy(p); /* try to free more memory */ pool_gc2(); } else { next = tick_first(next, p->stop_time); } } /* the rest below is just for frontends */ if (!(p->cap & PR_CAP_FE)) goto out; /* check the various reasons we may find to block the frontend */ if (unlikely(p->feconn >= p->maxconn)) { if (p->state == PR_STREADY) p->state = PR_STFULL; goto out; } /* OK we have no reason to block, so let's unblock if we were blocking */ if (p->state == PR_STFULL) p->state = PR_STREADY; if (p->fe_sps_lim && (wait = next_event_delay(&p->fe_sess_per_sec, p->fe_sps_lim, 0))) { /* we're blocking because a limit was reached on the number of * requests/s on the frontend. We want to re-check ASAP, which * means in 1 ms before estimated expiration date, because the * timer will have settled down. */ next = tick_first(next, tick_add(now_ms, wait)); goto out; } /* The proxy is not limited so we can re-enable any waiting listener */ if (!LIST_ISEMPTY(&p->listener_queue)) dequeue_all_listeners(&p->listener_queue); out: t->expire = next; task_queue(t); return t; }