Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

memory leak #3

Open
RekGRpth opened this issue Jul 25, 2021 · 28 comments
Open

memory leak #3

RekGRpth opened this issue Jul 25, 2021 · 28 comments

Comments

@RekGRpth
Copy link

предлагаю

  1. не выделять дополнительную память в create_loc_conf, т.к. она будет просто так выделена для всех многочисленнных location, даже конфигурация модуля написана всего один раз в http
  2. не создавать/инициализировать большие (больше 1 элемента) массивы при конфигурации, т.к. это не приводит к увеличению производительности, зато приводит к пустой трате памяти
@RekGRpth
Copy link
Author

как насчёт такого решения?

diff --git a/ngx_http_response_body_module.c b/ngx_http_response_body_module.c
index 69cdd5d..3254127 100644
--- a/ngx_http_response_body_module.c
+++ b/ngx_http_response_body_module.c
@@ -19,6 +19,11 @@ typedef struct {
 } ngx_http_response_body_loc_conf_t;
 
 
+typedef struct {
+    ngx_flag_t enable;
+} ngx_http_response_body_main_conf_t;
+
+
 typedef struct {
     ngx_http_response_body_loc_conf_t   *blcf;
     ngx_buf_t                            buffer;
@@ -32,6 +37,7 @@ static ngx_int_t
 ngx_http_response_body_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
 
+static void *ngx_http_response_body_create_main_conf(ngx_conf_t *cf);
 static void *ngx_http_response_body_create_loc_conf(ngx_conf_t *cf);
 static char *ngx_http_response_body_merge_loc_conf(ngx_conf_t *cf, void *parent,
     void *child);
@@ -49,6 +55,8 @@ static ngx_int_t ngx_http_response_body_filter_body(ngx_http_request_t *r,
 
 static ngx_int_t ngx_http_response_body_init(ngx_conf_t *cf);
 
+static char *ngx_http_response_body_conf_command_check(ngx_conf_t *cf, void *post,
+    void *data);
 
 static char *
 ngx_conf_set_flag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
@@ -80,6 +88,10 @@ ngx_http_next_body_filter_stub(ngx_http_request_t *r, ngx_chain_t *in)
 }
 
 
+static ngx_conf_post_t  ngx_http_response_body_conf_command_post =
+    { ngx_http_response_body_conf_command_check };
+
+
 static ngx_command_t  ngx_http_response_body_commands[] = {
 
     { ngx_string("capture_response_body"),
@@ -101,7 +113,7 @@ static ngx_command_t  ngx_http_response_body_commands[] = {
       ngx_conf_set_keyval,
       NGX_HTTP_LOC_CONF_OFFSET,
       offsetof(ngx_http_response_body_loc_conf_t, conditions),
-      NULL },
+      &ngx_http_response_body_conf_command_post },
 
     { ngx_string("capture_response_body_if_1xx"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
@@ -161,7 +173,7 @@ static ngx_http_module_t  ngx_http_response_body_module_ctx = {
     ngx_http_response_body_add_variables,    /* preconfiguration */
     ngx_http_response_body_init,             /* postconfiguration */
 
-    NULL,                                    /* create main configuration */
+    ngx_http_response_body_create_main_conf, /* create main configuration */
     NULL,                                    /* init main configuration */
 
     NULL,                                    /* create server configuration */
@@ -217,6 +229,9 @@ ngx_conf_set_flag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 
     blcf->capture_body = *fp;
 
+    ngx_http_response_body_main_conf_t *main = ngx_http_conf_get_module_main_conf(cf, ngx_http_response_body_module);
+    main->enable = 1;
+
     return NGX_CONF_OK;
 }
 
@@ -239,6 +254,9 @@ ngx_conf_set_msec(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 
     blcf->capture_body = 1;
 
+    ngx_http_response_body_main_conf_t *main = ngx_http_conf_get_module_main_conf(cf, ngx_http_response_body_module);
+    main->enable = 1;
+
     return NGX_CONF_OK;
 }
 
@@ -272,6 +290,9 @@ ngx_conf_set_keyval(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 
     blcf->capture_body = 1;
 
+    ngx_http_response_body_main_conf_t *main = ngx_http_conf_get_module_main_conf(cf, ngx_http_response_body_module);
+    main->enable = 1;
+
     return NGX_CONF_OK;
 }
 
@@ -348,6 +369,20 @@ ngx_http_response_body_request_var(ngx_conf_t *cf, ngx_command_t *cmd,
 }
 
 
+static void *
+ngx_http_response_body_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_response_body_main_conf_t  *bmcf;
+
+    bmcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_response_body_main_conf_t));
+
+    if (bmcf == NULL)
+        return NULL;
+
+    return bmcf;
+}
+
+
 static void *
 ngx_http_response_body_create_loc_conf(ngx_conf_t *cf)
 {
@@ -360,9 +395,8 @@ ngx_http_response_body_create_loc_conf(ngx_conf_t *cf)
 
     blcf->latency      = NGX_CONF_UNSET_MSEC;
     blcf->buffer_size  = NGX_CONF_UNSET_SIZE;
-    blcf->conditions   = ngx_array_create(cf->pool, 10, sizeof(ngx_keyval_t));
-    blcf->cv           = ngx_array_create(cf->pool, 10,
-        sizeof(ngx_http_complex_value_t));
+    blcf->conditions   = NGX_CONF_UNSET_PTR;
+    blcf->cv           = NGX_CONF_UNSET_PTR;
     blcf->status_1xx   = NGX_CONF_UNSET;
     blcf->status_2xx   = NGX_CONF_UNSET;
     blcf->status_3xx   = NGX_CONF_UNSET;
@@ -370,30 +404,27 @@ ngx_http_response_body_create_loc_conf(ngx_conf_t *cf)
     blcf->status_5xx   = NGX_CONF_UNSET;
     blcf->capture_body = NGX_CONF_UNSET;
 
-    if (blcf->conditions == NULL || blcf->cv == NULL)
-        return NULL;
-
     return blcf;
 }
 
 
 static ngx_int_t
-ngx_array_merge(ngx_array_t *l, ngx_array_t *r)
+ngx_array_merge(ngx_array_t **conf, ngx_array_t **prev, void *cmp)
 {
-    void  *p;
-
-    if (r->nelts == 0)
-        return NGX_OK;
-
-    if (r->size != l->size)
-        return NGX_ERROR;
-    
-    p = ngx_array_push_n(l, r->nelts);
-    if (p == NULL)
-        return NGX_ERROR;
-
-    ngx_memcpy(p, r->elts, r->size * r->nelts);
-
+    if (*conf == cmp || (*conf)->nelts == 0) {
+        *conf = *prev;
+    } else if (*prev != cmp && (*prev)->nelts) {
+        ngx_uint_t orig_len = (*conf)->nelts;
+        if (!ngx_array_push_n(*conf, (*prev)->nelts)) return NGX_ERROR;
+        char *elts = (*conf)->elts;
+        for (ngx_uint_t i = 0; i < orig_len; i++) {
+            ngx_memcpy(elts + (*conf)->size * ((*conf)->nelts - 1 - i), elts + (*conf)->size * (orig_len - 1 - i), (*conf)->size);
+        }
+        char *prev_elts = (*prev)->elts;
+        for (ngx_uint_t i = 0; i < (*prev)->nelts; i++) {
+            ngx_memcpy(elts + (*prev)->size * i, prev_elts + (*prev)->size * i, (*prev)->size);
+        }
+    }
     return NGX_OK;
 }
 
@@ -403,15 +434,13 @@ ngx_http_response_body_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
 {
     ngx_http_response_body_loc_conf_t  *prev = parent;
     ngx_http_response_body_loc_conf_t  *conf = child;
-    ngx_http_compile_complex_value_t    ccv;
-    ngx_http_complex_value_t           *cv;
-    ngx_uint_t                          j;
-    ngx_keyval_t                       *kv;
 
     ngx_conf_merge_msec_value(conf->latency, prev->latency, (ngx_msec_int_t) 0);
     ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
                               (size_t) ngx_pagesize);
-    if (ngx_array_merge(conf->conditions, prev->conditions) == NGX_ERROR)
+    if (ngx_array_merge(&conf->conditions, &prev->conditions, NGX_CONF_UNSET_PTR) == NGX_ERROR)
+        return NGX_CONF_ERROR;
+    if (ngx_array_merge(&conf->cv, &prev->cv, NGX_CONF_UNSET_PTR) == NGX_ERROR)
         return NGX_CONF_ERROR;
     ngx_conf_merge_value(conf->status_1xx, prev->status_1xx, 0);
     ngx_conf_merge_value(conf->status_2xx, prev->status_2xx, 0);
@@ -420,29 +449,6 @@ ngx_http_response_body_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
     ngx_conf_merge_value(conf->status_5xx, prev->status_5xx, 0);
     ngx_conf_merge_value(conf->capture_body, prev->capture_body, 0);
 
-    cv = ngx_array_push_n(conf->cv, conf->conditions->nelts);
-    if (cv == NULL)
-        return NGX_CONF_ERROR;
-    ngx_memzero(cv, conf->cv->size * conf->cv->nalloc);
-    kv = conf->conditions->elts;
-
-    for (j = 0; j < conf->cv->nelts; j++) {
-
-        ngx_memzero(&ccv, sizeof(ccv));
-
-        ccv.cf = cf;
-        ccv.value = &kv[j].key;
-        ccv.complex_value = &cv[j];
-        ccv.zero = 0;
-
-        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
-
-            ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
-                               "can't compile '%V'", &kv[j].key);
-            return NGX_CONF_ERROR;
-        }
-    }
-
     return NGX_CONF_OK;
 }
 
