Boost logo

Boost :

Subject: [boost] [iostreams] Error reporting for buffered streams
From: Eric MALENFANT (Eric.Malenfant_at_[hidden])
Date: 2009-01-21 12:04:57


By default, filtering_streams do buffering. This means that what is written to them does not necessarily reach the underliyng Filters and Device immediately. It is thus my understanding that, in order to be sure that all data was written, one should somehow flush the buffered data, and then check the stream state before closing it.

As a first attempt, I tried flush(), but it does not seem to do what I expected. Looking at the docs, I then found what seemed to be what I was looking for: strict_sync(), whose documentation states that "A return value of true guarantees that all buffered data has been successfully forwarded.".
Moreover, the FAQ entry "Can I swap Filters or Devices in the middle of a sequence of i/o operations?" seems to confirm this, as it says: "First call strict_sync. If it returns true, you can safely call set_auto_close(false) and pop one or more components without closing the stream. This applies to instances of filtering_stream, filtering_streambuf and chain."
But again, this did not work: strict_sync() returns true although buffered data remains.

Consider this contrived example:

    // A codecvt facet that always fails
    class always_fail_cvt : public std::codecvt<wchar_t,char,mbstate_t>
    {
    protected:
        virtual result do_out(
            state_type&,
            const intern_type*, const intern_type*, const intern_type*&,
            char* to, char*, char*&) const
        {
            return std::codecvt_base::error;
        }

        //...
    };

    int main(int argc, char* argv[])
    {
        std::wofstream fs("whatever");
        fs.imbue(std::locale(fs.getloc(), new always_fail_cvt));
        boost::iostreams::filtering_wostream s(fs);
        s << L"Hello";
        std::cout << std::boolalpha << s.good() << ', ';
        s.flush();
        std::cout << s.good();
        return 0;
    }

Running this (on Windows with MSVC 7.1) produces "true, true"

The first "true", is not surprising, as the characters are buffered and code conversion has not taken place yet, but the second is. (to me at least).
Considering the presence of strict_sync(), I assumed that the "forgiveness" of flush() is intentional, to allow stateful filters to retain buffered data, maybe.
However, replacing the "s.flush();" line in the above program with "std::cout << s.strict_sync() << ", ";" does not seem to change things much. Even though a conversion failure occured and data remains buffered, strict_sync() returns true, and the stream's state is still good().

So, to sum up:
1) If my understanding of the semantics of strict_sync() is correct, it seems that indirect_streambuf::strict_sync() should verify that the buffer is empty after the call to sync_impl(), and fail if it is not the case.
Something like this maybe:
    template<typename T, typename Tr, typename Alloc, typename Mode>
    bool indirect_streambuf<T, Tr, Alloc, Mode>::strict_sync()
    {
        try { // sync() is no-throw.
            sync_impl();
            return obj().flush(next_) && ((pptr() - pbase()) == 0);
                     // Added this --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        } catch (...) { return false; }
    }

2) Is it "correct" that filtering_stream::flush() does not completely flush without reporting an error?

3) Does this come from an incompatibility between Boost.IOStreams idea of error-reporting (throwing exceptions) and that of the std streambufs (returning EOF, mainly)?
In particular, Boost.IOStreams writes in a wrapped std::streambuf by using sputn(), which, AFAI(don't)U, does not have a clear error reporting strategy.
IOWs, should write_device_impl<streambuf_tag>::write() be modified so that it reports errors by an exception. Something like this maybe:
    template<typename T>
    static std::streamsize write
        (T& t, const typename char_type_of<T>::type* s, std::streamsize n)
  // { return t.sputn(s, n); }
    {
       std::streamsize count = t.sputn(s, n);
       if (count == n){
           return count;
       }
       // If all characters could not be written, check if it is because of an error or only because of blocking
       if (T::traits_type::eq_int_type(t.sputc(s[count]), T::traits_type::eof())){
           throw std::ios_base::failure("write error");
       }
       else{
           return count + 1;
       }
    }

Sorry for the longish post, and thanks for reading up to here,

Éric Malenfant


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk