1

For what reason ostream::write(...) function swallows all exceptions inside it?

From STL "ostream" header:

...
 #define _CATCH_IO_END  _CATCH_ALL  /* catch block for _Myios */ \
...

_Myt& __CLR_OR_THIS_CALL write(const _Elem *_Str,
    streamsize _Count)
    {   // insert _Count characters from array _Str
    ios_base::iostate _State = ios_base::goodbit;
    const sentry _Ok(*this);

    if (!_Ok)
        _State |= ios_base::badbit;
    else if (0 < _Count)
        {   // state okay, insert characters
        _DEBUG_POINTER(_Str);
        _TRY_IO_BEGIN
        if (_Myios::rdbuf()->sputn(_Str, _Count) != _Count)
            _State |= ios_base::badbit;
        _CATCH_IO_END
        }

    _Myios::setstate(_State);
    return (*this);
    }

From STL "xstddef" header:

 #if _HAS_EXCEPTIONS
 #define _TRY_BEGIN try {
 #define _CATCH(x)  } catch (x) {
 #define _CATCH_ALL } catch (...) {
 #define _CATCH_END 

So, ostream::write(..) function would newer throw any exception.

I wrote custom streambuf, which allows one thread to put characters to it, and another thread to get characters from it - with appropriate blocking. If reader thread closed this streambuf, any further invocation of owerflow(...) would throw an exception. But I don't want ostream::write(...) to swallow this exception (and don't want to write custom ostream).

Of course, I can check closed() status every time before writing, but I beleive exceptions is the most elegant way in that case.

Full code:

#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

#include <iostream>
#include <streambuf>
#include <string>
#include <stdexcept>
#include <vector>

class pipe_closed_exception : public std::exception
{
};


template<class _Elem, class _Traits = std::char_traits<_Elem>>
class basic_pipebuf: 
    public std::basic_streambuf<_Elem, _Traits>
{
public:
    typedef typename std::vector<_Elem>     buffer_type;
    typedef typename buffer_type::size_type buffer_size_type;

    std::mutex                      m_mutex;
    std::condition_variable     m_condition;
    bool                                m_closed;

    buffer_type                     m_buffer;
    _Elem*                          m_begin;
    _Elem*                          m_end;
    buffer_size_type                m_chunk_size;

public:
    basic_pipebuf(buffer_size_type buffer_size = 128, buffer_size_type chunk_size = 16):
        m_closed(false),
        m_buffer(buffer_size),
        m_begin(&m_buffer[0]),
        m_end(&m_buffer[0] + m_buffer.size()),
        m_chunk_size(chunk_size)
    {
        setp(m_begin + m_chunk_size, m_begin + 2 * m_chunk_size);
        setg(m_begin, m_begin + m_chunk_size, m_begin + m_chunk_size);
    }

    void close()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_closed = true;
        m_condition.notify_all();
    }

private:

    int_type overflow(int_type c)
    {
        std::unique_lock<std::mutex> lock(m_mutex); 

        if (m_closed)
        {
            throw pipe_closed_exception();
        }

        if (_Traits::eq_int_type(_Traits::eof(), c))
            return _Traits::not_eof(c);

        int_type ret = _Traits::eof();

        if (epptr() < m_end)
        {
            while (epptr() == eback() && !m_closed)
                m_condition.wait_for(lock, std::chrono::seconds(1));

            if (epptr() != eback())
            { 
                setp(pbase() + m_chunk_size, epptr() + m_chunk_size);
                ret = *_Pninc() = c;
            }
        }
        else
        {
            while (!(eback() > m_begin || m_closed))
                m_condition.wait_for(lock, std::chrono::seconds(1));

            if (eback() > m_begin)
            {
                setp(m_begin, m_begin + m_chunk_size);
                ret = *_Pninc() = c;
            }
        }

        m_condition.notify_one();
        return ret;
    }

    int_type underflow()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        int_type ret = _Traits::eof();

        if (eback() != pbase())
        {
            if (egptr() < m_end)
            {
                while (egptr() == pbase() && !m_closed)
                    m_condition.wait_for(lock, std::chrono::seconds(1));

                if (egptr() != pbase())
                {
                    setg(eback() + m_chunk_size, eback() + m_chunk_size, egptr() + m_chunk_size);
                }
                else // if m_closed
                {
                    setg(pbase(), pbase(), pptr());
                }

                ret = _Traits::to_int_type(*gptr());
            }
            else
            {
                while (!(pbase() > m_begin || m_closed))
                    m_condition.wait_for(lock, std::chrono::seconds(1));

                if (pbase() > m_begin)
                {
                    setg(m_begin, m_begin, m_begin + m_chunk_size);
                }
                else // if m_closed
                {
                    setg(pbase(), pbase(), pptr());
                }

                ret = _Traits::to_int_type(*gptr());
            }
        }

        m_condition.notify_one();
        return ret;
    }
};