@@ -450,6 +456,9 @@ ngx_http_response_body_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
 static ngx_int_t
 ngx_http_response_body_init(ngx_conf_t *cf)
 {
+    ngx_http_response_body_main_conf_t *main = ngx_http_conf_get_module_main_conf(cf, ngx_http_response_body_module);
+    if (!main->enable) return NGX_OK;
+
     ngx_http_next_header_filter = ngx_http_top_header_filter;
     ngx_http_top_header_filter = ngx_http_response_body_filter_header;
 
@@ -562,6 +571,11 @@ ngx_http_response_body_filter_header(ngx_http_request_t *r)
         && ctx->blcf->latency <= ngx_http_response_body_request_time(r))
         return ngx_http_next_header_filter(r);
 
+    if (ctx->blcf->conditions == NGX_CONF_UNSET_PTR || ctx->blcf->conditions == NULL) {
+        ngx_http_set_ctx(r, NULL, ngx_http_response_body_module);
+        return ngx_http_next_header_filter(r);
+    }
+
     cv = ctx->blcf->cv->elts;
     kv = ctx->blcf->conditions->elts;
 
@@ -639,3 +653,33 @@ ngx_http_response_body_filter_body(ngx_http_request_t *r, ngx_chain_t *in)
 
     return ngx_http_next_body_filter(r, in);
 }
