IPC: add native key support to QSharedMemory

And deprecate the non-native key support API. Qt 7 may not even store
the old, non-native key.

Documentation in a new commit, when the dust settles.

Drive-by updates to the tst_QSharedMemory::attach row names, to fix
grammar and remove spaces and apostrophe.

Change-Id: I12a088d1ae424825abd3fffd171d3025c77f94d2
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Thiago Macieira 2022-10-14 15:49:42 -07:00
parent 3ae052d3bb
commit 2c286561bb
10 changed files with 415 additions and 340 deletions

View File

@ -313,6 +313,15 @@ int QMetaType::idHelper() const
return registerHelper(d_ptr);
}
#if QT_CONFIG(sharedmemory)
#include "qsharedmemory.h"
void QSharedMemory::setNativeKey(const QString &key)
{
setNativeKey(key, QNativeIpcKey::legacyDefaultTypeForOs());
}
#endif
#include "qvariant.h"
// these implementations aren't as efficient as they used to be prior to

View File

@ -49,192 +49,83 @@ inline QNativeIpcKey QSharedMemoryPrivate::semaphoreNativeKey() const
\brief The QSharedMemory class provides access to a shared memory segment.
QSharedMemory provides access to a shared memory segment by multiple
threads and processes. It also provides a way for a single thread or
process to lock the memory for exclusive access.
QSharedMemory provides access to a \l{Shared Memory}{shared memory segment}
by multiple threads and processes. Shared memory segments are identified by a
key, represented by \l QNativeIpcKey. A key can be created in a
cross-platform manner by using platformSafeKey().
When using this class, be aware of the following platform
differences:
One QSharedMemory object must create() the segment and this call specifies
the size of the segment. All other processes simply attach() to the segment
that must already exist. After either operation is successful, the
application may call data() to obtain a pointer to the data.
\list
To support non-atomic operations, QSharedMemory provides API to gain
exclusive access: you may lock the shared memory with lock() before reading
from or writing to the shared memory, but remember to release the lock with
unlock() after you are done.
\li Windows: QSharedMemory does not "own" the shared memory segment.
When all threads or processes that have an instance of QSharedMemory
attached to a particular shared memory segment have either destroyed
their instance of QSharedMemory or exited, the Windows kernel
releases the shared memory segment automatically.
By default, QSharedMemory automatically destroys the shared memory segment
when the last instance of QSharedMemory is \l{detach()}{detached} from the
segment, and no references to the segment remain.
\li Unix: QSharedMemory "owns" the shared memory segment. When the
last thread or process that has an instance of QSharedMemory
attached to a particular shared memory segment detaches from the
segment by destroying its instance of QSharedMemory, the destructor
releases the shared memory segment. But if that last thread or
process crashes without running the QSharedMemory destructor, the
shared memory segment survives the crash.
For details on the key types, platform-specific limitations, and
interoperability with older or non-Qt applications, see the \l{Native IPC
Key} documentation. That includes important information for sandboxed
applications on Apple platforms, including all apps obtained via the Apple
App Store.
\li Unix: QSharedMemory can be implemented by one of two different
backends, selected at Qt build time: System V or POSIX. Qt defaults to
using the System V API if it is available, and POSIX if not. These two
backends do not interoperate, so two applications must ensure they use the
same one, even if the native key (see setNativeKey()) is the same.
The POSIX backend can be explicitly selected using the
\c{-feature-ipc_posix} option to the Qt configure script. If it is enabled,
the \c{QT_POSIX_IPC} macro will be defined.
\li Sandboxed applications on Apple platforms (including apps
shipped through the Apple App Store): This environment requires
the use of POSIX shared memory (instead of System V shared memory).
Qt for iOS is built with support for POSIX shared memory out of the box.
However, Qt for \macos builds (including those from the Qt installer) default
to System V, making them unsuitable for App Store submission if QSharedMemory
is needed. See above for instructions to explicitly select the POSIX backend
when building Qt.
In addition, in a sandboxed environment, the following caveats apply:
\list
\li The key must be in the form \c {<application group identifier>/<custom identifier>},
as documented \l {https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24}
{here} and \l {https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups}
{here}.
\li The key length is limited to 30 characters.
\li On process exit, the named shared memory entries are not
cleaned up, so restarting the application and re-creating the
shared memory under the same name will fail. To work around this,
fall back to attaching to the existing shared memory entry:
\code
QSharedMemory shm("DEVTEAMID.app-group/shared");
if (!shm.create(42) && shm.error() == QSharedMemory::AlreadyExists)
shm.attach();
\endcode
\endlist
\li Android: QSharedMemory is not supported.
\endlist
Remember to lock the shared memory with lock() before reading from
or writing to the shared memory, and remember to release the lock
with unlock() after you are done.
QSharedMemory automatically destroys the shared memory segment when
the last instance of QSharedMemory is detached from the segment, and
no references to the segment remain.
\warning QSharedMemory changes the key in a Qt-specific way, unless otherwise
specified. Interoperation with non-Qt applications is achieved by first creating
a default shared memory with QSharedMemory() and then setting a native key with
setNativeKey(), after ensuring they use the same low-level API (System V or
POSIX). When using native keys, shared memory is not protected against multiple
accesses on it (for example, unable to lock()) and a user-defined mechanism
should be used to achieve such protection.
\section2 Alternative: Memory-Mapped File
Another way to share memory between processes is by opening the same file
using \l QFile and mapping it into memory using QFile::map() (without
specifying the QFileDevice::MapPrivateOption option). Any writes to the
mapped segment will be observed by all other processes that have mapped the
same file. This solution has the major advantages of being independent of the
backend API and of being simpler to interoperate with from non-Qt
applications. And since \l{QTemporaryFile} is a \l{QFile}, applications can
use that class to achieve clean-up semantics and to create unique shared
memory segments too.
To achieve locking of the shared memory segment, applications will need to
deploy their own mechanisms. This can be achieved by using \l
QBasicAtomicInteger or \c{std::atomic} in a pre-determined offset in the
segment itself. Higher-level locking primitives may be available on some
operating systems; for example, on Linux, \c{pthread_mutex_create()} can be
passed a flag to indicate that the mutex resides in a shared memory segment.
A major drawback of using file-backed shared memory is that the operating
system will attempt to write the data to permanent storage, possibly causing
noticeable performance penalties. To avoid this, applications should locate a
RAM-backed filesystem, such as \c{tmpfs} on Linux (see
QStorageInfo::fileSystemType()), or pass a flag to the native file-opening
function to inform the OS to avoid committing the contents to storage.
File-backed shared memory must be used with care if another process
participating is untrusted. The files may be truncated/shrunk and cause
applications accessing memory beyond the file's size to crash.
\section3 Linux hints on memory-mapped files
On modern Linux systems, while the \c{/tmp} directory is often a \c{tmpfs}
mount point, that is not a requirement. However, the \c{/dev/shm} directory
is required to be a \c{tmpfs} and exists for this very purpose. Do note that
it is world-readable and writable (like \c{/tmp} and \c{/var/tmp}), so one
must be careful of the contents revealed there. Another alternative is to use
the XDG Runtime Directory (see QStandardPaths::writableLocation() and
\l{QStandardPaths::RuntimeLocation}), which on Linux systems using systemd is
a user-specific \c{tmpfs}.
An even more secure solution is to create a "memfd" using \c{memfd_create(2)}
and use interprocess communication to pass the file descriptor, like
\l{QDBusUnixFileDescriptor} or by letting the child process of a \l{QProcess}
inherit it. "memfds" can also be sealed against being shrunk, so they are
safe to be used when communicating with processes with a different privilege
level.
\section3 FreeBSD hints on memory-mapped files
FreeBSD also has \c{memfd_create(2)} and can pass file descriptors to other
processes using the same techniques as Linux. It does not have temporary
filesystems mounted by default.
\section3 Windows hints on memory-mapped files
On Windows, the application can request the operating system avoid committing
the file's contents to permanent storage. This request is performed by
passing the \c{FILE_ATTRIBUTE_TEMPORARY} flag in the \c{dwFlagsAndAttributes}
\c{CreateFile} Win32 function, the \c{_O_SHORT_LIVED} flag to \c{_open()}
low-level function, or by including the modifier "T" to the \c{fopen()} C
runtime function.
There's also a flag to inform the operating system to delete the file when
the last handle to it is closed (\c{FILE_FLAG_DELETE_ON_CLOSE},
\c{_O_TEMPORARY}, and the "D" modifier), but do note that all processes
attempting to open the file must agree on using this flag or not using it. A
mismatch will likely cause a sharing violation and failure to open the file.
\sa Inter-Process Communication, QSystemSemaphore
*/
/*!
\overload QSharedMemory()
Constructs a shared memory object with the given \a parent. The
shared memory object's key is not set by the constructor, so the
shared memory object does not have an underlying shared memory
segment attached. The key must be set with setKey() or setNativeKey()
before create() or attach() can be used.
Constructs a shared memory object with the given \a parent. The shared memory
object's key is not set by the constructor, so the shared memory object does
not have an underlying shared memory segment attached. The key must be set
with setNativeKey() before create() or attach() can be used.
\sa setKey()
\sa setNativeKey()
*/
QSharedMemory::QSharedMemory(QObject *parent)
: QObject(*new QSharedMemoryPrivate, parent)
: QSharedMemory(QNativeIpcKey(), parent)
{
}
/*!
\overload
Constructs a shared memory object with the given \a parent and with
its key set to \a key. Because its key is set, its create() and
attach() functions can be called.
\sa setNativeKey(), create(), attach()
*/
QSharedMemory::QSharedMemory(const QNativeIpcKey &key, QObject *parent)
: QObject(*new QSharedMemoryPrivate, parent)
{
setNativeKey(key);
}
/*!
\deprecated
Constructs a shared memory object with the given \a parent and with
the legacy key set to \a key. Because its key is set, its create() and
attach() functions can be called.
Legacy keys are deprecated. See \l{Native IPC Keys} for more information.
\sa setKey(), create(), attach()
*/
QSharedMemory::QSharedMemory(const QString &key, QObject *parent)
: QObject(*new QSharedMemoryPrivate, parent)
{
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
setKey(key);
QT_WARNING_POP
}
/*!
@ -248,67 +139,91 @@ QSharedMemory::QSharedMemory(const QString &key, QObject *parent)
*/
QSharedMemory::~QSharedMemory()
{
setKey(QString());
setNativeKey(QNativeIpcKey());
}
/*!
Sets the platform independent \a key for this shared memory object. If \a key
is the same as the current key, the function returns without doing anything.
\overload
You can call key() to retrieve the platform independent key. Internally,
QSharedMemory converts this key into a platform specific key. If you instead
call nativeKey(), you will get the platform specific, converted key.
If the shared memory object is attached to an underlying shared memory
Sets the legacy \a key for this shared memory object. If \a key is the same
as the current key, the function returns without doing anything. Otherwise,
if the shared memory object is attached to an underlying shared memory
segment, it will \l {detach()} {detach} from it before setting the new key.
This function does not do an attach().
You can call key() to retrieve the legacy key. This function is mostly the
same as:
\code
shm.setNativeKey(QSharedMemory::legacyNativeKey(key));
\endcode
except that it enables obtaining the legacy key using key().
\sa key(), nativeKey(), isAttached()
*/
void QSharedMemory::setKey(const QString &key)
{
Q_D(QSharedMemory);
QString newNativeKey =
QtIpcCommon::legacyPlatformSafeKey(key, QtIpcCommon::IpcType::SharedMemory);
if (key == d->key && newNativeKey == d->nativeKey)
return;
if (isAttached())
detach();
d->cleanHandle();
d->key = key;
d->nativeKey = newNativeKey;
setNativeKey(legacyNativeKey(key));
d->legacyKey = key;
}
/*!
\since 4.8
\fn void QSharedMemory::setNativeKey(const QString &key, QNativeIpcKey::Type type)
Sets the native, platform specific, \a key for this shared memory object of
type \a type (the type parameter has been available since Qt 6.6). If \a key
is the same as the current native key, the function returns without doing
anything. Otherwise, if the shared memory object is attached to an underlying
shared memory segment, it will \l {detach()} {detach} from it before setting
the new key. This function does not do an attach().
This function is useful if the native key was shared from another process,
though the application must take care to ensure the key type matches what the
other process expects. See \l{Native IPC Keys} for more information.
Portable native keys can be obtained using platformSafeKey().
You can call nativeKey() to retrieve the native key.
\sa nativeKey(), nativeKeyType(), isAttached()
*/
/*!
\since 6.6
Sets the native, platform specific, \a key for this shared memory object. If
\a key is the same as the current native key, the function returns without
doing anything. If all you want is to assign a key to a segment, you should
call setKey() instead.
doing anything. Otherwise, if the shared memory object is attached to an
underlying shared memory segment, it will \l {detach()} {detach} from it
before setting the new key. This function does not do an attach().
You can call nativeKey() to retrieve the native key. If a native key has been
assigned, calling key() will return a null string.
This function is useful if the native key was shared from another process.
See \l{Native IPC Keys} for more information.
If the shared memory object is attached to an underlying shared memory
segment, it will \l {detach()} {detach} from it before setting the new key.
This function does not do an attach().
Portable native keys can be obtained using platformSafeKey().
The application will not be portable if you set a native key.
You can call nativeKey() to retrieve the native key.
\sa nativeKey(), key(), isAttached()
\sa nativeKey(), nativeKeyType(), isAttached()
*/
void QSharedMemory::setNativeKey(const QString &key)
void QSharedMemory::setNativeKey(const QNativeIpcKey &key)
{
Q_D(QSharedMemory);
if (key == d->nativeKey && d->key.isNull())
if (key == d->nativeKey && key.isEmpty())
return;
if (!isKeyTypeSupported(key.type())) {
d->setError(KeyError, tr("%1: unsupported key type")
.arg("QSharedMemory::setNativeKey"_L1));
return;
}
if (isAttached())
detach();
d->cleanHandle();
d->key = QString();
d->legacyKey = QString();
d->nativeKey = key;
}
@ -352,7 +267,8 @@ bool QSharedMemoryPrivate::initKey()
}
/*!
Returns the key assigned with setKey() to this shared memory, or a null key
\deprecated
Returns the legacy key assigned with setKey() to this shared memory, or a null key
if no key has been assigned, or if the segment is using a nativeKey(). The
key is the identifier used by Qt applications to identify the shared memory
segment.
@ -365,7 +281,7 @@ bool QSharedMemoryPrivate::initKey()
QString QSharedMemory::key() const
{
Q_D(const QSharedMemory);
return d->key;
return d->legacyKey;
}
/*!
@ -377,10 +293,30 @@ QString QSharedMemory::key() const
You can use the native key to access shared memory segments that have not
been created by Qt, or to grant shared memory access to non-Qt applications.
See \l{Native IPC Keys} for more information.
\sa setKey(), setNativeKey()
\sa setNativeKey(), nativeKeyType()
*/
QString QSharedMemory::nativeKey() const
{
Q_D(const QSharedMemory);
return d->nativeKey.nativeKey();
}
/*!
\since 6.6
Returns the key type for this shared memory object. The key type complements
the nativeKey() as the identifier used by the operating system to identify
the shared memory segment.
You can use the native key to access shared memory segments that have not
been created by Qt, or to grant shared memory access to non-Qt applications.
See \l{Native IPC Keys} for more information.
\sa nativeKey(), setNativeKey()
*/
QNativeIpcKey QSharedMemory::nativeIpcKey() const
{
Q_D(const QSharedMemory);
return d->nativeKey;
@ -388,7 +324,7 @@ QString QSharedMemory::nativeKey() const
/*!
Creates a shared memory segment of \a size bytes with the key passed to the
constructor, set with setKey() or set with setNativeKey(), then attaches to
constructor or set with setNativeKey(), then attaches to
the new shared memory segment with the given access \a mode and returns
\tt true. If a shared memory segment identified by the key already exists,
the attach operation is not performed and \tt false is returned. When the
@ -407,14 +343,14 @@ bool QSharedMemory::create(qsizetype size, AccessMode mode)
#ifndef Q_OS_WIN
// Take ownership and force set initialValue because the semaphore
// might have already existed from a previous crash.
d->systemSemaphore.setKey(d->key, 1, QSystemSemaphore::Create);
d->systemSemaphore.setKey(d->semaphoreNativeKey(), 1, QSystemSemaphore::Create);
#endif
#endif
QString function = "QSharedMemory::create"_L1;
#if QT_CONFIG(systemsemaphore)
QSharedMemoryLocker lock(this);
if (!d->key.isNull() && !d->tryLocker(&lock, function))
if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, function))
return false;
#endif
@ -461,7 +397,7 @@ qsizetype QSharedMemory::size() const
/*!
Attempts to attach the process to the shared memory segment
identified by the key that was passed to the constructor or to a
call to setKey() or setNativeKey(). The access \a mode is \l {QSharedMemory::}
call to setNativeKey(). The access \a mode is \l {QSharedMemory::}
{ReadWrite} by default. It can also be \l {QSharedMemory::}
{ReadOnly}. Returns \c true if the attach operation is successful. If
false is returned, call error() to determine which error occurred.
@ -478,7 +414,7 @@ bool QSharedMemory::attach(AccessMode mode)
return false;
#if QT_CONFIG(systemsemaphore)
QSharedMemoryLocker lock(this);
if (!d->key.isNull() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1))
if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::attach"_L1))
return false;
#endif
@ -518,7 +454,7 @@ bool QSharedMemory::detach()
#if QT_CONFIG(systemsemaphore)
QSharedMemoryLocker lock(this);
if (!d->key.isNull() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1))
if (!d->nativeKey.isEmpty() && !d->tryLocker(&lock, "QSharedMemory::detach"_L1))
return false;
#endif
@ -526,11 +462,14 @@ bool QSharedMemory::detach()
}
/*!
Returns a pointer to the contents of the shared memory segment, if
one is attached. Otherwise it returns null. Remember to lock the
shared memory with lock() before reading from or writing to the
shared memory, and remember to release the lock with unlock() after
you are done.
Returns a pointer to the contents of the shared memory segment, if one is
attached. Otherwise it returns null. The value returned by this function will
not change until a \l {detach()}{detach} happens, so it is safe to store this
pointer.
If the memory operations are not atomic, you may lock the shared memory with
lock() before reading from or writing, but remember to release the lock with
unlock() after you are done.
\sa attach()
*/
@ -541,11 +480,14 @@ void *QSharedMemory::data()
}
/*!
Returns a const pointer to the contents of the shared memory
segment, if one is attached. Otherwise it returns null. Remember to
lock the shared memory with lock() before reading from or writing to
the shared memory, and remember to release the lock with unlock()
after you are done.
Returns a const pointer to the contents of the shared memory segment, if one
is attached. Otherwise it returns null. The value returned by this function
will not change until a \l {detach()}{detach} happens, so it is safe to store
this pointer.
If the memory operations are not atomic, you may lock the shared memory with
lock() before reading from or writing, but remember to release the lock with
unlock() after you are done.
\sa attach(), create()
*/
@ -703,6 +645,21 @@ void QSharedMemoryPrivate::setUnixErrorString(QLatin1StringView function)
}
}
bool QSharedMemory::isKeyTypeSupported(QNativeIpcKey::Type type)
{
return QSharedMemoryPrivate::DefaultBackend::supports(type);
}
QNativeIpcKey QSharedMemory::platformSafeKey(const QString &key, QNativeIpcKey::Type type)
{
return { QtIpcCommon::platformSafeKey(key, IpcType::SharedMemory, type), type };
}
QNativeIpcKey QSharedMemory::legacyNativeKey(const QString &key, QNativeIpcKey::Type type)
{
return { legacyPlatformSafeKey(key, IpcType::SharedMemory, type), type };
}
#endif // QT_CONFIG(sharedmemory)
QT_END_NAMESPACE

