As discussed in GitHub issue #624 Lua scripts should not use variables that are never going to be read, because the memory for variable names is never going to be freed.
Add an optional `ifexist` parameter to the `set_var` function that allows a Lua developer to set variables that are going to be ignored if the variable name was not used elsewhere before. Usually this mean that there is no `var()` sample fetch for the variable in question within the configuration. --- doc/lua-api/index.rst | 18 +++++++++++++++--- reg-tests/lua/set_var.lua | 13 +++++++++++++ reg-tests/lua/set_var.vtc | 24 +++++++++++++++++++++++- src/hlua.c | 30 ++++++++++++++++++++++++------ 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/doc/lua-api/index.rst b/doc/lua-api/index.rst index f3e5cb786..3d8282cdb 100644 --- a/doc/lua-api/index.rst +++ b/doc/lua-api/index.rst @@ -1710,7 +1710,7 @@ TXN class :param class_txn txn: The class txn object containing the data. :param opaque data: The data which is stored in the transaction. -.. js:function:: TXN.set_var(TXN, var, value) +.. js:function:: TXN.set_var(TXN, var, value[, ifexist]) Converts a Lua type in a HAProxy type and store it in a variable <var>. @@ -1718,6 +1718,10 @@ TXN class :param string var: The variable name according with the HAProxy variable syntax. :param type value: The value associated to the variable. The type can be string or integer. + :param boolean ifexist: If this parameter is set to a truthy value the variable + will only be set if it was defined elsewhere (i.e. used + within the configuration). It is highly recommended to + always set this to true. .. js:function:: TXN.unset_var(TXN, var) @@ -2513,7 +2517,7 @@ AppletHTTP class :param opaque data: The data which is stored in the transaction. :see: :js:func:`AppletHTTP.get_priv` -.. js:function:: AppletHTTP.set_var(applet, var, value) +.. js:function:: AppletHTTP.set_var(applet, var, value[, ifexist]) Converts a Lua type in a HAProxy type and store it in a variable <var>. @@ -2521,6 +2525,10 @@ AppletHTTP class :param string var: The variable name according with the HAProxy variable syntax. :param type value: The value associated to the variable. The type ca be string or integer. + :param boolean ifexist: If this parameter is set to a truthy value the variable + will only be set if it was defined elsewhere (i.e. used + within the configuration). It is highly recommended to + always set this to true. :see: :js:func:`AppletHTTP.unset_var` :see: :js:func:`AppletHTTP.get_var` @@ -2624,7 +2632,7 @@ AppletTCP class :param opaque data: The data which is stored in the transaction. :see: :js:func:`AppletTCP.get_priv` -.. js:function:: AppletTCP.set_var(applet, var, value) +.. js:function:: AppletTCP.set_var(applet, var, value[, ifexist]) Converts a Lua type in a HAProxy type and stores it in a variable <var>. @@ -2632,6 +2640,10 @@ AppletTCP class :param string var: The variable name according with the HAProxy variable syntax. :param type value: The value associated to the variable. The type can be string or integer. + :param boolean ifexist: If this parameter is set to a truthy value the variable + will only be set if it was defined elsewhere (i.e. used + within the configuration). It is highly recommended to + always set this to true. :see: :js:func:`AppletTCP.unset_var` :see: :js:func:`AppletTCP.get_var` diff --git a/reg-tests/lua/set_var.lua b/reg-tests/lua/set_var.lua index 78d1fd495..f4d5e7af4 100644 --- a/reg-tests/lua/set_var.lua +++ b/reg-tests/lua/set_var.lua @@ -10,3 +10,16 @@ core.register_service("set_var", "http", function(applet) applet:start_response() applet:send("") end) + +core.register_service("set_var_ifexist", "http", function(applet) + local var_name = applet.headers["var"][0] + local result = applet:set_var(var_name, "value", true) + if result then + applet:set_status(202) + else + applet:set_status(400) + end + applet:add_header("echo", applet:get_var(var_name) or "(nil)") + applet:start_response() + applet:send("") +end) diff --git a/reg-tests/lua/set_var.vtc b/reg-tests/lua/set_var.vtc index 2f1ba4ee9..5d37fcb88 100644 --- a/reg-tests/lua/set_var.vtc +++ b/reg-tests/lua/set_var.vtc @@ -13,11 +13,20 @@ haproxy h1 -conf { bind "fd@${fe1}" http-request use-service lua.set_var + + frontend fe2 + mode http + ${no-htx} option http-use-htx + bind "fd@${fe2}" + + http-request set-header Dummy %[var(txn.fe2_foo)] + + http-request use-service lua.set_var_ifexist } -start client c0 -connect ${h1_fe1_sock} { txreq -url "/" \ - -hdr "Var: txn.foo" + -hdr "Var: txn.fe1_foo" rxresp expect resp.status == 202 expect resp.http.echo == "value" @@ -27,3 +36,16 @@ client c0 -connect ${h1_fe1_sock} { expect resp.status == 400 expect resp.http.echo == "(nil)" } -run + +client c1 -connect ${h1_fe2_sock} { + txreq -url "/" \ + -hdr "Var: txn.fe2_foo" + rxresp + expect resp.status == 202 + expect resp.http.echo == "value" + txreq -url "/" \ + -hdr "Var: txn.fe2_bar" + rxresp + expect resp.status == 400 + expect resp.http.echo == "(nil)" +} -run diff --git a/src/hlua.c b/src/hlua.c index 0ce57c7ed..f0d7e0f9e 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -3475,7 +3475,8 @@ __LJMP static int hlua_applet_tcp_set_var(lua_State *L) size_t len; struct sample smp; - MAY_LJMP(check_args(L, 3, "set_var")); + if (lua_gettop(L) < 3 || lua_gettop(L) > 4) + WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. @@ -3489,7 +3490,12 @@ __LJMP static int hlua_applet_tcp_set_var(lua_State *L) /* Store the sample in a variable. */ smp_set_owner(&smp, s->be, s->sess, s, 0); - lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); + + if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) + lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); + else + lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); + return 1; } @@ -3953,7 +3959,8 @@ __LJMP static int hlua_applet_http_set_var(lua_State *L) size_t len; struct sample smp; - MAY_LJMP(check_args(L, 3, "set_var")); + if (lua_gettop(L) < 3 || lua_gettop(L) > 4) + WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. @@ -3967,7 +3974,12 @@ __LJMP static int hlua_applet_http_set_var(lua_State *L) /* Store the sample in a variable. */ smp_set_owner(&smp, s->be, s->sess, s, 0); - lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); + + if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) + lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); + else + lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); + return 1; } @@ -5040,7 +5052,8 @@ __LJMP static int hlua_set_var(lua_State *L) size_t len; struct sample smp; - MAY_LJMP(check_args(L, 3, "set_var")); + if (lua_gettop(L) < 3 || lua_gettop(L) > 4) + WILL_LJMP(luaL_error(L, "'set_var' needs between 3 and 4 arguments")); /* It is useles to retrieve the stream, but this function * runs only in a stream context. @@ -5053,7 +5066,12 @@ __LJMP static int hlua_set_var(lua_State *L) /* Store the sample in a variable. */ smp_set_owner(&smp, htxn->p, htxn->s->sess, htxn->s, htxn->dir & SMP_OPT_DIR); - lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); + + if (lua_gettop(L) == 4 && lua_toboolean(L, 4)) + lua_pushboolean(L, vars_set_by_name_ifexist(name, len, &smp) != 0); + else + lua_pushboolean(L, vars_set_by_name(name, len, &smp) != 0); + return 1; } -- 2.26.2