+
+static char *
+ngx_http_response_body_conf_command_check(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_http_compile_complex_value_t ccv;
+    ngx_http_complex_value_t *cv;
+    ngx_http_response_body_loc_conf_t *conf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_response_body_module);
+    ngx_keyval_t *kv = data;
+    if (conf->cv == NGX_CONF_UNSET_PTR || conf->cv == NULL) {
+        conf->cv = ngx_array_create(cf->pool, 1, sizeof(ngx_http_complex_value_t));
+        if (conf->cv == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+    cv = ngx_array_push(conf->cv);
+    if (cv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+    ngx_memzero(&ccv, sizeof(ccv));
+    ccv.cf = cf;
+    ccv.value = &kv->key;
+    ccv.complex_value = cv;
+    ccv.zero = 0;
+    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
+                           "can't compile '%V'", &kv->key);
+        return NGX_CONF_ERROR;
+    }
+    return NGX_CONF_OK;
+}

@ZigzagAK
Copy link
Owner

Ну, все это замечательно, но, к сожалению, я не готов на такие изменения, тк модуль работает уже давно в критичном проде. Выгод от этих изменений особо не вижу. Можно сделать компромиссный вариант:

  1. Фичу с main->enable = 1
  2. Массивы резервировать с размером 1.

@RekGRpth
Copy link
Author

  • Фичу с main->enable = 1

  • Массивы резервировать с размером 1.

если сделать только это, то всё равно будет много неиспользованной памяти в конфигурации, в которой много location

@ZigzagAK
Copy link
Owner

Много понятие относительное. У нас сервис на nginx под 10gb съедает. Эта конфигурация - капля в море.

@RekGRpth
Copy link
Author

вот так по капле и уходит вся память...
я в ngx_waf нашёл подобные ошибки, после того, как автор их исправил, nginx перестал есть по полгига на процесс...

@ZigzagAK
Copy link
Owner

Вы предлагаете большие оптимизации. Я не буду спорить, что они сэкономят какую-то память (здесь еще надо считать - я не уверен, что это даст какой-то визуальный эффект). Чтобы их принять, мне необходимо все тщательно проверять. На это нужно время, а время ресурс самый дорогой.

@RekGRpth
Copy link
Author

необходимо все тщательно проверять

для этого тесты можно написать, что существенно сэкономит время

@ZigzagAK
Copy link
Owner

ZigzagAK commented Jul 27, 2021

для этого тесты можно написать, что существенно сэкономит время

You are welcome.

@ZigzagAK
Copy link
Owner

