PageRenderTime 582ms CodeModel.GetById 51ms app.highlight 476ms RepoModel.GetById 38ms app.codeStats 1ms

/mordor/streams/pipe.cpp

http://github.com/mozy/mordor
C++ | 334 lines | 300 code | 29 blank | 5 comment | 54 complexity | 8a34a6020bb892b9aa84f84d721c7027 MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include "pipe.h"
  4
  5#include <boost/thread/mutex.hpp>
  6
  7#include "buffer.h"
  8#include "mordor/assert.h"
  9#include "mordor/fiber.h"
 10#include "mordor/scheduler.h"
 11#include "stream.h"
 12
 13namespace Mordor {
 14
 15static Logger::ptr g_log = Log::lookup("mordor:streams:pipe");
 16
 17class PipeStream : public Stream
 18{
 19    friend std::pair<Stream::ptr, Stream::ptr> pipeStream(size_t);
 20public:
 21    typedef boost::shared_ptr<PipeStream> ptr;
 22    typedef boost::weak_ptr<PipeStream> weak_ptr;
 23
 24public:
 25    PipeStream(size_t bufferSize);
 26    ~PipeStream();
 27
 28    bool supportsHalfClose() { return true; }
 29    bool supportsRead() { return true; }
 30    bool supportsWrite() { return true; }
 31
 32    void close(CloseType type = BOTH);
 33    using Stream::read;
 34    size_t read(Buffer &b, size_t len);
 35    void cancelRead();
 36    using Stream::write;
 37    size_t write(const Buffer &b, size_t len);
 38    void cancelWrite();
 39    void flush(bool flushParent = true);
 40
 41    boost::signals2::connection onRemoteClose(
 42        const boost::signals2::slot<void ()> &slot);
 43
 44private:
 45    PipeStream::weak_ptr m_otherStream;
 46    boost::shared_ptr<boost::mutex> m_mutex;
 47    Buffer m_readBuffer;
 48    size_t m_bufferSize;
 49    bool m_cancelledRead, m_cancelledWrite;
 50    CloseType m_closed, m_otherClosed;
 51    Scheduler *m_pendingWriterScheduler, *m_pendingReaderScheduler;
 52    boost::shared_ptr<Fiber> m_pendingWriter, m_pendingReader;
 53    boost::signals2::signal<void ()> m_onRemoteClose;
 54};
 55
 56std::pair<Stream::ptr, Stream::ptr> pipeStream(size_t bufferSize)
 57{
 58    if (bufferSize == ~0u)
 59        bufferSize = 65536;
 60    std::pair<PipeStream::ptr, PipeStream::ptr> result;
 61    result.first.reset(new PipeStream(bufferSize));
 62    result.second.reset(new PipeStream(bufferSize));
 63    MORDOR_LOG_VERBOSE(g_log) << "pipeStream(" << bufferSize << "): {"
 64        << result.first << ", " << result.second << "}";
 65    result.first->m_otherStream = result.second;
 66    result.second->m_otherStream = result.first;
 67    result.first->m_mutex.reset(new boost::mutex());
 68    result.second->m_mutex = result.first->m_mutex;
 69    return result;
 70}
 71
 72PipeStream::PipeStream(size_t bufferSize)
 73: m_bufferSize(bufferSize),
 74  m_cancelledRead(false),
 75  m_cancelledWrite(false),
 76  m_closed(NONE),
 77  m_otherClosed(NONE),
 78  m_pendingWriterScheduler(NULL),
 79  m_pendingReaderScheduler(NULL)
 80{}
 81
 82PipeStream::~PipeStream()
 83{
 84    MORDOR_LOG_VERBOSE(g_log) << this << " destructing";
 85    PipeStream::ptr otherStream = m_otherStream.lock();
 86    boost::mutex::scoped_lock lock(*m_mutex);
 87    if (otherStream) {
 88        MORDOR_NOTHROW_ASSERT(!otherStream->m_pendingReader);
 89        MORDOR_NOTHROW_ASSERT(!otherStream->m_pendingReaderScheduler);
 90        MORDOR_NOTHROW_ASSERT(!otherStream->m_pendingWriter);
 91        MORDOR_NOTHROW_ASSERT(!otherStream->m_pendingWriterScheduler);
 92        if (!m_readBuffer.readAvailable())
 93            otherStream->m_otherClosed = (CloseType)(otherStream->m_otherClosed | READ);
 94        else
 95            otherStream->m_otherClosed = (CloseType)(otherStream->m_otherClosed & ~READ);
 96        otherStream->m_onRemoteClose();
 97    }
 98    if (m_pendingReader) {
 99        MORDOR_NOTHROW_ASSERT(m_pendingReaderScheduler);
100        MORDOR_LOG_DEBUG(g_log) << otherStream << " scheduling read";
101        m_pendingReaderScheduler->schedule(m_pendingReader);
102        m_pendingReader.reset();
103        m_pendingReaderScheduler = NULL;
104    }
105    if (m_pendingWriter) {
106        MORDOR_NOTHROW_ASSERT(m_pendingWriterScheduler);
107        MORDOR_LOG_DEBUG(g_log) << otherStream << " scheduling write";
108        m_pendingWriterScheduler->schedule(m_pendingWriter);
109        m_pendingWriter.reset();
110        m_pendingWriterScheduler = NULL;
111    }
112}
113
114void
115PipeStream::close(CloseType type)
116{
117    PipeStream::ptr otherStream = m_otherStream.lock();
118    boost::mutex::scoped_lock lock(*m_mutex);
119    bool closeWriteFirstTime = !(m_closed & WRITE) && (type & WRITE);
120    m_closed = (CloseType)(m_closed | type);
121    if (otherStream) {
122        otherStream->m_otherClosed = m_closed;
123        if (closeWriteFirstTime)
124            otherStream->m_onRemoteClose();
125    }
126    if (m_pendingReader && (m_closed & WRITE)) {
127        MORDOR_ASSERT(m_pendingReaderScheduler);
128        MORDOR_LOG_DEBUG(g_log) << otherStream << " scheduling read";
129        m_pendingReaderScheduler->schedule(m_pendingReader);
130        m_pendingReader.reset();
131        m_pendingReaderScheduler = NULL;
132    }
133    if (m_pendingWriter && (m_closed & READ)) {
134        MORDOR_ASSERT(m_pendingWriterScheduler);
135        MORDOR_LOG_DEBUG(g_log) << otherStream << " scheduling write";
136        m_pendingWriterScheduler->schedule(m_pendingWriter);
137        m_pendingWriter.reset();
138        m_pendingWriterScheduler = NULL;
139    }
140}
141
142size_t
143PipeStream::read(Buffer &b, size_t len)
144{
145    MORDOR_ASSERT(len != 0);
146    while (true) {
147        {
148            PipeStream::ptr otherStream = m_otherStream.lock();
149            boost::mutex::scoped_lock lock(*m_mutex);
150            if (m_closed & READ)
151                MORDOR_THROW_EXCEPTION(BrokenPipeException());
152            if (!otherStream && !(m_otherClosed & WRITE))
153                MORDOR_THROW_EXCEPTION(BrokenPipeException());
154            size_t avail = m_readBuffer.readAvailable();
155            if (avail > 0) {
156                size_t todo = (std::min)(len, avail);
157                b.copyIn(m_readBuffer, todo);
158                m_readBuffer.consume(todo);
159                if (m_pendingWriter) {
160                    MORDOR_ASSERT(m_pendingWriterScheduler);
161                    MORDOR_LOG_DEBUG(g_log) << otherStream << " scheduling write";
162                    m_pendingWriterScheduler->schedule(m_pendingWriter);
163                    m_pendingWriter.reset();
164                    m_pendingWriterScheduler = NULL;
165                }
166                MORDOR_LOG_TRACE(g_log) << this << " read(" << len << "): "
167                    << todo;
168                return todo;
169            }
170
171            if (m_otherClosed & WRITE) {
172                MORDOR_LOG_TRACE(g_log) << this << " read(" << len << "): "
173                    << 0;
174                return 0;
175            }
176
177            if (m_cancelledRead)
178                MORDOR_THROW_EXCEPTION(OperationAbortedException());
179
180            // Wait for the other stream to schedule us
181            MORDOR_ASSERT(!otherStream->m_pendingReader);
182            MORDOR_ASSERT(!otherStream->m_pendingReaderScheduler);
183            MORDOR_LOG_DEBUG(g_log) << this << " waiting to read";
184            otherStream->m_pendingReader = Fiber::getThis();
185            otherStream->m_pendingReaderScheduler = Scheduler::getThis();
186        }
187        try {
188            Scheduler::yieldTo();
189        } catch (...) {
190            PipeStream::ptr otherStream = m_otherStream.lock();
191            boost::mutex::scoped_lock lock(*m_mutex);
192            if (otherStream && otherStream->m_pendingReader == Fiber::getThis()) {
193                MORDOR_ASSERT(otherStream->m_pendingReaderScheduler == Scheduler::getThis());
194                otherStream->m_pendingReader.reset();
195                otherStream->m_pendingReaderScheduler = NULL;
196            }
197            throw;
198        }
199    }
200}
201
202void
203PipeStream::cancelRead()
204{
205    PipeStream::ptr otherStream = m_otherStream.lock();
206    boost::mutex::scoped_lock lock(*m_mutex);
207    m_cancelledRead = true;
208    if (otherStream && otherStream->m_pendingReader) {
209        MORDOR_ASSERT(otherStream->m_pendingReaderScheduler);
210        MORDOR_LOG_DEBUG(g_log) << this << " cancelling read";
211        otherStream->m_pendingReaderScheduler->schedule(otherStream->m_pendingReader);
212        otherStream->m_pendingReader.reset();
213        otherStream->m_pendingReaderScheduler = NULL;
214    }
215}
216
217size_t
218PipeStream::write(const Buffer &b, size_t len)
219{
220    MORDOR_ASSERT(len != 0);
221    while (true) {
222        {
223            PipeStream::ptr otherStream = m_otherStream.lock();
224            boost::mutex::scoped_lock lock(*m_mutex);
225            if (m_closed & WRITE)
226                MORDOR_THROW_EXCEPTION(BrokenPipeException());
227            if (!otherStream || (otherStream->m_closed & READ))
228                MORDOR_THROW_EXCEPTION(BrokenPipeException());
229
230            size_t available = otherStream->m_readBuffer.readAvailable();
231            size_t todo = (std::min)(m_bufferSize - available, len);
232            if (todo != 0) {
233                otherStream->m_readBuffer.copyIn(b, todo);
234                if (m_pendingReader) {
235                    MORDOR_ASSERT(m_pendingReaderScheduler);
236                    MORDOR_LOG_DEBUG(g_log) << otherStream << " scheduling read";
237                    m_pendingReaderScheduler->schedule(m_pendingReader);
238                    m_pendingReader.reset();
239                    m_pendingReaderScheduler = NULL;
240                }
241                MORDOR_LOG_TRACE(g_log) << this << " write(" << len << "): "
242                    << todo;
243                return todo;
244            }
245
246            if (m_cancelledWrite)
247                MORDOR_THROW_EXCEPTION(OperationAbortedException());
248
249            // Wait for the other stream to schedule us
250            MORDOR_ASSERT(!otherStream->m_pendingWriter);
251            MORDOR_ASSERT(!otherStream->m_pendingWriterScheduler);
252            MORDOR_LOG_DEBUG(g_log) << this << " waiting to write";
253            otherStream->m_pendingWriter = Fiber::getThis();
254            otherStream->m_pendingWriterScheduler = Scheduler::getThis();
255        }
256        try {
257            Scheduler::yieldTo();
258        } catch (...) {
259            PipeStream::ptr otherStream = m_otherStream.lock();
260            boost::mutex::scoped_lock lock(*m_mutex);
261            if (otherStream && otherStream->m_pendingWriter == Fiber::getThis()) {
262                MORDOR_ASSERT(otherStream->m_pendingWriterScheduler == Scheduler::getThis());
263                otherStream->m_pendingWriter.reset();
264                otherStream->m_pendingWriterScheduler = NULL;
265            }
266            throw;
267        }
268    }
269}
270
271void
272PipeStream::cancelWrite()
273{
274    PipeStream::ptr otherStream = m_otherStream.lock();
275    boost::mutex::scoped_lock lock(*m_mutex);
276    m_cancelledWrite = true;
277    if (otherStream && otherStream->m_pendingWriter) {
278        MORDOR_ASSERT(otherStream->m_pendingWriterScheduler);
279        MORDOR_LOG_DEBUG(g_log) << this << " cancelling write";
280        otherStream->m_pendingWriterScheduler->schedule(otherStream->m_pendingWriter);
281        otherStream->m_pendingWriter.reset();
282        otherStream->m_pendingWriterScheduler = NULL;
283    }
284}
285
286void
287PipeStream::flush(bool flushParent)
288{
289    while (true) {
290        {
291            PipeStream::ptr otherStream = m_otherStream.lock();
292            boost::mutex::scoped_lock lock(*m_mutex);
293            if (m_cancelledWrite)
294                MORDOR_THROW_EXCEPTION(OperationAbortedException());
295            if (!otherStream) {
296                // See if they read everything before destructing
297                if (m_otherClosed & READ)
298                    return;
299                MORDOR_THROW_EXCEPTION(BrokenPipeException());
300            }
301
302            if (otherStream->m_readBuffer.readAvailable() == 0)
303                return;
304            if (otherStream->m_closed & READ)
305                MORDOR_THROW_EXCEPTION(BrokenPipeException());
306            // Wait for the other stream to schedule us
307            MORDOR_ASSERT(!otherStream->m_pendingWriter);
308            MORDOR_ASSERT(!otherStream->m_pendingWriterScheduler);
309            MORDOR_LOG_DEBUG(g_log) << this << " waiting to flush";
310            otherStream->m_pendingWriter = Fiber::getThis();
311            otherStream->m_pendingWriterScheduler = Scheduler::getThis();
312        }
313        try {
314            Scheduler::yieldTo();
315        } catch (...) {
316            PipeStream::ptr otherStream = m_otherStream.lock();
317            boost::mutex::scoped_lock lock(*m_mutex);
318            if (otherStream && otherStream->m_pendingWriter == Fiber::getThis()) {
319                MORDOR_ASSERT(otherStream->m_pendingWriterScheduler == Scheduler::getThis());
320                otherStream->m_pendingWriter.reset();
321                otherStream->m_pendingWriterScheduler = NULL;
322            }
323            throw;
324        }
325    }
326}
327
328boost::signals2::connection
329PipeStream::onRemoteClose(const boost::signals2::slot<void ()> &slot)
330{
331    return m_onRemoteClose.connect(slot);
332}
333
334}