| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 | /******************************************************************************* copyright: Copyright (c) 2008 Steven Schveighoffer. All rights reserved license: BSD style: $(LICENSE) version: Jun 2008: Initial release author: schveiguy *******************************************************************************/ module tango.io.device.ThreadPipe; private import tango.core.Exception; private import tango.io.device.Conduit; private import tango.core.sync.Condition; /** * Conduit to support a data stream between 2 threads. One creates a * ThreadPipe, then uses the OutputStream and the InputStream from it to * communicate. All traffic is automatically synchronized, so one just uses * the streams like they were normal device streams. * * It works by maintaining a circular buffer, where data is written to, and * read from, in a FIFO fashion. * --- * auto tc = new ThreadPipe; * void outFunc() * { * Stdout.copy(tc.input); * } * * auto t = new Thread(&outFunc); * t.start(); * tc.write("hello, thread!"); * tc.close(); * t.join(); * --- */ class ThreadPipe : Conduit { private bool _closed; private size_t _readIdx, _remaining; private void[] _buf; private Mutex _mutex; private Condition _condition; /** * Create a new ThreadPipe with the given buffer size. * * Params: * bufferSize = The size to allocate the buffer. */ this(size_t bufferSize=(1024*16)) { _buf = new ubyte[bufferSize]; _closed = false; _readIdx = _remaining = 0; _mutex = new Mutex; _condition = new Condition(_mutex); } /** * Implements IConduit.bufferSize. * * Returns the appropriate buffer size that should be used to buffer the * ThreadPipe. Note that this is simply the buffer size passed in, and * since all the ThreadPipe data is in memory, buffering doesn't make * much sense. */ override const size_t bufferSize() { return _buf.length; } /** * Implements IConduit.toString * * Returns "<thread conduit>" */ override string toString() { return "<threadpipe>"; } /** * Returns true if there is data left to be read, and the write end isn't * closed. */ override const bool isAlive() { synchronized(_mutex) { return !_closed || _remaining != 0; } } /** * Return the number of bytes remaining to be read in the circular buffer. */ size_t remaining() { synchronized(_mutex) return _remaining; } /** * Return the number of bytes that can be written to the circular buffer. */ size_t writable() { synchronized(_mutex) return _buf.length - _remaining; } /** * Close the write end of the conduit. Writing to the conduit after it is * closed will return Eof. * * The read end is not closed until the buffer is empty. */ void stop() { // // close write end. The read end can stay open until the remaining // bytes are read. // synchronized(_mutex) { _closed = true; _condition.notifyAll(); } } /** * This does nothing because we have no clue whether the members have been * collected, and detach is run in the destructor. To stop communications, * use stop(). * * TODO: move stop() functionality to detach when it becomes possible to * have fully-owned members */ override void detach() { } /** * Implements InputStream.read. * * Read from the conduit into a target array. The provided dst will be * populated with content from the stream. * * Returns the number of bytes read, which may be less than requested in * dst. Eof is returned whenever an end-of-flow condition arises. */ override size_t read(void[] dst) { // // don't block for empty read // if(dst.length == 0) return 0; synchronized(_mutex) { // // see if any remaining data is present // size_t r; while((r = _remaining) == 0 && !_closed) _condition.wait(); // // read all data that is available // if(r == 0) return Eof; if(r > dst.length) r = dst.length; auto result = r; // // handle wrapping // if(_readIdx + r >= _buf.length) { size_t x = _buf.length - _readIdx; dst[0..x] = _buf[_readIdx..$]; _readIdx = 0; _remaining -= x; r -= x; dst = dst[x..$]; } dst[0..r] = _buf[_readIdx..(_readIdx + r)]; _readIdx = (_readIdx + r) % _buf.length; _remaining -= r; _condition.notifyAll(); return result; } } /** * Implements InputStream.clear(). * * Clear any buffered content. */ ThreadPipe clear() { synchronized(_mutex) { if(_remaining != 0) { /* * this isn't technically necessary, but we do it because it * preserves the most recent data first */ _readIdx = (_readIdx + _remaining) % _buf.length; _remaining = 0; _condition.notifyAll(); } } return this; } /** * Implements OutputStream.write. * * Write to stream from a source array. The provided src content will be * written to the stream. * * Returns the number of bytes written from src, which may be less than * the quantity provided. Eof is returned when an end-of-flow condition * arises. */ override size_t write(const(void)[] src) { // // don't block for empty write // if(src.length == 0) return 0; synchronized(_mutex) { size_t w; while((w = _buf.length - _remaining) == 0 && !_closed) _condition.wait(); if(_closed) return Eof; if(w > src.length) w = src.length; auto writeIdx = (_readIdx + _remaining) % _buf.length; auto result = w; if(w + writeIdx >= _buf.length) { auto x = _buf.length - writeIdx; _buf[writeIdx..$] = src[0..x]; writeIdx = 0; w -= x; _remaining += x; src = src[x..$]; } _buf[writeIdx..(writeIdx + w)] = src[0..w]; _remaining += w; _condition.notifyAll(); return result; } } } debug(UnitTest) { import tango.core.Thread; unittest { auto source = new uint[1000]; foreach(i, ref x; source) x = cast(uint)i; ThreadPipe tp = new ThreadPipe(16); void threadA() { void[] sourceBuf = source; while(sourceBuf.length > 0) { sourceBuf = sourceBuf[tp.write(sourceBuf)..$]; } tp.stop(); } Thread a = new Thread(&threadA); a.start(); int readval; int last = -1; size_t nread; while((nread = tp.read((&readval)[0..1])) == readval.sizeof) { assert(readval == last + 1); last = readval; } assert(nread == tp.Eof); a.join(); } } |