├── .github ├── dependabot.yml └── workflows │ ├── docker-publish.yml │ └── update-license.yml ├── LICENSE ├── README.md ├── config ├── ngx_http_upstream_queue_module.c └── queue.h /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | jobs: 8 | dispatch: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - env: 12 | GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} 13 | INPUTS_CLIENT_PAYLOAD: '{"repository":${{ toJson(github.event.repository.name) }}}' 14 | INPUTS_EVENT_TYPE: latest 15 | INPUTS_REPOSITORY: ${{ github.repository_owner }}/${{ matrix.repo }} 16 | uses: rekgrpth/github-repository-dispatch-shell-action@v1 17 | strategy: 18 | matrix: 19 | repo: 20 | - angie.docker 21 | - freenginx.docker 22 | - nginx.docker 23 | -------------------------------------------------------------------------------- /.github/workflows/update-license.yml: -------------------------------------------------------------------------------- 1 | name: License 2 | on: 3 | schedule: 4 | - cron: '0 19 1 1 *' 5 | workflow_dispatch: 6 | jobs: 7 | license: 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: rekgrpth/git-clone-shell-action@v1 13 | - uses: rekgrpth/update-license-year-shell-action@v1 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 RekGRpth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nginx upstream queue 2 | 3 | # Directive 4 | 5 | queue 6 | ------------- 7 | * Syntax: **queue** *number* [ timeout=*time* ] 8 | * Default: -- 9 | * Context: upstream 10 | 11 | If an upstream server cannot be selected immediately while processing a request, the request will be placed into the queue. The directive specifies the maximum *number* of requests that can be in the queue at the same time. If the queue is filled up, or the server to pass the request to cannot be selected within the time period specified in the timeout parameter, the 502 (Bad Gateway) error will be returned to the client. 12 | 13 | The default value of the timeout parameter is 60 seconds. 14 | 15 | When using load balancer methods other than the default round-robin method, it is necessary to activate them before the queue directive. 16 | 17 | queue_detect_all_peer_down; 18 | ------------- 19 | * Syntax: queue_detect_all_peer_down on | off; 20 | * Default: off 21 | * Context: upstream 22 | 23 | Enables/disables detect all peer down 24 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_upstream_queue_module 2 | 3 | NGX_HTTP_UPSTREAM_QUEUE_SRCS=$ngx_addon_dir/ngx_http_upstream_queue_module.c 4 | 5 | if test -n "$ngx_module_link"; then 6 | ngx_module_name=$ngx_addon_name 7 | ngx_module_srcs=$NGX_HTTP_UPSTREAM_QUEUE_SRCS 8 | ngx_module_type=HTTP 9 | 10 | . auto/module 11 | else 12 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 13 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $NGX_HTTP_UPSTREAM_QUEUE_SRCS" 14 | fi 15 | -------------------------------------------------------------------------------- /ngx_http_upstream_queue_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ngx_http_upstream.c" 3 | #include "queue.h" 4 | 5 | ngx_module_t ngx_http_upstream_queue_module; 6 | 7 | typedef struct { 8 | ngx_flag_t detect; 9 | ngx_http_upstream_peer_t peer; 10 | ngx_msec_t timeout; 11 | ngx_uint_t max; 12 | queue_t queue; 13 | } ngx_http_upstream_queue_srv_conf_t; 14 | 15 | typedef struct { 16 | ngx_event_t connect_timeout; 17 | ngx_event_t timeout; 18 | ngx_http_request_t *request; 19 | ngx_peer_connection_t peer; 20 | queue_t queue; 21 | } ngx_http_upstream_queue_data_t; 22 | 23 | static void ngx_http_upstream_queue_peer_free(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) { 24 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "%s", __func__); 25 | ngx_http_upstream_queue_data_t *d = data; 26 | d->peer.free(pc, d->peer.data, state); 27 | ngx_http_request_t *r = d->request; 28 | ngx_http_upstream_t *u = r->upstream; 29 | ngx_http_upstream_srv_conf_t *uscf = u->conf->upstream; 30 | ngx_http_upstream_queue_srv_conf_t *qscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upstream_queue_module); 31 | if (queue_empty(&qscf->queue)) return; 32 | queue_t *q = queue_head(&qscf->queue); 33 | queue_remove(q); 34 | d = queue_data(q, ngx_http_upstream_queue_data_t, queue); 35 | if (d->connect_timeout.timer_set) ngx_del_timer(&d->connect_timeout); 36 | if (d->timeout.timer_set) ngx_del_timer(&d->timeout); 37 | queue_init(&d->queue); 38 | r = d->request; 39 | u = r->upstream; 40 | ngx_connection_t *c = u->peer.connection; 41 | ngx_close_connection(c); 42 | c->shared = 0; 43 | ngx_http_upstream_handler_pt read_event_handler = u->read_event_handler; 44 | ngx_http_upstream_handler_pt write_event_handler = u->write_event_handler; 45 | ngx_http_upstream_connect(r, u); 46 | u->read_event_handler = read_event_handler; 47 | u->write_event_handler = write_event_handler; 48 | } 49 | 50 | static void ngx_http_upstream_queue_cleanup_handler(void *data) { 51 | ngx_http_upstream_queue_data_t *d = data; 52 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, d->request->connection->log, 0, "%s", __func__); 53 | if (!queue_empty(&d->queue)) queue_remove(&d->queue); 54 | if (d->connect_timeout.timer_set) ngx_del_timer(&d->connect_timeout); 55 | if (d->timeout.timer_set) ngx_del_timer(&d->timeout); 56 | } 57 | 58 | static void ngx_http_upstream_queue_connect_timeout_handler(ngx_event_t *e) { 59 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->log, 0, e->write ? "write" : "read"); 60 | ngx_connection_t *c = e->data; 61 | if (c->write->timer_set) ngx_del_timer(c->write); 62 | } 63 | 64 | static void ngx_http_upstream_queue_timeout_handler(ngx_event_t *e) { 65 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->log, 0, e->write ? "write" : "read"); 66 | ngx_http_request_t *r = e->data; 67 | if (!r->connection || r->connection->error) return; 68 | ngx_http_upstream_t *u = r->upstream; 69 | ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); 70 | } 71 | 72 | static ngx_int_t ngx_http_upstream_queue_peer_get(ngx_peer_connection_t *pc, void *data) { 73 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "%s", __func__); 74 | ngx_http_upstream_queue_data_t *d = data; 75 | ngx_int_t rc = d->peer.get(pc, d->peer.data); 76 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "peer.get = %i", rc); 77 | if (rc != NGX_BUSY) return rc; 78 | ngx_http_request_t *r = d->request; 79 | ngx_http_upstream_t *u = r->upstream; 80 | ngx_http_upstream_srv_conf_t *uscf = u->conf->upstream; 81 | ngx_http_upstream_queue_srv_conf_t *qscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upstream_queue_module); 82 | if (qscf->detect) { 83 | ngx_http_upstream_rr_peer_data_t *rrp = d->peer.data; 84 | time_t now = ngx_time(); 85 | ngx_flag_t all_peers_down = 1; 86 | for (ngx_http_upstream_rr_peer_t *peer = rrp->peers->peer; peer; peer = peer->next) { 87 | if (!peer->down) { 88 | if (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) continue; 89 | all_peers_down = 0; 90 | break; 91 | } 92 | } 93 | if (all_peers_down) return rc; 94 | } 95 | if (queue_size(&qscf->queue) >= qscf->max) return rc; 96 | if (!(pc->connection = ngx_get_connection(0, pc->log))) { ngx_log_error(NGX_LOG_ERR, pc->log, 0, "!ngx_get_connection"); return NGX_ERROR; } 97 | pc->connection->shared = 1; 98 | ngx_pool_cleanup_t *cln; 99 | if (!(cln = ngx_pool_cleanup_add(r->pool, 0))) { ngx_log_error(NGX_LOG_ERR, pc->log, 0, "!ngx_pool_cleanup_add"); return NGX_ERROR; } 100 | cln->handler = ngx_http_upstream_queue_cleanup_handler; 101 | cln->data = d; 102 | if (u->conf->connect_timeout < qscf->timeout) { 103 | d->connect_timeout.data = pc->connection; 104 | d->connect_timeout.handler = ngx_http_upstream_queue_connect_timeout_handler; 105 | d->connect_timeout.log = pc->log; 106 | ngx_add_timer(&d->connect_timeout, u->conf->connect_timeout / 2); 107 | } 108 | d->timeout.data = r; 109 | d->timeout.handler = ngx_http_upstream_queue_timeout_handler; 110 | d->timeout.log = pc->log; 111 | ngx_add_timer(&d->timeout, qscf->timeout); 112 | queue_insert_tail(&qscf->queue, &d->queue); 113 | return NGX_AGAIN; 114 | } 115 | 116 | #if (NGX_HTTP_SSL) 117 | static ngx_int_t ngx_http_upstream_queue_peer_set_session(ngx_peer_connection_t *pc, void *data) { 118 | ngx_http_upstream_queue_data_t *d = data; 119 | return d->peer.set_session(pc, d->peer.data); 120 | } 121 | 122 | static void ngx_http_upstream_queue_peer_save_session(ngx_peer_connection_t *pc, void *data) { 123 | ngx_http_upstream_queue_data_t *d = data; 124 | d->peer.save_session(pc, d->peer.data); 125 | } 126 | #endif 127 | 128 | static ngx_int_t ngx_http_upstream_queue_peer_init(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *uscf) { 129 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "%s", __func__); 130 | ngx_http_upstream_queue_srv_conf_t *qscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upstream_queue_module); 131 | ngx_http_upstream_queue_data_t *d; 132 | if (!(d = ngx_pcalloc(r->pool, sizeof(*d)))) return NGX_ERROR; 133 | queue_init(&d->queue); 134 | if (qscf->peer.init(r, uscf) != NGX_OK) return NGX_ERROR; 135 | ngx_http_upstream_t *u = r->upstream; 136 | u->conf->upstream = uscf; 137 | d->peer = u->peer; 138 | d->request = r; 139 | u->peer.data = d; 140 | u->peer.free = ngx_http_upstream_queue_peer_free; 141 | u->peer.get = ngx_http_upstream_queue_peer_get; 142 | #if (NGX_HTTP_SSL) 143 | u->peer.save_session = ngx_http_upstream_queue_peer_save_session; 144 | u->peer.set_session = ngx_http_upstream_queue_peer_set_session; 145 | #endif 146 | return NGX_OK; 147 | } 148 | 149 | static ngx_int_t ngx_http_upstream_queue_peer_init_upstream(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *uscf) { 150 | ngx_http_upstream_queue_srv_conf_t *qscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upstream_queue_module); 151 | ngx_conf_init_value(qscf->detect, 0); 152 | ngx_conf_init_msec_value(qscf->timeout, 60000); 153 | if (qscf->peer.init_upstream(cf, uscf) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "init_upstream != NGX_OK"); return NGX_ERROR; } 154 | qscf->peer.init = uscf->peer.init; 155 | uscf->peer.init = ngx_http_upstream_queue_peer_init; 156 | queue_init(&qscf->queue); 157 | return NGX_OK; 158 | } 159 | 160 | static void *ngx_http_upstream_queue_create_srv_conf(ngx_conf_t *cf) { 161 | ngx_http_upstream_queue_srv_conf_t *conf; 162 | if (!(conf = ngx_pcalloc(cf->pool, sizeof(*conf)))) return NULL; 163 | conf->detect = NGX_CONF_UNSET; 164 | conf->timeout = NGX_CONF_UNSET_MSEC; 165 | return conf; 166 | } 167 | 168 | static char *ngx_http_upstream_queue_ups_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { 169 | ngx_http_upstream_queue_srv_conf_t *qscf = conf; 170 | if (qscf->max) return "is duplicate"; 171 | ngx_str_t *value = cf->args->elts; 172 | ngx_int_t n = ngx_atoi(value[1].data, value[1].len); 173 | if (n == NGX_ERROR || !n) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid value \"%V\" in \"%V\" directive", &value[1], &cmd->name); return NGX_CONF_ERROR; } 174 | qscf->max = n; 175 | if (cf->args->nelts > 2) { 176 | if (value[2].len <= sizeof("timeout=") - 1 || ngx_strncmp(value[2].data, (u_char *)"timeout=", sizeof("timeout=") - 1)) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid name \"%V\" in \"%V\" directive", &value[2], &cmd->name); return NGX_CONF_ERROR; } 177 | value[2].data += sizeof("timeout=") - 1; 178 | value[2].len -= sizeof("timeout=") - 1; 179 | ngx_int_t n = ngx_parse_time(&value[2], 0); 180 | if (n == NGX_ERROR) return "ngx_parse_time == NGX_ERROR"; 181 | qscf->timeout = (ngx_msec_t)n; 182 | } 183 | ngx_http_upstream_srv_conf_t *uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); 184 | qscf->peer.init_upstream = uscf->peer.init_upstream ? uscf->peer.init_upstream : ngx_http_upstream_init_round_robin; 185 | uscf->peer.init_upstream = ngx_http_upstream_queue_peer_init_upstream; 186 | return NGX_CONF_OK; 187 | } 188 | 189 | static ngx_http_module_t ngx_http_upstream_queue_ctx = { 190 | .preconfiguration = NULL, 191 | .postconfiguration = NULL, 192 | .create_main_conf = NULL, 193 | .init_main_conf = NULL, 194 | .create_srv_conf = ngx_http_upstream_queue_create_srv_conf, 195 | .merge_srv_conf = NULL, 196 | .create_loc_conf = NULL, 197 | .merge_loc_conf = NULL 198 | }; 199 | 200 | static ngx_command_t ngx_http_upstream_queue_commands[] = { 201 | { ngx_string("queue"), NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12, ngx_http_upstream_queue_ups_conf, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL }, 202 | { ngx_string("queue_detect_all_peer_down"), NGX_HTTP_UPS_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_SRV_CONF_OFFSET, .offset = offsetof(ngx_http_upstream_queue_srv_conf_t, detect), NULL }, 203 | ngx_null_command 204 | }; 205 | 206 | ngx_module_t ngx_http_upstream_queue_module = { 207 | NGX_MODULE_V1, 208 | .ctx = &ngx_http_upstream_queue_ctx, 209 | .commands = ngx_http_upstream_queue_commands, 210 | .type = NGX_HTTP_MODULE, 211 | .init_master = NULL, 212 | .init_module = NULL, 213 | .init_process = NULL, 214 | .init_thread = NULL, 215 | .exit_thread = NULL, 216 | .exit_process = NULL, 217 | .exit_master = NULL, 218 | NGX_MODULE_V1_PADDING 219 | }; 220 | -------------------------------------------------------------------------------- /queue.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUEUE_H_ 2 | #define _QUEUE_H_ 3 | 4 | typedef struct queue_t { 5 | struct queue_t *prev; 6 | union { 7 | struct queue_t *parent; 8 | size_t size; 9 | }; 10 | struct queue_t *next; 11 | } queue_t; 12 | 13 | #define queue_parent(q) (q)->parent 14 | 15 | #define queue_size(q) (q)->size 16 | 17 | #define queue_init(q) \ 18 | do { \ 19 | (q)->prev = q; \ 20 | (q)->size = 0; \ 21 | (q)->next = q; \ 22 | } while (0) 23 | 24 | #define queue_empty(h) ((h) == (h)->prev) 25 | 26 | #define queue_insert_head(h, x) \ 27 | do { \ 28 | (x)->next = (h)->next; \ 29 | (x)->next->prev = x; \ 30 | (x)->prev = h; \ 31 | (h)->next = x; \ 32 | (x)->parent = h; \ 33 | (h)->size++; \ 34 | } while (0) 35 | 36 | #define queue_insert_after queue_insert_head 37 | 38 | #define queue_insert_tail(h, x) \ 39 | do { \ 40 | (x)->prev = (h)->prev; \ 41 | (x)->prev->next = x; \ 42 | (x)->next = h; \ 43 | (h)->prev = x; \ 44 | (x)->parent = h; \ 45 | (h)->size++; \ 46 | } while (0) 47 | 48 | #define queue_head(h) (h)->next 49 | 50 | #define queue_last(h) (h)->prev 51 | 52 | #define queue_sentinel(h) (h) 53 | 54 | #define queue_next(q) (q)->next 55 | 56 | #define queue_prev(q) (q)->prev 57 | 58 | #define queue_remove(x) \ 59 | do { \ 60 | (x)->next->prev = (x)->prev; \ 61 | (x)->prev->next = (x)->next; \ 62 | (x)->prev = NULL; \ 63 | (x)->next = NULL; \ 64 | (x)->parent->size--; \ 65 | (x)->parent = NULL; \ 66 | } while (0) 67 | 68 | #define queue_data(q, t, o) (t *)((char *)q - offsetof(t, o)) 69 | 70 | #define queue_each(h, q) for (queue_t *(q) = (h)->next, *_; (q) != (h) && (_ = (q)->next); (q) = _) 71 | 72 | #endif // _QUEUE_H_ 73 | --------------------------------------------------------------------------------