template<class _Elem, class _Traits = std::char_traits<_Elem>>
class basic_opipestream : 
    public std::basic_ostream<_Elem, _Traits>
{
    typedef typename basic_pipebuf<_Elem, _Traits> buffer_type;
    buffer_type* mPbuf;

public:
    basic_opipestream(buffer_type* pBuf) :
        std::basic_ostream<_Elem, _Traits>(pBuf),
        mPbuf(pBuf)
    {
    }

    void close()
    {
        mPbuf->close();
    }
};

template<class _Elem, class _Traits = std::char_traits<_Elem>>
class basic_ipipestream :
    public std::basic_istream<_Elem, _Traits>
{
    typedef typename basic_pipebuf<_Elem, _Traits> buffer_type;
    buffer_type* mPbuf;

public:
    basic_ipipestream(buffer_type* pBuf) :
        std::basic_istream<_Elem, _Traits>(pBuf),
        mPbuf(pBuf)
    {
    }

    void close()
    {
        width();
        mPbuf->close();
    }
};

typedef basic_pipebuf<char>             pipebuf;
typedef basic_opipestream<char>         opipestream;
typedef basic_ipipestream<char>         ipipestream;

typedef basic_pipebuf<wchar_t>          wpipebuf;
typedef basic_opipestream<wchar_t>  wopipestream;
typedef basic_ipipestream<wchar_t>      wipipestream;

static void writer(wopipestream& out)
{
    try
    {
        for (int i = 0; i < 10; ++i)
        {
            out.write(L"12345678", 8);
            out.write(L"qwertyui", 8);
        }

        out.close();
    }
    catch (pipe_closed_exception&) // This catch would newer be called!!! (It's really sad)
    {
        std::wcout << "Pipe closed by reader, stopping write" << std::endl;
    }
}


static void reader(wipipestream& in)
{
    std::wstring mystr(6, L' ');
    in.read(&mystr[0], 6);

    std::wcout << "Read 6 symbols from stream: " << mystr << std::endl;
    std::wcout << "Closing buffer by reader" << std::endl;

    in.close();
}


int main()
{
    wpipebuf tb(12, 4);
    wopipestream os(&tb);
    wipipestream is(&tb);

    std::thread write(&::writer, std::ref(os));
    std::thread read(&::reader, std::ref(is));

    write.join();
    read.join();
}
  • What implementation is that, and what's in the catch block? – Mat Apr 24 '17 at 12:44
  • Do not use identifiers starting with underscore then a capital letter! They are reserved for the implementation. – aschepler Apr 24 '17 at 12:52
  • @Mat Uploaded full code – Programmer Programmer Apr 24 '17 at 12:55
  • @ProgrammerProgrammer: What std library are you using (what compiler) and what is in the catch block that you didn't show in the excerpt you posted? – Mat Apr 24 '17 at 12:58
  • @aschelper But why STL itself can use these undocumented protected functions - for example in basic_stringbuf? Using ret = *_Pninc() = c; instead of ret = sputc(c); would eliminate unnecessary check for awailable space in sputc(c) – Programmer Programmer Apr 24 '17 at 13:01
  • @Mat I Use MS Visual Studio 2015. In my case std::wcout << "Pipe closed by reader, stopping write" << std::endl; would never be called. – Programmer Programmer Apr 24 '17 at 13:03
  • Again: what is in the catch block that you didn't show in the excerpt you posted? – Mat Apr 24 '17 at 13:14
  • 2
    @ProgrammerProgrammer Did you look at http://stackoverflow.com/questions/3180268/why-are-c-stl-iostreams-not-exception-friendly?rq=1 ? Specifically the exceptions() method mentioned in the answer? – pcarter Apr 24 '17 at 13:24
  • @Mat Cleared this out – Programmer Programmer Apr 24 '17 at 13:26
  • @aschepler -- those first two code snippets (with the reserved identifiers) are from the standard library's headers. – Pete Becker Apr 24 '17 at 13:32
  • @ProgrammerProgrammer -- the reason that identifiers are **reserved to the implementation** is to give the implementation a safe space for its own names. – Pete Becker Apr 24 '17 at 13:33
  • @ProgrammerProgrammer: https://github.com/dblock/dotnetinstaller/blob/master/ThirdParty/Microsoft/Windows%20SDK%20v6.0/VC/INCLUDE/ostream#L21 line 21 is what I find for `__CATCH_IO_END`. Note the comment. – Mat Apr 24 '17 at 13:34
  • @pcarter - thanks, it's what I am looking for. – Programmer Programmer Apr 24 '17 at 13:39

0 Answers0