Mercurial > hg > nginx
annotate src/http/modules/ngx_http_random_index_module.c @ 9203:0de20f43db25
Fixed request termination with AIO and subrequests (ticket #2555).
When a request was terminated due to an error via ngx_http_terminate_request()
while an AIO operation was running in a subrequest, various issues were
observed. This happened because ngx_http_request_finalizer() was only set
in the subrequest where ngx_http_terminate_request() was called, but not
in the subrequest where the AIO operation was running. After completion
of the AIO operation normal processing of the subrequest was resumed, leading
to issues.
In particular, in case of the upstream module, termination of the request
called upstream cleanup, which closed the upstream connection. Attempts to
further work with the upstream connection after AIO operation completion
resulted in segfaults in ngx_ssl_recv(), "readv() failed (9: Bad file
descriptor) while reading upstream" errors, or socket leaks.
In ticket #2555, issues were observed with the following configuration
with cache background update (with thread writing instrumented to
introduce a delay, when a client closes the connection during an update):
location = /background-and-aio-write {
proxy_pass ...
proxy_cache one;
proxy_cache_valid 200 1s;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
aio threads;
aio_write on;
limit_rate 1000;
}
Similarly, the same issue can be seen with SSI, and can be caused by
errors in subrequests, such as in the following configuration
(where "/proxy" uses AIO, and "/sleep" returns 444 after some delay,
causing request termination):
location = /ssi-active-boom {
ssi on;
ssi_types *;
return 200 '
<!--#include virtual="/proxy" -->
<!--#include virtual="/sleep" -->
';
limit_rate 1000;
}
Or the same with both AIO operation and the error in non-active subrequests
(which needs slightly different handling, see below):
location = /ssi-non-active-boom {
ssi on;
ssi_types *;
return 200 '
<!--#include virtual="/static" -->
<!--#include virtual="/proxy" -->
<!--#include virtual="/sleep" -->
';
limit_rate 1000;
}
Similarly, issues can be observed with just static files. However,
with static files potential impact is limited due to timeout safeguards
in ngx_http_writer(), and the fact that c->error is set during request
termination.
In a simple configuration with an AIO operation in the active subrequest,
such as in the following configuration, the connection is closed right
after completion of the AIO operation anyway, since ngx_http_writer()
tries to write to the connection and fails due to c->error set:
location = /ssi-active-static-boom {
ssi on;
ssi_types *;
return 200 '
<!--#include virtual="/static-aio" -->
<!--#include virtual="/sleep" -->
';
limit_rate 1000;
}
In the following configuration, with an AIO operation in a non-active
subrequest, the connection is closed only after send_timeout expires:
location = /ssi-non-active-static-boom {
ssi on;
ssi_types *;
return 200 '
<!--#include virtual="/static" -->
<!--#include virtual="/static-aio" -->
<!--#include virtual="/sleep" -->
';
limit_rate 1000;
}
Fix is to introduce r->main->terminated flag, which is to be checked
by AIO event handlers when the r->main->blocked counter is decremented.
When the flag is set, handlers are expected to wake up the connection
instead of the subrequest (which might be already cleaned up).
Additionally, now ngx_http_request_finalizer() is always set in the
active subrequest, so waking up the connection properly finalizes the
request even if termination happened in a non-active subrequest.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Tue, 30 Jan 2024 03:20:05 +0300 |
parents | 061ec464813f |
children |
rev | line source |
---|---|
2235 | 1 |
2 /* | |
3 * Copyright (C) Igor Sysoev | |
4412 | 4 * Copyright (C) Nginx, Inc. |
2235 | 5 */ |
6 | |
7 | |
8 #include <ngx_config.h> | |
9 #include <ngx_core.h> | |
10 #include <ngx_http.h> | |
11 | |
12 | |
13 typedef struct { | |
14 ngx_flag_t enable; | |
15 } ngx_http_random_index_loc_conf_t; | |
16 | |
17 | |
18 #define NGX_HTTP_RANDOM_INDEX_PREALLOCATE 50 | |
19 | |
20 | |
21 static ngx_int_t ngx_http_random_index_error(ngx_http_request_t *r, | |
22 ngx_dir_t *dir, ngx_str_t *name); | |
2266
6223d5a9e87f
back out $random_index variable
Igor Sysoev <igor@sysoev.ru>
parents:
2258
diff
changeset
|
23 static ngx_int_t ngx_http_random_index_init(ngx_conf_t *cf); |
2235 | 24 static void *ngx_http_random_index_create_loc_conf(ngx_conf_t *cf); |
25 static char *ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf, | |
26 void *parent, void *child); | |
27 | |
28 | |
29 static ngx_command_t ngx_http_random_index_commands[] = { | |
30 | |
31 { ngx_string("random_index"), | |
32 NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, | |
33 ngx_conf_set_flag_slot, | |
34 NGX_HTTP_LOC_CONF_OFFSET, | |
35 offsetof(ngx_http_random_index_loc_conf_t, enable), | |
36 NULL }, | |
37 | |
38 ngx_null_command | |
39 }; | |
40 | |
41 | |
42 static ngx_http_module_t ngx_http_random_index_module_ctx = { | |
2266
6223d5a9e87f
back out $random_index variable
Igor Sysoev <igor@sysoev.ru>
parents:
2258
diff
changeset
|
43 NULL, /* preconfiguration */ |
2235 | 44 ngx_http_random_index_init, /* postconfiguration */ |
45 | |
46 NULL, /* create main configuration */ | |
47 NULL, /* init main configuration */ | |
48 | |
49 NULL, /* create server configuration */ | |
50 NULL, /* merge server configuration */ | |
51 | |
4499
778ef9c3fd2d
Fixed spelling in single-line comments.
Ruslan Ermilov <ru@nginx.com>
parents:
4412
diff
changeset
|
52 ngx_http_random_index_create_loc_conf, /* create location configuration */ |
778ef9c3fd2d
Fixed spelling in single-line comments.
Ruslan Ermilov <ru@nginx.com>
parents:
4412
diff
changeset
|
53 ngx_http_random_index_merge_loc_conf /* merge location configuration */ |
2235 | 54 }; |
55 | |
56 | |
57 ngx_module_t ngx_http_random_index_module = { | |
58 NGX_MODULE_V1, | |
59 &ngx_http_random_index_module_ctx, /* module context */ | |
60 ngx_http_random_index_commands, /* module directives */ | |
61 NGX_HTTP_MODULE, /* module type */ | |
62 NULL, /* init master */ | |
63 NULL, /* init module */ | |
64 NULL, /* init process */ | |
65 NULL, /* init thread */ | |
66 NULL, /* exit thread */ | |
67 NULL, /* exit process */ | |
68 NULL, /* exit master */ | |
69 NGX_MODULE_V1_PADDING | |
70 }; | |
71 | |
72 | |
73 static ngx_int_t | |
74 ngx_http_random_index_handler(ngx_http_request_t *r) | |
75 { | |
76 u_char *last, *filename; | |
77 size_t len, allocated, root; | |
78 ngx_err_t err; | |
79 ngx_int_t rc; | |
80 ngx_str_t path, uri, *name; | |
81 ngx_dir_t dir; | |
82 ngx_uint_t n, level; | |
83 ngx_array_t names; | |
84 ngx_http_random_index_loc_conf_t *rlcf; | |
85 | |
86 if (r->uri.data[r->uri.len - 1] != '/') { | |
87 return NGX_DECLINED; | |
88 } | |
89 | |
90 if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) { | |
91 return NGX_DECLINED; | |
92 } | |
93 | |
94 rlcf = ngx_http_get_module_loc_conf(r, ngx_http_random_index_module); | |
95 | |
96 if (!rlcf->enable) { | |
97 return NGX_DECLINED; | |
98 } | |
99 | |
100 #if (NGX_HAVE_D_TYPE) | |
7433
061ec464813f
Win32: removed NGX_DIR_MASK concept.
Maxim Dounin <mdounin@mdounin.ru>
parents:
6477
diff
changeset
|
101 len = 0; |
2235 | 102 #else |
103 len = NGX_HTTP_RANDOM_INDEX_PREALLOCATE; | |
104 #endif | |
105 | |
106 last = ngx_http_map_uri_to_path(r, &path, &root, len); | |
107 if (last == NULL) { | |
108 return NGX_HTTP_INTERNAL_SERVER_ERROR; | |
109 } | |
110 | |
111 allocated = path.len; | |
112 | |
113 path.len = last - path.data - 1; | |
114 path.data[path.len] = '\0'; | |
115 | |
116 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | |
117 "http random index: \"%s\"", path.data); | |
118 | |
119 if (ngx_open_dir(&path, &dir) == NGX_ERROR) { | |
120 err = ngx_errno; | |
121 | |
122 if (err == NGX_ENOENT | |
123 || err == NGX_ENOTDIR | |
124 || err == NGX_ENAMETOOLONG) | |
125 { | |
126 level = NGX_LOG_ERR; | |
127 rc = NGX_HTTP_NOT_FOUND; | |
128 | |
129 } else if (err == NGX_EACCES) { | |
130 level = NGX_LOG_ERR; | |
131 rc = NGX_HTTP_FORBIDDEN; | |
132 | |
133 } else { | |
134 level = NGX_LOG_CRIT; | |
135 rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | |
136 } | |
137 | |
138 ngx_log_error(level, r->connection->log, err, | |
139 ngx_open_dir_n " \"%s\" failed", path.data); | |
140 | |
141 return rc; | |
142 } | |
143 | |
144 if (ngx_array_init(&names, r->pool, 32, sizeof(ngx_str_t)) != NGX_OK) { | |
145 return ngx_http_random_index_error(r, &dir, &path); | |
146 } | |
147 | |
148 filename = path.data; | |
149 filename[path.len] = '/'; | |
150 | |
151 for ( ;; ) { | |
152 ngx_set_errno(0); | |
153 | |
154 if (ngx_read_dir(&dir) == NGX_ERROR) { | |
155 err = ngx_errno; | |
156 | |
157 if (err != NGX_ENOMOREFILES) { | |
158 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err, | |
159 ngx_read_dir_n " \"%V\" failed", &path); | |
160 return ngx_http_random_index_error(r, &dir, &path); | |
161 } | |
162 | |
163 break; | |
164 } | |
165 | |
166 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | |
167 "http random index file: \"%s\"", ngx_de_name(&dir)); | |
168 | |
169 if (ngx_de_name(&dir)[0] == '.') { | |
170 continue; | |
171 } | |
172 | |
173 len = ngx_de_namelen(&dir); | |
174 | |
3203
a56cccd588e6
update r3201: ngx_http_random_index_module should behave consistently
Igor Sysoev <igor@sysoev.ru>
parents:
3200
diff
changeset
|
175 if (dir.type == 0 || ngx_de_is_link(&dir)) { |
2235 | 176 |
177 /* 1 byte for '/' and 1 byte for terminating '\0' */ | |
178 | |
179 if (path.len + 1 + len + 1 > allocated) { | |
180 allocated = path.len + 1 + len + 1 | |
181 + NGX_HTTP_RANDOM_INDEX_PREALLOCATE; | |
182 | |
183 filename = ngx_pnalloc(r->pool, allocated); | |
184 if (filename == NULL) { | |
185 return ngx_http_random_index_error(r, &dir, &path); | |
186 } | |
187 | |
188 last = ngx_cpystrn(filename, path.data, path.len + 1); | |
189 *last++ = '/'; | |
190 } | |
191 | |
192 ngx_cpystrn(last, ngx_de_name(&dir), len + 1); | |
193 | |
194 if (ngx_de_info(filename, &dir) == NGX_FILE_ERROR) { | |
195 err = ngx_errno; | |
196 | |
197 if (err != NGX_ENOENT) { | |
198 ngx_log_error(NGX_LOG_CRIT, r->connection->log, err, | |
199 ngx_de_info_n " \"%s\" failed", filename); | |
200 return ngx_http_random_index_error(r, &dir, &path); | |
201 } | |
202 | |
203 if (ngx_de_link_info(filename, &dir) == NGX_FILE_ERROR) { | |
204 ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno, | |
205 ngx_de_link_info_n " \"%s\" failed", | |
206 filename); | |
207 return ngx_http_random_index_error(r, &dir, &path); | |
208 } | |
209 } | |
210 } | |
211 | |
212 if (!ngx_de_is_file(&dir)) { | |
213 continue; | |
214 } | |
215 | |
216 name = ngx_array_push(&names); | |
217 if (name == NULL) { | |
218 return ngx_http_random_index_error(r, &dir, &path); | |
219 } | |
220 | |
221 name->len = len; | |
222 | |
223 name->data = ngx_pnalloc(r->pool, len); | |
224 if (name->data == NULL) { | |
225 return ngx_http_random_index_error(r, &dir, &path); | |
226 } | |
227 | |
228 ngx_memcpy(name->data, ngx_de_name(&dir), len); | |
229 } | |
230 | |
231 if (ngx_close_dir(&dir) == NGX_ERROR) { | |
232 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, | |
6477
47daf95d0138
Fixed logging in close error handling.
Sergey Kandaurov <pluknet@nginx.com>
parents:
4499
diff
changeset
|
233 ngx_close_dir_n " \"%V\" failed", &path); |
2235 | 234 } |
235 | |
236 n = names.nelts; | |
237 | |
238 if (n == 0) { | |
239 return NGX_DECLINED; | |
240 } | |
241 | |
242 name = names.elts; | |
243 | |
244 n = (ngx_uint_t) (((uint64_t) ngx_random() * n) / 0x80000000); | |
245 | |
246 uri.len = r->uri.len + name[n].len; | |
247 | |
248 uri.data = ngx_pnalloc(r->pool, uri.len); | |
249 if (uri.data == NULL) { | |
250 return NGX_HTTP_INTERNAL_SERVER_ERROR; | |
251 } | |
252 | |
253 last = ngx_copy(uri.data, r->uri.data, r->uri.len); | |
254 ngx_memcpy(last, name[n].data, name[n].len); | |
255 | |
256 return ngx_http_internal_redirect(r, &uri, &r->args); | |
257 } | |
258 | |
259 | |
260 static ngx_int_t | |
261 ngx_http_random_index_error(ngx_http_request_t *r, ngx_dir_t *dir, | |
262 ngx_str_t *name) | |
263 { | |
264 if (ngx_close_dir(dir) == NGX_ERROR) { | |
265 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno, | |
266 ngx_close_dir_n " \"%V\" failed", name); | |
267 } | |
268 | |
269 return NGX_HTTP_INTERNAL_SERVER_ERROR; | |
270 } | |
271 | |
272 | |
273 static void * | |
274 ngx_http_random_index_create_loc_conf(ngx_conf_t *cf) | |
275 { | |
276 ngx_http_random_index_loc_conf_t *conf; | |
277 | |
278 conf = ngx_palloc(cf->pool, sizeof(ngx_http_random_index_loc_conf_t)); | |
279 if (conf == NULL) { | |
2912
c7d57b539248
return NULL instead of NGX_CONF_ERROR on a create conf failure
Igor Sysoev <igor@sysoev.ru>
parents:
2721
diff
changeset
|
280 return NULL; |
2235 | 281 } |
282 | |
283 conf->enable = NGX_CONF_UNSET; | |
284 | |
285 return conf; | |
286 } | |
287 | |
288 | |
289 static char * | |
290 ngx_http_random_index_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) | |
291 { | |
292 ngx_http_random_index_loc_conf_t *prev = parent; | |
293 ngx_http_random_index_loc_conf_t *conf = child; | |
294 | |
295 ngx_conf_merge_value(conf->enable, prev->enable, 0); | |
296 | |
297 return NGX_CONF_OK; | |
298 } | |
299 | |
300 | |
301 static ngx_int_t | |
302 ngx_http_random_index_init(ngx_conf_t *cf) | |
303 { | |
304 ngx_http_handler_pt *h; | |
305 ngx_http_core_main_conf_t *cmcf; | |
306 | |
307 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); | |
308 | |
309 h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers); | |
310 if (h == NULL) { | |
311 return NGX_ERROR; | |
312 } | |
313 | |
314 *h = ngx_http_random_index_handler; | |
315 | |
316 return NGX_OK; | |
317 } |