Вы можете создать PR.
Прошу придерживаться Nginx code style.
Когда я смогу выделать время я поработаю с Вашим PR.
Да, и я не очень понимаю, что Вы там делаете в array_merge.

@RekGRpth
Copy link
Author

что Вы там делаете в array_merge.

это же универсальный мержинг массивов!
работает как-так:

  1. если на текущей конфигурации массив не задан или нулевой длины, то возвращается массив из предыдущей конфигурации
  2. иначе - к массиву на преддыдущей конфигурации добавляется массив текущей конфигурации

@ZigzagAK
Copy link
Owner

иначе - к массиву на преддыдущей конфигурации добавляется массив текущей конфигурации

Ну вот меня как-то смущает код в этой части.

@RekGRpth
Copy link
Author

Ну вот меня как-то смущает код в этой части.

ну, сначала текущий массив увеличивается для длину предыдущего
потом, данные в текущем массиве сдвигаются на длину предыдущего вправо
и в конце, данные предыдущего заполняют начало текущего

@ZigzagAK
Copy link
Owner

Ну:

  1. Вы потеряли проверку if (r->size != l->size)
  2. Можно обойтись 2-мя ngx_memmove. Хотя здесь не принципиально вроде как и можно просто prev добавить в tail, не сохраняя порядок, тк он особо не важен. Там важно соответствие индекса cv и значения. https://github.com/ZigzagAK/ngx_http_response_body_module/blob/master/ngx_http_response_body_module.c#L568

@RekGRpth
Copy link
Author

1. Вы потеряли проверку if (r->size != l->size)

а для чего она?

2\. можно просто prev добавить в tail, не сохраняя порядок, тк он особо не важен

это у вас не важен, зато функцию можно использовать и там, где важен

@ZigzagAK
Copy link
Owner

а для чего она?

Ну мы же мержим массивы одинаковых объектов. Это хоть какая-то проверка.

зато функцию можно использовать и там, где важен

Сдйлайте через 2 ngx_memmove

@ZigzagAK
Copy link
Owner

Можно оставить вообще один массив структур где держать и CV и значения.

@RekGRpth
Copy link
Author

Ну мы же мержим массивы одинаковых объектов. Это хоть какая-то проверка.

мне не понятно такое ограничение. почему нельзя в http иметь две команды capture_response_body_if, а в одном location - три, а в другом - одну?

@ZigzagAK
Copy link
Owner

мне не понятно такое ограничение. почему нельзя в http иметь две команды capture_response_body_if, а в одном location - три, а в другом - одну?

size - это размер одного элемента массива, а не количество.

@RekGRpth
Copy link
Author

size - это размер одного элемента массива, а не количество.

хм... точно

@ZigzagAK
Copy link
Owner

точнее ngx_memmove (сдвиг в conf) + ngx_memcpy (копирование из prev)

@RekGRpth
Copy link
Author

ngx_memmove (сдвиг в conf)

сам себя не затрёт? если двигать на один массив из десяти элементов

@RekGRpth
Copy link
Author

size - это размер одного элемента массива, а не количество.

хм... точно

да, можно добавить для красоты

if ((*conf)->size != (*prev)->size) return NGX_ERROR;

хотя это и не выполнится при

    if (ngx_array_merge(&conf->conditions, &prev->conditions, NGX_CONF_UNSET_PTR) == NGX_ERROR)
        return NGX_CONF_ERROR;
    if (ngx_array_merge(&conf->cv, &prev->cv, NGX_CONF_UNSET_PTR) == NGX_ERROR)

@ZigzagAK
Copy link
Owner

Ну как не выполняется? Если не выполняется, то это попытка смержить ну совсем разные массивы. С чего оно не выполняется?

@RekGRpth
Copy link
Author

С чего оно не выполняется?

дак фнкция вызывается с массивами заведомо одинаковых объектов

@ZigzagAK
Copy link
Owner

ZigzagAK commented Jul 27, 2021

дак фнкция вызывается с массивами заведомо одинаковых объектов

Здесь нельзя проверить это на стадии компиляции никак.
Правильнее здесь использовать assert вместо возврата NGX_CONF_ERROR.
В общем, проверку оставьте как есть на будущее. мерж реализуйте через ngx_memmove + ngx_memcpy.

@ZigzagAK
Copy link
Owner

и code style.

@RekGRpth
Copy link
Author

через ngx_memmove

сам себя не затрёт? если двигать массив из десяти элементов на одну позицию вправо

@ZigzagAK
Copy link
Owner

memmove поддерживает копирование overlap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants