aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Ierusalimschy <roberto@inf.puc-rio.br>2020-10-06 13:37:41 -0300
committerRoberto Ierusalimschy <roberto@inf.puc-rio.br>2020-10-12 12:29:09 -0300
commit9ecd446141f572252a57cb33d2bba6aa00d96a55 (patch)
treececd4f91efc181f652d6647e18772732262422a5
parentfb172d0a929432856983a51d4139f705d4c01365 (diff)
downloadlua-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.c54
-rw-r--r--testes/cstack.lua50
2 files changed, 87 insertions, 17 deletions
diff --git a/ldo.c b/ldo.c
index 052c57a9..755db693 100644
--- a/ldo.c
+++ b/ldo.c
@@ -207,50 +207,72 @@ int luaD_reallocstack (lua_State *L, int newsize, int raiseerror) {
207*/ 207*/
208int luaD_growstack (lua_State *L, int n, int raiseerror) { 208int 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
234static int stackinuse (lua_State *L) { 239static 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*/
245void luaD_shrinkstack (lua_State *L) { 263void 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
5print"testing C-stack overflow detection" 5print"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)
99end 99end
100 100
101
102if 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"+"
147end
148
101print'OK' 149print'OK'