Skip to content

Commit

Permalink
Allow QWindowsPipe{Reader|Writer} to work with foreign event loops, t…
Browse files Browse the repository at this point in the history
…ake 2

When a foreign event loop that does not enter an alertable wait state
is running (which is also the case when a native dialog window is
modal), pipe handlers would freeze temporarily due to their APC
callbacks not being invoked.

We address this problem by moving the I/O callbacks to the Windows
thread pool, and only posting completion events to the main loop
from there. That makes the actual I/O completely independent from
any main loop, while the signal delivery works also with foreign
loops (because Qt event delivery uses Windows messages, which foreign
loops typically handle correctly).

As a nice side effect, performance (and in particular scalability)
is improved.

Several other approaches have been tried:
1) Using QWinEventNotifier was about a quarter slower and scaled much
   worse. Additionally, it also required a rather egregious hack to
   handle the (pathological) case of a single thread talking to both
   ends of a QLocalSocket synchronously.
2) Queuing APCs from the thread pool to the main thread and also
   posting wake-up events to its event loop, and handling I/O on the
   main thread; this performed roughly like this solution, but scaled
   half as well, and the separate wake-up path was still deemed hacky.
3) Only posting wake-up events to the main thread from the thread pool,
   and still handling I/O on the main thread; this still performed
   comparably to 2), and the pathological case was not handled at all.
4) Using this approach for reads and that of 3) for writes was slightly
   faster with big amounts of data, but scaled slightly worse, and the
   diverging implementations were deemed not desirable.

Fixes: QTBUG-64443
Change-Id: I66443c3021d6ba98639a214c3e768be97d2cf14b
Reviewed-by: Oswald Buddenhagen <[email protected]>
  • Loading branch information
Alex Trotsenko committed Mar 2, 2021
1 parent d316ae8 commit f265c87
Show file tree
Hide file tree
Showing 9 changed files with 852 additions and 361 deletions.
468 changes: 292 additions & 176 deletions src/corelib/io/qwindowspipereader.cpp

Large diffs are not rendered by default.

51 changes: 27 additions & 24 deletions src/corelib/io/qwindowspipereader_p.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2021 Alex Trotsenko <[email protected]>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
Expand Down Expand Up @@ -52,6 +53,8 @@
//

#include <qobject.h>
#include <qdeadlinetimer.h>
#include <qmutex.h>
#include <private/qringbuffer_p.h>

#include <qt_windows.h>
Expand All @@ -70,7 +73,7 @@ class Q_CORE_EXPORT QWindowsPipeReader : public QObject
void stop();
void drainAndStop();

void setMaxReadBufferSize(qint64 size) { readBufferMaxSize = size; }
void setMaxReadBufferSize(qint64 size);
qint64 maxReadBufferSize() const { return readBufferMaxSize; }

bool isPipeClosed() const { return pipeBroken; }
Expand All @@ -80,46 +83,46 @@ class Q_CORE_EXPORT QWindowsPipeReader : public QObject
bool waitForReadyRead(int msecs);
bool waitForPipeClosed(int msecs);

bool isReadOperationActive() const { return readSequenceStarted; }
bool isReadOperationActive() const;

Q_SIGNALS:
void winError(ulong, const QString &);
void readyRead();
void pipeClosed();
void _q_queueReadyRead(QPrivateSignal);

protected:
bool event(QEvent *e) override;

private:
void startAsyncReadHelper(qint64 bytesToRead);
void cancelAsyncRead();
static void CALLBACK readFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered,
OVERLAPPED *overlappedBase);
void notified(DWORD errorCode, DWORD numberOfBytesRead);
enum State { Stopped, Running, Draining };

void startAsyncReadLocked();
void cancelAsyncRead(State newState);
static void CALLBACK waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context,
PTP_WAIT wait, TP_WAIT_RESULT waitResult);
bool readCompleted(DWORD errorCode, DWORD numberOfBytesRead);
DWORD checkPipeState();
bool waitForNotification(int timeout);
void emitPendingReadyRead();
void emitPipeClosed();

class Overlapped : public OVERLAPPED
{
Q_DISABLE_COPY_MOVE(Overlapped)
public:
explicit Overlapped(QWindowsPipeReader *reader);
void clear();
QWindowsPipeReader *pipeReader;
};
bool waitForNotification(const QDeadlineTimer &deadline);
bool consumePendingAndEmit(bool allowWinActPosting);
bool consumePending();

HANDLE handle;
Overlapped overlapped;
HANDLE eventHandle;
HANDLE syncHandle;
PTP_WAIT waitObject;
OVERLAPPED overlapped;
qint64 readBufferMaxSize;
QRingBuffer readBuffer;
qint64 actualReadBufferSize;
qint64 bytesPending;
qint64 pendingReadBytes;
mutable QMutex mutex;
DWORD lastError;

enum State { Stopped, Running, Draining } state;
State state;
bool readSequenceStarted;
bool notifiedCalled;
bool pipeBroken;
bool readyReadPending;
bool winEventActPosted;
bool inReadyRead;
};

Expand Down
Loading

0 comments on commit f265c87

Please sign in to comment.