[Development] Deprecating QSharedMemory and QSystemSemaphore

Thiago Macieira thiago.macieira at intel.com
Thu Nov 16 19:56:28 CET 2023


Ref: https://codereview.qt-project.org/c/qt/qtbase/+/518039
http://bugreports.qt.io/browse/QTBUG-111855

For Qt 6.6, I undertook a refactor of those two classes to allow the backend 
to be selected at runtime, instead of compile-time, because Apple in their 
infinite wisdom[*] decided that App Store applications can't use the System V 
API. But in the process I've discovered that those APIs are flawed by design. 
That means they aren't really fixable without a source- and behaviour-
incompatible API rewrite. Whether someone decides to do that or not is not 
important: the APIs as they stand are flawed, racy, and leak memory.

Therefore, those classes are dangerous to use and need to be avoided. The 
change above marks them as deprecated, with no replacement, and schedules them 
for removal in Qt 7.0 (probably to be moved to a "Qt6CoreCompat" module).

Worse, the refactor I attempted for 6.6 *made it worse* for Unix systems, with 
unavoidable memory leaks.

[*] that was sarcastic. I don't think wisdom played a role at all. I think 
they found it too difficult to secure the API, so they didn't.


Q: What's wrong with those classes?
A: The API is full of unavoidable race conditions. In Qt 4.4 or 4.5 when Ben 
and I designed and reviewed those two classes, we attempted to make a common 
API that would work on Unix and Windows systems, by choosing a simple and 
common behaviour that would work across the two OS families. But "past us" had 
much less experience and there are definite flaws that we didn't notice.

There are two major flaws in both classes:

1) improperly separate creation from attachment to an existing object. This 
causes race conditions if two applications attempt to create the object at the 
same time. It isn't clear what happens: whether the object gets replaced or 
not, whether attachment happens for the failed creation, etc. QSharedMemory 
attempts to fix this by using a QSystemSemaphore to control access, but since 
QSystemSemaphore is racy on its own (and in a worse way), that just moves the 
goalpost and introduces a second failure point.

2) improperly inform ownership of the object. This is the biggest of the two. 
This happens because on Windows, the objects disappear when the last user 
disconnects from them, but not so on Unix systems, so the backends attempt to 
detect the situation and delete. However, the detection is racy and may choose 
to delete at the moment that another application attempts to attach (see 
problem #1). It's unclear what happens on Unix systems if two processes race 
the deletion or race a deletion with an attachment.

To add insult to injury, #2 got worse in Qt 6.6 because the POSIX backend (the 
one Apple wants us to use and I made default) does not support detecting the 
last user at all. This means QSharedMemory/POSIX *never deletes* the shared 
memory object. We needed an API for the user to explicitly choose to delete, 
but I find that's covering the Sun with a sieve.

There's a third, minor flaw in QSharedMemory:
3) QSharedMeory::locked() exists. That suggests to people that this is a good 
way to write shared memory code. Instead, they should strive to write thread-
safe code, and only if it is not possible use a locking primitive. Said 
locking should be optional.


Q: I still need shared memory, what can I use instead?
A: Use QFile::map(). A memory-mapped file without the QFile::MapPrivateOption 
is shared memory. See
https://doc.qt.io/qt-6/shared-memory.html#sharing-memory-via-memory-mapped-files

Files are well-understood and common across all OSes, which should also make 
reviewing code for problems and race conditions much simpler.

For example, using QTemporaryFile and renaming, you can prepare the shared 
memory area before committing it to visibility to other processes.


Q: I need an equivalent of QSystemSemaphore, what can I use instead?
A: Use QFile::map() and put an atomic or a PThread locking primitive there. 
Alternatively, use QLockFile.


Q: But who deletes the file?
A: That's up to you to decide for your application. If we could have had a 
one-size-fits-all solution, we'd have fixed QSharedMemory because this is 
exactly the nature of the second flaw above (POSIX shared memory objects *are* 
files on Linux).

My recommendation is that there's a central process that creates the file and 
is always responsible for deleting it. This process must start before and exit 
after any other processes that attempt to use the object.

If you really must know if anyone is still using it, add a QBasicAtomicInt 
somewhere in it and implement a never-increment-from-zero reference counter in 
it.


Q: What should I use for rendezvousing (i.e., this unclear-shared-ownership 
model)?
A: Here are a couple of suggestions:
 - a D-Bus well-known bus name
 - a listening socket
 - a Q(Temporary)File with a well-known name
aside from files, the others automatically clean up on application crashes too


Q: My code is working fine for now, I don't want to touch it. What should I do?
A: Add
 #define QT_NO_DEPRECATED_IPC
and the deprecation warnings will be silenced.


Q: Should we use the new API in Qt 6.6?
A: No, they're dead on arrival.


Q: Will you work on a replacement API?
A: No. QFile suffices for my needs and I've lost interest / motivation in this 
functionality.


Q: Will you accept if someone else works in fixing these classes or providing a 
replacement?
A: Sure. I'll be happy to review your code and offer my insight. I just won't 
do the coding myself.

-- 
Thiago Macieira - thiago.macieira (AT) intel.com
  Cloud Software Architect - Intel DCAI Cloud Engineering
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 5163 bytes
Desc: not available
URL: <http://lists.qt-project.org/pipermail/development/attachments/20231116/3ceaed9b/attachment.bin>


More information about the Development mailing list