mirror of
				https://git.haproxy.org/git/haproxy.git/
				synced 2025-10-31 16:41:01 +01:00 
			
		
		
		
	Released version 2.8-dev8 with the following main changes :
    - BUG/MEDIUM: cli: Set SE_FL_EOI flag for '_getsocks' and 'quit' commands
    - BUG/MEDIUM: cli: Eat output data when waiting for appctx shutdown
    - BUG/MEDIUM: http-client: Eat output data when waiting for appctx shutdown
    - BUG/MEDIUM: stats: Eat output data when waiting for appctx shutdown
    - BUG/MEDIUM: log: Eat output data when waiting for appctx shutdown
    - BUG/MEDIUM: dns: Kill idle DNS sessions during stopping stage
    - BUG/MINOR: resolvers: Wakeup DNS idle task on stopping
    - BUG/MEDIUM: resolvers: Force the connect timeout for DNS resolutions
    - MINOR: hlua: Stop to check the SC state when executing a hlua cli command
    - BUG/MEDIUM: mux-h1: Report EOI when a TCP connection is upgraded to H2
    - BUG/MEDIUM: mux-h2: Never set SE_FL_EOS without SE_FL_EOI or SE_FL_ERROR
    - MINOR: quic: Trace fix in quic_pto_pktns() (handshaske status)
    - BUG/MINOR: quic: Wrong packet number space probing before confirmed handshake
    - MINOR: quic: Modify qc_try_rm_hp() traces
    - MINOR: quic: Dump more information at proto level when building packets
    - MINOR: quic: Add a trace for packet with an ACK frame
    - MINOR: activity: add a line reporting the average CPU usage to "show activity"
    - BUG/MINOR: stick_table: alert when type len has incorrect characters
    - MINOR: thread: keep a bitmask of enabled groups in thread_set
    - MINOR: fd: optimize fd_claim_tgid() for use in fd_insert()
    - MINOR: fd: add a lock bit with the tgid
    - MINOR: fd: implement fd_migrate_on() to migrate on a non-local thread
    - MINOR: receiver: reserve special values for "shards"
    - MINOR: bind-conf: support a new shards value: "by-group"
    - BUG/MEDIUM: fd: don't wait for tmask to stabilize if we're not in it.
    - MINOR: quic: Add packet loss and maximum cc window to "show quic"
    - BUG/MINOR: quic: Ignored less than 1ms RTTs
    - MINOR: quic: Add connection flags to traces
    - BUG/MEDIUM: quic: Code sanitization about acknowledgements requirements
    - BUG/MINOR: quic: Possible wrapped values used as ACK tree purging limit.
    - BUG/MINOR: quic: SIGFPE in quic_cubic_update()
    - MINOR: quic: Display the packet number space flags in traces
    - MINOR: quic: Remove a useless test about probing in qc_prep_pkts()
    - BUG/MINOR: quic: Wrong Application encryption level selection when probing
    - CI: bump "actions/checkout" to v3 for cross zoo matrix
    - CI: enable monthly test on Fedora Rawhide
    - BUG/MINOR: stream: Fix test on SE_FL_ERROR on the wrong entity
    - BUG/MEDIUM: stream: Report write timeouts before testing the flags
    - BUG/MEDIUM: stconn: Do nothing in sc_conn_recv() when the SC needs more room
    - MINOR: stream: Uninline and export sess_set_term_flags() function
    - MINOR: filters: Review and simplify errors handling
    - REGTESTS: fix the race conditions in log_uri.vtc
    - MINOR: channel: Forwad close to other side on abort
    - MINOR: stream: Introduce stream_abort() to abort on both sides in same time
    - MINOR: stconn: Rename SC_FL_SHUTR_NOW in SC_FL_ABRT_WANTED
    - MINOR: channel/stconn: Replace channel_shutr_now() by sc_schedule_abort()
    - MINOR: stconn: Rename SC_FL_SHUTW_NOW in SC_FL_SHUT_WANTED
    - MINOR: channel/stconn: Replace channel_shutw_now() by sc_schedule_shutdown()
    - MINOR: stconn: Rename SC_FL_SHUTR in SC_FL_ABRT_DONE
    - MINOR: channel/stconn: Replace sc_shutr() by sc_abort()
    - MINOR: stconn: Rename SC_FL_SHUTW in SC_FL_SHUT_DONE
    - MINOR: channel/stconn: Replace sc_shutw() by sc_shutdown()
    - MINOR: tree-wide: Replace several chn_cons() by the corresponding SC
    - MINOR: tree-wide: Replace several chn_prod() by the corresponding SC
    - BUG/MINOR: cli: Don't close when SE_FL_ERR_PENDING is set in cli analyzer
    - MINOR: stconn: Stop to set SE_FL_ERROR on sending path
    - MEDIUM: stconn: Forbid applets with more to deliver if EOI was reached
    - MINOR: stconn: Don't clear SE_FL_ERROR when endpoint is reset
    - MINOR: stconn: Add a flag to ack endpoint errors at SC level
    - MINOR: backend: Set SC_FL_ERROR on connection error
    - MINOR: stream: Set SC_FL_ERROR on channels' buffer allocation error
    - MINOR: tree-wide: Test SC_FL_ERROR with SE_FL_ERROR from upper layer
    - MEDIUM: tree-wide: Stop to set SE_FL_ERROR from upper layer
    - MEDIUM: backend: Stop to use SE flags to detect connection errors
    - MEDIUM: stream: Stop to use SE flags to detect read errors from analyzers
    - MEDIUM: stream: Stop to use SE flags to detect endpoint errors
    - MEDIUM: stconn: Rely on SC flags to handle errors instead of SE flags
    - BUG/MINOR: stconn: Don't set SE_FL_ERROR at the end of sc_conn_send()
    - BUG/MINOR: quic: Do not use ack delay during the handshakes
    - CLEANUP: use "offsetof" where appropriate
    - MINOR: ssl: remove OpenSSL 1.0.2 mention into certificate loading error
    - BUG/MEDIUM: http-ana: Properly switch the request in tunnel mode on upgrade
    - BUG/MEDIUM: log: Properly handle client aborts in syslog applet
    - MINOR: stconn: Add a flag to report EOS at the stream-connector level
    - MINOR: stconn: Propagate EOS from a mux to the attached stream-connector
    - MINOR: stconn: Propagate EOS from an applet to the attached stream-connector
    - MINOR: mux-h2: make the initial window size configurable per side
    - MINOR: mux-h2: make the max number of concurrent streams configurable per side
    - BUG/MINOR: task: allow to use tasklet_wakeup_after with tid -1
    - CLEANUP: quic: remove unused QUIC_LOCK label
    - CLEANUP: quic: remove unused scid_node
    - CLEANUP: quic: remove unused qc param on stateless reset token
    - CLEANUP: quic: rename quic_connection_id vars
    - MINOR: quic: remove uneeded tasklet_wakeup after accept
    - MINOR: quic: adjust Rx packet type parsing
    - MINOR: quic: adjust quic CID derive API
    - MINOR: quic: remove TID ref from quic_conn
    - MEDIUM: quic: use a global CID trees list
    - MINOR: quic: remove TID encoding in CID
    - MEDIUM: quic: handle conn bootstrap/handshake on a random thread
    - MINOR: quic: do not proceed to accept for closing conn
    - MINOR: protocol: define new callback set_affinity
    - MINOR: quic: delay post handshake frames after accept
    - MEDIUM: quic: implement thread affinity rebinding
    - BUG/MINOR: quic: transform qc_set_timer() as a reentrant function
    - MINOR: quic: properly finalize thread rebinding
    - MAJOR: quic: support thread balancing on accept
    - MINOR: listener: remove unneeded local accept flag
    - BUG/MINOR: http-ana: Update analyzers on both sides when switching in TUNNEL mode
    - CLEANUP: backend: Remove useless debug message in assign_server()
    - CLEANUP: cli: Remove useless debug message in cli_io_handler()
    - BUG/MEDIUM: stconn: Propagate error on the SC on sending path
    - MINOR: config: add "no-alpn" support for bind lines
    - REGTESTS: add a new "ssl_alpn" test to test ALPN negotiation
    - DOC: add missing documentation for "no-alpn" on bind lines
    - MINOR: ssl: do not set ALPN callback with the empty string
    - MINOR: ssl_crtlist: dump "no-alpn" on "show crtlist" when "no-alpn" was set
    - MEDIUM: config: set useful ALPN defaults for HTTPS and QUIC
    - BUG/MEDIUM: quic: prevent crash on Retry sending
    - BUG/MINOR: cfgparse: make sure to include openssl-compat
    - MINOR: clock: add now_mono_time_fast() function
    - MINOR: clock: add now_cpu_time_fast() function
    - MEDIUM: hlua: reliable timeout detection
    - MEDIUM: hlua: introduce tune.lua.burst-timeout
    - CLEANUP: hlua: avoid confusion between internal timers and tick based timers
    - MINOR: hlua: hook yield on known lua state
    - MINOR: hlua: safe coroutine.create()
    - BUG/MINOR: quic: Stop removing ACK ranges when building packets
    - MINOR: quic: Do not allocate too much ack ranges
    - BUG/MINOR: quic: Unchecked buffer length when building the token
    - BUG/MINOR: quic: Wrong Retry token generation timestamp computing
    - BUG/MINOR: mux-quic: fix crash with app ops install failure
    - BUG/MINOR: mux-quic: properly handle STREAM frame alloc failure
    - BUG/MINOR: h3: fix crash on h3s alloc failure
    - BUG/MINOR: quic: prevent crash on qc_new_conn() failure
    - BUG/MINOR: quic: consume Rx datagram even on error
    - CLEANUP: errors: fix obsolete function comments
    - CLEANUP: server: fix update_status() function comment
    - MINOR: server/event_hdl: add proxy_uuid to event_hdl_cb_data_server
    - MINOR: hlua/event_hdl: rely on proxy_uuid instead of proxy_name for lookups
    - MINOR: hlua/event_hdl: expose proxy_uuid variable in server events
    - MINOR: hlua/event_hdl: fix return type for hlua_event_hdl_cb_data_push_args
    - MINOR: server/event_hdl: prepare for upcoming refactors
    - BUG/MINOR: event_hdl: don't waste 1 event subtype slot
    - CLEANUP: event_hdl: updating obsolete comment for EVENT_HDL_CB_DATA
    - CLEANUP: event_hdl: fix comment typo about _sync assertion
    - MINOR: event_hdl: dynamically allocated event data members
    - MINOR: event_hdl: provide event->when for advanced handlers
    - MINOR: hlua/event_hdl: timestamp for events
    - DOC: lua: restore 80 char limitation
    - BUG/MINOR: server: incorrect report for tracking servers leaving drain
    - MINOR: server: explicitly commit state change in srv_update_status()
    - BUG/MINOR: server: don't miss proxy stats update on server state transitions
    - BUG/MINOR: server: don't miss server stats update on server state transitions
    - BUG/MINOR: server: don't use date when restoring last_change from state file
    - MINOR: server: central update for server counters on state change
    - MINOR: server: propagate server state change to lb through single function
    - MINOR: server: propagate lb changes through srv_lb_propagate()
    - MINOR: server: change adm_st_chg_cause storage type
    - MINOR: server: srv_append_status refacto
    - MINOR: server: change srv_op_st_chg_cause storage type
    - CLEANUP: server: remove unused variables in srv_update_status()
    - CLEANUP: server: fix srv_set_{running, stopping, stopped} function comment
    - MINOR: server: pass adm and op cause to srv_update_status()
    - MEDIUM: server: split srv_update_status() in two functions
    - MINOR: server/event_hdl: prepare for server event data wrapper
    - MINOR: quic: support migrating the listener as well
    - MINOR: quic_sock: index li->per_thr[] on local thread id, not global one
    - MINOR: listener: support another thread dispatch mode: "fair"
    - MINOR: receiver: add a struct shard_info to store info about each shard
    - MINOR: receiver: add RX_F_MUST_DUP to indicate that an rx must be duped
    - MEDIUM: proto: duplicate receivers marked RX_F_MUST_DUP
    - MINOR: proto: skip socket setup for duped FDs
    - MEDIUM: config: permit to start a bind on multiple groups at once
    - MINOR: listener: make accept_queue index atomic
    - MEDIUM: listener: rework thread assignment to consider all groups
    - MINOR: listener: use a common thr_idx from the reference listener
    - MINOR: listener: resync with the thread index before heavy calculations
    - MINOR: listener: make sure to avoid ABA updates in per-thread index
    - MINOR: listener: always compare the local thread as well
    - MINOR: Make `tasklet_free()` safe to be called with `NULL`
    - CLEANUP: Stop checking the pointer before calling `tasklet_free()`
    - CLEANUP: Stop checking the pointer before calling `pool_free()`
    - CLEANUP: Stop checking the pointer before calling `task_free()`
    - CLEANUP: Stop checking the pointer before calling `ring_free()`
    - BUG/MINOR: cli: clarify error message about stats bind-process
    - CI: cirrus-ci: bump FreeBSD image to 13-1
    - REGTESTS: remove unsupported "stats bind-process" keyword
    - CI: extend spellchecker whitelist, add "clen" as well
    - CLEANUP: assorted typo fixes in the code and comments
    - BUG/MINOR: sock_inet: use SO_REUSEPORT_LB where available
    - BUG/MINOR: tools: check libssl and libcrypto separately
    - BUG/MINOR: config: fix NUMA topology detection on FreeBSD
    - BUILD: sock_inet: forward-declare struct receiver
    - BUILD: proto_tcp: export the correct names for proto_tcpv[46]
    - CLEANUP: protocol: move the l3_addrlen to plug a hole in proto_fam
    - CLEANUP: protocol: move the nb_receivers to plug a hole in protocol
    - REORG: listener: move the bind_conf's thread setup code to listener.c
    - MINOR: proxy: make proxy_type_str() recognize peers sections
    - MEDIUM: peers: call bind_complete_thread_setup() to finish the config
    - MINOR: protocol: add a flags field to store info about protocols
    - MINOR: protocol: move the global reuseport flag to the protocols
    - MINOR: listener: automatically adjust shards based on support for SO_REUSEPORT
    - MINOR: protocol: add a function to check if some features are supported
    - MINOR: sock: add a function to check for SO_REUSEPORT support at runtime
    - MINOR: protocol: perform a live check for SO_REUSEPORT support
    - MINOR: listener: do not restrict CLI to first group anymore
    - MINOR: listener: add a new global tune.listener.default-shards setting
    - MEDIUM: listener: switch the default sharding to by-group
		
	
			
		
			
				
	
	
		
			968 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			968 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|                    Lua: Architecture and first steps
 | ||
|                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | ||
|                              version 2.8
 | ||
| 
 | ||
|                         author: Thierry FOURNIER
 | ||
|                  contact: tfournier at arpalert dot org
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| HAProxy is a powerful load balancer. It embeds many options and many
 | ||
| configuration styles in order to give a solution to many load balancing
 | ||
| problems. However, HAProxy is not universal and some special or specific
 | ||
| problems do not have solution with the native software.
 | ||
| 
 | ||
| This text is not a full explanation of the Lua syntax.
 | ||
| 
 | ||
| This text is not a replacement of the HAProxy Lua API documentation. The API
 | ||
| documentation can be found at the project root, in the documentation directory.
 | ||
| The goal of this text is to discover how Lua is implemented in HAProxy and using
 | ||
| it efficiently.
 | ||
| 
 | ||
| However, this can be read by Lua beginners. Some examples are detailed.
 | ||
| 
 | ||
| Why a scripting language in HAProxy
 | ||
| ===================================
 | ||
| 
 | ||
| HAProxy 1.5 makes at possible to do many things using samples, but some people
 | ||
| want to more combining results of samples fetches, programming conditions and
 | ||
| loops which is not possible. Sometimes people implement these functionalities
 | ||
| in patches which have no meaning outside their network. These people must
 | ||
| maintain these patches, or worse we must integrate them in the HAProxy
 | ||
| mainstream.
 | ||
| 
 | ||
| Their need is to have an embedded programming language in order to no longer
 | ||
| modify the HAProxy source code, but to write their own control code. Lua is
 | ||
| encountered very often in the software industry, and in some open source
 | ||
| projects. It is easy to understand, efficient, light without external
 | ||
| dependencies, and leaves the resource control to the implementation. Its design
 | ||
| is close to the HAProxy philosophy which uses components for what they do
 | ||
| perfectly.
 | ||
| 
 | ||
| The HAProxy control block allows one to take a decision based on the comparison
 | ||
| between samples and patterns. The samples are extracted using fetch functions
 | ||
| easily extensible, and are used by actions which are also extensible. It seems
 | ||
| natural to allow Lua to give samples, modify them, and to be an action target.
 | ||
| So, Lua uses the same entities as the configuration language. This is the most
 | ||
| natural and reliable way for the Lua integration. So, the Lua engine allows one
 | ||
| to add new sample fetch functions, new converter functions and new actions.
 | ||
| These new entities can access the existing samples fetches and converters
 | ||
| allowing to extend them without rewriting them.
 | ||
| 
 | ||
| The writing of the first Lua functions shows that implementing complex concepts
 | ||
| like protocol analysers is easy and can be extended to full services. It appears
 | ||
| that these services are not easy to implement with the HAProxy configuration
 | ||
| model which is based on four steps: fetch, convert, compare and action. HAProxy
 | ||
| is extended with a notion of services which are a formalisation of the existing
 | ||
| services like stats, cli and peers. The service is an autonomous entity with a
 | ||
| behaviour pattern close to that of an external client or server. The Lua engine
 | ||
| inherits from this new service and offers new possibilities for writing
 | ||
| services.
 | ||
| 
 | ||
| This scripting language is useful for testing new features as proof of concept.
 | ||
| Later, if there is general interest, the proof of concept could be integrated
 | ||
| with C language in the HAProxy core.
 | ||
| 
 | ||
| The HAProxy Lua integration also provides a simple way for distributing Lua
 | ||
| packages. The final user needs only to install the Lua file, load it in HAProxy
 | ||
| and follow the attached documentation.
 | ||
| 
 | ||
| Design and technical things
 | ||
| ===========================
 | ||
| 
 | ||
| Lua is integrated into the HAProxy event driven core. We want to preserve the
 | ||
| fast processing of HAProxy. To ensure this, we implement some technical concepts
 | ||
| between HAProxy and the Lua library.
 | ||
| 
 | ||
| The following paragraph also describes the interactions between Lua and HAProxy
 | ||
| from a technical point of view.
 | ||
| 
 | ||
| Prerequisite
 | ||
| -----------
 | ||
| 
 | ||
| Reading the following documentation links is required to understand the
 | ||
| current paragraph:
 | ||
| 
 | ||
|    HAProxy doc: http://docs.haproxy.org/
 | ||
|    Lua API:     http://www.lua.org/manual/5.3/
 | ||
|    HAProxy API: http://www.arpalert.org/src/haproxy-lua-api/2.6/index.html
 | ||
|    Lua guide:   http://www.lua.org/pil/
 | ||
| 
 | ||
| more about Lua choice
 | ||
| ---------------------
 | ||
| 
 | ||
| Lua language is very simple to extend. It is easy to add new functions written
 | ||
| in C in the core language. It is not required to embed very intrusive libraries,
 | ||
| and we do not change compilation processes.
 | ||
| 
 | ||
| The amount of memory consumed can be controlled, and the issues due to lack of
 | ||
| memory are perfectly caught. The maximum amount of memory allowed for the Lua
 | ||
| processes is configurable. If some memory is missing, the current Lua action
 | ||
| fails, and the HAProxy processing flow continues.
 | ||
| 
 | ||
| Lua provides a way for implementing event driven design. When the Lua code
 | ||
| wants to do a blocking action, the action is started, it executes non blocking
 | ||
| operations, and returns control to the HAProxy scheduler when it needs to wait
 | ||
| for some external event.
 | ||
| 
 | ||
| The Lua process can be interrupted after a number of instructions executed. The
 | ||
| Lua execution will resume later. This is a useful way for controlling the
 | ||
| execution time. This system also keeps HAProxy responsive. When the Lua
 | ||
| execution is interrupted, HAProxy accepts some connections or transfers pending
 | ||
| data. The Lua execution does not block the main HAProxy processing, except in
 | ||
| some cases which we will see later.
 | ||
| 
 | ||
| Lua function integration
 | ||
| ------------------------
 | ||
| 
 | ||
| The Lua actions, sample fetches, converters and services are integrated in
 | ||
| HAProxy with "register_*" functions. The register system is a choice for
 | ||
| providing HAProxy Lua packages easily. The register system adds new sample
 | ||
| fetches, converters, actions or services usable in the HAProxy configuration
 | ||
| file.
 | ||
| 
 | ||
| The register system is defined in the "core" functions collection. This
 | ||
| collection is provided by HAProxy and is always available. Below, the list of
 | ||
| these functions:
 | ||
| 
 | ||
|  - core.register_action()
 | ||
|  - core.register_converters()
 | ||
|  - core.register_fetches()
 | ||
|  - core.register_init()
 | ||
|  - core.register_service()
 | ||
|  - core.register_task()
 | ||
| 
 | ||
| These functions are the execution entry points.
 | ||
| 
 | ||
| HTTP action must be used for manipulating HTTP request headers. This action
 | ||
| can not manipulates HTTP content. It is dangerous to use the channel
 | ||
| manipulation object with an HTTP request in an HTTP action. The channel
 | ||
| manipulation can transform a valid request in an invalid request. In this case,
 | ||
| the action will never resume and the processing will be frozen. HAProxy
 | ||
| discards the request after the reception timeout.
 | ||
| 
 | ||
| Non blocking design
 | ||
| -------------------
 | ||
| 
 | ||
| HAProxy is an event driven software, so blocking system calls are absolutely
 | ||
| forbidden. However, the Lua allows to do blocking actions. When an action
 | ||
| blocks, HAProxy is waiting and do nothing, so the basic functionalities like
 | ||
| accepting connections or forwarding data are blocked while the end of the system
 | ||
| call. In this case HAProxy will be less responsive.
 | ||
| 
 | ||
| This is very insidious because when the developer tries to execute its Lua code
 | ||
| with only one stream, HAProxy seems to run fine. When the code is used with
 | ||
| production stream, HAProxy encounters some slow processing, and it cannot
 | ||
| hold the load.
 | ||
| 
 | ||
| However, during the initialisation state, you can obviously using blocking
 | ||
| functions. There are typically used for loading files.
 | ||
| 
 | ||
| The list of prohibited standard Lua functions during the runtime contains all
 | ||
| that do filesystem access:
 | ||
| 
 | ||
|  - os.remove()
 | ||
|  - os.rename()
 | ||
|  - os.tmpname()
 | ||
|  - package.*()
 | ||
|  - io.*()
 | ||
|  - file.*()
 | ||
| 
 | ||
| Some other functions are prohibited:
 | ||
| 
 | ||
|  - os.execute(), waits for the end of the required execution blocking HAProxy.
 | ||
| 
 | ||
|  - os.exit(), is not really dangerous for the process, but it's not the good way
 | ||
|               for exiting the HAProxy process.
 | ||
| 
 | ||
|  - print(), writes data on stdout. In some cases these writes are blocking, the
 | ||
|             best practice is reserving this call for debugging. We must prefer
 | ||
|             to use core.log() or TXN.log() for sending messages.
 | ||
| 
 | ||
| Some HAProxy functions have a blocking behaviour pattern in the Lua code, but
 | ||
| there are compatible with the non blocking design. These functions are:
 | ||
| 
 | ||
|  - All the socket class
 | ||
|  - core.sleep()
 | ||
| 
 | ||
| Responsive design
 | ||
| -----------------
 | ||
| 
 | ||
| HAProxy must process connections accept, forwarding data and processing timeouts
 | ||
| as soon as possible. The first thing is to believe that a Lua script with a long
 | ||
| execution time should impact the expected responsive behaviour.
 | ||
| 
 | ||
| It is not the case, the Lua script execution are regularly interrupted, and
 | ||
| HAProxy can process other things. These interruptions are exprimed in number of
 | ||
| Lua instructions. The number of interruptions between two interrupts is
 | ||
| configured with the following "tune" option:
 | ||
| 
 | ||
|    tune.lua.forced-yield <nb>
 | ||
| 
 | ||
| The default value is 10 000. For determining it, I ran benchmark on my laptop.
 | ||
| I executed a Lua loop between 10 seconds with different values for the
 | ||
| "tune.lua.forced-yield" option, and I noted the results:
 | ||
| 
 | ||
|     configured    | Number of
 | ||
|     instructions  | loops executed
 | ||
|     between two   | in millions
 | ||
|     forced yields |
 | ||
|    ---------------+---------------
 | ||
|     10            | 160
 | ||
|     500           | 670
 | ||
|     1000          | 680
 | ||
|     5000          | 700
 | ||
|     7000          | 700
 | ||
|     8000          | 700
 | ||
|     9000          | 710 <- ceil
 | ||
|     10000         | 710
 | ||
|     100000        | 710
 | ||
|     1000000       | 710
 | ||
| 
 | ||
| The result showed that from 9000 instructions between two interrupt, we reached
 | ||
| a ceil, so the default parameter is 10 000.
 | ||
| 
 | ||
| When HAProxy interrupts the Lua processing, we have two states possible:
 | ||
| 
 | ||
|  - Lua is resumable, and it returns control to the HAProxy scheduler,
 | ||
|  - Lua is not resumable, and we just check the execution timeout.
 | ||
| 
 | ||
| The second case occurs if it is required by the HAProxy core. This state is
 | ||
| forced if the Lua is processed in a non resumable HAProxy part, like sample
 | ||
| fetches or converters.
 | ||
| 
 | ||
| It occurs also if the Lua is non resumable. For example, if some code is
 | ||
| executed through the Lua pcall() function, the execution is not resumable. This
 | ||
| is explained later.
 | ||
| 
 | ||
| So, the Lua code must be fast and simple when is executed as sample fetches and
 | ||
| converters, it could be slow and complex when is executed as actions and
 | ||
| services.
 | ||
| 
 | ||
| Execution time
 | ||
| --------------
 | ||
| 
 | ||
| The Lua execution time is measured and limited. Each group of functions has its
 | ||
| own timeout configured. The time measured is the real Lua execution time, and
 | ||
| not the difference between the end time and the start time. The groups are:
 | ||
| 
 | ||
|  - main code and init are not submitted to the timeout,
 | ||
|  - fetches, converters and action have a default timeout of 4s,
 | ||
|  - task, by default does not have timeout,
 | ||
|  - service have a default timeout of 4s.
 | ||
| 
 | ||
| The corresponding tune options are:
 | ||
| 
 | ||
|  - tune.lua.session-timeout (action, filter, cli)
 | ||
|  - tune.lua.task-timeout    (task)
 | ||
|  - tune.lua.service-timeout (services)
 | ||
|  - tune.lua.burst-timeout (max time between two lua yields)
 | ||
| 
 | ||
| The task does not have a timeout because it runs in background along the
 | ||
| HAProxy process life.
 | ||
| 
 | ||
| For example, if an Lua script is executed during 1.1s and the script executes a
 | ||
| sleep of 1 second, the effective measured running time is 0.1s.
 | ||
| 
 | ||
| This timeout is useful for preventing infinite loops. During the runtime, it
 | ||
| should be never triggered.
 | ||
| 
 | ||
| The stack and the coprocess
 | ||
| ---------------------------
 | ||
| 
 | ||
| The Lua execution is organized around a stack. Each Lua action, even out of the
 | ||
| effective execution, affects the stack. HAProxy integration uses one main stack,
 | ||
| which is common for all the process, and a secondary one used as coprocess.
 | ||
| After the initialization, the main stack is no longer used by HAProxy, except
 | ||
| for global storage. The second type of stack is used by all the Lua functions
 | ||
| called from different Lua actions declared in HAProxy. The main stack permits
 | ||
| to store coroutines pointers, and some global variables.
 | ||
| 
 | ||
| Do you want to see an example of how seems Lua C development around a stack ?
 | ||
| Some examples follows. This first one, is a simple addition:
 | ||
| 
 | ||
|    lua_pushnumber(L, 1)
 | ||
|    lua_pushnumber(L, 2)
 | ||
|    lua_arith(L, LUA_OPADD)
 | ||
| 
 | ||
| It's easy, we push 1 on the stack, after, we push 2, and finally, we perform an
 | ||
| addition. The two top entries of the stack are added, popped, and the result is
 | ||
| pushed. It is a classic way with a stack.
 | ||
| 
 | ||
| Now an example for constructing array and objects. It's a little bit more
 | ||
| complicated. The difficult consist to keep in mind the state of the stack while
 | ||
| we write the code. The goal is to create the entity described below. Note that
 | ||
| the notation "*1" is a metatable reference. The metatable will be explained
 | ||
| later.
 | ||
| 
 | ||
|    name*1 = {
 | ||
|       [0] = <userdata>,
 | ||
|    }
 | ||
| 
 | ||
|    *1 = {
 | ||
|       "__index" = {
 | ||
|          "method1" = <function>,
 | ||
|          "method2" = <function>
 | ||
|       }
 | ||
|       "__gc" = <function>
 | ||
|    }
 | ||
| 
 | ||
| Let's go:
 | ||
| 
 | ||
|    lua_newtable()               // The "name" table
 | ||
|    lua_newtable()               // The metatable *1
 | ||
|    lua_pushstring("__index")
 | ||
|    lua_newtable()               // The "__index" table
 | ||
|    lua_pushstring("method1")
 | ||
|    lua_pushfunction(function)
 | ||
|    lua_settable(-3)             // -3 is an index in the stack. insert method1
 | ||
|    lua_pushstring("method2")
 | ||
|    lua_pushfunction(function)
 | ||
|    lua_settable(-3)             // insert method2
 | ||
|    lua_settable(-3)             // insert "__index"
 | ||
|    lua_pushstring("__gc")
 | ||
|    lua_pushfunction(function)
 | ||
|    lua_settable()               // insert "__gc"
 | ||
|    lua_setmetatable(-1)         // attach metatable to "name"
 | ||
|    lua_pushnumber(0)
 | ||
|    lua_pushuserdata(userdata)
 | ||
|    lua_settable(-3)
 | ||
|    lua_setglobal("name")
 | ||
| 
 | ||
| So, coding for Lua in C, is not complex, but it needs some mental gymnastic.
 | ||
| 
 | ||
| The object concept and the HAProxy format
 | ||
| -----------------------------------------
 | ||
| 
 | ||
| The object seems to be not a native concept. An Lua object is a table. We can
 | ||
| note that the table notation accept three forms:
 | ||
| 
 | ||
|    1. mytable["entry"](mytable, "param")
 | ||
|    2. mytable.entry(mytable, "param")
 | ||
|    3. mytable:entry("param")
 | ||
| 
 | ||
| These three notation have the same behaviour pattern: a function is executed
 | ||
| with the table itself as first parameter and string "param" as second parameter
 | ||
| The notation with [] is commonly used for storing data in a hash table, and the
 | ||
| dotted notation is used for objects. The notation with ":" indicates that the
 | ||
| first parameter is the element at the left of the symbol ":".
 | ||
| 
 | ||
| So, an object is a table and each entry of the table is a variable. A variable
 | ||
| can be a function. These are the first concepts of the object notation in the
 | ||
| Lua, but it is not the end.
 | ||
| 
 | ||
| With the objects, we usually expect classes and inheritance. This is the role of
 | ||
| the metable. A metable is a table with predefined entries. These entries modify
 | ||
| the default behaviour of the table. The simplest example is the "__index" entry.
 | ||
| If this entry exists, it is called when a value is requested in the table. The
 | ||
| behaviour is the following:
 | ||
| 
 | ||
|    1 - looks in the table if the entry exists, and if it the case, return it
 | ||
| 
 | ||
|    2 - looks if a metatable exists, and if the "__index" entry exists
 | ||
| 
 | ||
|    3 - if "__index" is a function, execute it with the key as parameter, and
 | ||
|        returns the result of the function.
 | ||
| 
 | ||
|    4 - if "__index" is a table, looks if the requested entry exists, and if
 | ||
|        exists, return it.
 | ||
| 
 | ||
|    5 - if not exists, return to step 2
 | ||
| 
 | ||
| The behaviour of the point 5 represents the inheritance.
 | ||
| 
 | ||
| In HAProxy all the provided objects are tables, the entry "[0]" contains private
 | ||
| data, there are often userdata or lightuserdata. The metatable is registered in
 | ||
| the global part of the main Lua stack, and it is called with the case sensitive
 | ||
| class name. A great part of these class must not be used directly because it
 | ||
| requires an initialisation using the HAProxy internal structs.
 | ||
| 
 | ||
| The HAProxy objects use unified conventions. An Lua object is always a table.
 | ||
| In most cases, an HAProxy Lua object needs some private data. These are always
 | ||
| set in the index [0] of the array. The metatable entry "__tostring" returns the
 | ||
| object name.
 | ||
| 
 | ||
| The Lua developer can add entries to the HAProxy object. They just work carefully
 | ||
| and prevent to modify the index [0].
 | ||
| 
 | ||
| Common HAProxy objects are:
 | ||
| 
 | ||
|  - TXN        : manipulates the transaction between the client and the server
 | ||
|  - Channel    : manipulates proxified data between the client and the server
 | ||
|  - HTTP       : manipulates HTTP between the client and the server
 | ||
|  - Map        : manipulates HAProxy maps.
 | ||
|  - Fetches    : access to all HAProxy sample fetches
 | ||
|  - Converters : access to all HAProxy sample converters
 | ||
|  - AppletTCP  : process client request like a TCP server
 | ||
|  - AppletHTTP : process client request like an HTTP server
 | ||
|  - Socket     : establish tcp connection to a server (ipv4/ipv6/socket/ssl/...)
 | ||
| 
 | ||
| The garbage collector and the memory allocation
 | ||
| -----------------------------------------------
 | ||
| 
 | ||
| Lua doesn't really have a global memory limit, but HAProxy implements it. This
 | ||
| permits to control the amount of memory dedicated to the Lua processes. It is
 | ||
| specially useful with embedded environments.
 | ||
| 
 | ||
| When the memory limit is reached, HAProxy refuses to give more memory to the Lua
 | ||
| scripts. The current Lua execution is terminated with an error and HAProxy
 | ||
| continues its processing.
 | ||
| 
 | ||
| The max amount of memory is configured with the option:
 | ||
| 
 | ||
|    tune.lua.maxmem
 | ||
| 
 | ||
| As many other script languages, Lua uses a garbage collector for reusing its
 | ||
| memory. The Lua developer can work without memory preoccupation. Usually, the
 | ||
| garbage collector is controlled by the Lua core, but sometimes it will be useful
 | ||
| to run when the user/developer requires. So the garbage collector can be called
 | ||
| from C part or Lua part.
 | ||
| 
 | ||
| Sometimes, objects using lightuserdata or userdata requires to free some memory
 | ||
| block or close filedescriptor not controlled by the Lua. A dedicated garbage
 | ||
| collection function is provided through the metatable. It is referenced with the
 | ||
| special entry "__gc".
 | ||
| 
 | ||
| Generally, in HAProxy, the garbage collector does this job without any
 | ||
| intervention. However some objects use a great amount of memory, and we want to
 | ||
| release as quickly as possible. The problem is that only the GC knows if the
 | ||
| object is in use or not. The reason is simple variable containing objects can be
 | ||
| shared between coroutines and the main thread, so an object can be used
 | ||
| everywhere in HAProxy.
 | ||
| 
 | ||
| The only one example is the HAProxy sockets. These are explained later, just for
 | ||
| understanding the GC issues, a quick overview of the socket follows. The HAProxy
 | ||
| socket uses an internal session and stream, the session uses resources like
 | ||
| memory and file descriptor and in some cases keeps a socket open while it is no
 | ||
| longer used by Lua.
 | ||
| 
 | ||
| If the HAProxy socket is used, we forcing a garbage collector cycle after the
 | ||
| end of each function using HAProxy socket. The reason is simple: if the socket
 | ||
| is no longer used, we want to close the connection quickly.
 | ||
| 
 | ||
| A special flag is used in HAProxy indicating that a HAProxy socket is created.
 | ||
| If this flag is set, a full GC cycle is started after each Lua action. This is
 | ||
| not free, we loose about 10% of performances, but it is the only way for closing
 | ||
| sockets quickly.
 | ||
| 
 | ||
| The yield concept / longjmp issues
 | ||
| ----------------------------------
 | ||
| 
 | ||
| The "yield" is an action which does some Lua processing in pause and give back
 | ||
| the hand to the HAProxy core. This action is do when the Lua needs to wait about
 | ||
| data or other things. The most basically example is the sleep() function. In an
 | ||
| event driven software the code must not process blocking systems call, so the
 | ||
| sleep blocks the software between a lot of time. In HAProxy, an Lua sleep does a
 | ||
| yield, and ask to the scheduler to be woken up in a required sleep time.
 | ||
| Meanwhile, the HAProxy scheduler does other things, like accepting new
 | ||
| connection or forwarding data.
 | ||
| 
 | ||
| A yield is also executed regularly, after a lot of Lua instructions processed.
 | ||
| This yield permits to control the effective execution time, and also give back
 | ||
| the hand to the HAProxy core. When HAProxy finishes to process the pending jobs,
 | ||
| the Lua execution continues.
 | ||
| 
 | ||
| This special "yield" uses the Lua "debug" functions. Lua provides a debug method
 | ||
| called "lua_sethook()" which permits to interrupt the execution after some
 | ||
| configured condition and call a function. This condition used in HAProxy is
 | ||
| a number of instructions processed and when a function returns. The function
 | ||
| called controls the effective execution time, and if it is possible to send a
 | ||
| "yield".
 | ||
| 
 | ||
| The yield system is based on a couple setjmp/longjmp. In brief, the setjmp()
 | ||
| stores a stack state, and the longjmp restores the stack in its state which had
 | ||
| before the last Lua execution.
 | ||
| 
 | ||
| Lua can immediately stop its execution if an error occurs. This system uses also
 | ||
| the longjmp system. In HAProxy, we try to use this system only for unrecoverable
 | ||
| errors. Maybe some trivial errors target an exception, but we try to remove it.
 | ||
| 
 | ||
| It seems that Lua uses the longjmp system for having a behaviour like the java
 | ||
| try / catch. We can use the function pcall() to execute some code. The function
 | ||