View File

@ -4,7 +4,7 @@
#ifndef QSHAREDMEMORY_H
#define QSHAREDMEMORY_H
#include <QtCore/qglobal.h>
#include <QtCore/qtipccommon.h>
#ifndef QT_NO_QOBJECT
# include <QtCore/qobject.h>
#else
@ -12,8 +12,8 @@
# include <QtCore/qscopedpointer.h>
# include <QtCore/qstring.h>
#endif
QT_BEGIN_NAMESPACE
QT_BEGIN_NAMESPACE
#if QT_CONFIG(sharedmemory)
@ -45,13 +45,26 @@ public:
};
QSharedMemory(QObject *parent = nullptr);
QSharedMemory(const QString &key, QObject *parent = nullptr);
QSharedMemory(const QNativeIpcKey &key, QObject *parent = nullptr);
~QSharedMemory();
#if QT_DEPRECATED_SINCE(6, 9)
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
QSharedMemory(const QString &key, QObject *parent = nullptr);
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
void setKey(const QString &key);
QT_DEPRECATED_VERSION_X_6_9("Please refer to 'Native IPC Key' documentation")
QString key() const;
void setNativeKey(const QString &key);
#endif
void setNativeKey(const QNativeIpcKey &key);
void setNativeKey(const QString &key, QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs())
{ setNativeKey({ key, type }); }
QString nativeKey() const;
QNativeIpcKey nativeIpcKey() const;
#if QT_CORE_REMOVED_SINCE(6, 5)
void setNativeKey(const QString &key);
#endif
bool create(qsizetype size, AccessMode mode = ReadWrite);
qsizetype size() const;
@ -72,6 +85,12 @@ public:
SharedMemoryError error() const;
QString errorString() const;
static bool isKeyTypeSupported(QNativeIpcKey::Type type) Q_DECL_CONST_FUNCTION;
static QNativeIpcKey platformSafeKey(const QString &key,
QNativeIpcKey::Type type = QNativeIpcKey::DefaultTypeForOs);
static QNativeIpcKey legacyNativeKey(const QString &key,
QNativeIpcKey::Type type = QNativeIpcKey::legacyDefaultTypeForOs());
private:
Q_DISABLE_COPY(QSharedMemory)
};

