PageRenderTime 186ms CodeModel.GetById 101ms app.highlight 44ms RepoModel.GetById 36ms app.codeStats 1ms

/mordor/streams/buffered.cpp

http://github.com/mozy/mordor
C++ | 364 lines | 312 code | 32 blank | 20 comment | 78 complexity | 392cbb87c17f6903e315c24c4f0e9af9 MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include "buffered.h"
  4
  5#include "mordor/config.h"
  6#include "mordor/exception.h"
  7#include "mordor/log.h"
  8
  9namespace Mordor {
 10
 11static ConfigVar<size_t>::ptr g_defaultBufferSize =
 12    Config::lookup<size_t>("stream.buffered.defaultbuffersize", 65536,
 13    "Default buffer size for new BufferedStreams");
 14
 15static Logger::ptr g_log = Log::lookup("mordor:streams:buffered");
 16
 17BufferedStream::BufferedStream(Stream::ptr parent, bool own)
 18: FilterStream(parent, own)
 19{
 20    m_bufferSize = g_defaultBufferSize->val();
 21    m_allowPartialReads = false;
 22    m_flushMultiplesOfBuffer = false;
 23}
 24
 25void
 26BufferedStream::close(CloseType type)
 27{
 28    MORDOR_LOG_VERBOSE(g_log) << this << " close(" << type << ")";
 29    if (type & READ)
 30        m_readBuffer.clear();
 31    try {
 32        if ((type & WRITE) && m_writeBuffer.readAvailable())
 33            flush(false);
 34    } catch (...) {
 35        if (ownsParent())
 36            parent()->close(type);
 37        throw;
 38    }
 39    if (ownsParent())
 40        parent()->close(type);
 41}
 42
 43size_t
 44BufferedStream::read(Buffer &buffer, size_t length)
 45{
 46    return readInternal(buffer, length);
 47}
 48
 49size_t
 50BufferedStream::read(void *buffer, size_t length)
 51{
 52    return readInternal(buffer, length);
 53}
 54
 55// Buffer keeps track of its position automatically
 56static void advance(Buffer &buffer, size_t amount)
 57{}
 58
 59// void * does not
 60static void advance(void *&buffer, size_t amount)
 61{
 62    (unsigned char *&)buffer += amount;
 63}
 64
 65template <class T>
 66size_t
 67BufferedStream::readInternal(T &buffer, size_t length)
 68{
 69    if (supportsSeek())
 70        flush(false);
 71    size_t remaining = length;
 72
 73    size_t buffered = (std::min)(m_readBuffer.readAvailable(), remaining);
 74    m_readBuffer.copyOut(buffer, buffered);
 75    m_readBuffer.consume(buffered);
 76    advance(buffer, buffered);
 77    remaining -= buffered;
 78
 79    MORDOR_LOG_VERBOSE(g_log) << this << " read(" << length << "): "
 80        << buffered << " read from buffer";
 81
 82    if (remaining == 0)
 83        return length;
 84
 85    if (buffered == 0 || !m_allowPartialReads) {
 86        size_t result;
 87        do {
 88            // Read enough to satisfy this request, plus up to a multiple of
 89            // the buffer size
 90            size_t todo = ((remaining - 1) / m_bufferSize + 1) * m_bufferSize;
 91            try {
 92                MORDOR_LOG_TRACE(g_log) << this << " parent()->read(" << todo
 93                    << ")";
 94                result = parent()->read(m_readBuffer, todo);
 95                MORDOR_LOG_DEBUG(g_log) << this << " parent()->read(" << todo
 96                    << "): " << result;
 97            } catch (...) {
 98                if (remaining == length) {
 99                    MORDOR_LOG_VERBOSE(g_log) << this << " forwarding exception";
100                    throw;
101                } else {
102                    MORDOR_LOG_VERBOSE(g_log) << this << " swallowing exception";
103                    // Swallow the exception
104                    return length - remaining;
105                }
106            }
107
108            buffered = (std::min)(m_readBuffer.readAvailable(), remaining);
109            m_readBuffer.copyOut(buffer, buffered);
110            m_readBuffer.consume(buffered);
111            advance(buffer, buffered);
112            remaining -= buffered;
113        } while (remaining > 0 && !m_allowPartialReads && result != 0);
114    }
115
116    return length - remaining;
117}
118
119size_t
120BufferedStream::write(const Buffer &buffer, size_t length)
121{
122    m_writeBuffer.copyIn(buffer, length);
123    size_t result = flushWrite(length);
124    // Partial writes not allowed
125    MORDOR_ASSERT(result == length);
126    return result;
127}
128
129size_t
130BufferedStream::write(const void *buffer, size_t length)
131{
132    m_writeBuffer.reserve((std::max)(m_bufferSize, length));
133    m_writeBuffer.copyIn(buffer, length);
134    size_t result = flushWrite(length);
135    // Partial writes not allowed
136    MORDOR_ASSERT(result == length);
137    return result;
138}
139
140size_t
141BufferedStream::flushWrite(size_t length)
142{
143    while (m_writeBuffer.readAvailable() >= m_bufferSize)
144    {
145        size_t result;
146        try {
147            if (supportsSeek() && m_readBuffer.readAvailable()) {
148                parent()->seek(-(long long)m_readBuffer.readAvailable(), CURRENT);
149                m_readBuffer.clear();
150            }
151            size_t toWrite = m_writeBuffer.readAvailable();
152            if (m_flushMultiplesOfBuffer)
153                toWrite = toWrite / m_bufferSize * m_bufferSize;
154            MORDOR_LOG_TRACE(g_log) << this << " parent()->write("
155                << toWrite << ")";
156            result = parent()->write(m_writeBuffer, toWrite);
157            MORDOR_LOG_DEBUG(g_log) << this << " parent()->write("
158                << toWrite << "): " << result;
159            m_writeBuffer.consume(result);
160        } catch (...) {
161            // If this entire write is still in our buffer,
162            // back it out and report the error
163            if (m_writeBuffer.readAvailable() >= length) {
164                MORDOR_LOG_VERBOSE(g_log) << this << " forwarding exception";
165                Buffer tempBuffer;
166                tempBuffer.copyIn(m_writeBuffer, m_writeBuffer.readAvailable()
167                    - length);
168                m_writeBuffer.clear();
169                m_writeBuffer.copyIn(tempBuffer);
170                throw;
171            } else {
172                // Otherwise we have to say we succeeded,
173                // because we're not allowed to have a partial
174                // write, and we can't report an error because
175                // the caller will think he needs to repeat
176                // the entire write
177                MORDOR_LOG_VERBOSE(g_log) << this << " swallowing exception";
178                return length;
179            }
180        }
181    }
182    return length;
183}
184
185long long
186BufferedStream::seek(long long offset, Anchor anchor)
187{
188    MORDOR_ASSERT(parent()->supportsTell());
189    long long parentPos = parent()->tell();
190    long long bufferedPos = parentPos - m_readBuffer.readAvailable()
191        + m_writeBuffer.readAvailable();
192    long long parentSize = parent()->supportsSize() ? parent()->size() : -1ll;
193    // Check for no change in position
194    if ((offset == 0 && anchor == CURRENT) ||
195        (offset == bufferedPos && anchor == BEGIN) ||
196        (parentSize != -1ll && offset + parentSize == bufferedPos &&
197        anchor == END))
198        return bufferedPos;
199
200    MORDOR_ASSERT(supportsSeek());
201    flush(false);
202    MORDOR_ASSERT(m_writeBuffer.readAvailable() == 0u);
203    switch (anchor) {
204        case BEGIN:
205            MORDOR_ASSERT(offset >= 0);
206            if (offset >= bufferedPos && offset <= parentPos) {
207                m_readBuffer.consume((size_t)(offset - bufferedPos));
208                return offset;
209            }
210            m_readBuffer.clear();
211            break;
212        case CURRENT:
213            if (offset > 0 && offset <= (long long)m_readBuffer.readAvailable()) {
214                m_readBuffer.consume((size_t)offset);
215                return bufferedPos + offset;
216            }
217            offset -= m_readBuffer.readAvailable();
218            m_readBuffer.clear();
219            break;
220        case END:
221            if (parentSize == -1ll)
222                throw std::invalid_argument("Can't seek from end without known size");
223            if (parentSize + offset >= bufferedPos && parentSize + offset <= parentPos) {
224                m_readBuffer.consume((size_t)(parentSize + offset - bufferedPos));
225                return parentSize + offset;
226            }
227            m_readBuffer.clear();
228            break;
229        default:
230            MORDOR_NOTREACHED();
231    }
232    return parent()->seek(offset, anchor);
233}
234
235long long
236BufferedStream::size()
237{
238    long long size = parent()->size();
239    if (parent()->supportsTell()) {
240        return (std::max)(size, tell());
241    } else {
242        // not a seekable stream; we can only write to the end
243        size += m_writeBuffer.readAvailable();
244    }
245    return size;
246}
247
248void
249BufferedStream::truncate(long long size)
250{
251    if (!parent()->supportsTell() ||
252        parent()->tell() + (long long)m_writeBuffer.readAvailable() >= size)
253        flush(false);
254    // TODO: truncate/clear m_readBuffer only if necessary
255    m_readBuffer.clear();
256    parent()->truncate(size);
257}
258
259void
260BufferedStream::flush(bool flushParent)
261{
262    while (m_writeBuffer.readAvailable()) {
263        if (supportsSeek() && m_readBuffer.readAvailable()) {
264            parent()->seek(-(long long)m_readBuffer.readAvailable(), CURRENT);
265            m_readBuffer.clear();
266        }
267        MORDOR_LOG_TRACE(g_log) << this << " parent()->write("
268            << m_writeBuffer.readAvailable() << ")";
269        size_t result = parent()->write(m_writeBuffer, m_writeBuffer.readAvailable());
270        MORDOR_LOG_DEBUG(g_log) << this << " parent()->write("
271            << m_writeBuffer.readAvailable() << "): " << result;
272        MORDOR_ASSERT(result > 0);
273        m_writeBuffer.consume(result);
274    }
275    if (flushParent)
276        parent()->flush();
277}
278
279ptrdiff_t
280BufferedStream::find(char delim, size_t sanitySize, bool throwIfNotFound)
281{
282    if (supportsSeek())
283        flush(false);
284    if (sanitySize == (size_t)~0)
285        sanitySize = 2 * m_bufferSize;
286    ++sanitySize;
287    while (true) {
288        size_t readAvailable = m_readBuffer.readAvailable();
289        if (readAvailable > 0) {
290            ptrdiff_t result = m_readBuffer.find(delim,
291                (std::min)(sanitySize, readAvailable));
292            if (result != -1) {
293                return result;
294            }
295        }
296        if (readAvailable >= sanitySize) {
297            if (throwIfNotFound)
298                MORDOR_THROW_EXCEPTION(BufferOverflowException());
299            return -(ptrdiff_t)m_readBuffer.readAvailable() - 1;
300        }
301
302        MORDOR_LOG_TRACE(g_log) << this << " parent()->read(" << m_bufferSize
303            << ")";
304        size_t result = parent()->read(m_readBuffer, m_bufferSize);
305        MORDOR_LOG_DEBUG(g_log) << this << " parent()->read(" << m_bufferSize
306            << "): " << result;
307        if (result == 0) {
308            // EOF
309            if (throwIfNotFound)
310                MORDOR_THROW_EXCEPTION(UnexpectedEofException());
311            return -(ptrdiff_t)m_readBuffer.readAvailable() - 1;
312        }
313    }
314}
315
316ptrdiff_t
317BufferedStream::find(const std::string &str, size_t sanitySize, bool throwIfNotFound)
318{
319    if (supportsSeek())
320        flush(false);
321    if (sanitySize == (size_t)~0)
322        sanitySize = 2 * m_bufferSize;
323    sanitySize += str.size();
324    while (true) {
325        size_t readAvailable = m_readBuffer.readAvailable();
326        if (readAvailable > 0) {
327            ptrdiff_t result = m_readBuffer.find(str,
328                (std::min)(sanitySize, readAvailable));
329            if (result != -1) {
330                return result;
331            }
332        }
333        if (readAvailable >= sanitySize) {
334            if (throwIfNotFound)
335                MORDOR_THROW_EXCEPTION(BufferOverflowException());
336            return -(ptrdiff_t)m_readBuffer.readAvailable() - 1;
337        }
338
339        MORDOR_LOG_TRACE(g_log) << this << " parent()->read(" << m_bufferSize
340            << ")";
341        size_t result = parent()->read(m_readBuffer, m_bufferSize);
342        MORDOR_LOG_DEBUG(g_log) << this << " parent()->read(" << m_bufferSize
343            << "): " << result;
344        if (result == 0) {
345            // EOF
346            if (throwIfNotFound)
347                MORDOR_THROW_EXCEPTION(UnexpectedEofException());
348            return -(ptrdiff_t)m_readBuffer.readAvailable() - 1;
349        }
350    }
351}
352
353void
354BufferedStream::unread(const Buffer &b, size_t len)
355{
356    MORDOR_ASSERT(supportsUnread());
357    Buffer tempBuffer;
358    tempBuffer.copyIn(b, len);
359    tempBuffer.copyIn(m_readBuffer);
360    m_readBuffer.clear();
361    m_readBuffer.copyIn(tempBuffer);
362}
363
364}