-- binary trees: benchmark game -- with resource limit tutorial -- by justine tunney local outofcpu = false local oldsigxcpufunc = nil local oldsigxcpuflags = nil local oldsigxcpumask = nil -- This is our signal handler. function OnSigxcpu(sig) -- Please note, it's dangerous to do complicated things from inside -- asynchronous signal handlers. Even Log() isn't async signal safe -- The best possible practice is to have them simply set a variable outofcpu = true end -- These two functions are what's being benchmarked. It's a popular -- torture test for interpreted languages since it generates a lot of -- garbage. Lua does well on this test, going faster than many other -- languages, e.g. Racket, Python. local function MakeTree(depth) if outofcpu then error({reason='MakeTree() caught SIGXCPU'}) end if depth > 0 then depth = depth - 1 left, right = MakeTree(depth), MakeTree(depth) return { left, right } else return { } end end local function CheckTree(tree) if outofcpu then error({reason='CheckTree() caught SIGXCPU'}) end if tree[1] then return 1 + CheckTree(tree[1]) + CheckTree(tree[2]) else return 1 end end -- Now for our redbean web server code... local function WriteForm(depth, suggestion) Write([[ redbean binary trees

binary trees benchmark demo

This demo is from the computer language benchmark game. It's a torture test for the Lua garbage collector. ProTip: Try loading this page while the terminal memory monitor feature is active, so you can see memory shuffle around in a dual screen display. We use setrlimit to set a 512mb memory limit. So if you specify a number higher than 23 (a good meaty benchmark) then the Lua GC should panic a bit trying to recover (due to malloc failure) and ultimately the worker process should die. However the server and your system will survive.

]] % {EscapeHtml(depth), suggestion}) end local function BinaryTreesDemo() if GetMethod() == 'GET' or GetMethod() == 'HEAD' then WriteForm("18", 18) elseif GetMethod() == 'POST' and not HasParam('depth') then ServeError(400) elseif GetMethod() == 'POST' then -- write out the page so user can submit again WriteForm(GetParam('depth'), 18) Write('
\r\n') Write('
Output\r\n') Write('
\r\n') -- impose quotas to protect the server unix.setrlimit(unix.RLIMIT_AS, 1024 * 1024 * 1024) unix.setrlimit(unix.RLIMIT_CPU, 2, 4) -- when RLIMIT_AS (virtual address space) runs out of memory, then -- mmap() and malloc() start to fail, and lua reacts by trying to -- run the garbage collector over and over again. so we also place -- a limit on the number of "cpu time" seconds too. the behavior -- when we hit the soft limit, is the kernel gives us a friendly -- warning by sending the signal SIGXCPU which we must catch here -- once we catch it, we've got 2 seconds of grace time to clean up -- before we hit the "hard limit" and the kernel sends SIGKILL (9) oldsigxcpufunc, oldsigxcpuflags, oldsigxcpumask = unix.sigaction(unix.SIGXCPU, OnSigxcpu) -- get the user-supplied parameter depth = tonumber(GetParam('depth')) -- run the benchmark, recording how long it takes secs1, nanos1 = unix.clock_gettime() res = CheckTree(MakeTree(depth)) secs2, nanos2 = unix.clock_gettime() -- turn 128-bit timestamps into 64-bit nanosecond interval if secs2 == secs1 then nanos = nanos2 - nanos1 else nanos = (secs2 - secs1) * 1000000000 + (1000000000 - nanos1) + nanos2 end -- write out result of benchmark Write('0%o\r\n' % {res}) Write('
Time Elapsed\r\n') Write('
\r\n') Write('%g seconds, or
\r\n' % {nanos / 1e9}) Write('%g milliseconds, or
\r\n' % {nanos / 1e6}) Write('%g microseconds, or
\r\n' % {nanos / 1e6}) Write('%d nanoseconds\r\n' % {nanos}) Write('
unix.clock_gettime() #1\r\n') Write('
%d, %d\r\n' % {secs1, nanos1}) Write('
unix.clock_gettime() #2\r\n') Write('
%d, %d\r\n' % {secs2, nanos2}) Write('
\r\n') else ServeError(405) SetHeader('Allow', 'GET, HEAD, POST') end end local function main() -- catch exceptions ok, err = pcall(BinaryTreesDemo) -- we don't need to restore the old handler -- but it's generally a good practice to clean up if oldsigxcpufunc then unix.sigaction(unix.SIGXCPU, oldsigxcpufunc, oldsigxcpuflags, oldsigxcpumask) end -- handle exceptions if not ok then -- whenever anything, at all, goes wrong, with anything -- always with few exceptions close the connection asap SetHeader('Connection', 'close') if err.reason then -- show our error message withoun internal code leaking out Write(err.reason) Write('\r\n') else -- just rethrow exception error('unexpected failure: ' .. tostring(err)) end end end main()