Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 1 | Lua: Architecture and first steps |
| 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
Willy Tarreau | 3b50e5c | 2023-04-23 10:21:37 +0200 | [diff] [blame] | 3 | version 2.8 |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 4 | |
| 5 | author: Thierry FOURNIER |
| 6 | contact: tfournier at arpalert dot org |
| 7 | |
| 8 | |
| 9 | |
| 10 | HAProxy is a powerful load balancer. It embeds many options and many |
| 11 | configuration styles in order to give a solution to many load balancing |
| 12 | problems. However, HAProxy is not universal and some special or specific |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 13 | problems do not have solution with the native software. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 14 | |
| 15 | This text is not a full explanation of the Lua syntax. |
| 16 | |
| 17 | This text is not a replacement of the HAProxy Lua API documentation. The API |
| 18 | documentation can be found at the project root, in the documentation directory. |
| 19 | The goal of this text is to discover how Lua is implemented in HAProxy and using |
| 20 | it efficiently. |
| 21 | |
| 22 | However, this can be read by Lua beginners. Some examples are detailed. |
| 23 | |
| 24 | Why a scripting language in HAProxy |
| 25 | =================================== |
| 26 | |
| 27 | HAProxy 1.5 makes at possible to do many things using samples, but some people |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 28 | want to more combining results of samples fetches, programming conditions and |
Ilya Shipitsin | 2075ca8 | 2020-03-06 23:22:22 +0500 | [diff] [blame] | 29 | loops which is not possible. Sometimes people implement these functionalities |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 30 | in patches which have no meaning outside their network. These people must |
| 31 | maintain these patches, or worse we must integrate them in the HAProxy |
| 32 | mainstream. |
| 33 | |
| 34 | Their need is to have an embedded programming language in order to no longer |
| 35 | modify the HAProxy source code, but to write their own control code. Lua is |
| 36 | encountered very often in the software industry, and in some open source |
| 37 | projects. It is easy to understand, efficient, light without external |
Joseph Herlant | 71b4b15 | 2018-11-13 16:55:16 -0800 | [diff] [blame] | 38 | dependencies, and leaves the resource control to the implementation. Its design |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 39 | is close to the HAProxy philosophy which uses components for what they do |
| 40 | perfectly. |
| 41 | |
| 42 | The HAProxy control block allows one to take a decision based on the comparison |
| 43 | between samples and patterns. The samples are extracted using fetch functions |
| 44 | easily extensible, and are used by actions which are also extensible. It seems |
| 45 | natural to allow Lua to give samples, modify them, and to be an action target. |
| 46 | So, Lua uses the same entities as the configuration language. This is the most |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 47 | natural and reliable way for the Lua integration. So, the Lua engine allows one |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 48 | to add new sample fetch functions, new converter functions and new actions. |
| 49 | These new entities can access the existing samples fetches and converters |
| 50 | allowing to extend them without rewriting them. |
| 51 | |
| 52 | The writing of the first Lua functions shows that implementing complex concepts |
| 53 | like protocol analysers is easy and can be extended to full services. It appears |
| 54 | that these services are not easy to implement with the HAProxy configuration |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 55 | model which is based on four steps: fetch, convert, compare and action. HAProxy |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 56 | is extended with a notion of services which are a formalisation of the existing |
| 57 | services like stats, cli and peers. The service is an autonomous entity with a |
| 58 | behaviour pattern close to that of an external client or server. The Lua engine |
| 59 | inherits from this new service and offers new possibilities for writing |
| 60 | services. |
| 61 | |
| 62 | This scripting language is useful for testing new features as proof of concept. |
| 63 | Later, if there is general interest, the proof of concept could be integrated |
| 64 | with C language in the HAProxy core. |
| 65 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 66 | The HAProxy Lua integration also provides a simple way for distributing Lua |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 67 | packages. The final user needs only to install the Lua file, load it in HAProxy |
| 68 | and follow the attached documentation. |
| 69 | |
| 70 | Design and technical things |
| 71 | =========================== |
| 72 | |
| 73 | Lua is integrated into the HAProxy event driven core. We want to preserve the |
| 74 | fast processing of HAProxy. To ensure this, we implement some technical concepts |
| 75 | between HAProxy and the Lua library. |
| 76 | |
| 77 | The following paragraph also describes the interactions between Lua and HAProxy |
| 78 | from a technical point of view. |
| 79 | |
| 80 | Prerequisite |
| 81 | ----------- |
| 82 | |
| 83 | Reading the following documentation links is required to understand the |
| 84 | current paragraph: |
| 85 | |
Willy Tarreau | 77ec462 | 2022-04-16 07:58:19 +0200 | [diff] [blame] | 86 | HAProxy doc: http://docs.haproxy.org/ |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 87 | Lua API: http://www.lua.org/manual/5.3/ |
Willy Tarreau | 77ec462 | 2022-04-16 07:58:19 +0200 | [diff] [blame] | 88 | HAProxy API: http://www.arpalert.org/src/haproxy-lua-api/2.6/index.html |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 89 | Lua guide: http://www.lua.org/pil/ |
| 90 | |
| 91 | more about Lua choice |
| 92 | --------------------- |
| 93 | |
| 94 | Lua language is very simple to extend. It is easy to add new functions written |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 95 | in C in the core language. It is not required to embed very intrusive libraries, |
| 96 | and we do not change compilation processes. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 97 | |
| 98 | The amount of memory consumed can be controlled, and the issues due to lack of |
| 99 | memory are perfectly caught. The maximum amount of memory allowed for the Lua |
| 100 | processes is configurable. If some memory is missing, the current Lua action |
| 101 | fails, and the HAProxy processing flow continues. |
| 102 | |
| 103 | Lua provides a way for implementing event driven design. When the Lua code |
| 104 | wants to do a blocking action, the action is started, it executes non blocking |
| 105 | operations, and returns control to the HAProxy scheduler when it needs to wait |
| 106 | for some external event. |
| 107 | |
| 108 | The Lua process can be interrupted after a number of instructions executed. The |
| 109 | Lua execution will resume later. This is a useful way for controlling the |
| 110 | execution time. This system also keeps HAProxy responsive. When the Lua |
| 111 | execution is interrupted, HAProxy accepts some connections or transfers pending |
| 112 | data. The Lua execution does not block the main HAProxy processing, except in |
| 113 | some cases which we will see later. |
| 114 | |
| 115 | Lua function integration |
| 116 | ------------------------ |
| 117 | |
| 118 | The Lua actions, sample fetches, converters and services are integrated in |
| 119 | HAProxy with "register_*" functions. The register system is a choice for |
| 120 | providing HAProxy Lua packages easily. The register system adds new sample |
| 121 | fetches, converters, actions or services usable in the HAProxy configuration |
| 122 | file. |
| 123 | |
| 124 | The register system is defined in the "core" functions collection. This |
| 125 | collection is provided by HAProxy and is always available. Below, the list of |
| 126 | these functions: |
| 127 | |
| 128 | - core.register_action() |
| 129 | - core.register_converters() |
| 130 | - core.register_fetches() |
| 131 | - core.register_init() |
| 132 | - core.register_service() |
| 133 | - core.register_task() |
| 134 | |
| 135 | These functions are the execution entry points. |
| 136 | |
| 137 | HTTP action must be used for manipulating HTTP request headers. This action |
| 138 | can not manipulates HTTP content. It is dangerous to use the channel |
| 139 | manipulation object with an HTTP request in an HTTP action. The channel |
| 140 | manipulation can transform a valid request in an invalid request. In this case, |
| 141 | the action will never resume and the processing will be frozen. HAProxy |
| 142 | discards the request after the reception timeout. |
| 143 | |
| 144 | Non blocking design |
| 145 | ------------------- |
| 146 | |
| 147 | HAProxy is an event driven software, so blocking system calls are absolutely |
| 148 | forbidden. However, the Lua allows to do blocking actions. When an action |
| 149 | blocks, HAProxy is waiting and do nothing, so the basic functionalities like |
| 150 | accepting connections or forwarding data are blocked while the end of the system |
| 151 | call. In this case HAProxy will be less responsive. |
| 152 | |
| 153 | This is very insidious because when the developer tries to execute its Lua code |
| 154 | with only one stream, HAProxy seems to run fine. When the code is used with |
| 155 | production stream, HAProxy encounters some slow processing, and it cannot |
| 156 | hold the load. |
| 157 | |
| 158 | However, during the initialisation state, you can obviously using blocking |
| 159 | functions. There are typically used for loading files. |
| 160 | |
| 161 | The list of prohibited standard Lua functions during the runtime contains all |
| 162 | that do filesystem access: |
| 163 | |
| 164 | - os.remove() |
| 165 | - os.rename() |
| 166 | - os.tmpname() |
| 167 | - package.*() |
| 168 | - io.*() |
| 169 | - file.*() |
| 170 | |
| 171 | Some other functions are prohibited: |
| 172 | |
| 173 | - os.execute(), waits for the end of the required execution blocking HAProxy. |
| 174 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 175 | - os.exit(), is not really dangerous for the process, but it's not the good way |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 176 | for exiting the HAProxy process. |
| 177 | |
| 178 | - print(), writes data on stdout. In some cases these writes are blocking, the |
| 179 | best practice is reserving this call for debugging. We must prefer |
| 180 | to use core.log() or TXN.log() for sending messages. |
| 181 | |
| 182 | Some HAProxy functions have a blocking behaviour pattern in the Lua code, but |
| 183 | there are compatible with the non blocking design. These functions are: |
| 184 | |
| 185 | - All the socket class |
| 186 | - core.sleep() |
| 187 | |
| 188 | Responsive design |
| 189 | ----------------- |
| 190 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 191 | HAProxy must process connections accept, forwarding data and processing timeouts |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 192 | as soon as possible. The first thing is to believe that a Lua script with a long |
| 193 | execution time should impact the expected responsive behaviour. |
| 194 | |
| 195 | It is not the case, the Lua script execution are regularly interrupted, and |
| 196 | HAProxy can process other things. These interruptions are exprimed in number of |
| 197 | Lua instructions. The number of interruptions between two interrupts is |
| 198 | configured with the following "tune" option: |
| 199 | |
| 200 | tune.lua.forced-yield <nb> |
| 201 | |
| 202 | The default value is 10 000. For determining it, I ran benchmark on my laptop. |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 203 | I executed a Lua loop between 10 seconds with different values for the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 204 | "tune.lua.forced-yield" option, and I noted the results: |
| 205 | |
| 206 | configured | Number of |
| 207 | instructions | loops executed |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 208 | between two | in millions |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 209 | forced yields | |
| 210 | ---------------+--------------- |
| 211 | 10 | 160 |
| 212 | 500 | 670 |
| 213 | 1000 | 680 |
| 214 | 5000 | 700 |
| 215 | 7000 | 700 |
| 216 | 8000 | 700 |
| 217 | 9000 | 710 <- ceil |
| 218 | 10000 | 710 |
| 219 | 100000 | 710 |
| 220 | 1000000 | 710 |
| 221 | |
| 222 | The result showed that from 9000 instructions between two interrupt, we reached |
| 223 | a ceil, so the default parameter is 10 000. |
| 224 | |
| 225 | When HAProxy interrupts the Lua processing, we have two states possible: |
| 226 | |
| 227 | - Lua is resumable, and it returns control to the HAProxy scheduler, |
| 228 | - Lua is not resumable, and we just check the execution timeout. |
| 229 | |
| 230 | The second case occurs if it is required by the HAProxy core. This state is |
| 231 | forced if the Lua is processed in a non resumable HAProxy part, like sample |
| 232 | fetches or converters. |
| 233 | |
| 234 | It occurs also if the Lua is non resumable. For example, if some code is |
| 235 | executed through the Lua pcall() function, the execution is not resumable. This |
| 236 | is explained later. |
| 237 | |
| 238 | So, the Lua code must be fast and simple when is executed as sample fetches and |
| 239 | converters, it could be slow and complex when is executed as actions and |
| 240 | services. |
| 241 | |
| 242 | Execution time |
| 243 | -------------- |
| 244 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 245 | The Lua execution time is measured and limited. Each group of functions has its |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 246 | own timeout configured. The time measured is the real Lua execution time, and |
| 247 | not the difference between the end time and the start time. The groups are: |
| 248 | |
| 249 | - main code and init are not submitted to the timeout, |
| 250 | - fetches, converters and action have a default timeout of 4s, |
| 251 | - task, by default does not have timeout, |
| 252 | - service have a default timeout of 4s. |
| 253 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 254 | The corresponding tune options are: |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 255 | |
Aurelien DARRAGON | 58e36e5 | 2023-04-06 22:51:56 +0200 | [diff] [blame] | 256 | - tune.lua.session-timeout (action, filter, cli) |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 257 | - tune.lua.task-timeout (task) |
| 258 | - tune.lua.service-timeout (services) |
Aurelien DARRAGON | 58e36e5 | 2023-04-06 22:51:56 +0200 | [diff] [blame] | 259 | - tune.lua.burst-timeout (max time between two lua yields) |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 260 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 261 | The task does not have a timeout because it runs in background along the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 262 | HAProxy process life. |
| 263 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 264 | For example, if an Lua script is executed during 1.1s and the script executes a |
| 265 | sleep of 1 second, the effective measured running time is 0.1s. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 266 | |
| 267 | This timeout is useful for preventing infinite loops. During the runtime, it |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 268 | should be never triggered. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 269 | |
| 270 | The stack and the coprocess |
| 271 | --------------------------- |
| 272 | |
| 273 | The Lua execution is organized around a stack. Each Lua action, even out of the |
| 274 | effective execution, affects the stack. HAProxy integration uses one main stack, |
| 275 | which is common for all the process, and a secondary one used as coprocess. |
| 276 | After the initialization, the main stack is no longer used by HAProxy, except |
| 277 | for global storage. The second type of stack is used by all the Lua functions |
| 278 | called from different Lua actions declared in HAProxy. The main stack permits |
| 279 | to store coroutines pointers, and some global variables. |
| 280 | |
| 281 | Do you want to see an example of how seems Lua C development around a stack ? |
| 282 | Some examples follows. This first one, is a simple addition: |
| 283 | |
| 284 | lua_pushnumber(L, 1) |
| 285 | lua_pushnumber(L, 2) |
| 286 | lua_arith(L, LUA_OPADD) |
| 287 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 288 | It's easy, we push 1 on the stack, after, we push 2, and finally, we perform an |
Ilya Shipitsin | 2075ca8 | 2020-03-06 23:22:22 +0500 | [diff] [blame] | 289 | addition. The two top entries of the stack are added, popped, and the result is |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 290 | pushed. It is a classic way with a stack. |
| 291 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 292 | Now an example for constructing array and objects. It's a little bit more |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 293 | complicated. The difficult consist to keep in mind the state of the stack while |
| 294 | we write the code. The goal is to create the entity described below. Note that |
| 295 | the notation "*1" is a metatable reference. The metatable will be explained |
| 296 | later. |
| 297 | |
| 298 | name*1 = { |
| 299 | [0] = <userdata>, |
| 300 | } |
| 301 | |
| 302 | *1 = { |
| 303 | "__index" = { |
| 304 | "method1" = <function>, |
| 305 | "method2" = <function> |
| 306 | } |
| 307 | "__gc" = <function> |
| 308 | } |
| 309 | |
| 310 | Let's go: |
| 311 | |
| 312 | lua_newtable() // The "name" table |
| 313 | lua_newtable() // The metatable *1 |
| 314 | lua_pushstring("__index") |
| 315 | lua_newtable() // The "__index" table |
| 316 | lua_pushstring("method1") |
| 317 | lua_pushfunction(function) |
| 318 | lua_settable(-3) // -3 is an index in the stack. insert method1 |
| 319 | lua_pushstring("method2") |
| 320 | lua_pushfunction(function) |
| 321 | lua_settable(-3) // insert method2 |
| 322 | lua_settable(-3) // insert "__index" |
| 323 | lua_pushstring("__gc") |
| 324 | lua_pushfunction(function) |
| 325 | lua_settable() // insert "__gc" |
| 326 | lua_setmetatable(-1) // attach metatable to "name" |
| 327 | lua_pushnumber(0) |
| 328 | lua_pushuserdata(userdata) |
| 329 | lua_settable(-3) |
| 330 | lua_setglobal("name") |
| 331 | |
| 332 | So, coding for Lua in C, is not complex, but it needs some mental gymnastic. |
| 333 | |
| 334 | The object concept and the HAProxy format |
| 335 | ----------------------------------------- |
| 336 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 337 | The object seems to be not a native concept. An Lua object is a table. We can |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 338 | note that the table notation accept three forms: |
| 339 | |
| 340 | 1. mytable["entry"](mytable, "param") |
| 341 | 2. mytable.entry(mytable, "param") |
| 342 | 3. mytable:entry("param") |
| 343 | |
| 344 | These three notation have the same behaviour pattern: a function is executed |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 345 | with the table itself as first parameter and string "param" as second parameter |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 346 | The notation with [] is commonly used for storing data in a hash table, and the |
| 347 | dotted notation is used for objects. The notation with ":" indicates that the |
| 348 | first parameter is the element at the left of the symbol ":". |
| 349 | |
| 350 | So, an object is a table and each entry of the table is a variable. A variable |
| 351 | can be a function. These are the first concepts of the object notation in the |
| 352 | Lua, but it is not the end. |
| 353 | |
| 354 | With the objects, we usually expect classes and inheritance. This is the role of |
| 355 | the metable. A metable is a table with predefined entries. These entries modify |
| 356 | the default behaviour of the table. The simplest example is the "__index" entry. |
| 357 | If this entry exists, it is called when a value is requested in the table. The |
| 358 | behaviour is the following: |
| 359 | |
| 360 | 1 - looks in the table if the entry exists, and if it the case, return it |
| 361 | |
| 362 | 2 - looks if a metatable exists, and if the "__index" entry exists |
| 363 | |
| 364 | 3 - if "__index" is a function, execute it with the key as parameter, and |
| 365 | returns the result of the function. |
| 366 | |
| 367 | 4 - if "__index" is a table, looks if the requested entry exists, and if |
| 368 | exists, return it. |
| 369 | |
| 370 | 5 - if not exists, return to step 2 |
| 371 | |
| 372 | The behaviour of the point 5 represents the inheritance. |
| 373 | |
| 374 | In HAProxy all the provided objects are tables, the entry "[0]" contains private |
Ilya Shipitsin | 11057a3 | 2020-06-21 21:18:27 +0500 | [diff] [blame] | 375 | data, there are often userdata or lightuserdata. The metatable is registered in |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 376 | the global part of the main Lua stack, and it is called with the case sensitive |
| 377 | class name. A great part of these class must not be used directly because it |
| 378 | requires an initialisation using the HAProxy internal structs. |
| 379 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 380 | The HAProxy objects use unified conventions. An Lua object is always a table. |
| 381 | In most cases, an HAProxy Lua object needs some private data. These are always |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 382 | set in the index [0] of the array. The metatable entry "__tostring" returns the |
| 383 | object name. |
| 384 | |
Jackie Tapia | 749f74c | 2020-07-22 18:59:40 -0500 | [diff] [blame] | 385 | The Lua developer can add entries to the HAProxy object. They just work carefully |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 386 | and prevent to modify the index [0]. |
| 387 | |
Willy Tarreau | 714f345 | 2021-05-09 06:47:26 +0200 | [diff] [blame] | 388 | Common HAProxy objects are: |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 389 | |
| 390 | - TXN : manipulates the transaction between the client and the server |
| 391 | - Channel : manipulates proxified data between the client and the server |
| 392 | - HTTP : manipulates HTTP between the client and the server |
| 393 | - Map : manipulates HAProxy maps. |
| 394 | - Fetches : access to all HAProxy sample fetches |
| 395 | - Converters : access to all HAProxy sample converters |
| 396 | - AppletTCP : process client request like a TCP server |
| 397 | - AppletHTTP : process client request like an HTTP server |
| 398 | - Socket : establish tcp connection to a server (ipv4/ipv6/socket/ssl/...) |
| 399 | |
| 400 | The garbage collector and the memory allocation |
| 401 | ----------------------------------------------- |
| 402 | |
| 403 | Lua doesn't really have a global memory limit, but HAProxy implements it. This |
| 404 | permits to control the amount of memory dedicated to the Lua processes. It is |
| 405 | specially useful with embedded environments. |
| 406 | |
| 407 | When the memory limit is reached, HAProxy refuses to give more memory to the Lua |
| 408 | scripts. The current Lua execution is terminated with an error and HAProxy |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 409 | continues its processing. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 410 | |
| 411 | The max amount of memory is configured with the option: |
| 412 | |
| 413 | tune.lua.maxmem |
| 414 | |
| 415 | As many other script languages, Lua uses a garbage collector for reusing its |
Michael Prokop | 4438c60 | 2019-05-24 10:25:45 +0200 | [diff] [blame] | 416 | memory. The Lua developer can work without memory preoccupation. Usually, the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 417 | garbage collector is controlled by the Lua core, but sometimes it will be useful |
| 418 | to run when the user/developer requires. So the garbage collector can be called |
| 419 | from C part or Lua part. |
| 420 | |
| 421 | Sometimes, objects using lightuserdata or userdata requires to free some memory |
| 422 | block or close filedescriptor not controlled by the Lua. A dedicated garbage |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 423 | collection function is provided through the metatable. It is referenced with the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 424 | special entry "__gc". |
| 425 | |
| 426 | Generally, in HAProxy, the garbage collector does this job without any |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 427 | intervention. However some objects use a great amount of memory, and we want to |
| 428 | release as quickly as possible. The problem is that only the GC knows if the |
| 429 | object is in use or not. The reason is simple variable containing objects can be |
| 430 | shared between coroutines and the main thread, so an object can be used |
| 431 | everywhere in HAProxy. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 432 | |
| 433 | The only one example is the HAProxy sockets. These are explained later, just for |
| 434 | understanding the GC issues, a quick overview of the socket follows. The HAProxy |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 435 | socket uses an internal session and stream, the session uses resources like |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 436 | memory and file descriptor and in some cases keeps a socket open while it is no |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 437 | longer used by Lua. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 438 | |
| 439 | If the HAProxy socket is used, we forcing a garbage collector cycle after the |
| 440 | end of each function using HAProxy socket. The reason is simple: if the socket |
| 441 | is no longer used, we want to close the connection quickly. |
| 442 | |
| 443 | A special flag is used in HAProxy indicating that a HAProxy socket is created. |
| 444 | If this flag is set, a full GC cycle is started after each Lua action. This is |
| 445 | not free, we loose about 10% of performances, but it is the only way for closing |
| 446 | sockets quickly. |
| 447 | |
| 448 | The yield concept / longjmp issues |
| 449 | ---------------------------------- |
| 450 | |
| 451 | The "yield" is an action which does some Lua processing in pause and give back |
| 452 | the hand to the HAProxy core. This action is do when the Lua needs to wait about |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 453 | data or other things. The most basically example is the sleep() function. In an |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 454 | event driven software the code must not process blocking systems call, so the |
| 455 | sleep blocks the software between a lot of time. In HAProxy, an Lua sleep does a |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 456 | yield, and ask to the scheduler to be woken up in a required sleep time. |
| 457 | Meanwhile, the HAProxy scheduler does other things, like accepting new |
| 458 | connection or forwarding data. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 459 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 460 | A yield is also executed regularly, after a lot of Lua instructions processed. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 461 | This yield permits to control the effective execution time, and also give back |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 462 | the hand to the HAProxy core. When HAProxy finishes to process the pending jobs, |
| 463 | the Lua execution continues. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 464 | |
| 465 | This special "yield" uses the Lua "debug" functions. Lua provides a debug method |
| 466 | called "lua_sethook()" which permits to interrupt the execution after some |
| 467 | configured condition and call a function. This condition used in HAProxy is |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 468 | a number of instructions processed and when a function returns. The function |
| 469 | called controls the effective execution time, and if it is possible to send a |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 470 | "yield". |
| 471 | |
| 472 | The yield system is based on a couple setjmp/longjmp. In brief, the setjmp() |
| 473 | stores a stack state, and the longjmp restores the stack in its state which had |
| 474 | before the last Lua execution. |
| 475 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 476 | Lua can immediately stop its execution if an error occurs. This system uses also |
Joseph Herlant | 71b4b15 | 2018-11-13 16:55:16 -0800 | [diff] [blame] | 477 | the longjmp system. In HAProxy, we try to use this system only for unrecoverable |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 478 | errors. Maybe some trivial errors target an exception, but we try to remove it. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 479 | |
| 480 | It seems that Lua uses the longjmp system for having a behaviour like the java |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 481 | try / catch. We can use the function pcall() to execute some code. The function |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 482 | pcall() run a setjmp(). So, if any error occurs while the Lua code execution, |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 483 | the flow immediately returns from the pcall() with an error. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 484 | |
| 485 | The big issue of this behaviour is that we cannot do a yield. So if some Lua code |
| 486 | executes a library using pcall for catching errors, HAProxy must be wait for the |
| 487 | end of execution without processing any accept or any stream. The cause is the |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 488 | yield must be jump to the root of execution. The intermediate setjmp() avoids |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 489 | this behaviour. |
| 490 | |
| 491 | |
Willy Tarreau | 714f345 | 2021-05-09 06:47:26 +0200 | [diff] [blame] | 492 | HAProxy start Lua execution |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 493 | + Lua puts a setjmp() |
| 494 | + Lua executes code |
| 495 | + Some code is executed in a pcall() |
| 496 | + pcall() puts a setjmp() |
| 497 | + Lua executes code |
| 498 | + A yield is require for a sleep function |
| 499 | it cannot be jumps to the Lua root execution. |
| 500 | |
| 501 | |
| 502 | Another issue with the processing of strong errors is the manipulation of the |
| 503 | Lua stack outside of an Lua processing. If one of the functions called occurs a |
| 504 | strong error, the default behaviour is an abort(). It is not acceptable when |
| 505 | HAProxy is in runtime mode. The Lua documentation propose to use another |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 506 | setjmp/longjmp to avoid the abort(). The goal is to put a setjmp between |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 507 | manipulating the Lua stack and using an alternative "panic" function which jumps |
| 508 | to the setjmp() in error case. |
| 509 | |
| 510 | All of these behaviours are very dangerous for the stability, and the internal |
| 511 | HAProxy code must be modified with many precautions. |
| 512 | |
| 513 | For preserving a good behaviour of HAProxy, the yield is mandatory. |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 514 | Unfortunately, some HAProxy parts are not adapted for resuming an execution |
| 515 | after a yield. These parts are the sample fetches and the sample converters. So, |
| 516 | the Lua code written in these parts of HAProxy must be quickly executed, and can |
| 517 | not do actions which require yield like TCP connection or simple sleep. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 518 | |
Willy Tarreau | 714f345 | 2021-05-09 06:47:26 +0200 | [diff] [blame] | 519 | HAProxy socket object |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 520 | --------------------- |
| 521 | |
| 522 | The HAProxy design is optimized for the data transfers between a client and a |
| 523 | server, and processing the many errors which can occurs during these exchanges. |
| 524 | HAProxy is not designed for having a third connection established to a third |
| 525 | party server. |
| 526 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 527 | The solution consist to put the main stream in pause waiting for the end of the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 528 | exchanges with the third connection. This is completed by a signal between |
| 529 | internal tasks. The following graph shows the HAProxy Lua socket: |
| 530 | |
| 531 | |
| 532 | +--------------------+ |
| 533 | | Lua processing | |
| 534 | ------------------\ | creates socket | ------------------\ |
| 535 | incoming request > | and puts the | Outgoing request > |
| 536 | ------------------/ | current processing | ------------------/ |
| 537 | Â Â | in pause waiting | |
| 538 | | for TCP applet | |
| 539 | +-----------------+--+ |
| 540 | ^ | |
| 541 | | | |
| 542 | | signal | read / write |
| 543 | | | data |
| 544 | | | |
| 545 | +-------------+---------+ v |
| 546 | | HAProxy internal +----------------+ |
| 547 | | applet send signals | | |
| 548 | | when data is received | | -------------------\ |
| 549 | | or some room is | Attached I/O | Client TCP stream > |
| 550 | | available | Buffers | -------------------/ |
| 551 | +--------------------+--+ | |
| 552 | | | |
| 553 | +-------------------+ |
| 554 | |
| 555 | |
| 556 | A more detailed graph is available in the "doc/internals" directory. |
| 557 | |
| 558 | The HAProxy Lua socket uses a full HAProxy session / stream for establishing the |
| 559 | connection. This mechanism provides all the facilities and HAProxy features, |
| 560 | like the SSL stack, many socket type, and support for namespaces. |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 561 | Technically it supports the proxy protocol, but there are no way to enable it. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 562 | |
| 563 | How compiling HAProxy with Lua |
| 564 | ============================== |
| 565 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 566 | HAProxy 1.6 requires Lua 5.3. Lua 5.3 offers some features which make easy the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 567 | integration. Lua 5.3 is young, and some distros do not distribute it. Luckily, |
| 568 | Lua is a great product because it does not require exotic dependencies, and its |
| 569 | build process is really easy. |
| 570 | |
| 571 | The compilation process for linux is easy: |
| 572 | |
| 573 | - download the source tarball |
| 574 | wget http://www.lua.org/ftp/lua-5.3.1.tar.gz |
| 575 | |
| 576 | - untar it |
| 577 | tar xf lua-5.3.1.tar.gz |
| 578 | |
| 579 | - enter the directory |
| 580 | cd lua-5.3.1 |
| 581 | |
| 582 | - build the library for linux |
| 583 | make linux |
| 584 | |
| 585 | - install it: |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 586 | sudo make INSTALL_TOP=/opt/lua-5.3.1 install |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 587 | |
| 588 | HAProxy builds with your favourite options, plus the following options for |
| 589 | embedding the Lua script language: |
| 590 | |
| 591 | - download the source tarball |
| 592 | wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.2.tar.gz |
| 593 | |
| 594 | - untar it |
| 595 | tar xf haproxy-1.6.2.tar.gz |
| 596 | |
| 597 | - enter the directory |
| 598 | cd haproxy-1.6.2 |
| 599 | |
| 600 | - build HAProxy: |
Willy Tarreau | d254aa8 | 2019-06-14 18:40:48 +0200 | [diff] [blame] | 601 | make TARGET=linux-glibc \ |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 602 | USE_LUA=1 \ |
| 603 | LUA_LIB=/opt/lua-5.3.1/lib \ |
| 604 | LUA_INC=/opt/lua-5.3.1/include |
| 605 | |
| 606 | - install it: |
| 607 | sudo make PREFIX=/opt/haproxy-1.6.2 install |
| 608 | |
| 609 | First steps with Lua |
| 610 | ==================== |
| 611 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 612 | Now, it's time to use Lua in HAProxy. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 613 | |
| 614 | Start point |
| 615 | ----------- |
| 616 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 617 | The HAProxy global directive "lua-load <file>" allows to load an Lua file. This |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 618 | is the entry point. This load become during the configuration parsing, and the |
| 619 | Lua file is immediately executed. |
| 620 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 621 | All the register_*() functions must be called at this time because they are used |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 622 | just after the processing of the global section, in the frontend/backend/listen |
| 623 | sections. |
| 624 | |
| 625 | The most simple "Hello world !" is the following line a loaded Lua file: |
| 626 | |
| 627 | core.Alert("Hello World !"); |
| 628 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 629 | It displays a log during the HAProxy startup: |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 630 | |
| 631 | [alert] 285/083533 (14465) : Hello World ! |
| 632 | |
Tristan | 2632d04 | 2023-10-23 13:07:39 +0100 | [diff] [blame] | 633 | Note: By default, logs originating from a LUA script are sent to the loggers |
| 634 | applicable to the current context, if any, and additionally to stderr. See |
| 635 | tune.lua.log.loggers and tune.lua.log.stderr for more information. |
| 636 | |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 637 | Default path and libraries |
| 638 | -------------------------- |
| 639 | |
| 640 | Lua can embed some libraries. These libraries can be included from different |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 641 | paths. It seems that Lua doesn't like subdirectories. In the following example, |
| 642 | I try to load a compiled library, so the first line is Lua code, the second line |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 643 | is an 'strace' extract proving that the library was opened. The next lines are |
| 644 | the associated error. |
| 645 | |
| 646 | require("luac/concat") |
| 647 | |
| 648 | open("./luac/concat.so", O_RDONLY|O_CLOEXEC) = 4 |
| 649 | |
Willy Tarreau | 9f903af | 2021-05-07 08:42:39 +0200 | [diff] [blame] | 650 | [ALERT] (22806) : parsing [commonstats.conf:15] : lua runtime |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 651 | error: error loading module 'luac/concat' from file './luac/concat.so': |
| 652 | ./luac/concat.so: undefined symbol: luaopen_luac/concat |
| 653 | |
| 654 | Lua tries to load the C symbol 'luaopen_luac/concat'. When Lua tries to open a |
| 655 | library, it tries to execute the function associated to the symbol |
| 656 | "luaopen_<libname>". |
| 657 | |
| 658 | The variable "<libname>" is defined using the content of the variable |
| 659 | "package.cpath" and/or "package.path". The default definition of the |
| 660 | "package.cpath" (on my computer is ) variable is: |
| 661 | |
| 662 | /usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so |
| 663 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 664 | The "<libname>" is the content which replaces the symbol "<?>". In the previous |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 665 | example, its "luac/concat", and obviously the Lua core try to load the function |
| 666 | associated with the symbol "luaopen_luac/concat". |
| 667 | |
| 668 | My conclusion is that Lua doesn't support subdirectories. So, for loading |
| 669 | libraries in subdirectory, it must fill the variable with the name of this |
| 670 | subdirectory. The extension .so must disappear, otherwise Lua try to execute the |
| 671 | function associated with the symbol "luaopen_concat.so". The following syntax is |
| 672 | correct: |
| 673 | |
| 674 | package.cpath = package.cpath .. ";./luac/?.so" |
| 675 | require("concat") |
| 676 | |
| 677 | First useful example |
| 678 | -------------------- |
| 679 | |
| 680 | core.register_fetches("my-hash", function(txn, salt) |
| 681 | return txn.sc:sdbm(salt .. txn.sf:req_fhdr("host") .. txn.sf:path() .. txn.sf:src(), 1) |
| 682 | end) |
| 683 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 684 | You will see that these 3 lines can generate a lot of explanations :) |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 685 | |
| 686 | Core.register_fetches() is executed during the processing of the global section |
| 687 | by the HAProxy configuration parser. A new sample fetch is declared with name |
| 688 | "my-hash", this name is always prefixed by "lua.". So this new declared |
| 689 | sample fetch will be used calling "lua.my-hash" in the HAProxy configuration |
| 690 | file. |
| 691 | |
| 692 | The second parameter is an inline declared anonymous function. Note the closed |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 693 | parenthesis after the keyword "end" which ends the function. The first parameter |
| 694 | of this anonymous function is "txn". It is an object of class TXN. It provides |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 695 | access functions. The second parameter is an arbitrary value provided by the |
| 696 | HAProxy configuration file. This parameter is optional, the developer must |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 697 | check if it is present. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 698 | |
| 699 | The anonymous function registration is executed when the HAProxy backend or |
| 700 | frontend configuration references the sample fetch "lua.my-hash". |
| 701 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 702 | This example can be written with another style, like below: |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 703 | |
| 704 | function my_hash(txn, salt) |
| 705 | return txn.sc:sdbm(salt .. txn.sf:req_fhdr("host") .. txn.sf:path() .. txn.sf:src(), 1) |
| 706 | end |
| 707 | |
| 708 | core.register_fetches("my-hash", my_hash) |
| 709 | |
| 710 | This second form is clearer, but the first one is compact. |
| 711 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 712 | The operator ".." is a string concatenation. If one of the two operands is not a |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 713 | string, an error occurs and the execution is immediately stopped. This is |
| 714 | important to keep in mind for the following things. |
| 715 | |
| 716 | Now I write the example on more than one line. Its an easiest way for commenting |
| 717 | the code: |
| 718 | |
| 719 | 1. function my_hash(txn, salt) |
| 720 | 2. local str = "" |
| 721 | 3. str = str .. salt |
| 722 | 4. str = str .. txn.sf:req_fhdr("host") |
| 723 | 5. str = str .. txn.sf:path() |
| 724 | 6. str = str .. txn.sf:src() |
| 725 | 7. local result = txn.sc:sdbm(str, 1) |
| 726 | 8. return result |
| 727 | 9. end |
| 728 | 10. |
| 729 | 11. core.register_fetches("my-hash", my_hash) |
| 730 | |
| 731 | local |
| 732 | ~~~~~ |
| 733 | |
| 734 | The first keyword is "local". This is a really important keyword. You must |
| 735 | understand that the function "my_hash" will be called for each HAProxy request |
| 736 | using the declared sample fetch. So, this function can be executed many times in |
| 737 | parallel. |
| 738 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 739 | By default, Lua uses global variables. So in this example, if the variable "str" |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 740 | is declared without the keyword "local", it will be shared by all the parallel |
| 741 | executions of the function and obviously, the content of the requests will be |
| 742 | shared. |
| 743 | |
| 744 | This warning is very important. I tried to write useful Lua code like a rewrite |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 745 | of the statistics page, and it is very hard thing to declare each variable as |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 746 | "local". |
| 747 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 748 | I guess that this behaviour will be the cause of many troubles on the mailing |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 749 | list. |
| 750 | |
| 751 | str = str .. |
| 752 | ~~~~~~~~~~~~ |
| 753 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 754 | Now a parenthesis about the form "str = str ..". This form allows to do string |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 755 | concatenations. Remember that Lua uses a garbage collector, so what happens when |
| 756 | we do "str = str .. 'another string'" ? |
| 757 | |
| 758 | str = str .. "another string" |
| 759 | ^ ^ ^ ^ |
| 760 | 1 2 3 4 |
| 761 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 762 | Lua executes first the concatenation operator (3), it allocates memory for the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 763 | resulting string and fill this memory with the concatenation of the operands 2 |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 764 | and 4. Next, it frees the variable 1, now the old content of 1 can be garbage |
| 765 | collected. And finally, the new content of 1 is the concatenation. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 766 | |
| 767 | what the matter ? when we do this operation many times, we consume a lot of |
| 768 | memory, and the string data is duplicated and move many times. So, this practice |
| 769 | is expensive in execution time and memory consumption. |
| 770 | |
| 771 | There are easy ways to prevent this behaviour. I guess that a C binding for |
| 772 | concatenation with chunks will be available ASAP (it is already written). I do |
| 773 | some benchmarks. I compare the execution time of 1 000 times, 1 000 |
| 774 | concatenation of 10 bytes written in pure Lua and with a C library. The result is |
| 775 | 10 times faster in C (1s in Lua, and 0.1s in C). |
| 776 | |
| 777 | txn |
| 778 | ~~~ |
| 779 | |
| 780 | txn is an HAProxy object of class TXN. The documentation is available in the |
| 781 | HAProxy Lua API reference. This class allow the access to the native HAProxy |
| 782 | sample fetches and converters. The object txn contains 2 members dedicated to |
| 783 | the sample fetches and 2 members dedicated to the converters. |
| 784 | |
| 785 | The sample fetches members are "f" (as sample-Fetch) and "sf" (as String |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 786 | sample-Fetch). These two members contain exactly the same functions. All the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 787 | HAProxy native sample fetches are available, obviously, the Lua registered sample |
| 788 | fetches are not available. Unfortunately, HAProxy sample fetches names are not |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 789 | compatible with the Lua function names, and they are renamed. The rename |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 790 | convention is simple, we replace all the '.', '+' and '-' by '_'. The '.' is the |
| 791 | object member separator, and the "-" and "+" is math operator. |
| 792 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 793 | Now, that I'm writing this article, I know the Lua better than I wrote the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 794 | sample-fetches wrapper. The original HAProxy sample-fetches name should be used |
| 795 | using alternative manner to call an object member, so the sample-fetch |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 796 | "req.fhdr" (actually renamed req_fhdr") should be used like this: |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 797 | |
| 798 | txn.f["req.fhdr"](txn.f, ...) |
| 799 | |
| 800 | However, I think that this form is not elegant. |
| 801 | |
| 802 | The "s" collection return a data with a type near to the original returned type. |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 803 | A string returns an Lua string, an integer returns an Lua integer and an IP |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 804 | address returns an Lua string. Sometime the data is not or not yet available, in |
| 805 | this case it returns the Lua nil value. |
| 806 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 807 | The "sf" collection guarantees that a string will be always returned. If the data |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 808 | is not available, an empty string is returned. The main usage of these collection |
| 809 | is to concatenate the returned sample-fetches without testing each function. |
| 810 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 811 | The parameters of the sample-fetches are according with the HAProxy |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 812 | documentation. |
| 813 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 814 | The converters run exactly with the same manner as the sample fetches. The |
| 815 | only one difference is that the first parameter is the converter entry element. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 816 | The "c" collection returns a precise result, and the "sc" collection returns |
| 817 | always a string. |
| 818 | |
| 819 | The sample-fetches used in the example function are "txn.sf:req_fhdr()", |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 820 | "txn.sf:path()" and "txn.sf:src()". The converter is "txn.sc:sdbm()". The same |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 821 | function with the "s" collection of sample-fetches and the "c" collection of |
| 822 | converter should be written like this: |
| 823 | |
| 824 | 1. function my_hash(txn, salt) |
| 825 | 2. local str = "" |
| 826 | 3. str = str .. salt |
| 827 | 4. str = str .. tostring(txn.f:req_fhdr("host")) |
| 828 | 5. str = str .. tostring(txn.f:path()) |
| 829 | 6. str = str .. tostring(txn.f:src()) |
| 830 | 7. local result = tostring(txn.c:sdbm(str, 1)) |
| 831 | 8. return result |
| 832 | 9. end |
| 833 | 10. |
| 834 | 11. core.register_fetches("my-hash", my_hash) |
| 835 | |
| 836 | tostring |
| 837 | ~~~~~~~~ |
| 838 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 839 | The function tostring ensures that its parameter is returned as a string. If the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 840 | parameter is a table or a thread or anything that will not have any sense as a |
| 841 | string, a form like the typename followed by a pointer is returned. For example: |
| 842 | |
| 843 | t = {} |
| 844 | print(tostring(t)) |
| 845 | |
| 846 | returns: |
| 847 | |
| 848 | table: 0x15facc0 |
| 849 | |
| 850 | For objects, if the special function __tostring() is registered in the attached |
| 851 | metatable, it will be called with the table itself as first argument. The |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 852 | HAProxy object returns its own type. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 853 | |
| 854 | About the converters entry point |
| 855 | -------------------------------- |
| 856 | |
| 857 | In HAProxy, a converter is a stateless function that takes a data as entry and |
| 858 | returns a transformation of this data as output. In Lua it is exactly the same |
| 859 | behaviour. |
| 860 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 861 | So, the registered Lua function doesn't have any special parameters, just a |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 862 | variable as input which contains the value to convert, and it must return data. |
| 863 | |
| 864 | The data required as input by the Lua converter is a string. So HAProxy will |
| 865 | always provide a string as input. If the native sample fetch is not a string it |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 866 | will be converted in best effort. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 867 | |
| 868 | The returned value will have anything type, it will be converted as sample of |
| 869 | the near HAProxy type. The conversion rules from Lua variables to HAProxy |
| 870 | samples are: |
| 871 | |
| 872 | Lua | HAProxy sample types |
| 873 | -----------+--------------------- |
| 874 | "number" | "sint" |
| 875 | "boolean" | "bool" |
| 876 | "string" | "str" |
| 877 | "userdata" | "bool" (false) |
| 878 | "nil" | "bool" (false) |
| 879 | "table" | "bool" (false) |
| 880 | "function" | "bool" (false) |
| 881 | "thread" | "bool" (false) |
| 882 | |
| 883 | The function used for registering a converter is: |
| 884 | |
| 885 | core.register_converters() |
| 886 | |
| 887 | The task entry point |
| 888 | -------------------- |
| 889 | |
| 890 | The function "core.register_task(fcn)" executes once the function "fcn" when the |
| 891 | scheduler starts. This way is used for executing background task. For example, |
Michael Prokop | 4438c60 | 2019-05-24 10:25:45 +0200 | [diff] [blame] | 892 | you can use this functionality for periodically checking the health of another |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 893 | service, and giving the result to each proxy needing it. |
| 894 | |
| 895 | The task is started once, if you want periodic actions, you can use the |
| 896 | "core.sleep()" or "core.msleep()" for waiting the next runtime. |
| 897 | |
| 898 | Storing Lua variable between function in the same session |
| 899 | --------------------------------------------------------- |
| 900 | |
| 901 | All the functions registered as action or sample fetch can share an Lua context. |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 902 | This context is a memory zone in the stack. sample fetch and action use the |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 903 | same stack, so both can access to the context. |
| 904 | |
| 905 | The context is accessible via the function get_priv and set_priv provided by an |
| 906 | object of class TXN. The value given to set_priv replaces the current stored |
| 907 | value. This value can be a table, it is useful if a lot of data can be shared. |
| 908 | |
| 909 | If the value stored is a table, you can add or remove entries from the table |
| 910 | without storing again the new table. Maybe an example will be clearer: |
| 911 | |
| 912 | local t = {} |
| 913 | txn:set_priv(t) |
| 914 | |
| 915 | t["entry1"] = "foo" |
| 916 | t["entry2"] = "bar" |
| 917 | |
| 918 | -- this will display "foo" |
| 919 | print(txn:get_priv()["entry1"]) |
| 920 | |
| 921 | HTTP actions |
| 922 | ============ |
| 923 | |
Joseph Herlant | 71b4b15 | 2018-11-13 16:55:16 -0800 | [diff] [blame] | 924 | ... coming soon ... |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 925 | |
| 926 | Lua is fast, but my service require more execution speed |
| 927 | ======================================================== |
| 928 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 929 | We can write C modules for Lua. These modules must run with HAProxy while they |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 930 | are compliant with the HAProxy Lua version. A simple example is the "concat" |
| 931 | module. |
| 932 | |
| 933 | It is very easy to write and compile a C Lua library, however, I don't see |
| 934 | documentation about this process. So the current chapter is a quick howto. |
| 935 | |
| 936 | The entry point |
| 937 | --------------- |
| 938 | |
| 939 | The entry point is called "luaopen_<name>", where <name> is the name of the ".so" |
| 940 | file. An hello world is like this: |
| 941 | |
| 942 | #include <stdio.h> |
| 943 | #include <lua.h> |
| 944 | #include <lauxlib.h> |
| 945 | |
| 946 | int luaopen_mymod(lua_State *L) |
| 947 | { |
| 948 | printf("Hello world\n"); |
| 949 | return 0; |
| 950 | } |
| 951 | |
| 952 | The build |
| 953 | --------- |
| 954 | |
| 955 | The compilation of the source file requires the Lua "include" directory. The |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 956 | compilation and the link of the object file requires the -fPIC option. That's |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 957 | all. |
| 958 | |
| 959 | cc -I/opt/lua/include -fPIC -shared -o mymod.so mymod.c |
| 960 | |
| 961 | Usage |
| 962 | ----- |
| 963 | |
| 964 | You can load this module with the following Lua syntax: |
| 965 | |
| 966 | require("mymod") |
| 967 | |
Godbach | 06c8099 | 2016-02-16 11:59:17 +0800 | [diff] [blame] | 968 | When you start HAProxy, this module just print "Hello world" when it is loaded. |
Thierry FOURNIER | 79c1051 | 2015-11-08 22:02:21 +0100 | [diff] [blame] | 969 | Please, remember that HAProxy doesn't allow blocking method, so if you write a |
| 970 | function doing filesystem access or synchronous network access, all the HAProxy |
| 971 | process will fail. |