| pcall() run a setjmp(). So, if any error occurs while the Lua code execution,
 | ||
| the flow immediately returns from the pcall() with an error.
 | ||
| 
 | ||
| The big issue of this behaviour is that we cannot do a yield. So if some Lua code
 | ||
| executes a library using pcall for catching errors, HAProxy must be wait for the
 | ||
| end of execution without processing any accept or any stream. The cause is the
 | ||
| yield must be jump to the root of execution. The intermediate setjmp() avoids
 | ||
| this behaviour.
 | ||
| 
 | ||
| 
 | ||
|    HAProxy start Lua execution
 | ||
|      + Lua puts a setjmp()
 | ||
|         + Lua executes code
 | ||
|         + Some code is executed in a pcall()
 | ||
|            + pcall() puts a setjmp()
 | ||
|               + Lua executes code
 | ||
|                  + A yield is require for a sleep function
 | ||
|                    it cannot be jumps to the Lua root execution.
 | ||
| 
 | ||
| 
 | ||
| Another issue with the processing of strong errors is the manipulation of the
 | ||
| Lua stack outside of an Lua processing. If one of the functions called occurs a
 | ||
| strong error, the default behaviour is an abort(). It is not acceptable when
 | ||
| HAProxy is in runtime mode. The Lua documentation propose to use another
 | ||
| setjmp/longjmp to avoid the abort(). The goal is to put a setjmp between
 | ||
| manipulating the Lua stack and using an alternative "panic" function which jumps
 | ||
| to the setjmp() in error case.
 | ||
| 
 | ||
| All of these behaviours are very dangerous for the stability, and the internal
 | ||
| HAProxy code must be modified with many precautions.
 | ||
| 
 | ||
| For preserving a good behaviour of HAProxy, the yield is mandatory.
 | ||
| Unfortunately, some HAProxy parts are not adapted for resuming an execution
 | ||
| after a yield. These parts are the sample fetches and the sample converters. So,
 | ||
| the Lua code written in these parts of HAProxy must be quickly executed, and can
 | ||
| not do actions which require yield like TCP connection or simple sleep.
 | ||
| 
 | ||
| HAProxy socket object
 | ||
| ---------------------
 | ||
| 
 | ||
| The HAProxy design is optimized for the data transfers between a client and a
 | ||
| server, and processing the many errors which can occurs during these exchanges.
 | ||
| HAProxy is not designed for having a third connection established to a third
 | ||
| party server.
 | ||
| 
 | ||
| The solution consist to put the main stream in pause waiting for the end of the
 | ||
| exchanges with the third connection. This is completed by a signal between
 | ||
| internal tasks. The following graph shows the HAProxy Lua socket:
 | ||
| 
 | ||
| 
 | ||
|                       +--------------------+
 | ||
|                       | Lua processing     |
 | ||
|  ------------------\  | creates socket     | ------------------\
 | ||
|   incoming request  > | and puts the       |  Outgoing request  >
 | ||
|  ------------------/  | current processing | ------------------/
 | ||
|                       | in pause waiting   |
 | ||
|                       | for TCP applet     |
 | ||
|                       +-----------------+--+
 | ||
|                         ^               |
 | ||
|                         |               |
 | ||
|                         | signal        | read / write
 | ||
|                         |               | data
 | ||
|                         |               |
 | ||
|           +-------------+---------+     v
 | ||
|           | HAProxy internal      +----------------+
 | ||
|           | applet send signals   |                |
 | ||
|           | when data is received |                | -------------------\
 | ||
|           | or some room is       |  Attached I/O  |  Client TCP stream  >
 | ||
|           | available             |  Buffers       | -------------------/
 | ||
|           +--------------------+--+                |
 | ||
|                                |                   |
 | ||
|                                +-------------------+
 | ||
| 
 | ||
| 
 | ||
| A more detailed graph is available in the "doc/internals" directory.
 | ||
| 
 | ||
| The HAProxy Lua socket uses a full HAProxy session / stream for establishing the
 | ||
| connection. This mechanism provides all the facilities and HAProxy features,
 | ||
| like the SSL stack, many socket type, and support for namespaces.
 | ||
| Technically it supports the proxy protocol, but there are no way to enable it.
 | ||
| 
 | ||
| How compiling HAProxy with Lua
 | ||
| ==============================
 | ||
| 
 | ||
| HAProxy 1.6 requires Lua 5.3. Lua 5.3 offers some features which make easy the
 | ||
| integration. Lua 5.3 is young, and some distros do not distribute it. Luckily,
 | ||
| Lua is a great product because it does not require exotic dependencies, and its
 | ||
| build process is really easy.
 | ||
| 
 | ||
| The compilation process for linux is easy:
 | ||
| 
 | ||
|  - download the source tarball
 | ||
|    wget http://www.lua.org/ftp/lua-5.3.1.tar.gz
 | ||
| 
 | ||
|  - untar it
 | ||
|    tar xf lua-5.3.1.tar.gz
 | ||
| 
 | ||
|  - enter the directory
 | ||
|    cd lua-5.3.1
 | ||
| 
 | ||
|  - build the library for linux
 | ||
|    make linux
 | ||
| 
 | ||
|  - install it:
 | ||
|    sudo make INSTALL_TOP=/opt/lua-5.3.1 install
 | ||
| 
 | ||
| HAProxy builds with your favourite options, plus the following options for
 | ||
| embedding the Lua script language:
 | ||
| 
 | ||
|  - download the source tarball
 | ||
|    wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.2.tar.gz
 | ||
| 
 | ||
|  - untar it
 | ||
|    tar xf haproxy-1.6.2.tar.gz
 | ||
| 
 | ||
|  - enter the directory
 | ||
|    cd haproxy-1.6.2
 | ||
| 
 | ||
|  - build HAProxy:
 | ||
|    make TARGET=linux-glibc \
 | ||
|         USE_LUA=1 \
 | ||
|         LUA_LIB=/opt/lua-5.3.1/lib \
 | ||
|         LUA_INC=/opt/lua-5.3.1/include
 | ||
| 
 | ||
|  - install it:
 | ||
|    sudo make PREFIX=/opt/haproxy-1.6.2 install
 | ||
| 
 | ||
| First steps with Lua
 | ||
| ====================
 | ||
| 
 | ||
| Now, it's time to use Lua in HAProxy.
 | ||
| 
 | ||
| Start point
 | ||
| -----------
 | ||
| 
 | ||
| The HAProxy global directive "lua-load <file>" allows to load an Lua file. This
 | ||
| is the entry point. This load become during the configuration parsing, and the
 | ||
| Lua file is immediately executed.
 | ||
| 
 | ||
| All the register_*() functions must be called at this time because they are used
 | ||
| just after the processing of the global section, in the frontend/backend/listen
 | ||
| sections.
 | ||
| 
 | ||
| The most simple "Hello world !" is the following line a loaded Lua file:
 | ||
| 
 | ||
|    core.Alert("Hello World !");
 | ||
| 
 | ||
| It displays a log during the HAProxy startup:
 | ||
| 
 | ||
|    [alert] 285/083533 (14465) : Hello World !
 | ||
| 
 | ||
| Default path and libraries
 | ||
| --------------------------
 | ||
| 
 | ||
| Lua can embed some libraries. These libraries can be included from different
 | ||
| paths. It seems that Lua doesn't like subdirectories. In the following example,
 | ||
| I try to load a compiled library, so the first line is Lua code, the second line
 | ||
| is an 'strace' extract proving that the library was opened. The next lines are
 | ||
| the associated error.
 | ||
| 
 | ||
|    require("luac/concat")
 | ||
| 
 | ||
|    open("./luac/concat.so", O_RDONLY|O_CLOEXEC) = 4
 | ||
| 
 | ||
| 	[ALERT]    (22806) : parsing [commonstats.conf:15] : lua runtime
 | ||
