void AsyncByteStream::InitAsyncHandler() { if ( _eventSource != nullptr ) throw std::logic_error("This stream is already set up for async operation."); Weak<RingBuffer> weakReadBuf = _readbuf; Weak<RingBuffer> weakWriteBuf = _writebuf; _eventSource = new RunLoop::EventSource([=](RunLoop::EventSource&) { // atomically pull out the event flags here ThreadEvent t = _event.exchange(Wait); bool hasRead = false, hasWritten = false; uint8_t buf[4096]; Shared<RingBuffer> readBuf = weakReadBuf.lock(); Shared<RingBuffer> writeBuf = weakWriteBuf.lock(); if ( (t & ReadSpaceAvailable) == ReadSpaceAvailable && readBuf ) { std::lock_guard<RingBuffer> _(*readBuf); size_type read = this->read_for_async(buf, readBuf->SpaceAvailable()); if ( read != 0 ) { readBuf->WriteBytes(buf, read); hasRead = true; } } if ( (t & DataToWrite) == DataToWrite && writeBuf ) { std::lock_guard<RingBuffer> _(*writeBuf); size_type written = writeBuf->ReadBytes(buf, writeBuf->BytesAvailable()); written = this->write_for_async(buf, written); if ( written != 0 ) { // only remove as much as actually went out writeBuf->RemoveBytes(written); hasWritten = true; } } auto invocation = [this, hasRead, hasWritten] () { if ( hasRead ) _eventHandler(AsyncEvent::HasBytesAvailable, this); if ( hasWritten ) _eventHandler(AsyncEvent::HasSpaceAvailable, this); }; if ( _targetRunLoop != nullptr ) { _targetRunLoop->PerformFunction(invocation); } else { invocation(); } }); if ( _asyncRunLoop == nullptr ) { std::mutex __mut; std::condition_variable __inited; std::unique_lock<std::mutex> __lock(__mut); _asyncIOThread = std::thread([&](){ AsyncByteStream::_asyncRunLoop = RunLoop::CurrentRunLoop(); { std::unique_lock<std::mutex> __(__mut); __inited.notify_all(); } // now run the run loop // only spin an empty run loop a certain amount of time before giving up // and exiting the thread entirely // FIXME: There's a gap here where a race could lose an EventSource addition static constexpr unsigned kMaxEmptyTicks(1000); static constexpr std::chrono::milliseconds kTickLen(10); unsigned __emptyTickCounter = 0; do { RunLoop::ExitReason __r = RunLoop::CurrentRunLoop()->Run(true, std::chrono::seconds(20)); if ( __r == RunLoop::ExitReason::RunFinished ) { if ( ++__emptyTickCounter == kMaxEmptyTicks ) break; // exit the thread // wait a bit and try again std::this_thread::sleep_for(kTickLen); } // by definition not an empty runloop __emptyTickCounter = 0; } while (1); // nullify the global before we quit // deletion isn't necessary, it's done by TLS in run_loop.cpp _asyncRunLoop = nullptr; }); // wait for the runloop to be set __inited.wait(__lock, [&](){return _asyncRunLoop != nullptr;}); } // install the event source into the run loop, then we're all done _asyncRunLoop->AddEventSource(_eventSource); }