Unexpected segfault appearing in PyQt6 6.9.1
Maurizio Berti
maurizio.berti at gmail.com
Wed Nov 19 03:05:23 GMT 2025
Il giorno lun 17 nov 2025 alle ore 00:45 Jakub Fránek <j.franek95 at gmail.com>
ha scritto:
> What my code did previously was something like this, in Pythonesque
> pseudocode:
>
> custom_source_model.beginResetModel()
> custom_source_model.load_data(new_data)
> source_index = custom_source_model.get_index(some_item)
> proxy_index = proxy.mapFromSource(source_index) # crash occurs here
> # irrelevant lines of code which utilize the proxy_index somehow...
> custom_source_model.endResetModel()
>
> My understanding is that what I did is incorrect and I should not perform
> any operations on the proxy model while the main model is in reset. When I
> postpone these index related operations until after the
> custom_source_model.endResetModel() call, the segmentation fault does not
> occur.
>
Actually, you should avoid performing *any* other model operation within a
begin/end model "block", as they cannot be nested.
Trying to get a QModelIndex in the middle of an operation that may change
the shape or layout of a model is inappropriate, as that index would easily
become invalid. Qt also uses internal pointers to map source indexes, and
if you change the source (and therefore what those pointers refer to)
before the proxy is notified, some issues may occur, as those pointers are
completely managed by Qt on the "C++ side", which has a more delicate
mechanism when dealing with memory access.
Also, as a rule of thumb, if a program using bindings like PyQt crashes
without any Python traceback, you should always assume that the problem may
as well (if not even more likely) be related to the underlying library used
by the binding, so it's important to always check its bug report system and
the changelogs for that original library. Also remember that PyQt and Qt
version numbers don't always match, even though Phil is trying to make them
more consistent than in the past, so always ensure if they actually match
(and notify it in your posts if they do, or show both versions if they
don't).
I'm quite inclined to think that the problem is caused by Qt indeed; PyQt
may have some involvement, but I don't think it's that relevant, as the
problem mostly resides on inappropriate implementation to begin with. Most
importantly, QSortFilterProxyModel uses the "end" modelReset signal to
actually reset its own pointers and persistent indexes, which it "believed"
were valid up to that point in your code, even if they're not anymore, as
the original model has already changed: the internal pointers used to
create the related QModelIndexes don't match the mapping that the proxy has
internally created.
I may be wrong, but I believe that the culprit could be the recent change
done for QTBUG-76976 ( https://bugreports.qt.io/browse/QTBUG-76976 ), which
seems to be quite compatible considering the accessed types and the
specific version: the new implementation resides on a recursive search
based on the existing mapping, and I think that the inconsistency created
by the change in the source (which hasn't notified the proxy about its
changes yet) is what causes the crash, as mapFromSource() is trying to
*access* pointers that are not valid anymore.
I may be completely wrong about this and the problem is probably caused by
a change in PyQt 6.9.1, but the following facts remain:
- never try to call any other Qt model function (including anything that
may affect any related Qt type, such as emitting signals that would notify
"watchers" of that model) within a change that involves begin/end functions
and related signals, especially *after* changes have been made and *before*
the "end" function call;
- remember that some aspects in the above "watchers" require that you
follow the expected procedure: complex synchronous objects like proxy
models expect that *both* "begin" and "end" signals are emitted
consistently, and their behavior depends on when their internal calls
happen, based on their known state: a "begin" signal possibly causes
"storing" (or assuming) an internal state based on known aspects of the
source *before* any change, and anything called *before* the "end" still
expects that any call on the source would still be valid/consistent with
the original state, or that the change would be taken into consideration;
- due to the above, you cannot reliably use an index of the source model in
the proxy, if the source shape/layout has changed in the meantime but the
change has not been finalized yet with the "end" signal;
- always try to follow proper encapsulation principles: beginResetModel()
and endResetModel() should normally be called "internally" by the custom
model (in your case, at the very beginning and end of load_data()); there
are cases for which that's not possible/viable/acceptable due to complex
implementation, but in those cases you should *really* know what you're
doing and the consequences of those actions: make efforts to keep model
changes by using "enclosed" functions, especially if they require more than
one logic step on the Qt side;
- while the "Occam's razor" principle is always a good hint to begin with,
never assume anything just because it's the most simple explanation:
bindings require complex implementations, abstraction layers and some level
of "hacking" the features of a programming language; if you're relying on
"something external", you should consider it as a possible cause as well;
Best regards,
MaurizioB
--
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20251119/965c74da/attachment.htm>
More information about the PyQt
mailing list