| 	error: error loading module 'luac/concat' from file './luac/concat.so':
 | ||
| 	        ./luac/concat.so: undefined symbol: luaopen_luac/concat
 | ||
| 
 | ||
| Lua tries to load the C symbol 'luaopen_luac/concat'. When Lua tries to open a
 | ||
| library, it tries to execute the function associated to the symbol
 | ||
| "luaopen_<libname>".
 | ||
| 
 | ||
| The variable "<libname>" is defined using the content of the variable
 | ||
| "package.cpath" and/or "package.path". The default definition of the
 | ||
| "package.cpath" (on my computer is ) variable is:
 | ||
| 
 | ||
|    /usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so
 | ||
| 
 | ||
| The "<libname>" is the content which replaces the symbol "<?>". In the previous
 | ||
| example, its "luac/concat", and obviously the Lua core try to load the function
 | ||
| associated with the symbol "luaopen_luac/concat".
 | ||
| 
 | ||
| My conclusion is that Lua doesn't support subdirectories. So, for loading
 | ||
| libraries in subdirectory, it must fill the variable with the name of this
 | ||
| subdirectory. The extension .so must disappear, otherwise Lua try to execute the
 | ||
| function associated with the symbol "luaopen_concat.so". The following syntax is
 | ||
| correct:
 | ||
| 
 | ||
|    package.cpath = package.cpath .. ";./luac/?.so"
 | ||
| 	require("concat")
 | ||
| 
 | ||
| First useful example
 | ||
| --------------------
 | ||
| 
 | ||
|    core.register_fetches("my-hash", function(txn, salt)
 | ||
|       return txn.sc:sdbm(salt .. txn.sf:req_fhdr("host") .. txn.sf:path() .. txn.sf:src(), 1)
 | ||
|    end)
 | ||
| 
 | ||
| You will see that these 3 lines can generate a lot of explanations :)
 | ||
| 
 | ||
| Core.register_fetches() is executed during the processing of the global section
 | ||
| by the HAProxy configuration parser. A new sample fetch is declared with name
 | ||
| "my-hash", this name is always prefixed by "lua.". So this new declared
 | ||
| sample fetch will be used calling "lua.my-hash" in the HAProxy configuration
 | ||
| file.
 | ||
| 
 | ||
| The second parameter is an inline declared anonymous function. Note the closed
 | ||
| parenthesis after the keyword "end" which ends the function. The first parameter
 | ||
| of this anonymous function is "txn". It is an object of class TXN. It provides
 | ||
| access functions. The second parameter is an arbitrary value provided by the
 | ||
| HAProxy configuration file. This parameter is optional, the developer must
 | ||
| check if it is present.
 | ||
| 
 | ||
| The anonymous function registration is executed when the HAProxy backend or
 | ||
| frontend configuration references the sample fetch "lua.my-hash".
 | ||
| 
 | ||
| This example can be written with another style, like below:
 | ||
| 
 | ||
|    function my_hash(txn, salt)
 | ||
|       return txn.sc:sdbm(salt .. txn.sf:req_fhdr("host") .. txn.sf:path() .. txn.sf:src(), 1)
 | ||
|    end
 | ||
| 
 | ||
|    core.register_fetches("my-hash", my_hash)
 | ||
| 
 | ||
| This second form is clearer, but the first one is compact.
 | ||
| 
 | ||
| The operator ".." is a string concatenation. If one of the two operands is not a
 | ||
| string, an error occurs and the execution is immediately stopped. This is
 | ||
| important to keep in mind for the following things.
 | ||
| 
 | ||
| Now I write the example on more than one line. Its an easiest way for commenting
 | ||
| the code:
 | ||
| 
 | ||
|     1. function my_hash(txn, salt)
 | ||
|     2.    local str = ""
 | ||
|     3.    str = str .. salt
 | ||
|     4.    str = str .. txn.sf:req_fhdr("host")
 | ||
|     5.    str = str .. txn.sf:path()
 | ||
|     6.    str = str .. txn.sf:src()
 | ||
|     7.    local result = txn.sc:sdbm(str, 1)
 | ||
|     8.    return result
 | ||
|     9. end
 | ||
|    10.
 | ||
|    11. core.register_fetches("my-hash", my_hash)
 | ||
| 
 | ||
| local
 | ||
| ~~~~~
 | ||
| 
 | ||
| The first keyword is "local". This is a really important keyword. You must
 | ||
| understand that the function "my_hash" will be called for each HAProxy request
 | ||
| using the declared sample fetch. So, this function can be executed many times in
 | ||
| parallel.
 | ||
| 
 | ||
| By default, Lua uses global variables. So in this example, if the variable "str"
 | ||
| is declared without the keyword "local", it will be shared by all the parallel
 | ||
| executions of the function and obviously, the content of the requests will be
 | ||
| shared.
 | ||
| 
 | ||
| This warning is very important. I tried to write useful Lua code like a rewrite
 | ||
| of the statistics page, and it is very hard thing to declare each variable as
 | ||
| "local".
 | ||
| 
 | ||
| I guess that this behaviour will be the cause of many troubles on the mailing
 | ||
| list.
 | ||
| 
 | ||
| str = str ..
 | ||
| ~~~~~~~~~~~~
 | ||
| 
 | ||
| Now a parenthesis about the form "str = str ..". This form allows to do string
 | ||
| concatenations. Remember that Lua uses a garbage collector, so what happens when
 | ||
| we do "str = str .. 'another string'" ?
 | ||
| 
 | ||
|    str = str .. "another string"
 | ||
|    ^     ^   ^  ^
 | ||
|    1     2   3  4
 | ||
| 
 | ||
| Lua executes first the concatenation operator (3), it allocates memory for the
 | ||
| resulting string and fill this memory with the concatenation of the operands 2
 | ||
| and 4. Next, it frees the variable 1, now the old content of 1 can be garbage
 | ||
| collected. And finally, the new content of 1 is the concatenation.
 | ||
| 
 | ||
| what the matter ? when we do this operation many times, we consume a lot of
 | ||
| memory, and the string data is duplicated and move many times. So, this practice
 | ||
| is expensive in execution time and memory consumption.
 | ||
| 
 | ||
| There are easy ways to prevent this behaviour. I guess that a C binding for
 | ||
| concatenation with chunks will be available ASAP (it is already written). I do
 | ||
| some benchmarks. I compare the execution time of 1 000 times, 1 000
 | ||
| concatenation of 10 bytes written in pure Lua and with a C library. The result is
 | ||
| 10 times faster in C (1s in Lua, and 0.1s in C).
 | ||
| 
 | ||
| txn
 | ||
| ~~~
 | ||
| 
 | ||
| txn is an HAProxy object of class TXN. The documentation is available in the
 | ||
| HAProxy Lua API reference. This class allow the access to the native HAProxy
 | ||
| sample fetches and converters. The object txn contains 2 members dedicated to
 | ||
| the sample fetches and 2 members dedicated to the converters.
 | ||
| 
 | ||
| The sample fetches members are "f" (as sample-Fetch) and "sf" (as String
 | ||
| sample-Fetch). These two members contain exactly the same functions. All the
 | ||
| HAProxy native sample fetches are available, obviously, the Lua registered sample
 | ||
| fetches are not available. Unfortunately, HAProxy sample fetches names are not
 | ||
| compatible with the Lua function names, and they are renamed. The rename
 | ||
| convention is simple, we replace all the '.', '+' and '-' by '_'. The '.' is the
 | ||
| object member separator, and the "-" and "+" is math operator.
 | ||
| 
 | ||
| Now, that I'm writing this article, I know the Lua better than I wrote the
 | ||
| sample-fetches wrapper. The original HAProxy sample-fetches name should be used
 | ||
| using alternative manner to call an object member, so the sample-fetch
 | ||