View File

@ -69,6 +69,9 @@ private:
class QSharedMemoryPosix
{
public:
static bool supports(QNativeIpcKey::Type type)
{ return type == QNativeIpcKey::Type::PosixRealtime; }
bool handle(QSharedMemoryPrivate *self);
bool cleanHandle(QSharedMemoryPrivate *self);
bool create(QSharedMemoryPrivate *self, qsizetype size);
@ -81,6 +84,9 @@ public:
class QSharedMemorySystemV
{
public:
static bool supports(QNativeIpcKey::Type type)
{ return quint16(type) <= 0xff; }
#if QT_CONFIG(sysv_sem)
key_t handle(QSharedMemoryPrivate *self);
bool cleanHandle(QSharedMemoryPrivate *self);
@ -88,6 +94,10 @@ public:
bool attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode);
bool detach(QSharedMemoryPrivate *self);
private:
void updateNativeKeyFile(const QNativeIpcKey &nativeKey);
QByteArray nativeKeyFile;
key_t unix_key = 0;
#endif
};
@ -95,6 +105,9 @@ public:
class QSharedMemoryWin32
{
public:
static bool supports(QNativeIpcKey::Type type)
{ return type == QNativeIpcKey::Type::Windows; }
Qt::HANDLE handle(QSharedMemoryPrivate *self);
bool cleanHandle(QSharedMemoryPrivate *self);
bool create(QSharedMemoryPrivate *self, qsizetype size);
@ -111,8 +124,7 @@ class Q_AUTOTEST_EXPORT QSharedMemoryPrivate : public QObjectPrivate
public:
void *memory = nullptr;
qsizetype size = 0;
QString key;
QString nativeKey;
QNativeIpcKey nativeKey;
QString errorString;
#if QT_CONFIG(systemsemaphore)
QSystemSemaphore systemSemaphore{ QNativeIpcKey() };
@ -168,6 +180,8 @@ public:
}
QNativeIpcKey semaphoreNativeKey() const;
#endif // QT_CONFIG(systemsemaphore)
QString legacyKey; // deprecated
};
QT_END_NAMESPACE

