e86d5ec239be78bfde25c56c07e75e8d867355fd
[project/luci.git] / libs / core / luasrc / util.lua
1 --[[
2 LuCI - Utility library
3
4 Description:
5 Several common useful Lua functions
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27 local io = require "io"
28 local math = require "math"
29 local table = require "table"
30 local debug = require "debug"
31 local ldebug = require "luci.debug"
32 local string = require "string"
33 local coroutine = require "coroutine"
34
35 local getmetatable, setmetatable = getmetatable, setmetatable
36 local rawget, rawset, unpack = rawget, rawset, unpack
37 local tostring, type, assert = tostring, type, assert
38 local ipairs, pairs, loadstring = ipairs, pairs, loadstring
39 local require, pcall, xpcall = require, pcall, xpcall
40 local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit
41
42 --- LuCI utility functions.
43 module "luci.util"
44
45 --
46 -- Pythonic string formatting extension
47 --
48 getmetatable("").__mod = function(a, b)
49         if not b then
50                 return a
51         elseif type(b) == "table" then
52                 return a:format(unpack(b))
53         else
54                 return a:format(b)
55         end
56 end
57
58
59 --
60 -- Class helper routines
61 --
62
63 -- Instantiates a class
64 local function _instantiate(class, ...)
65         local inst = setmetatable({}, {__index = class})
66
67         if inst.__init__ then
68                 inst:__init__(...)
69         end
70
71         return inst
72 end
73
74 --- Create a Class object (Python-style object model).
75 -- The class object can be instantiated by calling itself.
76 -- Any class functions or shared parameters can be attached to this object.
77 -- Attaching a table to the class object makes this table shared between
78 -- all instances of this class. For object parameters use the __init__ function.
79 -- Classes can inherit member functions and values from a base class.
80 -- Class can be instantiated by calling them. All parameters will be passed
81 -- to the __init__ function of this class - if such a function exists.
82 -- The __init__ function must be used to set any object parameters that are not shared
83 -- with other objects of this class. Any return values will be ignored.
84 -- @param base  The base class to inherit from (optional)
85 -- @return              A class object
86 -- @see                 instanceof
87 -- @see                 clone
88 function class(base)
89         return setmetatable({}, {
90                 __call  = _instantiate,
91                 __index = base
92         })
93 end
94
95 --- Test whether the given object is an instance of the given class.
96 -- @param object        Object instance
97 -- @param class         Class object to test against
98 -- @return                      Boolean indicating whether the object is an instance
99 -- @see                         class
100 -- @see                         clone
101 function instanceof(object, class)
102         local meta = getmetatable(object)
103         while meta and meta.__index do
104                 if meta.__index == class then
105                         return true
106                 end
107                 meta = getmetatable(meta.__index)
108         end
109         return false
110 end
111
112
113 --
114 -- Scope manipulation routines
115 --
116
117 --- Create a new or get an already existing thread local store associated with
118 -- the current active coroutine. A thread local store is private a table object
119 -- whose values can't be accessed from outside of the running coroutine.
120 -- @return      Table value representing the corresponding thread local store
121 function threadlocal()
122         local tbl = {}
123
124         local function get(self, key)
125                 local c = coroutine.running()
126                 local thread = coxpt[c] or c or 0
127                 if not rawget(self, thread) then
128                         return nil
129                 end
130                 return rawget(self, thread)[key]
131         end
132
133         local function set(self, key, value)
134                 local c = coroutine.running()
135                 local thread = coxpt[c] or c or 0
136                 if not rawget(self, thread) then
137                         rawset(self, thread, {})
138                 end
139                 rawget(self, thread)[key] = value
140         end
141
142         setmetatable(tbl, {__index = get, __newindex = set, __mode = "k"})
143
144         return tbl
145 end
146
147
148 --
149 -- Debugging routines
150 --
151
152 --- Write given object to stderr.
153 -- @param obj   Value to write to stderr
154 -- @return              Boolean indicating whether the write operation was successful
155 function perror(obj)
156         return io.stderr:write(tostring(obj) .. "\n")
157 end
158
159 --- Recursively dumps a table to stdout, useful for testing and debugging.
160 -- @param t     Table value to dump
161 -- @param maxdepth      Maximum depth
162 -- @return      Always nil
163 function dumptable(t, maxdepth, i, seen)
164         i = i or 0
165         seen = seen or setmetatable({}, {__mode="k"})
166
167         for k,v in pairs(t) do
168                 perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v))
169                 if type(v) == "table" and (not maxdepth or i < maxdepth) then
170                         if not seen[v] then
171                                 seen[v] = true
172                                 dumptable(v, maxdepth, i+1, seen)
173                         else
174                                 perror(string.rep("\t", i) .. "*** RECURSION ***")
175                         end
176                 end
177         end
178 end
179
180
181 --
182 -- String and data manipulation routines
183 --
184
185 --- Escapes all occurrences of the given character in given string.
186 -- @param s     String value containing unescaped characters
187 -- @param c     String value with character to escape (optional, defaults to "\")
188 -- @return      String value with each occurrence of character escaped with "\"
189 function escape(s, c)
190         c = c or "\\"
191         return s:gsub(c, "\\" .. c)
192 end
193
194 --- Create valid XML PCDATA from given string.
195 -- @param value String value containing the data to escape
196 -- @return              String value containing the escaped data
197 local function _pcdata_repl(c)
198         local i = string.byte(c)
199
200         if ( i >= 0x00 and i <= 0x08 ) or ( i >= 0x0B and i <= 0x0C ) or
201            ( i >= 0x0E and i <= 0x1F ) or ( i == 0x7F )
202         then
203                 return ""
204                 
205         elseif ( i == 0x26 ) or ( i == 0x27 ) or ( i == 0x22 ) or
206                ( i == 0x3C ) or ( i == 0x3E )
207         then
208                 return string.format("&#%i;", i)
209         end
210
211         return c
212 end
213
214 function pcdata(value)
215         return value and tostring(value):gsub("[&\"'<>%c]", _pcdata_repl)
216 end
217
218 --- Strip HTML tags from given string.
219 -- @param value String containing the HTML text
220 -- @return      String with HTML tags stripped of
221 function striptags(s)
222         return pcdata(s:gsub("</?[A-Za-z][A-Za-z0-9:_%-]*[^>]*>", " "):gsub("%s+", " "))
223 end
224
225 --- Splits given string on a defined separator sequence and return a table
226 -- containing the resulting substrings. The optional max parameter specifies
227 -- the number of bytes to process, regardless of the actual length of the given
228 -- string. The optional last parameter, regex, specifies whether the separator
229 -- sequence is interpreted as regular expression.
230 -- @param str           String value containing the data to split up
231 -- @param pat           String with separator pattern (optional, defaults to "\n")
232 -- @param max           Maximum times to split (optional)
233 -- @param regex         Boolean indicating whether to interpret the separator
234 --                                      pattern as regular expression (optional, default is false)
235 -- @return                      Table containing the resulting substrings
236 function split(str, pat, max, regex)
237         pat = pat or "\n"
238         max = max or #str
239
240         local t = {}
241         local c = 1
242
243         if #str == 0 then
244                 return {""}
245         end
246
247         if #pat == 0 then
248                 return nil
249         end
250
251         if max == 0 then
252                 return str
253         end
254
255         repeat
256                 local s, e = str:find(pat, c, not regex)
257                 max = max - 1
258                 if s and max < 0 then
259                         t[#t+1] = str:sub(c)
260                 else
261                         t[#t+1] = str:sub(c, s and s - 1)
262                 end
263                 c = e and e + 1 or #str + 1
264         until not s or max < 0
265
266         return t
267 end
268
269 --- Remove leading and trailing whitespace from given string value.
270 -- @param str   String value containing whitespace padded data
271 -- @return              String value with leading and trailing space removed
272 function trim(str)
273         return (str:gsub("^%s*(.-)%s*$", "%1"))
274 end
275
276 --- Count the occurences of given substring in given string.
277 -- @param str           String to search in
278 -- @param pattern       String containing pattern to find
279 -- @return                      Number of found occurences
280 function cmatch(str, pat)
281         local count = 0
282         for _ in str:gmatch(pat) do count = count + 1 end
283         return count
284 end
285
286 --- Parse certain units from the given string and return the canonical integer
287 -- value or 0 if the unit is unknown. Upper- or lower case is irrelevant.
288 -- Recognized units are:
289 --      o "y"   - one year   (60*60*24*366)
290 --  o "m"       - one month  (60*60*24*31)
291 --  o "w"       - one week   (60*60*24*7)
292 --  o "d"       - one day    (60*60*24)
293 --  o "h"       - one hour       (60*60)
294 --  o "min"     - one minute (60)
295 --  o "kb"  - one kilobyte (1024)
296 --  o "mb"      - one megabyte (1024*1024)
297 --  o "gb"      - one gigabyte (1024*1024*1024)
298 --  o "kib" - one si kilobyte (1000)
299 --  o "mib"     - one si megabyte (1000*1000)
300 --  o "gib"     - one si gigabyte (1000*1000*1000)
301 -- @param ustr  String containing a numerical value with trailing unit
302 -- @return              Number containing the canonical value
303 function parse_units(ustr)
304
305         local val = 0
306
307         -- unit map
308         local map = {
309                 -- date stuff
310                 y   = 60 * 60 * 24 * 366,
311                 m   = 60 * 60 * 24 * 31,
312                 w   = 60 * 60 * 24 * 7,
313                 d   = 60 * 60 * 24,
314                 h   = 60 * 60,
315                 min = 60,
316
317                 -- storage sizes
318                 kb  = 1024,
319                 mb  = 1024 * 1024,
320                 gb  = 1024 * 1024 * 1024,
321
322                 -- storage sizes (si)
323                 kib = 1000,
324                 mib = 1000 * 1000,
325                 gib = 1000 * 1000 * 1000
326         }
327
328         -- parse input string
329         for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do
330
331                 local num = spec:gsub("[^0-9%.]+$","")
332                 local spn = spec:gsub("^[0-9%.]+", "")
333
334                 if map[spn] or map[spn:sub(1,1)] then
335                         val = val + num * ( map[spn] or map[spn:sub(1,1)] )
336                 else
337                         val = val + num
338                 end
339         end
340
341
342         return val
343 end
344
345 -- also register functions above in the central string class for convenience
346 string.escape      = escape
347 string.pcdata      = pcdata
348 string.striptags   = striptags
349 string.split       = split
350 string.trim        = trim
351 string.cmatch      = cmatch
352 string.parse_units = parse_units
353
354
355 --- Appends numerically indexed tables or single objects to a given table.
356 -- @param src   Target table
357 -- @param ...   Objects to insert
358 -- @return              Target table
359 function append(src, ...)
360         for i, a in ipairs({...}) do
361                 if type(a) == "table" then
362                         for j, v in ipairs(a) do
363                                 src[#src+1] = v
364                         end
365                 else
366                         src[#src+1] = a
367                 end
368         end
369         return src
370 end
371
372 --- Combines two or more numerically indexed tables and single objects into one table.
373 -- @param tbl1  Table value to combine
374 -- @param tbl2  Table value to combine
375 -- @param ...   More tables to combine
376 -- @return              Table value containing all values of given tables
377 function combine(...)
378         return append({}, ...)
379 end
380
381 --- Checks whether the given table contains the given value.
382 -- @param table Table value
383 -- @param value Value to search within the given table
384 -- @return              Boolean indicating whether the given value occurs within table
385 function contains(table, value)
386         for k, v in pairs(table) do
387                 if value == v then
388                         return k
389                 end
390         end
391         return false
392 end
393
394 --- Update values in given table with the values from the second given table.
395 -- Both table are - in fact - merged together.
396 -- @param t                     Table which should be updated
397 -- @param updates       Table containing the values to update
398 -- @return                      Always nil
399 function update(t, updates)
400         for k, v in pairs(updates) do
401                 t[k] = v
402         end
403 end
404
405 --- Retrieve all keys of given associative table.
406 -- @param t     Table to extract keys from
407 -- @return      Sorted table containing the keys
408 function keys(t)
409         local keys = { }
410         if t then
411                 for k, _ in kspairs(t) do
412                         keys[#keys+1] = k
413                 end
414         end
415         return keys
416 end
417
418 --- Clones the given object and return it's copy.
419 -- @param object        Table value to clone
420 -- @param deep          Boolean indicating whether to do recursive cloning
421 -- @return                      Cloned table value
422 function clone(object, deep)
423         local copy = {}
424
425         for k, v in pairs(object) do
426                 if deep and type(v) == "table" then
427                         v = clone(v, deep)
428                 end
429                 copy[k] = v
430         end
431
432         return setmetatable(copy, getmetatable(object))
433 end
434
435
436 --- Create a dynamic table which automatically creates subtables.
437 -- @return      Dynamic Table
438 function dtable()
439         return setmetatable({}, { __index =
440                 function(tbl, key)
441                         return rawget(tbl, key)
442                          or rawget(rawset(tbl, key, dtable()), key)
443                 end
444         })
445 end
446
447
448 -- Serialize the contents of a table value.
449 function _serialize_table(t, seen)
450         assert(not seen[t], "Recursion detected.")
451         seen[t] = true
452
453         local data  = ""
454         local idata = ""
455         local ilen  = 0
456
457         for k, v in pairs(t) do
458                 if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then
459                         k = serialize_data(k, seen)
460                         v = serialize_data(v, seen)
461                         data = data .. ( #data > 0 and ", " or "" ) ..
462                                 '[' .. k .. '] = ' .. v
463                 elseif k > ilen then
464                         ilen = k
465                 end
466         end
467
468         for i = 1, ilen do
469                 local v = serialize_data(t[i], seen)
470                 idata = idata .. ( #idata > 0 and ", " or "" ) .. v
471         end
472
473         return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data
474 end
475
476 --- Recursively serialize given data to lua code, suitable for restoring
477 -- with loadstring().
478 -- @param val   Value containing the data to serialize
479 -- @return              String value containing the serialized code
480 -- @see                 restore_data
481 -- @see                 get_bytecode
482 function serialize_data(val, seen)
483         seen = seen or setmetatable({}, {__mode="k"})
484
485         if val == nil then
486                 return "nil"
487         elseif type(val) == "number" then
488                 return val
489         elseif type(val) == "string" then
490                 return "%q" % val
491         elseif type(val) == "boolean" then
492                 return val and "true" or "false"
493         elseif type(val) == "function" then
494                 return "loadstring(%q)" % get_bytecode(val)
495         elseif type(val) == "table" then
496                 return "{ " .. _serialize_table(val, seen) .. " }"
497         else
498                 return '"[unhandled data type:' .. type(val) .. ']"'
499         end
500 end
501
502 --- Restore data previously serialized with serialize_data().
503 -- @param str   String containing the data to restore
504 -- @return              Value containing the restored data structure
505 -- @see                 serialize_data
506 -- @see                 get_bytecode
507 function restore_data(str)
508         return loadstring("return " .. str)()
509 end
510
511
512 --
513 -- Byte code manipulation routines
514 --
515
516 --- Return the current runtime bytecode of the given data. The byte code
517 -- will be stripped before it is returned.
518 -- @param val   Value to return as bytecode
519 -- @return              String value containing the bytecode of the given data
520 function get_bytecode(val)
521         local code
522
523         if type(val) == "function" then
524                 code = string.dump(val)
525         else
526                 code = string.dump( loadstring( "return " .. serialize_data(val) ) )
527         end
528
529         return code and strip_bytecode(code)
530 end
531
532 --- Strips unnescessary lua bytecode from given string. Information like line
533 -- numbers and debugging numbers will be discarded. Original version by
534 -- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html)
535 -- @param code  String value containing the original lua byte code
536 -- @return              String value containing the stripped lua byte code
537 function strip_bytecode(code)
538         local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12)
539         local subint
540         if endian == 1 then
541                 subint = function(code, i, l)
542                         local val = 0
543                         for n = l, 1, -1 do
544                                 val = val * 256 + code:byte(i + n - 1)
545                         end
546                         return val, i + l
547                 end
548         else
549                 subint = function(code, i, l)
550                         local val = 0
551                         for n = 1, l, 1 do
552                                 val = val * 256 + code:byte(i + n - 1)
553                         end
554                         return val, i + l
555                 end
556         end
557
558         local function strip_function(code)
559                 local count, offset = subint(code, 1, size)
560                 local stripped = { string.rep("\0", size) }
561                 local dirty = offset + count
562                 offset = offset + count + int * 2 + 4
563                 offset = offset + int + subint(code, offset, int) * ins
564                 count, offset = subint(code, offset, int)
565                 for n = 1, count do
566                         local t
567                         t, offset = subint(code, offset, 1)
568                         if t == 1 then
569                                 offset = offset + 1
570                         elseif t == 4 then
571                                 offset = offset + size + subint(code, offset, size)
572                         elseif t == 3 then
573                                 offset = offset + num
574                         elseif t == 254 or t == 9 then
575                                 offset = offset + lnum
576                         end
577                 end
578                 count, offset = subint(code, offset, int)
579                 stripped[#stripped+1] = code:sub(dirty, offset - 1)
580                 for n = 1, count do
581                         local proto, off = strip_function(code:sub(offset, -1))
582                         stripped[#stripped+1] = proto
583                         offset = offset + off - 1
584                 end
585                 offset = offset + subint(code, offset, int) * int + int
586                 count, offset = subint(code, offset, int)
587                 for n = 1, count do
588                         offset = offset + subint(code, offset, size) + size + int * 2
589                 end
590                 count, offset = subint(code, offset, int)
591                 for n = 1, count do
592                         offset = offset + subint(code, offset, size) + size
593                 end
594                 stripped[#stripped+1] = string.rep("\0", int * 3)
595                 return table.concat(stripped), offset
596         end
597
598         return code:sub(1,12) .. strip_function(code:sub(13,-1))
599 end
600
601
602 --
603 -- Sorting iterator functions
604 --
605
606 function _sortiter( t, f )
607         local keys = { }
608
609         for k, v in pairs(t) do
610                 keys[#keys+1] = k
611         end
612
613         local _pos = 0
614
615         table.sort( keys, f )
616
617         return function()
618                 _pos = _pos + 1
619                 if _pos <= #keys then
620                         return keys[_pos], t[keys[_pos]]
621                 end
622         end
623 end
624
625 --- Return a key, value iterator which returns the values sorted according to
626 -- the provided callback function.
627 -- @param t     The table to iterate
628 -- @param f A callback function to decide the order of elements
629 -- @return      Function value containing the corresponding iterator
630 function spairs(t,f)
631         return _sortiter( t, f )
632 end
633
634 --- Return a key, value iterator for the given table.
635 -- The table pairs are sorted by key.
636 -- @param t     The table to iterate
637 -- @return      Function value containing the corresponding iterator
638 function kspairs(t)
639         return _sortiter( t )
640 end
641
642 --- Return a key, value iterator for the given table.
643 -- The table pairs are sorted by value.
644 -- @param t     The table to iterate
645 -- @return      Function value containing the corresponding iterator
646 function vspairs(t)
647         return _sortiter( t, function (a,b) return t[a] < t[b] end )
648 end
649
650
651 --
652 -- System utility functions
653 --
654
655 --- Test whether the current system is operating in big endian mode.
656 -- @return      Boolean value indicating whether system is big endian
657 function bigendian()
658         return string.byte(string.dump(function() end), 7) == 0
659 end
660
661 --- Execute given commandline and gather stdout.
662 -- @param command       String containing command to execute
663 -- @return                      String containing the command's stdout
664 function exec(command)
665         local pp   = io.popen(command)
666         local data = pp:read("*a")
667         pp:close()
668
669         return data
670 end
671
672 --- Return a line-buffered iterator over the output of given command.
673 -- @param command       String containing the command to execute
674 -- @return                      Iterator
675 function execi(command)
676         local pp = io.popen(command)
677
678         return pp and function()
679                 local line = pp:read()
680
681                 if not line then
682                         pp:close()
683                 end
684
685                 return line
686         end
687 end
688
689 -- Deprecated
690 function execl(command)
691         local pp   = io.popen(command)
692         local line = ""
693         local data = {}
694
695         while true do
696                 line = pp:read()
697                 if (line == nil) then break end
698                 data[#data+1] = line
699         end
700         pp:close()
701
702         return data
703 end
704
705 --- Returns the absolute path to LuCI base directory.
706 -- @return              String containing the directory path
707 function libpath()
708         return require "luci.fs".dirname(ldebug.__file__)
709 end
710
711
712 --
713 -- Coroutine safe xpcall and pcall versions modified for Luci
714 -- original version:
715 -- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org)
716 --
717 -- Copyright © 2005 Kepler Project.
718 -- Permission is hereby granted, free of charge, to any person obtaining a
719 -- copy of this software and associated documentation files (the "Software"),
720 -- to deal in the Software without restriction, including without limitation
721 -- the rights to use, copy, modify, merge, publish, distribute, sublicense,
722 -- and/or sell copies of the Software, and to permit persons to whom the
723 -- Software is furnished to do so, subject to the following conditions:
724 --
725 -- The above copyright notice and this permission notice shall be
726 -- included in all copies or substantial portions of the Software.
727 --
728 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
729 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
730 -- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
731 -- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
732 -- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
733 -- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
734 -- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
735
736 local performResume, handleReturnValue
737 local oldpcall, oldxpcall = pcall, xpcall
738 coxpt = {}
739 setmetatable(coxpt, {__mode = "kv"})
740
741 -- Identity function for copcall
742 local function copcall_id(trace, ...)
743   return ...
744 end
745
746 --- This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function
747 -- @param f             Lua function to be called protected
748 -- @param err   Custom error handler
749 -- @param ...   Parameters passed to the function
750 -- @return              A boolean whether the function call succeeded and the return
751 --                              values of either the function or the error handler
752 function coxpcall(f, err, ...)
753         local res, co = oldpcall(coroutine.create, f)
754         if not res then
755                 local params = {...}
756                 local newf = function() return f(unpack(params)) end
757                 co = coroutine.create(newf)
758         end
759         local c = coroutine.running()
760         coxpt[co] = coxpt[c] or c or 0
761
762         return performResume(err, co, ...)
763 end
764
765 --- This is a coroutine-safe drop-in replacement for Lua's "pcall"-function
766 -- @param f             Lua function to be called protected
767 -- @param ...   Parameters passed to the function
768 -- @return              A boolean whether the function call succeeded and the returns
769 --                              values of the function or the error object
770 function copcall(f, ...)
771         return coxpcall(f, copcall_id, ...)
772 end
773
774 -- Handle return value of protected call
775 function handleReturnValue(err, co, status, ...)
776         if not status then
777                 return false, err(debug.traceback(co, (...)), ...)
778         end
779         if coroutine.status(co) == 'suspended' then
780                 return performResume(err, co, coroutine.yield(...))
781         else
782                 return true, ...
783         end
784 end
785
786 -- Resume execution of protected function call
787 function performResume(err, co, ...)
788         if get_memory_limit and get_memory_limit() > 0 and
789            collectgarbage("count") > (get_memory_limit() * 0.8)
790         then
791                 collectgarbage("collect")
792         end
793
794         return handleReturnValue(err, co, coroutine.resume(co, ...))
795 end