| "req.fhdr" (actually renamed req_fhdr") should be used like this:
 | ||
| 
 | ||
|    txn.f["req.fhdr"](txn.f, ...)
 | ||
| 
 | ||
| However, I think that this form is not elegant.
 | ||
| 
 | ||
| The "s" collection return a data with a type near to the original returned type.
 | ||
| A string returns an Lua string, an integer returns an Lua integer and an IP
 | ||
| address returns an Lua string. Sometime the data is not or not yet available, in
 | ||
| this case it returns the Lua nil value.
 | ||
| 
 | ||
| The "sf" collection guarantees that a string will be always returned. If the data
 | ||
| is not available, an empty string is returned. The main usage of these collection
 | ||
| is to concatenate the returned sample-fetches without testing each function.
 | ||
| 
 | ||
| The parameters of the sample-fetches are according with the HAProxy
 | ||
| documentation.
 | ||
| 
 | ||
| The converters run exactly with the same manner as the sample fetches. The
 | ||
| only one difference is that the first parameter is the converter entry element.
 | ||
| The "c" collection returns a precise result, and the "sc" collection returns
 | ||
| always a string.
 | ||
| 
 | ||
| The sample-fetches used in the example function are "txn.sf:req_fhdr()",
 | ||
| "txn.sf:path()" and "txn.sf:src()". The converter is "txn.sc:sdbm()". The same
 | ||
| function with the "s" collection of sample-fetches and the "c" collection of
 | ||
| converter should be written like this:
 | ||
| 
 | ||
|     1. function my_hash(txn, salt)
 | ||
|     2.    local str = ""
 | ||
|     3.    str = str .. salt
 | ||
|     4.    str = str .. tostring(txn.f:req_fhdr("host"))
 | ||
|     5.    str = str .. tostring(txn.f:path())
 | ||
|     6.    str = str .. tostring(txn.f:src())
 | ||
|     7.    local result = tostring(txn.c:sdbm(str, 1))
 | ||
|     8.    return result
 | ||
|     9. end
 | ||
|    10.
 | ||
|    11. core.register_fetches("my-hash", my_hash)
 | ||
| 
 | ||
| tostring
 | ||
| ~~~~~~~~
 | ||
| 
 | ||
| The function tostring ensures that its parameter is returned as a string. If the
 | ||
| parameter is a table or a thread or anything that will not have any sense as a
 | ||
| string, a form like the typename followed by a pointer is returned. For example:
 | ||
| 
 | ||
|    t = {}
 | ||
|    print(tostring(t))
 | ||
| 
 | ||
| returns:
 | ||
| 
 | ||
|    table: 0x15facc0
 | ||
| 
 | ||
| For objects, if the special function __tostring() is registered in the attached
 | ||
| metatable, it will be called with the table itself as first argument. The
 | ||
| HAProxy object returns its own type.
 | ||
| 
 | ||
| About the converters entry point
 | ||
| --------------------------------
 | ||
| 
 | ||
| In HAProxy, a converter is a stateless function that takes a data as entry and
 | ||
| returns a transformation of this data as output. In Lua it is exactly the same
 | ||
| behaviour.
 | ||
| 
 | ||
| So, the registered Lua function doesn't have any special parameters, just a
 | ||
| variable as input which contains the value to convert, and it must return data.
 | ||
| 
 | ||
| The data required as input by the Lua converter is a string. So HAProxy will
 | ||
| always provide a string as input. If the native sample fetch is not a string it
 | ||
| will be converted in best effort.
 | ||
| 
 | ||
| The returned value will have anything type, it will be converted as sample of
 | ||
| the near HAProxy type. The conversion rules from Lua variables to HAProxy
 | ||
| samples are:
 | ||
| 
 | ||
|    Lua        | HAProxy sample types
 | ||
|    -----------+---------------------
 | ||
|    "number"   | "sint"
 | ||
|    "boolean"  | "bool"
 | ||
|    "string"   | "str"
 | ||
|    "userdata" | "bool" (false)
 | ||
|    "nil"      | "bool" (false)
 | ||
|    "table"    | "bool" (false)
 | ||
|    "function" | "bool" (false)
 | ||
|    "thread"   | "bool" (false)
 | ||
| 
 | ||
| The function used for registering a converter is:
 | ||
| 
 | ||
|    core.register_converters()
 | ||
| 
 | ||
| The task entry point
 | ||
| --------------------
 | ||
| 
 | ||
| The function "core.register_task(fcn)" executes once the function "fcn" when the
 | ||
| scheduler starts. This way is used for executing background task. For example,
 | ||
| you can use this functionality for periodically checking the health of another
 | ||
| service, and giving the result to each proxy needing it.
 | ||
| 
 | ||
| The task is started once, if you want periodic actions, you can use the
 | ||
| "core.sleep()" or "core.msleep()" for waiting the next runtime.
 | ||
| 
 | ||
| Storing Lua variable between function in the same session
 | ||
| ---------------------------------------------------------
 | ||
| 
 | ||
| All the functions registered as action or sample fetch can share an Lua context.
 | ||
| This context is a memory zone in the stack. sample fetch and action use the
 | ||
| same stack, so both can access to the context.
 | ||
| 
 | ||
| The context is accessible via the function get_priv and set_priv provided by an
 | ||
| object of class TXN. The value given to set_priv replaces the current stored
 | ||
| value. This value can be a table, it is useful if a lot of data can be shared.
 | ||
| 
 | ||
| If the value stored is a table, you can add or remove entries from the table
 | ||
| without storing again the new table. Maybe an example will be clearer:
 | ||
| 
 | ||
|    local t = {}
 | ||
|    txn:set_priv(t)
 | ||
| 
 | ||
|    t["entry1"] = "foo"
 | ||
|    t["entry2"] = "bar"
 | ||
| 
 | ||
|    -- this will display "foo"
 | ||
|    print(txn:get_priv()["entry1"])
 | ||
| 
 | ||
| HTTP actions
 | ||
| ============
 | ||
| 
 | ||
|    ... coming soon ...
 | ||
| 
 | ||
| Lua is fast, but my service require more execution speed
 | ||
| ========================================================
 | ||
| 
 | ||
| We can write C modules for Lua. These modules must run with HAProxy while they
 | ||
| are compliant with the HAProxy Lua version. A simple example is the "concat"
 | ||
| module.
 | ||
| 
 | ||
| It is very easy to write and compile a C Lua library, however, I don't see
 | ||
| documentation about this process. So the current chapter is a quick howto.
 | ||
| 
 | ||
| The entry point
 | ||
| ---------------
 | ||
| 
 | ||
| The entry point is called "luaopen_<name>", where <name> is the name of the ".so"
 | ||
| file. An hello world is like this:
 | ||
| 
 | ||
|    #include <stdio.h>
 | ||
|    #include <lua.h>
 | ||
|    #include <lauxlib.h>
 | ||
| 
 | ||
|    int luaopen_mymod(lua_State *L)
 | ||
|    {
 | ||
|       printf("Hello world\n");
 | ||
|       return 0;
 | ||
|    }
 | ||
| 
 | ||
| The build
 | ||
| ---------
 | ||
| 
 | ||
| The compilation of the source file requires the Lua "include" directory. The
 | ||
| compilation and the link of the object file requires the -fPIC option. That's
 | ||
| all.
 | ||
| 
 | ||
|    cc -I/opt/lua/include -fPIC -shared  -o mymod.so mymod.c
 | ||
| 
 | ||
| Usage
 | ||
| -----
 | ||
| 
 | ||
| You can load this module with the following Lua syntax:
 | ||
| 
 | ||
|    require("mymod")
 | ||
| 
 | ||
| When you start HAProxy, this module just print "Hello world" when it is loaded.
 | ||
| Please, remember that HAProxy doesn't allow blocking method, so if you write a
 | ||
| function doing filesystem access or synchronous network access, all the HAProxy
 | ||
| process will fail.
 |