PageRenderTime 42ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/mordor/streams/pipe.cpp

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