View File

@ -27,8 +27,7 @@ using namespace QtIpcCommon;
bool QSharedMemoryPosix::handle(QSharedMemoryPrivate *self)
{
// don't allow making handles on empty keys
const QString safeKey = legacyPlatformSafeKey(self->key, IpcType::SharedMemory);
if (safeKey.isEmpty()) {
if (self->nativeKey.isEmpty()) {
self->setError(QSharedMemory::KeyError,
QSharedMemory::tr("%1: key is empty").arg("QSharedMemory::handle"_L1));
return false;
@ -50,7 +49,7 @@ bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size)
if (!handle(self))
return false;
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory));
const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
int fd;
#ifdef O_CLOEXEC
@ -91,7 +90,7 @@ bool QSharedMemoryPosix::create(QSharedMemoryPrivate *self, qsizetype size)
bool QSharedMemoryPosix::attach(QSharedMemoryPrivate *self, QSharedMemory::AccessMode mode)
{
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory));
const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
const int oflag = (mode == QSharedMemory::ReadOnly ? O_RDONLY : O_RDWR);
const mode_t omode = (mode == QSharedMemory::ReadOnly ? 0400 : 0600);
@ -177,7 +176,7 @@ bool QSharedMemoryPosix::detach(QSharedMemoryPrivate *self)
// if there are no attachments then unlink the shared memory
if (shm_nattch == 0) {
const QByteArray shmName = QFile::encodeName(legacyPlatformSafeKey(self->key, IpcType::SharedMemory));
const QByteArray shmName = QFile::encodeName(self->nativeKey.nativeKey());
if (::shm_unlink(shmName.constData()) == -1 && errno != ENOENT)
self->setUnixErrorString("QSharedMemory::detach (shm_unlink)"_L1);
}

