#include "platform/pipe.h" #include "platform/api/platform.h" #include "platform/synchronized.h" namespace location { namespace nearby { namespace { using Platform = platform::ImplementationPlatform; } namespace pipe { class PipeInputStream : public InputStream { public: explicit PipeInputStream(Ptr pipe) : pipe_(pipe) {} ~PipeInputStream() override { close(); } ExceptionOr> read() override { return read(kChunkSize); } ExceptionOr> read(std::int64_t size) override { return pipe_->read(size); } Exception::Value close() override { pipe_->markInputStreamClosed(); return Exception::NONE; } private: static constexpr std::int64_t kChunkSize = 64 * 1024; Ptr pipe_; }; class PipeOutputStream : public OutputStream { public: explicit PipeOutputStream(Ptr pipe) : pipe_(pipe) {} ~PipeOutputStream() override { close(); } Exception::Value write(ConstPtr data) override { // Avoid leaks. ScopedPtr> scoped_data(data); return pipe_->write(scoped_data.release()); } Exception::Value flush() override { // No-op. return Exception::NONE; } Exception::Value close() override { pipe_->markOutputStreamClosed(); return Exception::NONE; } private: Ptr pipe_; }; } // namespace pipe Pipe::Pipe() : lock_(Platform::createLock()), cond_(Platform::createConditionVariable(lock_.get())), buffer_(), input_stream_closed_(false), output_stream_closed_(false), read_all_chunks_(false) {} Pipe::~Pipe() { // Deallocate all the chunks still left in buffer_. for (BufferType::iterator chunk_iter = buffer_.begin(); chunk_iter != buffer_.end(); ++chunk_iter) { (*chunk_iter).destroy(); } } Ptr Pipe::createInputStream(Ptr self) { assert(self.isRefCounted()); return MakeRefCountedPtr(new pipe::PipeInputStream(self)); } Ptr Pipe::createOutputStream(Ptr self) { assert(self.isRefCounted()); return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); } ExceptionOr> Pipe::read(std::int64_t size) { Synchronized s(lock_.get()); // We're done reading all the chunks that were written before the OutputStream // was closed, so there's nothing to do here other than return an empty chunk // to serve as an EOF indication to callers. if (read_all_chunks_) { ExceptionOr>(ConstPtr()); } while (buffer_.empty() && !input_stream_closed_) { Exception::Value wait_exception = cond_->wait(); if (Exception::NONE != wait_exception) { if (Exception::INTERRUPTED == wait_exception) { return ExceptionOr>(Exception::IO); } } } if (input_stream_closed_) { return ExceptionOr>(Exception::IO); } ScopedPtr> first_chunk(buffer_.front()); buffer_.pop_front(); // If we received our sentinel chunk, mark the fact that there cannot // possibly be any more chunks to read here on in, and return an empty chunk // to serve as an EOF indication to callers. if (first_chunk.isNull()) { read_all_chunks_ = true; return ExceptionOr>(ConstPtr()); } // If first_chunk is small enough to not overshoot the requested 'size', just // return that. if (first_chunk->size() <= size) { return ExceptionOr>(first_chunk.release()); } else { // Break first_chunk into 2 parts -- the first one of which (next_chunk) // will be 'size' bytes long, and will be returned, and the second one of // which (overflow_chunk) will be re-inserted into buffer_, at the head of // the queue, to be served up in the next call to read(). ScopedPtr> next_chunk( MakeConstPtr(new ByteArray(first_chunk->getData(), size))); ScopedPtr> overflow_chunk(MakeConstPtr(new ByteArray( first_chunk->getData() + size, first_chunk->size() - size))); buffer_.push_front(overflow_chunk.release()); return ExceptionOr>(next_chunk.release()); } } Exception::Value Pipe::write(ConstPtr data) { Synchronized s(lock_.get()); return writeLocked(data); } void Pipe::markInputStreamClosed() { Synchronized s(lock_.get()); input_stream_closed_ = true; // Trigger cond_ to unblock a potentially-blocked call to read(), and to let // it know to return Exception::IO. cond_->notify(); } void Pipe::markOutputStreamClosed() { Synchronized s(lock_.get()); // Write a sentinel null chunk before marking output_stream_closed as true. writeLocked(ConstPtr()); output_stream_closed_ = true; } Exception::Value Pipe::writeLocked(ConstPtr data) { // Avoid leaks. ScopedPtr> scoped_data(data); if (eitherStreamClosed()) { return Exception::IO; } buffer_.push_back(scoped_data.release()); // Trigger cond_ to unblock a potentially-blocked call to read(), now that // there's more data for it to consume. cond_->notify(); return Exception::NONE; } bool Pipe::eitherStreamClosed() const { return input_stream_closed_ || output_stream_closed_; } } // namespace nearby } // namespace location