Skip to content

Commit 1260252

Browse files
Merge branch 'fix/segment-fault' into develop
2 parents f5f6c44 + 3d847f6 commit 1260252

4 files changed

Lines changed: 28 additions & 45 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# PythonQwt Releases
22

3+
## Version 0.16.1
4+
5+
### Bug fixes
6+
7+
- Fixed [Issue #107](https://github.com/PlotPyStack/PythonQwt/issues/107): corrected a Windows crash/access violation that could occur in long-running sessions creating and rendering large amounts of data, due to GDI handle exhaustion caused by object lifetime and cache growth
8+
- Preserved the rendering performance improvements introduced in 0.16.0 while restoring safer Qt object ownership for internal text/scale-draw private data and adding defensive limits to font-metrics caches
9+
10+
311
## Version 0.16.0
412

513
### Performance

qwt/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from qwt.text import QwtText # noqa: F401
6464
from qwt.toqimage import array_to_qimage as toQImage # noqa: F401
6565

66-
__version__ = "0.16.0"
66+
__version__ = "0.16.1"
6767
QWT_VERSION_STR = "6.1.5"
6868

6969

qwt/scale_draw.py

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from qtpy.QtCore import (
2626
QLineF,
27+
QObject,
2728
QPoint,
2829
QPointF,
2930
QRect,
@@ -50,22 +51,11 @@
5051
_ALIGN_BOTTOM = int(Qt.AlignBottom)
5152

5253

53-
class QwtAbstractScaleDraw_PrivateData(object):
54-
# See QwtText_PrivateData: ``QObject`` inheritance is unused and the
55-
# base class' ``__init__`` is a measurable cost in tick-heavy renders.
56-
__slots__ = (
57-
"spacing",
58-
"penWidth",
59-
"minExtent",
60-
"components",
61-
"tick_length",
62-
"tick_lighter_factor",
63-
"map",
64-
"scaleDiv",
65-
"labelCache",
66-
)
54+
class QwtAbstractScaleDraw_PrivateData(QObject):
55+
# QObject base class restored for Qt parent/child ownership semantics.
6756

6857
def __init__(self):
58+
QObject.__init__(self)
6959
self.spacing = 4
7060
self.penWidth = 0
7161
self.minExtent = 0.0
@@ -492,20 +482,11 @@ def invalidateCache(self):
492482
self.__data.labelCache.clear()
493483

494484

495-
class QwtScaleDraw_PrivateData(object):
496-
# See QwtText_PrivateData: ``QObject`` inheritance is unused and the
497-
# base class' ``__init__`` is a measurable cost in tick-heavy renders.
498-
__slots__ = (
499-
"len",
500-
"alignment",
501-
"orientation",
502-
"labelAlignment",
503-
"labelRotation",
504-
"labelAutoSize",
505-
"pos",
506-
)
485+
class QwtScaleDraw_PrivateData(QObject):
486+
# QObject base class restored for Qt parent/child ownership semantics.
507487

508488
def __init__(self):
489+
QObject.__init__(self)
509490
self.len = 0
510491
self.alignment = QwtScaleDraw.BottomScale
511492
# Cached orientation - kept in sync by ``QwtScaleDraw.setAlignment``

qwt/text.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ def draw(self, painter, rect, flags, text):
234234
_FONT_TUPLE_CACHE: dict = {} # tuple_key -> tuple_key (interning, also acts
235235
# as the "have we seen this logical font" set)
236236
_FONT_KEY_CACHE_LIMIT = 1024
237+
_FM_CACHE_LIMIT = 256 # max QFontMetrics/QFontMetricsF entries per engine
237238

238239

239240
def _font_tuple_key(font):
@@ -317,13 +318,17 @@ def fontmetrics(self, font):
317318
try:
318319
return self._fm_cache[fid]
319320
except KeyError:
321+
if len(self._fm_cache) >= _FM_CACHE_LIMIT:
322+
self._fm_cache.clear()
320323
return self._fm_cache.setdefault(fid, QFontMetrics(font))
321324

322325
def fontmetrics_f(self, font):
323326
fid = font_key_cached(font)
324327
try:
325328
return self._fm_cache_f[fid]
326329
except KeyError:
330+
if len(self._fm_cache_f) >= _FM_CACHE_LIMIT:
331+
self._fm_cache_f.clear()
327332
return self._fm_cache_f.setdefault(fid, QFontMetricsF(font))
328333

329334
def heightForWidth(self, font, flags, text, width):
@@ -359,6 +364,8 @@ def effectiveAscent(self, font):
359364
ascent = ASCENTCACHE.get(fontKey)
360365
if ascent is not None:
361366
return ascent
367+
if len(ASCENTCACHE) >= _FM_CACHE_LIMIT:
368+
ASCENTCACHE.clear()
362369
return ASCENTCACHE.setdefault(fontKey, self.findAscent(font))
363370

364371
def findAscent(self, font):
@@ -411,6 +418,8 @@ def textMargins(self, font):
411418
if cached is None:
412419
fm = self.fontmetrics(font)
413420
cached = (0, 0, fm.ascent() - self.effectiveAscent(font), fm.descent())
421+
if len(self._margins_cache) >= _FM_CACHE_LIMIT:
422+
self._margins_cache.clear()
414423
self._margins_cache[fkey] = cached
415424
self._margins_last_id = font_id
416425
self._margins_last_value = cached
@@ -557,26 +566,11 @@ def textMargins(self, font):
557566
return 0, 0, 0, 0
558567

559568

560-
class QwtText_PrivateData(object):
561-
# ``QObject`` was previously used as the base class but no Qt signals
562-
# or events are ever emitted from ``_PrivateData`` containers and the
563-
# ``QObject.__init__`` call dominates ``QwtText.__init__`` (it is the
564-
# single most expensive line for tick-label-heavy renders, see
565-
# https://github.com/PlotPyStack/PythonQwt/issues/93).
566-
__slots__ = (
567-
"renderFlags",
568-
"borderRadius",
569-
"borderPen",
570-
"backgroundBrush",
571-
"paintAttributes",
572-
"layoutAttributes",
573-
"textEngine",
574-
"text",
575-
"font",
576-
"color",
577-
)
569+
class QwtText_PrivateData(QObject):
570+
# QObject base class restored for Qt parent/child ownership semantics.
578571

579572
def __init__(self):
573+
QObject.__init__(self)
580574
self.renderFlags = Qt.AlignCenter
581575
self.borderRadius = 0
582576
self.borderPen = Qt.NoPen

0 commit comments

Comments
 (0)