diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2020-10-06 13:37:41 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2020-10-12 12:29:09 -0300 |
commit | 9ecd446141f572252a57cb33d2bba6aa00d96a55 (patch) | |
tree | cecd4f91efc181f652d6647e18772732262422a5 | |
parent | fb172d0a929432856983a51d4139f705d4c01365 (diff) | |
download | lua-9ecd446141f572252a57cb33d2bba6aa00d96a55.tar.gz lua-9ecd446141f572252a57cb33d2bba6aa00d96a55.tar.bz2 lua-9ecd446141f572252a57cb33d2bba6aa00d96a55.zip |
Avoid shrinking stacks to often
Shrink a stack only when the final stack size can be at most 2/3 the
previous size with half of its entries empty. This commit also
improves the clarity of 'luaD_growstack'.
-rw-r--r-- | ldo.c | 54 | ||||
-rw-r--r-- | testes/cstack.lua | 50 |
2 files changed, 87 insertions, 17 deletions
@@ -207,50 +207,72 @@ int luaD_reallocstack (lua_State *L, int newsize, int raiseerror) { | |||
207 | */ | 207 | */ |
208 | int luaD_growstack (lua_State *L, int n, int raiseerror) { | 208 | int luaD_growstack (lua_State *L, int n, int raiseerror) { |
209 | int size = L->stacksize; | 209 | int size = L->stacksize; |
210 | int newsize = 2 * size; /* tentative new size */ | 210 | if (unlikely(size > LUAI_MAXSTACK)) { |
211 | if (unlikely(size > LUAI_MAXSTACK)) { /* need more space after extra size? */ | 211 | /* if stack is larger than maximum, thread is already using the |
212 | extra space reserved for errors, that is, thread is handling | ||
213 | a stack error; cannot grow further than that. */ | ||
214 | lua_assert(L->stacksize == ERRORSTACKSIZE); | ||
212 | if (raiseerror) | 215 | if (raiseerror) |
213 | luaD_throw(L, LUA_ERRERR); /* error inside message handler */ | 216 | luaD_throw(L, LUA_ERRERR); /* error inside message handler */ |
214 | else return 0; | 217 | return 0; /* if not 'raiseerror', just signal it */ |
215 | } | 218 | } |
216 | else { | 219 | else { |
220 | int newsize = 2 * size; /* tentative new size */ | ||
217 | int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK; | 221 | int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK; |
218 | if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ | 222 | if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ |
219 | newsize = LUAI_MAXSTACK; | 223 | newsize = LUAI_MAXSTACK; |
220 | if (newsize < needed) /* but must respect what was asked for */ | 224 | if (newsize < needed) /* but must respect what was asked for */ |
221 | newsize = needed; | 225 | newsize = needed; |
222 | if (unlikely(newsize > LUAI_MAXSTACK)) { /* stack overflow? */ | 226 | if (likely(newsize <= LUAI_MAXSTACK)) |
227 | return luaD_reallocstack(L, newsize, raiseerror); | ||
228 | else { /* stack overflow */ | ||
223 | /* add extra size to be able to handle the error message */ | 229 | /* add extra size to be able to handle the error message */ |
224 | luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); | 230 | luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); |
225 | if (raiseerror) | 231 | if (raiseerror) |
226 | luaG_runerror(L, "stack overflow"); | 232 | luaG_runerror(L, "stack overflow"); |
227 | else return 0; | 233 | return 0; |
228 | } | 234 | } |
229 | } /* else no errors */ | 235 | } |
230 | return luaD_reallocstack(L, newsize, raiseerror); | ||
231 | } | 236 | } |
232 | 237 | ||
233 | 238 | ||
234 | static int stackinuse (lua_State *L) { | 239 | static int stackinuse (lua_State *L) { |
235 | CallInfo *ci; | 240 | CallInfo *ci; |
241 | int res; | ||
236 | StkId lim = L->top; | 242 | StkId lim = L->top; |
237 | for (ci = L->ci; ci != NULL; ci = ci->previous) { | 243 | for (ci = L->ci; ci != NULL; ci = ci->previous) { |
238 | if (lim < ci->top) lim = ci->top; | 244 | if (lim < ci->top) lim = ci->top; |
239 | } | 245 | } |
240 | lua_assert(lim <= L->stack_last); | 246 | lua_assert(lim <= L->stack_last); |
241 | return cast_int(lim - L->stack) + 1; /* part of stack in use */ | 247 | res = cast_int(lim - L->stack) + 1; /* part of stack in use */ |
248 | if (res < LUA_MINSTACK) | ||
249 | res = LUA_MINSTACK; /* ensure a minimum size */ | ||
250 | return res; | ||
242 | } | 251 | } |
243 | 252 | ||
244 | 253 | ||
254 | /* | ||
255 | ** If stack size is more than 3 times the current use, reduce that size | ||
256 | ** to twice the current use. (So, the final stack size is at most 2/3 the | ||
257 | ** previous size, and half of its entries are empty.) | ||
258 | ** As a particular case, if stack was handling a stack overflow and now | ||
259 | ** it is not, 'max' (limited by LUAI_MAXSTACK) will be smaller than | ||
260 | ** 'stacksize' (equal to ERRORSTACKSIZE in this case), and so the stack | ||
261 | ** will be reduced to a "regular" size. | ||
262 | */ | ||
245 | void luaD_shrinkstack (lua_State *L) { | 263 | void luaD_shrinkstack (lua_State *L) { |
246 | int inuse = stackinuse(L); | 264 | int inuse = stackinuse(L); |
247 | int goodsize = inuse + BASIC_STACK_SIZE; | 265 | int nsize = inuse * 2; /* proposed new size */ |
248 | if (goodsize > LUAI_MAXSTACK) | 266 | int max = inuse * 3; /* maximum "reasonable" size */ |
249 | goodsize = LUAI_MAXSTACK; /* respect stack limit */ | 267 | if (max > LUAI_MAXSTACK) { |
268 | max = LUAI_MAXSTACK; /* respect stack limit */ | ||
269 | if (nsize > LUAI_MAXSTACK) | ||
270 | nsize = LUAI_MAXSTACK; | ||
271 | } | ||
250 | /* if thread is currently not handling a stack overflow and its | 272 | /* if thread is currently not handling a stack overflow and its |
251 | good size is smaller than current size, shrink its stack */ | 273 | size is larger than maximum "reasonable" size, shrink it */ |
252 | if (inuse <= (LUAI_MAXSTACK - EXTRA_STACK) && goodsize < L->stacksize) | 274 | if (inuse <= (LUAI_MAXSTACK - EXTRA_STACK) && L->stacksize > max) |
253 | luaD_reallocstack(L, goodsize, 0); /* ok if that fails */ | 275 | luaD_reallocstack(L, nsize, 0); /* ok if that fails */ |
254 | else /* don't change stack */ | 276 | else /* don't change stack */ |
255 | condmovestack(L,{},{}); /* (change only for debugging) */ | 277 | condmovestack(L,{},{}); /* (change only for debugging) */ |
256 | luaE_shrinkCI(L); /* shrink CI list */ | 278 | luaE_shrinkCI(L); /* shrink CI list */ |
@@ -625,7 +647,7 @@ static int recover (lua_State *L, int status) { | |||
625 | luaD_seterrorobj(L, status, oldtop); | 647 | luaD_seterrorobj(L, status, oldtop); |
626 | L->ci = ci; | 648 | L->ci = ci; |
627 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ | 649 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ |
628 | luaD_shrinkstack(L); | 650 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ |
629 | L->errfunc = ci->u.c.old_errfunc; | 651 | L->errfunc = ci->u.c.old_errfunc; |
630 | return 1; /* continue running the coroutine */ | 652 | return 1; /* continue running the coroutine */ |
631 | } | 653 | } |
@@ -768,7 +790,7 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u, | |||
768 | status = luaF_close(L, oldtop, status); | 790 | status = luaF_close(L, oldtop, status); |
769 | oldtop = restorestack(L, old_top); /* previous call may change stack */ | 791 | oldtop = restorestack(L, old_top); /* previous call may change stack */ |
770 | luaD_seterrorobj(L, status, oldtop); | 792 | luaD_seterrorobj(L, status, oldtop); |
771 | luaD_shrinkstack(L); | 793 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ |
772 | } | 794 | } |
773 | L->errfunc = old_errfunc; | 795 | L->errfunc = old_errfunc; |
774 | return status; | 796 | return status; |
diff --git a/testes/cstack.lua b/testes/cstack.lua index 5767adf6..8ac48e89 100644 --- a/testes/cstack.lua +++ b/testes/cstack.lua | |||
@@ -2,7 +2,7 @@ | |||
2 | -- See Copyright Notice in file all.lua | 2 | -- See Copyright Notice in file all.lua |
3 | 3 | ||
4 | 4 | ||
5 | print"testing C-stack overflow detection" | 5 | print"testing stack overflow detection" |
6 | 6 | ||
7 | -- Segmentation faults in these tests probably result from a C-stack | 7 | -- Segmentation faults in these tests probably result from a C-stack |
8 | -- overflow. To avoid these errors, you should set a smaller limit for | 8 | -- overflow. To avoid these errors, you should set a smaller limit for |
@@ -98,4 +98,52 @@ do | |||
98 | print("final count: ", count) | 98 | print("final count: ", count) |
99 | end | 99 | end |
100 | 100 | ||
101 | |||
102 | if T then | ||
103 | print("testing stack recovery") | ||
104 | local N = 0 -- trace number of calls | ||
105 | local LIM = -1 -- will store N just before stack overflow | ||
106 | |||
107 | -- trace stack size; after stack overflow, it should be | ||
108 | -- the maximum allowed stack size. | ||
109 | local stack1 | ||
110 | local dummy | ||
111 | |||
112 | local function err(msg) | ||
113 | assert(string.find(msg, "stack overflow")) | ||
114 | local _, stacknow = T.stacklevel() | ||
115 | assert(stacknow == stack1 + 200) | ||
116 | end | ||
117 | |||
118 | -- When LIM==-1, the 'if' is not executed, so this function only | ||
119 | -- counts and stores the stack limits up to overflow. Then, LIM | ||
120 | -- becomes N, and then the 'if' code is run when the stack is | ||
121 | -- full. Then, there is a stack overflow inside 'xpcall', after which | ||
122 | -- the stack must have been restored back to its maximum normal size. | ||
123 | local function f() | ||
124 | dummy, stack1 = T.stacklevel() | ||
125 | if N == LIM then | ||
126 | xpcall(f, err) | ||
127 | local _, stacknow = T.stacklevel() | ||
128 | assert(stacknow == stack1) | ||
129 | return | ||
130 | end | ||
131 | N = N + 1 | ||
132 | f() | ||
133 | end | ||
134 | |||
135 | local topB, sizeB -- top and size Before overflow | ||
136 | local topA, sizeA -- top and size After overflow | ||
137 | topB, sizeB = T.stacklevel() | ||
138 | xpcall(f, err) | ||
139 | topA, sizeA = T.stacklevel() | ||
140 | -- sizes should be comparable | ||
141 | assert(topA == topB and sizeA < sizeB * 2) | ||
142 | print(string.format("maximum stack size: %d", stack1)) | ||
143 | LIM = N -- will stop recursion at maximum level | ||
144 | N = 0 -- to count again | ||
145 | f() | ||
146 | print"+" | ||
147 | end | ||
148 | |||
101 | print'OK' | 149 | print'OK' |