Changeset 134 for trunk


Ignore:
Timestamp:
Oct 9, 2006, 12:14:18 AM (19 years ago)
Author:
dmik
Message:

Kernel: QProcess: Significantly improved the speed of reading data from the child process using readyReadStd[out|err]() signals (it's now approximately 380 times faster on my system for a 28 MB file).

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/include/qprocess.h

    r41 r134  
    166166    friend class QProc;
    167167#endif
     168#if defined(Q_OS_OS2)
     169    friend class QProcessMonitor;
     170#endif
    168171
    169172#if defined(Q_DISABLE_COPY) // Disabled copy constructor and operator=
  • trunk/src/kernel/qprocess_pm.cpp

    r51 r134  
    4545#include "qregexp.h"
    4646#include "qdir.h"
     47#include "qthread.h"
     48#include "qintdict.h"
     49#include "qmutex.h"
    4750#include "private/qinternal_p.h"
    4851#include "qt_os2.h"
     
    5255//#define QT_QPROCESS_DEBUG
    5356
    54 const HFILE HF_STDIN = 0;
    55 const HFILE HF_STDOUT = 1;
    56 const HFILE HF_STDERR = 2;
    57 const HFILE HF_ANY = 0xFFFFFFFF;
    58 
    59 const PID PID_NULL = ~1;
    60 
    61 const ULONG PIPE_SIZE = 1024;
    62 
    63 // just fake constants, for convenience
    64 const int FD_PIPE_STDOUT = 1;
    65 const int FD_PIPE_STDERR = 2;
     57#define HF_STDIN        HFILE( 0 )
     58#define HF_STDOUT       HFILE( 1 )
     59#define HF_STDERR       HFILE( 2 )
     60#define HF_NULL         HFILE( ~0 )
     61
     62#define HP_NULL         HPIPE( ~0 )
     63#define KEY_NULL        USHORT( ~0 )
     64
     65#define PID_NULL        PID( ~0 )
     66
     67enum
     68{
     69    PIPE_SIZE_STDIN = 65520, // max
     70    PIPE_SIZE_STDOUT = 65520, // max
     71    PIPE_SIZE_STDERR = 4096,
     72
     73    POLL_TIMER = 100,
     74
     75    // new pipe data notification
     76    WM_U_PIPE_RDATA = WM_USER + 0,
     77    WM_U_PIPE_CLOSE = WM_USER + 1,
     78};
    6679
    6780#if defined(QT_QPROCESS_DEBUG)
    6881#include <stdarg.h>
     82static HFILE StdErrHandle = HF_NULL;
     83QtMsgHandler OldMsgHandler = NULL;
     84static void StdErrMsgHandler( QtMsgType type, const char *msg )
     85{
     86    if ( OldMsgHandler == NULL ) {
     87       size_t len = strlen( msg );
     88       ULONG written = 0;
     89       DosWrite( StdErrHandle, msg, len, &written );
     90       const char *EOL = "\n\r";
     91       DosWrite( StdErrHandle, EOL, 2, &written );
     92    } else {
     93        OldMsgHandler( type, msg );
     94    }
     95}
     96#define InstallQtMsgHandler() \
     97    do { \
     98        DosDupHandle( HF_STDERR, &StdErrHandle ); \
     99        qInstallMsgHandler( StdErrMsgHandler ); \
     100    } while (0)
     101#define UninstallQtMsgHandler() \
     102    do { \
     103        qInstallMsgHandler( OldMsgHandler ); \
     104        DosClose( StdErrHandle ); \
     105    } while (0)
     106#else
     107#define InstallQtMsgHandler() do {} while (0)
     108#define UninstallQtMsgHandler() do {} while (0)
    69109#endif
    70110
     
    77117{
    78118public:
     119    struct Pipe
     120    {
     121        Pipe () : pipe( HP_NULL ), key( KEY_NULL ), pending( 0 )
     122                , closed( false ) {}
     123        HPIPE pipe;
     124        USHORT key;
     125        QMembuf buf;
     126        // note: when QProcess is watched by QProcessMonitor, the below fields
     127        // must be accessed from under the QProcessMonitor lock
     128        uint pending;
     129        bool closed : 1;
     130    };
     131
    79132    QProcessPrivate( QProcess *proc )
    80133    {
    81134        stdinBufRead = 0;
    82         pipeStdin = HF_ANY;
    83         pipeStdout = HF_ANY;
    84         pipeStderr = HF_ANY;
     135        pipeStdin = HP_NULL;
     136        pid = PID_NULL;
    85137        exitValuesCalculated = FALSE;
    86138
    87139        lookup = new QTimer( proc );
    88         qApp->connect( lookup, SIGNAL(timeout()),
    89                 proc, SLOT(timeout()) );
    90 
    91         pid = PID_NULL;
     140        qApp->connect( lookup, SIGNAL(timeout()), proc, SLOT(timeout()) );
    92141    }
    93142
     
    102151            delete stdinBuf.dequeue();
    103152        }
     153        stdinBufRead = 0;
    104154        closeHandles();
    105         stdinBufRead = 0;
    106         pipeStdin = HF_ANY;
    107         pipeStdout = HF_ANY;
    108         pipeStderr = HF_ANY;
     155        stdout.buf.clear();
     156        stderr.buf.clear();
     157        pid = PID_NULL;
    109158        exitValuesCalculated = FALSE;
    110 
    111         pid = PID_NULL;
    112     }
    113 
     159    }
     160
     161    Pipe *findPipe( USHORT key )
     162    {
     163        if ( stdout.key == key ) return &stdout;
     164        if ( stderr.key == key ) return &stderr;
     165        return NULL;
     166    }
     167   
     168    void closePipe( Pipe *pipe )
     169    {
     170        if ( pipe->pipe != HP_NULL ) {
     171            Q_ASSERT( pipe->key == KEY_NULL );
     172            DosDisConnectNPipe( pipe->pipe );
     173            DosClose( pipe->pipe );
     174            pipe->pipe = HP_NULL;
     175            pipe->pending = 0;
     176            pipe->closed = FALSE;
     177        }
     178    }
     179   
     180    bool readPipe( Pipe *pipe );
     181   
    114182    void closeHandles()
    115183    {
    116         if( pipeStdin != HF_ANY ) {
     184        if( pipeStdin != HP_NULL ) {
    117185            DosDisConnectNPipe( pipeStdin );
    118186            DosClose( pipeStdin );
    119             pipeStdin = HF_ANY;
     187            pipeStdin = HP_NULL;
    120188        }
    121         if( pipeStdout != HF_ANY ) {
    122             // check if STDERR was redirected to STDOUT
    123             if ( pipeStderr == pipeStdout )
    124                 pipeStderr = HF_ANY;
    125             DosDisConnectNPipe( pipeStdout );
    126             DosClose( pipeStdout );
    127             pipeStdout = HF_ANY;
    128         }
    129         if( pipeStderr != HF_ANY ) {
    130             DosDisConnectNPipe( pipeStderr );
    131             DosClose( pipeStderr );
    132             pipeStderr = HF_ANY;
    133         }
    134     }
    135 
    136     QMembuf bufStdout;
    137     QMembuf bufStderr;
     189        closePipe( &stdout );
     190        closePipe( &stderr );
     191    }
    138192
    139193    QPtrQueue <QByteArray> stdinBuf;
     194    uint stdinBufRead;
    140195
    141196    HPIPE pipeStdin;
    142     HPIPE pipeStdout;
    143     HPIPE pipeStderr;
     197   
     198    Pipe stdout;
     199    Pipe stderr;
    144200
    145201    PID pid;
     
    147203    QTimer *lookup;
    148204
    149     uint stdinBufRead;
    150 
    151     bool exitValuesCalculated;
     205    bool exitValuesCalculated : 1;
    152206};
    153207
     208class QProcessMonitor : public QPMObjectWindow
     209{
     210public:
     211    class Thread : public QThread
     212    {
     213    public:
     214        Thread( QProcessMonitor *m ) : mon( m ) {}
     215        void run() { mon->monitor(); }
     216        QProcessMonitor *mon;
     217    };
     218
     219    QProcessMonitor();
     220    ~QProcessMonitor();
     221
     222    QMutex &mlock() { return lock; }
     223   
     224    bool isOk() const { return pipeSem != 0 && thread != NULL; }
     225
     226    bool addProcess( QProcess *proc );
     227    void removeProcess( QProcessPrivate *d,
     228                        QProcessPrivate::Pipe *pipe = NULL );
     229
     230    void monitor();
     231    MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 );
     232   
     233private:
     234
     235    struct PipeStates
     236    {
     237        PipeStates() {
     238            size = 4;
     239            arr = new PIPESEMSTATE[ size ];
     240        }
     241        ~PipeStates() {
     242            delete[] arr;
     243        }
     244        void ensure( size_t sz ) {
     245            // best size for sz pipes is sz * 2 (to be able to store both
     246            // NPSS_RDATA & NPSS_CLOSE for every pipe) + one for NPSS_EOI
     247            size_t newSize = sz * 2 + 1;
     248            newSize = ((newSize + 5 - 1) / 5) * 5;
     249            if ( newSize != size ) {
     250                delete[] arr;
     251                size = newSize;
     252                arr = new PIPESEMSTATE[ size ];
     253            }
     254        }
     255        size_t dataSize() { return size * sizeof(PIPESEMSTATE); }
     256        PIPESEMSTATE *data() { return arr; }
     257        PIPESEMSTATE &operator[]( size_t i ) {
     258            Q_ASSERT( i < size );
     259            return arr[i];
     260        }
     261    private:
     262        PIPESEMSTATE *arr;
     263        size_t size;
     264    };
     265
     266    bool addPipe( QProcess *proc, QProcessPrivate::Pipe *pipe );
     267    void removePipe( QProcessPrivate::Pipe *pipe );
     268
     269    HEV pipeSem;
     270    Thread *thread;
     271    bool threadRunning;
     272    QIntDict<QProcess> pipeKeys;
     273    PipeStates pipeStates;
     274    QMutex lock;
     275};
     276
     277QProcessMonitor::QProcessMonitor()
     278    : pipeSem( 0 ), thread( NULL ), threadRunning( FALSE )
     279{
     280    APIRET rc = DosCreateEventSem( NULL, &pipeSem, DC_SEM_SHARED, 0 );
     281    if ( rc != NO_ERROR ) {
     282#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
     283        qSystemWarning( "Failed to create a semaphore!", rc );
     284#endif
     285        return;
     286    }
     287   
     288    thread = new Thread( this );
     289    Q_ASSERT( thread != NULL );
     290}
     291
     292QProcessMonitor::~QProcessMonitor()
     293{
     294    if ( thread ) {
     295        lock.lock();
     296        if ( threadRunning ) {
     297            threadRunning = FALSE;
     298            DosPostEventSem( pipeSem );
     299        }
     300        lock.unlock();
     301        thread->wait();
     302        delete thread;
     303    }
     304   
     305    if ( pipeSem )
     306        DosCloseEventSem( pipeSem );
     307}
     308
     309bool QProcessMonitor::addPipe( QProcess *proc, QProcessPrivate::Pipe *pipe )
     310{
     311    // Note: we use HPIPE as pipe keys in DosSetNPipeSem. However, HPIPE
     312    // is 32-bit (ulong), while usKey in PIPESEMSTATE is 16-bit (USHORT)
     313    // We unreasonably assume that the system will never assign HPIPE >= 0xFFFF,
     314    // and just cast HPIPE to USHORT. There is an assertion checking this
     315    // condition, so this method should simply return FALSE once our assumption
     316    // breaks.
     317
     318    Q_ASSERT( pipe->pipe < HPIPE( KEY_NULL ) );
     319    if ( pipe->pipe >= HPIPE( KEY_NULL ) )
     320        return FALSE;
     321
     322    Q_ASSERT( pipe->pipe != HP_NULL && pipe->key == KEY_NULL );
     323   
     324    pipe->key = USHORT( pipe->pipe );
     325    APIRET rc = DosSetNPipeSem( pipe->pipe, (HSEM) pipeSem, pipe->key );
     326    if ( rc != NO_ERROR ) {
     327#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
     328        qSystemWarning( "Failed to set the pipe semaphore!", rc );
     329#endif       
     330        return FALSE;
     331    }
     332
     333    pipeKeys.insert( pipe->key, proc );
     334    return TRUE;
     335}
     336
     337void QProcessMonitor::removePipe( QProcessPrivate::Pipe *pipe )
     338{
     339    Q_ASSERT( pipe->pipe != HP_NULL && pipe->key != KEY_NULL );
     340
     341    /// @todo (r=dmik) is this really necessary to detach pipeSem?
     342    DosSetNPipeSem( pipe->pipe, 0, 0 );
     343    bool ok = pipeKeys.remove( pipe->key );
     344    pipe->key = KEY_NULL;
     345    Q_ASSERT( ok );
     346    Q_UNUSED( ok );
     347}
     348
     349bool QProcessMonitor::addProcess( QProcess *proc )
     350{
     351#if defined(QT_QPROCESS_DEBUG)
     352    qDebug( "QProcessMonitor::addProcess(): proc=%p d=%p", proc, proc->d );
     353#endif
     354
     355    QProcessPrivate *d = proc->d;
     356   
     357    // check if we need to monitor this process before entering the lock
     358    if ( d->stdout.pipe == HP_NULL && d->stderr.pipe == HP_NULL )
     359        return TRUE;
     360   
     361    uint newPipes = 0;
     362
     363    QMutexLocker locker( &lock );
     364   
     365    if ( d->stdout.pipe != HP_NULL ) {
     366        if ( !addPipe( proc, &d->stdout ) )
     367            return FALSE;
     368        ++ newPipes;
     369    }
     370
     371    if ( d->stderr.pipe != HP_NULL ) {
     372        if ( !addPipe( proc, &d->stderr ) ) {
     373            removePipe( &d->stderr );
     374            return FALSE;
     375        }
     376        ++ newPipes;
     377    }
     378   
     379    Q_ASSERT( newPipes > 0 );
     380
     381    pipeStates.ensure( pipeKeys.count() );
     382   
     383    // start the monitor thread if necessary
     384    if ( pipeKeys.count() == newPipes ) {
     385        threadRunning = TRUE;
     386        thread->start();
     387    }
     388   
     389#if defined(QT_QPROCESS_DEBUG)
     390    qDebug( "QProcessMonitor::addProcess(): added %d pipes", newPipes );
     391#endif       
     392    return TRUE;
     393}
     394
     395void QProcessMonitor::removeProcess( QProcessPrivate *d,
     396                                     QProcessPrivate::Pipe *pipe /* = NULL */)
     397{
     398#if defined(QT_QPROCESS_DEBUG)
     399    qDebug( "QProcessMonitor::removeProcess(): d=%p pipe=%p key=%04hX",
     400            d, pipe, pipe ? pipe->key : KEY_NULL );
     401#endif
     402   
     403    // check if we monitor this process before entering the lock
     404    if ( d->stdout.pipe == HP_NULL && d->stderr.pipe == HP_NULL )
     405        return;
     406
     407    lock.lock();
     408
     409    if ( pipeKeys.count() == 0 ) {
     410        // Nothing to do. This is an outdated general removeProcess (d, NULL)
     411        // call from the QProcess destructor or from isRunning(). Just return.
     412        lock.unlock();
     413        return;
     414    }
     415   
     416    if ( pipe ) {
     417        removePipe( pipe );
     418    } else {
     419        if ( d->stdout.pipe != HP_NULL && d->stdout.key != KEY_NULL )
     420            removePipe( &d->stdout );
     421        if ( d->stderr.pipe != HP_NULL && d->stderr.key != KEY_NULL )
     422            removePipe( &d->stderr );
     423    }
     424
     425    pipeStates.ensure( pipeKeys.count() );
     426   
     427    // stop the monitor thread if no more necessary
     428    if ( pipeKeys.count() == 0 ) {
     429        Q_ASSERT( threadRunning );
     430        if ( threadRunning ) {
     431            threadRunning = FALSE;
     432            DosPostEventSem( pipeSem );
     433        }
     434        lock.unlock();
     435        thread->wait();
     436    } else {
     437        lock.unlock();
     438    }
     439}
     440
     441/** Monitor thread function */
     442void QProcessMonitor::monitor()
     443{
     444#if defined(QT_QPROCESS_DEBUG)
     445    qDebug( "QProcessMonitor::monitor() ENTER" );
     446#endif
     447
     448    lock.lock();
     449    while( 1 ) {
     450        lock.unlock();
     451        APIRET rc = DosWaitEventSem( pipeSem, SEM_INDEFINITE_WAIT );
     452        lock.lock();
     453        if ( rc != NO_ERROR ) {
     454#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
     455            qSystemWarning( "Failed to wait on event semaphore!", rc );
     456#endif           
     457            break;
     458        }
     459
     460        ULONG posts = 0;
     461        DosResetEventSem( pipeSem, &posts );
     462#if defined(QT_QPROCESS_DEBUG)       
     463        qDebug( "QProcessMonitor::monitor(): got semaphore (posts=%ld)", posts );
     464#endif
     465
     466        if ( !threadRunning )
     467            break;
     468
     469        rc = DosQueryNPipeSemState( (HSEM) pipeSem, pipeStates.data(),
     470                                    pipeStates.dataSize() );
     471        if ( rc != NO_ERROR ) {
     472#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
     473            qSystemWarning( "Failed to query pipe semaphore state!", rc );
     474#endif
     475            continue;
     476        }
     477
     478        // In the returned information array, CLOSE and READ records for the
     479        // same pipe key may be mixed. We need CLOSE messages to be posted after
     480        // READ messages, so we do two passes.
     481
     482        int pass = 0;
     483        for ( int i = 0; pass < 2; ++ i ) {
     484            if ( pipeStates[i].fStatus == NPSS_EOI ) {
     485                ++ pass;
     486                i = -1;
     487                continue;
     488            }
     489            if ( pass == 0 && pipeStates[i].fStatus != NPSS_RDATA )
     490                continue;
     491            if ( pass == 1 && pipeStates[i].fStatus != NPSS_CLOSE )
     492                continue;
     493
     494#if defined(QT_QPROCESS_DEBUG)
     495            qDebug( " %d/%d: fStatus=%u fFlag=%02X usKey=%04hX usAvail=%hu",
     496                    pass, i, (uint) pipeStates[i].fStatus,
     497                    (uint) pipeStates[i].fFlag, pipeStates[i].usKey,
     498                    pipeStates[i].usAvail );
     499#endif
     500            QProcess *proc = pipeKeys.find( pipeStates[i].usKey );
     501            Q_ASSERT( proc );
     502            if ( !proc )
     503                continue;
     504#if defined(QT_QPROCESS_DEBUG)
     505            qDebug( "  proc=%p (%s/%s) d=%p",
     506                       proc, proc->name(), proc->className(), proc->d );
     507#endif
     508            QProcessPrivate::Pipe *pipe = proc->d->findPipe( pipeStates[i].usKey );
     509            Q_ASSERT( pipe );
     510            if ( !pipe )
     511                continue;
     512
     513            if ( pipeStates[i].fStatus == NPSS_RDATA ) {
     514                bool wasPending = pipe->pending > 0;   
     515                pipe->pending = pipeStates[i].usAvail;
     516                // inform the GUI thread that there is new data
     517                if ( !wasPending )
     518                    WinPostMsg( hwnd(), WM_U_PIPE_RDATA, MPFROMP( proc ),
     519                                MPFROMSHORT( pipe->key ) );
     520            } else if ( pipeStates[i].fStatus == NPSS_CLOSE ) {
     521                // there may be repeated CLOSE records for the same pipe
     522                // in subsequent DosQueryNPipeSemState() calls
     523                if ( pipe->closed == FALSE ) {
     524                    pipe->closed = TRUE;
     525                    // inform the GUI thread that the client's pipe end
     526                    // was closed
     527                    WinPostMsg( hwnd(), WM_U_PIPE_CLOSE, MPFROMP( proc ),
     528                                MPFROMSHORT( pipe->key ) );
     529                }
     530            }
     531        }
     532    }
     533    lock.unlock();
     534
     535#if defined(QT_QPROCESS_DEBUG)
     536    qDebug( "QProcessMonitor::monitor() LEAVE" );
     537#endif           
     538}
     539
     540MRESULT QProcessMonitor::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
     541{
     542    switch ( msg ) {
     543        case WM_U_PIPE_RDATA: {
     544            QProcess *proc = (QProcess *) PVOIDFROMMP( mp1 );
     545            USHORT key = SHORT1FROMMP( mp2 );
     546#if defined(QT_QPROCESS_DEBUG)
     547            qDebug( "QProcessMonitor::WM_U_PIPE_RDATA: proc=%p (%s/%s) d=%p "
     548                    "key=%04hX",
     549                    proc, proc->name(), proc->className(), proc->d, key );
     550#endif
     551            // check parameter validity (we can safely do it outside the lock
     552            // because the GUI thread is the only that can modify pipeKeys)
     553            if ( proc == pipeKeys.find( key ) ) {
     554                QProcessPrivate *d = proc->d;
     555                if ( d->stdout.key == key ) {
     556                    emit proc->readyReadStdout();
     557                } else {
     558                    Q_ASSERT( d->stderr.key == key );
     559                    emit proc->readyReadStderr();
     560                }
     561            }
     562            break;
     563        }
     564        case WM_U_PIPE_CLOSE: {
     565            QProcess *proc = (QProcess *) PVOIDFROMMP( mp1 );
     566            USHORT key = SHORT1FROMMP( mp2 );
     567#if defined(QT_QPROCESS_DEBUG)
     568            qDebug( "QProcessMonitor::WM_U_PIPE_CLOSE: proc=%p (%s/%s) d=%p "
     569                    "key=%04hX",
     570                    proc, proc->name(), proc->className(), proc->d, key );
     571#endif
     572            // check parameter validity (we can safely do it outside the lock
     573            // because the GUI thread is the only that can modify pipeKeys)
     574            if ( proc == pipeKeys.find( key ) ) {
     575                QProcessPrivate *d = proc->d;
     576                QProcessPrivate::Pipe *pipe = d->findPipe( key );
     577                Q_ASSERT( pipe );
     578                if ( pipe ) {
     579                    Q_ASSERT( pipe->closed );
     580                    // remove the single pipe from watching
     581                    removeProcess( d, pipe );
     582                    // close the pipe since no more necessary
     583                    // (pipe is not watched anymore, no need to lock access)
     584                    if ( pipe->pending == 0 )
     585                        d->closePipe( pipe );
     586                }
     587            }
     588            break;
     589        }
     590        default:
     591            break;
     592    }
     593   
     594    return FALSE;
     595}
     596
     597static QProcessMonitor *processMonitor = NULL;
     598
     599void QProcessMonitor_cleanup()
     600{
     601#if defined(QT_QPROCESS_DEBUG)
     602    qDebug( "QProcessMonitor_cleanup()" );
     603#endif
     604    delete processMonitor;
     605    processMonitor = NULL;
     606}
     607
     608/** Returns true if some data was successfully read */
     609bool QProcessPrivate::readPipe( Pipe *pipe )
     610{
     611#if defined(QT_QPROCESS_DEBUG)
     612    qDebug( "QProcessPrivate::readPipe(): this=%p key=%04hX", this, pipe->key );
     613#endif
     614
     615    if ( pipe->pipe == HP_NULL )
     616        return false;
     617
     618    // Pipe::pending and Pipe::closed need the lock
     619    QMutex *lock = NULL;
     620   
     621    // Notes:
     622    // 1. If procesMonitor is NULL, it which means we're somewhere inside
     623    //    the QApplication destruction procedure.
     624    // 2. If pipe->key us KEY_NULL, it means GUI thread has already processed
     625    //    the WM_U_PIPE_CLOSE message and we're free to access fields w/o a
     626    //    lock. pipe->key is assigned only on the GUI thread, so it's safe
     627    //    to check it here w/o the lock.
     628
     629    if ( processMonitor && pipe->key != KEY_NULL )
     630        lock = &processMonitor->mlock();
     631   
     632    QByteArray *ba = NULL;
     633    bool close = FALSE;
     634   
     635    {
     636        QMutexLocker locker( lock );
     637       
     638        if ( pipe->pending == 0 )
     639            return false;
     640       
     641        ba = new QByteArray( pipe->pending );
     642           
     643        ULONG read = 0;
     644        APIRET rc = DosRead( pipe->pipe, ba->data(), pipe->pending, &read );
     645        if ( rc != NO_ERROR ) {
     646            delete ba;
     647#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
     648            qSystemWarning( "Failed to read from the pipe!", rc );
     649#endif
     650            return false;
     651        }
     652   
     653        Q_ASSERT( read == pipe->pending );
     654        if ( read < pipe->pending )
     655            ba->resize( read );
     656       
     657        pipe->pending -= read;
     658       
     659        close = pipe->pending == 0 && pipe->closed;
     660    }
     661   
     662    if ( close ) {
     663        // the client's end of pipe has been closed, so close our end as well
     664        if ( processMonitor && pipe->key != KEY_NULL ) {
     665            // WM_U_PIPE_CLOSE has been posted but not yet processed
     666            processMonitor->removeProcess( this, pipe );
     667        }
     668        closePipe( pipe );
     669    }
     670       
     671    pipe->buf.append( ba );
     672    return true;
     673}
    154674
    155675/***********************************************************************
     
    164684    exitStat = 0;
    165685    exitNormal = FALSE;
     686   
     687#if defined(QT_QPROCESS_DEBUG)
     688    qDebug( "~QProcess::init(): d=%p", d );
     689#endif
    166690}
    167691
    168692void QProcess::reset()
    169693{
     694    // remove monitoring if the monitor is there (note that it's done before
     695    // resetting QProcessPrivate which has fields protected by the
     696    // QProcessMonitor lock)
     697    if ( processMonitor )
     698        processMonitor->removeProcess( d );
     699   
    170700    d->reset();
    171701    exitStat = 0;
    172702    exitNormal = FALSE;
    173     d->bufStdout.clear();
    174     d->bufStderr.clear();
    175703}
    176704
    177705QMembuf* QProcess::membufStdout()
    178706{
    179     if( d->pipeStdout != 0 )
    180         socketRead( FD_PIPE_STDOUT );
    181     return &d->bufStdout;
     707    if( d->stdout.pipe != 0 )
     708        d->readPipe( &d->stdout );
     709    return &d->stdout.buf;
    182710}
    183711
    184712QMembuf* QProcess::membufStderr()
    185713{
    186     if( d->pipeStderr != 0 )
    187         socketRead( FD_PIPE_STDERR );
    188     return &d->bufStderr;
     714    if( d->stderr.pipe != 0 )
     715        d->readPipe( &d->stderr );
     716    return &d->stderr.buf;
    189717}
    190718
    191719QProcess::~QProcess()
    192720{
     721#if defined(QT_QPROCESS_DEBUG)
     722    qDebug( "~QProcess::QProcess(): d=%p", d );
     723#endif
     724
     725    // remove monitoring if the monitor is there (note that it's done before
     726    // resetting QProcessPrivate which has fields protected by the
     727    // QProcessMonitor lock)
     728    if ( processMonitor )
     729        processMonitor->removeProcess( d );
     730
    193731    delete d;
    194732}
    195733
    196 #if defined(QT_QPROCESS_DEBUG)
    197 static void DosPrintf( HFILE fd, const char *fmt, ... )
    198 {
    199    static char buf[ 1024 ];
    200    
    201    va_list args;
    202    va_start( args, fmt );
    203    vsnprintf( buf, sizeof(buf), fmt, args );
    204    va_end ( args );
    205    
    206    ULONG written = 0;
    207    DosWrite( fd, buf, strlen( buf ), &written );
    208 }
    209 #endif
    210 
    211734bool QProcess::start( QStringList *env )
    212735{
     
    214737    qDebug( "QProcess::start()" );
    215738#endif
     739
    216740    reset();
    217741
     
    444968    // create STDIN/OUT/ERR redirection pipes
    445969   
    446     HFILE tmpStdin = HF_ANY, tmpStdout = HF_ANY, tmpStderr = HF_ANY;
     970    if ( comms & (Stdout | Stderr) ) {
     971        // lazily create the process monitor
     972        if ( !processMonitor ) {
     973            processMonitor = new QProcessMonitor();
     974            Q_ASSERT( processMonitor );
     975            if ( !processMonitor )
     976                return FALSE;
     977            if ( !processMonitor->isOk() ) {
     978                QProcessMonitor_cleanup();
     979                return FALSE;
     980            }
     981            qAddPostRoutine( QProcessMonitor_cleanup );
     982        }
     983    }
     984   
     985    HFILE tmpStdin = HF_NULL, tmpStdout = HF_NULL, tmpStderr = HF_NULL;
    447986    HFILE realStdin = HF_STDIN, realStdout = HF_STDOUT, realStderr = HF_STDERR;
    448987
     
    451990    DosGetInfoBlocks( NULL, &ppib );
    452991
     992    // use the custom Qt message handler to print errors/wranings
     993    // to the console when the real STDERR handle is redirected
     994    InstallQtMsgHandler();
     995
    453996    if ( comms & Stdin ) {
    454         HFILE clientStdin = HF_ANY;
     997        HFILE clientStdin = HF_NULL;
    455998        do {
    456999            // create a Stdin pipe
     
    4591002            rc = DosCreateNPipe( pathBuf, &d->pipeStdin,
    4601003                                 NP_ACCESS_OUTBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
    461                                  PIPE_SIZE, PIPE_SIZE, 0 );
     1004                                 PIPE_SIZE_STDIN, 0, 0 );
    4621005            if ( rc != NO_ERROR )
    4631006                break;
     
    4781021        } while( 0 );
    4791022        // close the client end anyway (no more necessary)
    480         if ( clientStdin != HF_ANY )
     1023        if ( clientStdin != HF_NULL )
    4811024            DosClose ( clientStdin );
    4821025        // close all opened handles on error
    4831026        if ( rc  != NO_ERROR ) {
    4841027#if defined(QT_QPROCESS_DEBUG)
    485             DosPrintf( tmpStderr != HF_ANY ? tmpStderr : realStderr,
    486                        "Failed to create a STDIN redirection (%s): SYS%04ld\n\r",
    487                        pathBuf, rc );
    488 #endif
    489             if ( tmpStdin != HF_ANY )
     1028            qDebug( "Failed to create a STDIN redirection (%s): SYS%04ld",
     1029                    pathBuf, rc );
     1030#endif
     1031            if ( tmpStdin != HF_NULL )
    4901032                DosClose ( tmpStdin );
    4911033            d->closeHandles();
     1034            UninstallQtMsgHandler();
    4921035            return FALSE;
    4931036        }
    4941037    }
    4951038    if ( comms & Stdout ) {
    496         HFILE clientStdout = HF_ANY;
     1039        HFILE clientStdout = HF_NULL;
    4971040        do {
    4981041            // create a Stdout pipe
    4991042            sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stdout",
    5001043                     ppib->pib_ulpid, this );
    501             rc = DosCreateNPipe( pathBuf, &d->pipeStdout,
     1044            rc = DosCreateNPipe( pathBuf, &d->stdout.pipe,
    5021045                                 NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
    503                                  PIPE_SIZE, PIPE_SIZE, 0 );
     1046                                 0, PIPE_SIZE_STDOUT, 0 );
    5041047            if ( rc != NO_ERROR )
    5051048                break;
    506             DosConnectNPipe( d->pipeStdout );
     1049            DosConnectNPipe( d->stdout.pipe );
    5071050            // open a client end of the Stdout pipe
    5081051            ULONG action = 0;
     
    5201063        } while( 0 );
    5211064        // close the client end anyway (no more necessary)
    522         if ( clientStdout != HF_ANY )
     1065        if ( clientStdout != HF_NULL )
    5231066            DosClose ( clientStdout );
    5241067        // close all opened handles on error
    5251068        if ( rc != NO_ERROR ) {
    5261069#if defined(QT_QPROCESS_DEBUG)
    527             DosPrintf( tmpStderr != HF_ANY ? tmpStderr : realStderr,
    528                        "Failed to create a STDOUT redirection (%s): SYS%04ld\n\r",
    529                        pathBuf, rc );
    530 #endif
    531             if ( tmpStdin != HF_ANY ) {
     1070            qDebug( "Failed to create a STDOUT redirection (%s): SYS%04ld",
     1071                    pathBuf, rc );
     1072#endif
     1073            if ( tmpStdin != HF_NULL ) {
    5321074                DosDupHandle( tmpStdin, &realStdin );
    5331075                DosClose( tmpStdin );
    5341076            }
    535             if ( tmpStdout != HF_ANY )
     1077            if ( tmpStdout != HF_NULL )
    5361078                DosClose ( tmpStdout );
    5371079            d->closeHandles();
     1080            UninstallQtMsgHandler();
    5381081            return FALSE;
    5391082        }
    5401083    }
    5411084    if ( (comms & Stderr) && !(comms & DupStderr) ) {
    542         HFILE clientStderr = HF_ANY;
     1085        HFILE clientStderr = HF_NULL;
    5431086        do {
    5441087            // create a Stderr pipe
    5451088            sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stderr",
    5461089                     ppib->pib_ulpid, this );
    547             rc = DosCreateNPipe( pathBuf, &d->pipeStderr,
     1090            rc = DosCreateNPipe( pathBuf, &d->stderr.pipe,
    5481091                                 NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
    549                                  PIPE_SIZE, PIPE_SIZE, 0 );
     1092                                 0, PIPE_SIZE_STDERR, 0 );
    5501093            if ( rc != NO_ERROR )
    5511094                break;
    552             DosConnectNPipe( d->pipeStderr );
     1095            DosConnectNPipe( d->stderr.pipe );
    5531096            // open a client end of the Stderr pipe
    5541097            ULONG action = 0;
     
    5661109        } while( 0 );
    5671110        // close the client end anyway (no more necessary)
    568         if ( clientStderr != HF_ANY )
     1111        if ( clientStderr != HF_NULL )
    5691112            DosClose ( clientStderr );
    5701113        // close all opened handles on error
    5711114        if ( rc != NO_ERROR ) {
    5721115#if defined(QT_QPROCESS_DEBUG)
    573             DosPrintf( tmpStderr != HF_ANY ? tmpStderr : realStderr,
    574                        "Failed to create a STDERR redirection (%s): SYS%04ld\n\r",
    575                        pathBuf, rc );
    576 #endif
    577             if ( tmpStdin != HF_ANY ) {
     1116            qDebug( "Failed to create a STDERR redirection (%s): SYS%04ld",
     1117                    pathBuf, rc );
     1118#endif
     1119            if ( tmpStdin != HF_NULL ) {
    5781120                DosDupHandle( tmpStdin, &realStdin );
    5791121                DosClose( tmpStdin );
    5801122            }
    581             if ( tmpStdout != HF_ANY ) {
     1123            if ( tmpStdout != HF_NULL ) {
    5821124                DosDupHandle( tmpStdout, &realStdout );
    5831125                DosClose( tmpStdout );
    5841126            }
    585             if ( tmpStderr != HF_ANY )
     1127            if ( tmpStderr != HF_NULL )
    5861128                DosClose ( tmpStderr );
    5871129            d->closeHandles();
     1130            UninstallQtMsgHandler();
    5881131            return FALSE;
    5891132        }
    5901133    }
    5911134    if ( comms & DupStderr ) {
    592         d->pipeStderr = d->pipeStdout;
     1135        // leave d->stderr.pipe equal to HP_NULL
    5931136        do {
    5941137            // save the real STDERR handle
     
    6021145        if ( rc != NO_ERROR ) {
    6031146#if defined(QT_QPROCESS_DEBUG)
    604             DosPrintf( tmpStderr != HF_ANY ? tmpStderr : realStderr,
    605                        "Failed to redirect STDERR to STDOUT: SYS%04ld\n\r",
    606                        rc );
    607 #endif
    608             if ( tmpStdin != HF_ANY ) {
     1147            qDebug( "Failed to redirect STDERR to STDOUT: SYS%04ld",
     1148                    rc );
     1149#endif
     1150            if ( tmpStdin != HF_NULL ) {
    6091151                DosDupHandle( tmpStdin, &realStdin );
    6101152                DosClose( tmpStdin );
    6111153            }
    612             if ( tmpStdout != HF_ANY ) {
     1154            if ( tmpStdout != HF_NULL ) {
    6131155                DosDupHandle( tmpStdout, &realStdout );
    6141156                DosClose( tmpStdout );
    6151157            }
    616             if ( tmpStderr != HF_ANY )
     1158            if ( tmpStderr != HF_NULL )
    6171159                DosClose ( tmpStderr );
    6181160            d->closeHandles();
     1161            UninstallQtMsgHandler();
    6191162            return FALSE;
    6201163        }
    6211164    }
    6221165
     1166    // add this process to the monitor
     1167    if ( !processMonitor->addProcess( this ) ) {
     1168        if ( tmpStdin != HF_NULL ) {
     1169            DosDupHandle( tmpStdin, &realStdin );
     1170            DosClose( tmpStdin );
     1171        }
     1172        if ( tmpStdout != HF_NULL ) {
     1173            DosDupHandle( tmpStdout, &realStdout );
     1174            DosClose( tmpStdout );
     1175        }
     1176        if ( tmpStderr != HF_NULL ) {
     1177            DosDupHandle( tmpStderr, &realStderr );
     1178            DosClose ( tmpStderr );
     1179        }
     1180        d->closeHandles();
     1181        UninstallQtMsgHandler();
     1182        return FALSE;
     1183    }
     1184   
    6231185    // set the working directory
    6241186    QString curDir = QDir::currentDirPath();
    6251187    QDir::setCurrent( workingDir.path() );
    6261188#if defined(QT_QPROCESS_DEBUG)
    627     DosPrintf( tmpStderr != HF_ANY ? tmpStderr : realStderr,
    628                "QProcess::start(): curDir='%s', workingDir='%s'\n\r",
    629                curDir.local8Bit().data(),
    630                QDir::currentDirPath().local8Bit().data() );
     1189    qDebug( "QProcess::start(): curDir='%s', workingDir='%s'",
     1190            curDir.local8Bit().data(),
     1191            QDir::currentDirPath().local8Bit().data() );
    6311192#endif
    6321193   
     
    6421203   
    6431204    // cancel STDIN/OUT/ERR redirections
    644     if ( tmpStdin != HF_ANY ) {
     1205    if ( tmpStdin != HF_NULL ) {
    6451206        DosDupHandle( tmpStdin, &realStdin );
    6461207        DosClose( tmpStdin );
    6471208    }
    648     if ( tmpStdout != HF_ANY ) {
     1209    if ( tmpStdout != HF_NULL ) {
    6491210        DosDupHandle( tmpStdout, &realStdout );
    6501211        DosClose( tmpStdout );
    6511212    }
    652     if ( tmpStderr != HF_ANY ) {
     1213    if ( tmpStderr != HF_NULL ) {
    6531214        DosDupHandle( tmpStderr, &realStderr );
    6541215        DosClose( tmpStderr );
    6551216    }
     1217   
     1218    UninstallQtMsgHandler();
    6561219   
    6571220    if ( rc != NO_ERROR ) {
     
    6641227        qSystemWarning( "Failed to start a new process!", rc );
    6651228#endif
     1229        processMonitor->removeProcess( d );
    6661230        d->closeHandles();
    6671231        return FALSE;
     
    6711235    d->pid = resc.codeTerminate;
    6721236   
    673     if ( ioRedirection || notifyOnExit ) {
    674         d->lookup->start( 100 );
     1237    // timer is not necessary for ioRedirection (we use the monitor thread)
     1238    if ( /* ioRedirection || */ notifyOnExit ) {
     1239        d->lookup->start( POLL_TIMER );
    6751240    }
    6761241
     
    7251290    if ( rc == ERROR_CHILD_NOT_COMPLETE )
    7261291        return TRUE;
    727    
    728     // there might be data to read
     1292
    7291293    QProcess *that = (QProcess *) this;
    730     that->socketRead( FD_PIPE_STDOUT ); // try stdout
    731     that->socketRead( FD_PIPE_STDERR ); // try stderr
     1294
     1295    // There might be data to read, but WM_U_PIPE_RDATA messages won't be
     1296    // converted to signals after removeProcess() is called below. Therefore,
     1297    // emit signals ourselved if necessary.
     1298
     1299    if ( d->readPipe( &d->stdout ) )
     1300        emit that->readyReadStdout();
     1301    if ( d->readPipe( &d->stderr ) )
     1302        emit that->readyReadStderr();
    7321303   
    7331304    // compute the exit values
     
    7371308        d->exitValuesCalculated = TRUE;
    7381309    }
     1310
     1311    processMonitor->removeProcess( d );
    7391312   
    7401313    d->pid = PID_NULL;
     
    7451318bool QProcess::canReadLineStdout() const
    7461319{
    747     if( d->pipeStdout == HF_ANY )
    748         return d->bufStdout.size() != 0;
     1320    if( d->stdout.pipe == HP_NULL )
     1321        return d->stdout.buf.size() != 0;
    7491322
    7501323    QProcess *that = (QProcess *) this;
     
    7541327bool QProcess::canReadLineStderr() const
    7551328{
    756     if( d->pipeStderr == HF_ANY )
    757         return d->bufStderr.size() != 0;
     1329    if( d->stderr.pipe == HP_NULL )
     1330        return d->stderr.buf.size() != 0;
    7581331
    7591332    QProcess *that = (QProcess *) this;
     
    7691342void QProcess::closeStdin( )
    7701343{
    771     if ( d->pipeStdin != HF_ANY ) {
     1344    if ( d->pipeStdin != HP_NULL ) {
    7721345        DosDisConnectNPipe( d->pipeStdin );
    7731346        DosClose( d->pipeStdin );
    774         d->pipeStdin = HF_ANY;
    775     }
    776 }
    777 
    778 void QProcess::socketRead( int fd )
    779 {
    780     Q_ASSERT( fd == FD_PIPE_STDOUT || fd == FD_PIPE_STDERR );
    781    
    782     HPIPE pipe = HF_ANY;
    783     switch( fd ) {
    784         case FD_PIPE_STDOUT: pipe = d->pipeStdout; break;
    785         case FD_PIPE_STDERR: pipe = d->pipeStderr; break;
    786     }
    787    
    788     if ( pipe == HF_ANY )
    789         return;
    790    
    791     // get the number of bytes that are waiting to be read
    792     ULONG read = 0;
    793     AVAILDATA avail = { 0 };
    794     ULONG status = 0;
    795     APIRET rc = DosPeekNPipe( pipe, NULL, 0, &read, &avail, &status );
    796     if ( rc != NO_ERROR ) {
    797 #if defined(QT_QPROCESS_DEBUG)
    798         qSystemWarning( "Failed to peek from the pipe!", rc );
    799 #endif
    800         return;
    801     }
    802    
    803     if ( avail.cbpipe > 0 ) {
    804         QMembuf *buffer;
    805         if ( fd == FD_PIPE_STDOUT )
    806             buffer = &d->bufStdout;
    807         else
    808             buffer = &d->bufStderr;
    809        
    810         QByteArray *ba = new QByteArray( avail.cbpipe );
    811        
    812         rc = DosRead( pipe, ba->data(), avail.cbpipe, &read );
    813         if ( rc != NO_ERROR ) {
    814             delete ba;
    815 #if defined(QT_QPROCESS_DEBUG)
    816             qSystemWarning( "Failed to read from the pipe!", rc );
    817 #endif
    818             return;
    819         }
    820        
    821         if ( read < avail.cbpipe )
    822             ba->resize( avail.cbpipe );
    823 
    824         buffer->append( ba );
    825         if ( fd == FD_PIPE_STDOUT )
    826             emit readyReadStdout();
    827         else
    828             emit readyReadStderr();
    829     }
     1347        d->pipeStdin = HP_NULL;
     1348    }
     1349}
     1350
     1351/* dummy, since MOC doesn't understand preprocessor defines */
     1352void QProcess::socketRead( int fd )
     1353{
    8301354}
    8311355
    8321356void QProcess::socketWrite( int )
    8331357{
    834     if ( d->pipeStdin == HF_ANY )
     1358    if ( d->pipeStdin == HP_NULL )
    8351359        return;
    8361360   
     
    8391363        APIRET rc = DosWrite( d->pipeStdin,
    8401364                              d->stdinBuf.head()->data() + d->stdinBufRead,
    841                               QMIN( PIPE_SIZE, d->stdinBuf.head()->size() - d->stdinBufRead ),
     1365                              QMIN( PIPE_SIZE_STDIN,
     1366                                    d->stdinBuf.head()->size() - d->stdinBufRead ),
    8421367                              &written );
    8431368        if ( rc != NO_ERROR ) {
    844 #if defined(QT_QPROCESS_DEBUG)
     1369#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
    8451370            qSystemWarning( "Failed to write to the pipe!", rc );
    8461371#endif
    847             d->lookup->start( 100 );
     1372            d->lookup->start( POLL_TIMER );
    8481373            return;
    8491374        }
     
    8781403        socketWrite( 0 );
    8791404
    880     if ( ioRedirection ) {
    881         socketRead( FD_PIPE_STDOUT ); // try stdout
    882         socketRead( FD_PIPE_STDERR ); // try stderr
    883     }
    884 
    8851405    if ( isRunning() ) {
    8861406        // enable timer again, if needed
    887         if ( !d->stdinBuf.isEmpty() || ioRedirection || notifyOnExit )
    888             d->lookup->start( 100 );
     1407        // timer is not necessary for ioRedirection (we use the monitor thread)
     1408        if ( !d->stdinBuf.isEmpty() || /* ioRedirection || */ notifyOnExit )
     1409            d->lookup->start( POLL_TIMER );
    8891410    } else if ( notifyOnExit ) {
    8901411        emit processExited();
     
    8991420{
    9001421    ioRedirection = value;
     1422    // timer is not necessary for ioRedirection (we use the monitor thread)
     1423#if 0
    9011424    if ( !ioRedirection && !notifyOnExit )
    9021425        d->lookup->stop();
    9031426    if ( ioRedirection ) {
    9041427        if ( isRunning() )
    905             d->lookup->start( 100 );
    906     }
     1428            d->lookup->start( POLL_TIMER );
     1429    }
     1430#endif
    9071431}
    9081432
     
    9141438{
    9151439    notifyOnExit = value;
    916     if ( !ioRedirection && !notifyOnExit )
     1440    // timer is not necessary for ioRedirection (we use the monitor thread)
     1441    if ( /* !ioRedirection && */ !notifyOnExit )
    9171442        d->lookup->stop();
    9181443    if ( notifyOnExit ) {
    9191444        if ( isRunning() )
    920             d->lookup->start( 100 );
     1445            d->lookup->start( POLL_TIMER );
    9211446    }
    9221447}
Note: See TracChangeset for help on using the changeset viewer.