When uninterrupted, long-running scripts execute under the Javascript debugger, and those scripts have conditional breakpoints, WebProcess will consume huge amounts of memory for seemingly little reason. On the test case attached, with the conditional breakpoints, memory consumption continually grows to the point where there's no more free memory available. With the conditional breakpoints off, memory usage remains about stable. STEPS TO REPRODUCE 1. Launch the attached web page 2. Open the Web inspector, start the debugger 3. Add 4 conditional breakpoints, one per line inside the `for` loop of the `foo` function, and give them an unlikely condition (`i == -1` for instance) 4. With your favorite diagnostics tool, watch WebProcess eat up all the memory it can
Created attachment 154228 [details] The test case
This problem is more obvious in memory-limited environment (e.g. mobile). The root reason is that, for failed condition of breakpoint, JSC doesn't run the EventLoop, so in a dense loop of js code, there is no chance to recycle garbage caused by evaluating the breakpoint's condition. There is an interesting test. if you put a normal breakpoint after the breakpoint with failed condition, the GC is working well. Since for a success breakpoint JSC run an EventLoop.
Created attachment 179222 [details] Patch
Comment on attachment 179222 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=179222&action=review > Source/WebCore/ChangeLog:11 > + No new test case. Can you add a layout test for this? Since the problem is on the back-end you can add a protocol test for it(LayoutTests/inspector-protocol)
(In reply to comment #4) > (From update of attachment 179222 [details]) > View in context: https://bugs.webkit.org/attachment.cgi?id=179222&action=review > > > Source/WebCore/ChangeLog:11 > > + No new test case. > > Can you add a layout test for this? Since the problem is on the back-end you can add a protocol test for it(LayoutTests/inspector-protocol) ok. Let me a try.
(In reply to comment #2) > This problem is more obvious in memory-limited environment (e.g. mobile). > > The root reason is that, for failed condition of breakpoint, JSC doesn't run the EventLoop, so in a dense loop of js code, there is no chance to recycle garbage caused by evaluating the breakpoint's condition. > It sounds weird that the issue is specific to breakpoint conditions. How does it work in case of eval producing a lot of garbage in a tight loop in user code? > There is an interesting test. if you put a normal breakpoint after the breakpoint with failed condition, the GC is working well. Since for a success breakpoint JSC run an EventLoop.
(In reply to comment #6) > (In reply to comment #2) > > This problem is more obvious in memory-limited environment (e.g. mobile). > > > > The root reason is that, for failed condition of breakpoint, JSC doesn't run the EventLoop, so in a dense loop of js code, there is no chance to recycle garbage caused by evaluating the breakpoint's condition. > > > It sounds weird that the issue is specific to breakpoint conditions. How does it work in case of eval producing a lot of garbage in a tight loop in user code? > Sorry to reply so late, since I spent some time to investigate the related code of JSC. For the code like this: for (var i=0;i<0xfffff;i++) { ... eval("..."); ... } The "EvalExecutable" is allocated just once and saved as an argument of current callFrame (Interpreter.cpp:149). But to evaluate the condition of breakpoint, the DebuggerCallFrame::evaluate is invoked, the "EvalExecutable" is allocated every time. Maybe, because it's difficult to bind the breakpoint-condition's string with a certain callFrame.
(In reply to comment #7) > (In reply to comment #6) > > (In reply to comment #2) > > > This problem is more obvious in memory-limited environment (e.g. mobile). > > > > > > The root reason is that, for failed condition of breakpoint, JSC doesn't run the EventLoop, so in a dense loop of js code, there is no chance to recycle garbage caused by evaluating the breakpoint's condition. > > > > > It sounds weird that the issue is specific to breakpoint conditions. How does it work in case of eval producing a lot of garbage in a tight loop in user code? > > > Sorry to reply so late, since I spent some time to investigate the related code of JSC. For the code like this: > for (var i=0;i<0xfffff;i++) { > ... > eval("..."); > ... > } > The "EvalExecutable" is allocated just once and saved as an argument of current callFrame (Interpreter.cpp:149). > > But to evaluate the condition of breakpoint, the DebuggerCallFrame::evaluate is invoked, the "EvalExecutable" is allocated every time. Maybe, because it's difficult to bind the breakpoint-condition's string with a certain callFrame. Why JSC cannot run GC automatically during the breakpoint condition evaluation if it needs to free some memory? We need someone who understands JSC garbage collector details to look at this change.
(In reply to comment #8) > (In reply to comment #7) > > (In reply to comment #6) > > > (In reply to comment #2) > > > > This problem is more obvious in memory-limited environment (e.g. mobile). > > > > > > > > The root reason is that, for failed condition of breakpoint, JSC doesn't run the EventLoop, so in a dense loop of js code, there is no chance to recycle garbage caused by evaluating the breakpoint's condition. > > > > > > > It sounds weird that the issue is specific to breakpoint conditions. How does it work in case of eval producing a lot of garbage in a tight loop in user code? > > > > > Sorry to reply so late, since I spent some time to investigate the related code of JSC. For the code like this: > > for (var i=0;i<0xfffff;i++) { > > ... > > eval("..."); > > ... > > } > > The "EvalExecutable" is allocated just once and saved as an argument of current callFrame (Interpreter.cpp:149). > > > > But to evaluate the condition of breakpoint, the DebuggerCallFrame::evaluate is invoked, the "EvalExecutable" is allocated every time. Maybe, because it's difficult to bind the breakpoint-condition's string with a certain callFrame. > > Why JSC cannot run GC automatically during the breakpoint condition evaluation if it needs to free some memory? We need someone who understands JSC garbage collector details to look at this change. I'm not expert of it. It depends on the strategy of port, usually JSC does GC with a interval-changeable timer. So need to run EventLoop to handle the timer message. I believe the design of JSC takes repeat "eval()" into account, but the mechanism of debugger works-around it. Welcome JSC's experts to have a look.
This still repros on TOT.
<rdar://problem/19096505>