Skip to content

Commit bea5d8b

Browse files
committed
Fix LogDrawer line accumulation causing slowdown over time
1 parent a928541 commit bea5d8b

File tree

3 files changed

+46
-1
lines changed

3 files changed

+46
-1
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ Release Notes ⋮
3636
otherwise. See <doc/model_durability_and_atomicity.md> for details.
3737
* Failed transactions are now explicitly rolled back correctly.
3838

39+
* Performance improvements
40+
* The log drawer no longer slows down over time when many lines are written.
41+
Old lines are automatically trimmed once the log exceeds 5,000 lines.
42+
3943
* Shell changes
4044
* `Resource.delete()` and `ResourceRevision.delete()` now are asynchronous
4145
rather than synchronous, returning a `Future` to track completion.

src/crystal/tests/ui/main_window/test_log_drawer.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from collections.abc import AsyncIterator
2-
from contextlib import asynccontextmanager
2+
from contextlib import asynccontextmanager, redirect_stdout
33
from crystal.server import get_request_url
4+
from crystal.tests.util.asserts import assertEqual
45
from crystal.tests.util.server import assert_does_open_webbrowser_to, extracted_project
56
from crystal.tests.util.windows import MainWindow, OpenOrCreateDialog
67
from crystal.ui.log_drawer import LogDrawer
78
from crystal.util.controls import click_button, TreeItem
9+
from io import StringIO
810
from unittest import skip
11+
from unittest.mock import patch
912
import wx
1013
from wx.richtext import RichTextCtrl
1114

@@ -65,6 +68,36 @@ def test_always_displays_vertical_scrollbar_and_never_displays_horizontal_scroll
6568
pass
6669

6770

71+
async def test_when_write_many_lines_to_writer_beyond_max_then_leading_lines_trimmed() -> None:
72+
MAX_LINE_COUNT = 50
73+
with patch('crystal.ui.log_drawer._MAX_LINE_COUNT', MAX_LINE_COUNT):
74+
async with _log_drawer_visible() as (mw, log_drawer):
75+
textarea = log_drawer._textarea
76+
writer = log_drawer.writer
77+
78+
# Count lines already present (from server startup banner)
79+
initial_line_count = textarea.GetNumberOfLines()
80+
81+
# Write lines until we reach exactly MAX_LINE_COUNT
82+
lines_to_add = MAX_LINE_COUNT - initial_line_count
83+
with redirect_stdout(StringIO()):
84+
for i in range(lines_to_add):
85+
writer.write(f'line {i}\n')
86+
assertEqual(MAX_LINE_COUNT, textarea.GetNumberOfLines())
87+
88+
# Write one more line, which should cause the leading line to be trimmed
89+
final_line_text = 'this is the final line'
90+
with redirect_stdout(StringIO()):
91+
writer.write(f'{final_line_text}\n')
92+
assertEqual(MAX_LINE_COUNT, textarea.GetNumberOfLines(),
93+
'Line count should still be MAX_LINE_COUNT after trimming')
94+
95+
# Verify the last line of content is the one we just wrote
96+
last_content_line = textarea.GetLineText(MAX_LINE_COUNT - 2)
97+
assertEqual(final_line_text, last_content_line,
98+
'Last content line should be the final line we wrote')
99+
100+
68101
# === Test: Drag or Double-Click Sash ===
69102

70103
@skip('not yet automated')

src/crystal/ui/log_drawer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def _hexcolor_to_color(hexcolor: str) -> wx.Colour:
3535
(cli.TERMINAL_FG_PURPLE, _hexcolor_to_color('#D43BD2')),
3636
]) # type: dict[str, wx.Colour]
3737
_RESET_CODE = cli.TERMINAL_RESET
38+
_MAX_LINE_COUNT = 5000 # prevent unbounded growth over time
3839

3940

4041
# TODO: Avoid inheriting directly from wx.Frame because doing so
@@ -476,6 +477,13 @@ def fg_task() -> None:
476477
# from any text copied (or cut) from the text area.
477478
textarea.WriteText('\u200b') # zero-width space
478479

480+
# Trim leading lines if the total exceeds the maximum
481+
line_count = textarea.GetNumberOfLines()
482+
if line_count > _MAX_LINE_COUNT:
483+
lines_to_remove = line_count - _MAX_LINE_COUNT
484+
end_of_removed = textarea.XYToPosition(0, lines_to_remove)
485+
textarea.Remove(0, end_of_removed)
486+
479487
if textarea_was_scrolled_to_bottom:
480488
self._drawer._scroll_to_bottom()
481489
fg_call_later(fg_task)

0 commit comments

Comments
 (0)