View File

@ -26,6 +26,13 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace QtIpcCommon;
inline void QSharedMemorySystemV::updateNativeKeyFile(const QNativeIpcKey &nativeKey)
{
Q_ASSERT(nativeKeyFile.isEmpty() );
if (!nativeKey.nativeKey().isEmpty())
nativeKeyFile = QFile::encodeName(nativeKey.nativeKey());
}
/*!
\internal
@ -38,7 +45,9 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
return unix_key;
// don't allow making handles on empty keys
if (self->nativeKey.isEmpty()) {
if (nativeKeyFile.isEmpty())
updateNativeKeyFile(self->nativeKey);
if (nativeKeyFile.isEmpty()) {
self->setError(QSharedMemory::KeyError,
QSharedMemory::tr("%1: key is empty")
.arg("QSharedMemory::handle:"_L1));
@ -46,14 +55,14 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
}
// ftok requires that an actual file exists somewhere
if (!QFile::exists(self->nativeKey)) {
if (!QFile::exists(nativeKeyFile)) {
self->setError(QSharedMemory::NotFound,
QSharedMemory::tr("%1: UNIX key file doesn't exist")
.arg("QSharedMemory::handle:"_L1));
return 0;
}
unix_key = ftok(QFile::encodeName(self->nativeKey).constData(), 'Q');
unix_key = ftok(nativeKeyFile, int(self->nativeKey.type()));
if (-1 == unix_key) {
self->setError(QSharedMemory::KeyError,
QSharedMemory::tr("%1: ftok failed")
@ -66,6 +75,7 @@ key_t QSharedMemorySystemV::handle(QSharedMemoryPrivate *self)
bool QSharedMemorySystemV::cleanHandle(QSharedMemoryPrivate *)
{
unix_key = 0;
nativeKeyFile.clear();
return true;
}
@ -73,7 +83,7 @@ bool QSharedMemorySystemV::create(QSharedMemoryPrivate *self, qsizetype size)
{
// build file if needed
bool createdFile = false;
QByteArray nativeKeyFile = QFile::encodeName(self->nativeKey);
updateNativeKeyFile(self->nativeKey);
int built = createUnixKeyFile(nativeKeyFile);
if (built == -1) {
self->setError(QSharedMemory::KeyError,
@ -161,6 +171,7 @@ bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self)
// Get the number of current attachments
int id = shmget(unix_key, 0, 0400);
QByteArray oldNativeKeyFile = nativeKeyFile;
cleanHandle(self);
struct shmid_ds shmid_ds;
@ -186,7 +197,7 @@ bool QSharedMemorySystemV::detach(QSharedMemoryPrivate *self)
}
// remove file
if (!unlink(QFile::encodeName(self->nativeKey)))
if (unlink(oldNativeKeyFile) < 0)
return false;
}
return true;

View File

@ -60,7 +60,7 @@ HANDLE QSharedMemoryWin32::handle(QSharedMemoryPrivate *self)
return 0;
}
hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false,
reinterpret_cast<const wchar_t *>(self->nativeKey.utf16()));
reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16()));
if (!hand) {
self->setWindowsErrorString(function);
return 0;
@ -96,7 +96,7 @@ bool QSharedMemoryWin32::create(QSharedMemoryPrivate *self, qsizetype size)
high = 0;
low = DWORD(size_t(size) & 0xffffffff);
hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, high, low,
reinterpret_cast<const wchar_t *>(self->nativeKey.utf16()));
reinterpret_cast<const wchar_t *>(self->nativeKey.nativeKey().utf16()));
self->setWindowsErrorString(function);
// hand is valid when it already exists unlike unix so explicitly check

View File

@ -5,7 +5,7 @@ if(NOT ANDROID AND NOT UIKIT)
if(QT_FEATURE_sharedmemory OR QT_FEATURE_systemsemaphore)
add_subdirectory(qnativeipckey)
endif()
if(QT_FEATURE_sharedmemory AND QT_FEATURE_private_tests)
if(QT_FEATURE_sharedmemory)
add_subdirectory(qsharedmemory)
endif()
if(QT_FEATURE_systemsemaphore)

View File

@ -17,10 +17,9 @@ QChar get(QSharedMemory &sm, int i)
return QChar::fromLatin1(((char*)sm.data())[i]);
}
int readonly_segfault()
int readonly_segfault(const QNativeIpcKey &key)
{
QSharedMemory sharedMemory;
sharedMemory.setKey("readonly_segfault");
QSharedMemory sharedMemory(key);
sharedMemory.create(1024, QSharedMemory::ReadOnly);
sharedMemory.lock();
set(sharedMemory, 0, 'a');
@ -28,10 +27,9 @@ int readonly_segfault()
return EXIT_SUCCESS;
}
int producer()
int producer(const QNativeIpcKey &key)
{
QSharedMemory producer;
producer.setKey("market");
QSharedMemory producer(key);
int size = 1024;
if (!producer.create(size)) {
@ -100,10 +98,9 @@ int producer()
return EXIT_SUCCESS;
}
int consumer()
int consumer(const QNativeIpcKey &key)
{
QSharedMemory consumer;
consumer.setKey("market");
QSharedMemory consumer(key);
//qDebug("consumer starting");
int tries = 0;
@ -155,17 +152,21 @@ int main(int argc, char *argv[])
QCoreApplication app(argc, argv);
QStringList arguments = app.arguments();
if (app.arguments().size() != 2) {
qWarning("Please call the helper with the function to call as argument");
if (app.arguments().size() != 3) {
fprintf(stderr, "Usage: %s <mode> <key>\n"
"<mode> is one of: readonly_segfault, producer, consumer\n",
argv[0]);
return EXIT_FAILURE;
}
QString function = arguments.at(1);
QNativeIpcKey key = QNativeIpcKey::fromString(arguments.at(2));
if (function == QLatin1String("readonly_segfault"))
return readonly_segfault();
return readonly_segfault(key);
else if (function == QLatin1String("producer"))
return producer();
return producer(key);
else if (function == QLatin1String("consumer"))
return consumer();
return consumer(key);
else
qWarning() << "Unknown function" << arguments.at(1);

View File

@ -1,4 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2022 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QDebug>
@ -11,6 +12,14 @@
#include <QThread>
#include <QElapsedTimer>
#include <errno.h>
#include <stdio.h>
#ifdef Q_OS_UNIX
# include <unistd.h>
#endif
#include "private/qtcore-config_p.h"
#define EXISTING_SHARE "existing"
#define EXISTING_SIZE 1024
@ -33,8 +42,10 @@ public Q_SLOTS:
private slots:
// basics
void constructor();
void key_data();
void key();
void nativeKey_data();
void nativeKey();
void legacyKey_data() { nativeKey_data(); }
void legacyKey();
void create_data();
void create();
void attach_data();
@ -76,20 +87,25 @@ private slots:
void uniqueKey();
protected:
int remove(const QString &key);
void remove(const QNativeIpcKey &key);
QString rememberKey(const QString &key)
QNativeIpcKey platformSafeKey(const QString &key)
{
if (key == EXISTING_SHARE)
return key;
if (!keys.contains(key)) {
keys.append(key);
remove(key);
}
return key;
QNativeIpcKey::Type keyType = QNativeIpcKey::DefaultTypeForOs;
return QSharedMemory::platformSafeKey(key, keyType);
}
QStringList keys;
QNativeIpcKey rememberKey(const QString &key)
{
QNativeIpcKey ipcKey = platformSafeKey(key);
if (!keys.contains(ipcKey)) {
keys.append(ipcKey);
remove(ipcKey);
}
return ipcKey;
}
QList<QNativeIpcKey> keys;
QList<QSharedMemory*> jail;
QSharedMemory *existingSharedMemory;
@ -109,10 +125,12 @@ tst_QSharedMemory::~tst_QSharedMemory()
void tst_QSharedMemory::init()
{
existingSharedMemory = new QSharedMemory(EXISTING_SHARE);
QNativeIpcKey key = platformSafeKey(EXISTING_SHARE);
existingSharedMemory = new QSharedMemory(key);
if (!existingSharedMemory->create(EXISTING_SIZE)) {
QCOMPARE(existingSharedMemory->error(), QSharedMemory::AlreadyExists);
}
keys.append(key);
}
void tst_QSharedMemory::cleanup()
@ -121,7 +139,6 @@ void tst_QSharedMemory::cleanup()
qDeleteAll(jail.begin(), jail.end());
jail.clear();
keys.append(EXISTING_SHARE);
for (int i = 0; i < keys.size(); ++i) {
QSharedMemory sm(keys.at(i));
if (!sm.create(1024)) {
@ -134,65 +151,70 @@ void tst_QSharedMemory::cleanup()
}
}
#ifndef Q_OS_WIN
#include <private/qtipccommon_p.h>
#if QT_CONFIG(posix_shm)
#include <sys/types.h>
#include <sys/mman.h>
#endif
#if QT_CONFIG(sysv_shm)
#include <sys/types.h>
#ifndef QT_POSIX_IPC
#include <sys/ipc.h>
#include <sys/shm.h>
#else
#include <sys/mman.h>
#endif // QT_POSIX_IPC
#include <errno.h>
#endif
int tst_QSharedMemory::remove(const QString &key)
void tst_QSharedMemory::remove(const QNativeIpcKey &key)
{
#ifdef Q_OS_WIN
Q_UNUSED(key);
return 0;
#else
// On unix the shared memory might exists from a previously failed test
// On Unix, the shared memory might exist from a previously failed test
// or segfault, remove it it does
if (key.isEmpty())
return -1;
return;
QString fileName = QtIpcCommon::legacyPlatformSafeKey(key, QtIpcCommon::IpcType::SharedMemory);
switch (key.type()) {
case QNativeIpcKey::Type::Windows:
return;
#ifndef QT_POSIX_IPC
// ftok requires that an actual file exists somewhere
if (!QFile::exists(fileName)) {
//qDebug() << "exits failed";
return -2;
case QNativeIpcKey::Type::PosixRealtime:
#if QT_CONFIG(posix_shm)
if (shm_unlink(QFile::encodeName(key.nativeKey()).constData()) == -1) {
if (errno != ENOENT) {
perror("shm_unlink");
return;
}
}
#endif
return;
case QNativeIpcKey::Type::SystemV:
break;
}
int unix_key = ftok(fileName.toLatin1().constData(), 'Q');
#if QT_CONFIG(sysv_shm)
// ftok requires that an actual file exists somewhere
QString fileName = key.nativeKey();
if (!QFile::exists(fileName)) {
//qDebug() << "exits failed";
return;
}
int unix_key = ftok(fileName.toLatin1().constData(), int(key.type()));
if (-1 == unix_key) {
qDebug() << "ftok failed";
return -3;
perror("ftok");
return;
}
int id = shmget(unix_key, 0, 0600);
if (-1 == id) {
qDebug() << "shmget failed" << strerror(errno);
return -4;
if (errno != ENOENT)
perror("shmget");
return;
}
struct shmid_ds shmid_ds;
if (-1 == shmctl(id, IPC_RMID, &shmid_ds)) {
qDebug() << "shmctl failed";
return -5;
perror("shmctl");
return;
}
#else
if (shm_unlink(QFile::encodeName(fileName).constData()) == -1) {
if (errno != ENOENT) {
qDebug() << "shm_unlink failed";
return -5;
}
}
#endif // QT_POSIX_IPC
return QFile::remove(fileName);
QFile::remove(fileName);
#endif // Q_OS_WIN
}
@ -202,19 +224,25 @@ int tst_QSharedMemory::remove(const QString &key)
void tst_QSharedMemory::constructor()
{
QSharedMemory sm;
QCOMPARE(sm.key(), QString());
QVERIFY(!sm.isAttached());
QVERIFY(!sm.data());
QCOMPARE(sm.nativeKey(), QString());
QCOMPARE(sm.nativeIpcKey(), QNativeIpcKey());
QCOMPARE(sm.size(), 0);
QCOMPARE(sm.error(), QSharedMemory::NoError);
QCOMPARE(sm.errorString(), QString());
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
QCOMPARE(sm.key(), QString());
QT_WARNING_POP
}
void tst_QSharedMemory::key_data()
void tst_QSharedMemory::nativeKey_data()
{
QTest::addColumn<QString>("constructorKey");
QTest::addColumn<QString>("setKey");
QTest::addColumn<QString>("setNativeKey");
QTest::addColumn<QString>("setNativeKey"); // only used in the legacyKey test
QTest::newRow("null, null, null") << QString() << QString() << QString();
QTest::newRow("one, null, null") << QString("one") << QString() << QString();
@ -230,12 +258,43 @@ void tst_QSharedMemory::key_data()
/*!
Basic key testing
*/
void tst_QSharedMemory::key()
void tst_QSharedMemory::nativeKey()
{
QFETCH(QString, constructorKey);
QFETCH(QString, setKey);
QFETCH(QString, setNativeKey);
QNativeIpcKey constructorIpcKey = platformSafeKey(constructorKey);
QNativeIpcKey setIpcKey = platformSafeKey(setKey);
QSharedMemory sm(constructorIpcKey);
QCOMPARE(sm.nativeIpcKey(), constructorIpcKey);
QCOMPARE(sm.nativeKey(), constructorIpcKey.nativeKey());
sm.setNativeKey(setIpcKey);
QCOMPARE(sm.nativeIpcKey(), setIpcKey);
QCOMPARE(sm.nativeKey(), setIpcKey.nativeKey());
QCOMPARE(sm.isAttached(), false);
QCOMPARE(sm.error(), QSharedMemory::NoError);
QCOMPARE(sm.errorString(), QString());
QVERIFY(!sm.data());
QCOMPARE(sm.size(), 0);
QCOMPARE(sm.detach(), false);
}
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
void tst_QSharedMemory::legacyKey()
{
QFETCH(QString, constructorKey);
QFETCH(QString, setKey);
QFETCH(QString, setNativeKey);
#ifdef Q_OS_QNX
QSKIP("The legacy native key type is incorrectly set on QNX");
#endif
QSharedMemory sm(constructorKey);
QCOMPARE(sm.key(), constructorKey);
QCOMPARE(sm.nativeKey().isEmpty(), constructorKey.isEmpty());
@ -254,6 +313,7 @@ void tst_QSharedMemory::key()
QCOMPARE(sm.detach(), false);
}
QT_WARNING_POP
void tst_QSharedMemory::create_data()
{
@ -282,11 +342,12 @@ void tst_QSharedMemory::create()
QFETCH(bool, canCreate);
QFETCH(QSharedMemory::SharedMemoryError, error);
QSharedMemory sm(rememberKey(key));
QNativeIpcKey nativeKey = rememberKey(key);
QSharedMemory sm(nativeKey);
QCOMPARE(sm.create(size), canCreate);
if (sm.error() != error)
qDebug() << sm.errorString();
QCOMPARE(sm.key(), key);
QCOMPARE(sm.nativeIpcKey(), nativeKey);
if (canCreate) {
QCOMPARE(sm.errorString(), QString());
QVERIFY(sm.data() != 0);
@ -303,13 +364,10 @@ void tst_QSharedMemory::attach_data()
QTest::addColumn<bool>("exists");
QTest::addColumn<QSharedMemory::SharedMemoryError>("error");
QTest::newRow("null key") << QString() << false << QSharedMemory::KeyError;
QTest::newRow("doesn't exists") << QString("doesntexists") << false << QSharedMemory::NotFound;
QTest::newRow("null") << QString() << false << QSharedMemory::KeyError;
QTest::newRow("doesntexists") << QString("doesntexist") << false << QSharedMemory::NotFound;
// HPUX doesn't allow for multiple attaches per process.
#ifndef Q_OS_HPUX
QTest::newRow("already exists") << QString(EXISTING_SHARE) << true << QSharedMemory::NoError;
#endif
QTest::newRow(EXISTING_SHARE) << QString(EXISTING_SHARE) << true << QSharedMemory::NoError;
}
/*!
@ -321,11 +379,12 @@ void tst_QSharedMemory::attach()
QFETCH(bool, exists);
QFETCH(QSharedMemory::SharedMemoryError, error);
QSharedMemory sm(key);
QNativeIpcKey nativeKey = platformSafeKey(key);
QSharedMemory sm(nativeKey);
QCOMPARE(sm.attach(), exists);
QCOMPARE(sm.isAttached(), exists);
QCOMPARE(sm.error(), error);
QCOMPARE(sm.key(), key);
QCOMPARE(sm.nativeIpcKey(), nativeKey);
if (exists) {
QVERIFY(sm.data() != 0);
QVERIFY(sm.size() != 0);
@ -352,7 +411,7 @@ void tst_QSharedMemory::lock()
QVERIFY(!shm.lock());
QCOMPARE(shm.error(), QSharedMemory::LockError);
shm.setKey(rememberKey(QLatin1String("qsharedmemory")));
shm.setNativeKey(rememberKey(QLatin1String("qsharedmemory")));
QVERIFY(!shm.lock());
QCOMPARE(shm.error(), QSharedMemory::LockError);
@ -376,12 +435,13 @@ void tst_QSharedMemory::removeWhileAttached()
rememberKey("one");
// attach 1
QSharedMemory *smOne = new QSharedMemory(QLatin1String("one"));
QNativeIpcKey keyOne = platformSafeKey("one");
QSharedMemory *smOne = new QSharedMemory(keyOne);
QVERIFY(smOne->create(1024));
QVERIFY(smOne->isAttached());
// attach 2
QSharedMemory *smTwo = new QSharedMemory(QLatin1String("one"));
QSharedMemory *smTwo = new QSharedMemory(platformSafeKey("one"));
QVERIFY(smTwo->attach());
QVERIFY(smTwo->isAttached());
@ -389,13 +449,13 @@ void tst_QSharedMemory::removeWhileAttached()
delete smOne;
delete smTwo;
#ifdef QT_POSIX_IPC
// POSIX IPC doesn't guarantee that the shared memory is removed
remove("one");
#endif
if (keyOne.type() == QNativeIpcKey::Type::PosixRealtime) {
// POSIX IPC doesn't guarantee that the shared memory is removed
remove(keyOne);
}
// three shouldn't be able to attach
QSharedMemory smThree(QLatin1String("one"));
QSharedMemory smThree(keyOne);
QVERIFY(!smThree.attach());
QCOMPARE(smThree.error(), QSharedMemory::NotFound);
}
@ -430,11 +490,12 @@ void tst_QSharedMemory::readOnly()
#elif defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
QSKIP("ASan prevents the crash this test is looking for.", SkipAll);
#else
rememberKey("readonly_segfault");
QNativeIpcKey key = rememberKey("readonly_segfault");
// ### on windows disable the popup somehow
QProcess p;
p.start(m_helperBinary, QStringList("readonly_segfault"));
p.setProcessChannelMode(QProcess::ForwardedChannels);
p.start(m_helperBinary, { "readonly_segfault", key.toString() });
p.waitForFinished();
QCOMPARE(p.error(), QProcess::Crashed);
#endif
@ -451,7 +512,8 @@ void tst_QSharedMemory::useTooMuchMemory()
int count = 0;
while (success) {
QString key = QLatin1String("maxmemorytest_") + QString::number(count++);
QSharedMemory *sm = new QSharedMemory(rememberKey(key));
QNativeIpcKey nativeKey = rememberKey(key);
QSharedMemory *sm = new QSharedMemory(nativeKey);
QVERIFY(sm);
jail.append(sm);
int size = 32768 * 1024;
@ -465,7 +527,7 @@ void tst_QSharedMemory::useTooMuchMemory()
if (!success) {
QVERIFY(!sm->isAttached());
QCOMPARE(sm->key(), key);
QCOMPARE(sm->nativeIpcKey(), nativeKey);
QCOMPARE(sm->size(), 0);
QVERIFY(!sm->data());
if (sm->error() != QSharedMemory::OutOfResources)
@ -496,12 +558,12 @@ void tst_QSharedMemory::attachTooMuch()
QSharedMemory government(rememberKey("government"));
QVERIFY(government.create(1024));
while (true) {
QSharedMemory *war = new QSharedMemory(government.key());
QSharedMemory *war = new QSharedMemory(government.nativeIpcKey());
QVERIFY(war);
jail.append(war);
if (!war->attach()) {
QVERIFY(!war->isAttached());
QCOMPARE(war->key(), government.key());
QCOMPARE(war->nativeIpcKey(), government.nativeIpcKey());
QCOMPARE(war->size(), 0);
QVERIFY(!war->data());
QCOMPARE(war->error(), QSharedMemory::OutOfResources);
@ -537,8 +599,8 @@ void tst_QSharedMemory::simpleProducerConsumer()
QFETCH(QSharedMemory::AccessMode, mode);
rememberKey(QLatin1String("market"));
QSharedMemory producer(QLatin1String("market"));
QSharedMemory consumer(QLatin1String("market"));
QSharedMemory producer(platformSafeKey("market"));
QSharedMemory consumer(platformSafeKey("market"));
int size = 512;
QVERIFY(producer.create(size));
QVERIFY(consumer.attach(mode));
@ -560,19 +622,21 @@ void tst_QSharedMemory::simpleProducerConsumer()
#ifndef Q_OS_HPUX
void tst_QSharedMemory::simpleDoubleProducerConsumer()
{
rememberKey(QLatin1String("market"));
QSharedMemory producer(QLatin1String("market"));
QNativeIpcKey nativeKey = rememberKey(QLatin1String("market"));
QSharedMemory producer(nativeKey);
int size = 512;
QVERIFY(producer.create(size));
QVERIFY(producer.detach());
#ifdef QT_POSIX_IPC
// POSIX IPC doesn't guarantee that the shared memory is removed
remove("market");
#endif
if (nativeKey.type() == QNativeIpcKey::Type::PosixRealtime) {
// POSIX IPC doesn't guarantee that the shared memory is removed
remove(nativeKey);
}
QVERIFY(producer.create(size));
{
QSharedMemory consumer(QLatin1String("market"));
QSharedMemory consumer(nativeKey);
QVERIFY(consumer.attach());
}
}
@ -580,11 +644,13 @@ void tst_QSharedMemory::simpleDoubleProducerConsumer()
class Consumer : public QThread
{
public:
QNativeIpcKey nativeKey;
Consumer(const QNativeIpcKey &nativeKey) : nativeKey(nativeKey) {}
void run() override
{
QSharedMemory consumer(QLatin1String("market"));
QSharedMemory consumer(nativeKey);
while (!consumer.attach()) {
if (consumer.error() != QSharedMemory::NotFound)
qDebug() << "consumer: failed to connect" << consumer.error() << consumer.errorString();
@ -615,9 +681,8 @@ public:
class Producer : public QThread
{
public:
Producer() : producer(QLatin1String("market"))
Producer(const QNativeIpcKey &nativeKey) : producer(nativeKey)
{
int size = 1024;
if (!producer.create(size)) {
@ -682,16 +747,16 @@ void tst_QSharedMemory::simpleThreadedProducerConsumer()
{
QFETCH(bool, producerIsThread);
QFETCH(int, threads);
rememberKey(QLatin1String("market"));
QNativeIpcKey nativeKey = rememberKey(QLatin1String("market"));
Producer p;
Producer p(nativeKey);
QVERIFY(p.producer.isAttached());
if (producerIsThread)
p.start();
QList<Consumer*> consumers;
for (int i = 0; i < threads; ++i) {
consumers.append(new Consumer());
consumers.append(new Consumer(nativeKey));
consumers.last()->start();
}
@ -730,17 +795,17 @@ void tst_QSharedMemory::simpleProcessProducerConsumer()
QSKIP("This test is unstable: QTBUG-25655");
rememberKey("market");
QNativeIpcKey nativeKey = rememberKey("market");
QProcess producer;
producer.start(m_helperBinary, QStringList("producer"));
producer.start(m_helperBinary, { "producer", nativeKey.toString() });
QVERIFY2(producer.waitForStarted(), "Could not start helper binary");
QVERIFY2(producer.waitForReadyRead(), "Helper process failed to create shared memory segment: " +
producer.readAllStandardError());
QList<QProcess*> consumers;
unsigned int failedProcesses = 0;
const QStringList consumerArguments = QStringList("consumer");
QStringList consumerArguments = { "consumer", nativeKey.toString() };
for (int i = 0; i < processes; ++i) {
QProcess *p = new QProcess;
p->setProcessChannelMode(QProcess::ForwardedChannels);
@ -791,11 +856,11 @@ void tst_QSharedMemory::uniqueKey()
QFETCH(QString, key1);
QFETCH(QString, key2);
QSharedMemory sm1(key1);
QSharedMemory sm2(key2);
QSharedMemory sm1(platformSafeKey(key1));
QSharedMemory sm2(platformSafeKey(key2));
bool setEqual = (key1 == key2);
bool keyEqual = (sm1.key() == sm2.key());
bool keyEqual = (sm1.nativeIpcKey() == sm2.nativeIpcKey());
bool nativeEqual = (sm1.nativeKey() == sm2.nativeKey());
QCOMPARE(keyEqual, setEqual);