Тупик при закрытии трубы фд - PullRequest
0 голосов
/ 22 марта 2011

Я портирую некоторый код Unix в Windows, который перенаправляет stderr и stdout в канал, который я создал, и есть поток, который читает из этого канала и затем отправляет вывод в консоль отладки.Это прекрасно работает на Unix, но я не могу заставить его работать на Windows.Проблема возникает, когда сторона чтения канала закрыта.Вместо записи EOF в канал, который приведет к выходу из потока, он блокируется.Почему?

Один из обходных путей - пропустить звонок на закрытие, что меня немного беспокоит, но, поскольку мой процесс недолговечный, возможно, это не имеет большого значения?

Вот пример кода, который иллюстрирует проблему ... Я использую VS 2010:

#include <cstdio>
#include <tchar.h>
#include <iostream>
#include <vector>
#include <fcntl.h> 
#include <Windows.h>
#include <io.h>
#define posix_open _open
#define posix_read _read
#define posix_write _write
#define posix_pipe( fds ) _pipe( fds, 8096, _O_BINARY)
#define posix_close _close
#define posix_dup _dup
#define posix_dup2 _dup2
#define posix_fileno _fileno

using namespace std;

static const int PIPE_READ  = 0;
static const int PIPE_WRITE = 1;

DWORD __stdcall PipeReaderFunc(void* readFd)  
{  
    int pipeFd = *((int*)readFd);
    vector< char > buffer(8096);
    while( posix_read(pipeFd, &buffer[0], buffer.size() ) != 0 )
    {
        OutputDebugString( &buffer[0] );
    }
    return 0;
} 

void test() 
{
    int pipefd[2] = {-1,-1};
    if( posix_pipe( pipefd ) < 0 )
    { throw std::exception( "Failed to initialize pipe." );}

    int stdoutOrig = posix_dup( _fileno(stdout) );
    int stderrOrig = posix_dup( _fileno(stderr) );
    if( -1 == posix_dup2( pipefd[PIPE_WRITE], posix_fileno(stdout) ) ) // closes stdout
    {throw exception( "Failed to dup stdout fd." );}

    if( -1 == posix_dup2( pipefd[PIPE_WRITE], posix_fileno(stderr) ) ) // closes stderr
    {throw exception( "Failed to dup stderr fd." );}

    HANDLE hThread = CreateThread( NULL, 0, PipeReaderFunc, &pipefd[PIPE_READ], 0, NULL); 
    if( NULL == hThread )
    {throw exception("Failed to create thread");}

    cout << "This should go to the debug console" << endl;
    Sleep(1000); // Give time for the thread to read from the pipe

    posix_close( stdoutOrig ); 
    posix_close( stderrOrig ); 
    posix_close( pipefd[PIPE_WRITE] );

    // Deadlock occurs on this line
    posix_close( pipefd[PIPE_READ] );

    // This is commented out because it has no effect right now.
    //WaitForSingleObject( hThread, INFINITE );
}

int _tmain(int argc, _TCHAR* argv[])
{
    try 
    { test(); } 
    catch( exception& ex ) 
    { cerr << ex.what() << endl; }
    return 0;
}

Спасибо за любые идеи, как решить эту проблему !!

Ответы [ 2 ]

3 голосов
/ 22 марта 2011

Возможно, что реализация _read в windows вернет -1, когда вызывается _close, пока она заблокирована. Если он вызывается после завершения _close, он должен вернуть -1 в соответствии с документацией. Итак, похоже, что ваш поток застрянет в главном цикле, потому что он завершается только тогда, когда возвращаемое значение равно нулю. Возможно, вам следует изменить условие цикла с != 0 на > 0 и попробовать его.


EDIT:

Я искал не ту часть документации. Согласно документации, дескриптор из pipe закрывается, когда все дескрипторы, указывающие на него, закрыты. Я думаю, что вы хотели это реализовать, но допустили небольшую ошибку. Если вы замените:

posix_close( stdoutOrig ); 
posix_close( stderrOrig ); 

с

posix_close( posix_fileno(stdout) );
posix_close( posix_fileno(stderr) );

программа выполняется и корректно завершается. Это потому, что есть два fd, указывающие на дескриптор читателя, а вы закрываете только один. Таким образом, поток был успешно заблокирован, ожидая больше данных. Когда вы также закрываете дескриптор dup ed, read возвращает ноль (как вы указали), и поток завершается.

0 голосов
/ 22 марта 2011

Если вы используете fork() и одну из семейства функций exec*() в Unix вместо CreateThread() в Windows, дочерний процесс закроет конец канала для чтения и записи после выполнения операций dup2() и, прежде чем делать Exec. Родительский процесс закроет тот конец канала, который он не собирается использовать. Это необходимо (в общем), чтобы гарантировать, что нет никаких случайных открытых файловых дескрипторов, ссылающихся на канал. Если в канале имеется открытый случайный конец записи, считыватели на канале никогда не получат EOF. Если в канале имеется открытый случай чтения конца, писатель может заблокировать (тупик) ожидание чтения данных читателем - даже если этот читатель - тот же процесс, который выполняет запись.

По аналогии, вы должны убедиться, что ваша нить имеет только те концы трубы, в которых она нуждается, и должна закрыть все остальные детали.

...