Loading plugins/media/media.cpp +54 −6 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ #include <algorithm> #include <cstdint> #include <cstring> #include <thread> #include <uuidp.h> Loading Loading @@ -152,6 +153,14 @@ namespace blogi { } ~Media(){ if(_reaperThread.joinable()){ { std::lock_guard<std::mutex> lk(_reaperMtx); _reaperStop = true; } _reaperCv.notify_all(); _reaperThread.join(); } } const std::string getName(){ Loading Loading @@ -851,6 +860,10 @@ namespace blogi { for (int i = 0; i < Args->maxthreads; ++i) _dbAvailableIdx.push_back(i); // See _reaperLoop()'s doc comment for why this can't just run // opportunistically from _storeStreamState() alone. _reaperThread = std::thread([this]{ _reaperLoop(); }); } bool Controller(const int tid,libhttppp::HttpRequest &req,libhtmlpp::HtmlElement *page,const std::string &sessionid){ Loading Loading @@ -1862,12 +1875,23 @@ namespace blogi { std::chrono::steady_clock::time_point last_activity = std::chrono::steady_clock::now(); }; // Plugins get no callback when the browser-facing connection dies // mid-stream (no Disconnect hook in PluginApi), so a StreamState // whose owning req is abandoned would otherwise sit in _streams // forever — Response() simply stops being called for it, holding // its pooled mediadb connection open (and un-drained) permanently. // Sweep those out opportunistically whenever a new stream starts. // Disconnect(tid, req) exists on PluginApi, but for H2 it is called // with the real (persistent, multiplexed) connection object — never // with the per-stream `tempreq` that Controller()/Response() actually // see and that _streams below is keyed on (HttpEvent::_dispatchH2Stream // hands each stream its own throwaway HttpRequest; see httpd.cpp). // A browser aborting one image download therefore never matches an // entry here, so a StreamState whose owning req is abandoned would // otherwise sit in _streams indefinitely — Response() simply stops // being called for it, holding its pooled mediadb connection open // (and un-drained) permanently. // // _reaperLoop() below sweeps this on a timer regardless of new // traffic — relying solely on the opportunistic call from // _storeStreamState() meant a quiet period after a burst of aborted // downloads (tab closed, page navigated away mid-load) left those // connections held hostage forever, since nothing else would ever // call _reapStaleStreamsLocked() again to release them back. static constexpr std::chrono::seconds STREAM_IDLE_TIMEOUT{30}; void _reapStaleStreamsLocked(){ Loading @@ -1885,6 +1909,26 @@ namespace blogi { } } // Runs for the plugin's whole lifetime, independent of request // traffic, so orphaned getimage streams get their pooled mediadb // connection back within STREAM_IDLE_TIMEOUT even during a lull // (no new stream to piggyback the opportunistic sweep in // _storeStreamState() on). Woken early by the destructor via // _reaperCv so shutdown doesn't wait out a full sleep interval. void _reaperLoop(){ std::unique_lock<std::mutex> lk(_reaperMtx); while(!_reaperStop){ _reaperCv.wait_for(lk, std::chrono::seconds(5), [this]{ return _reaperStop; }); if(_reaperStop) break; lk.unlock(); { std::unique_lock<std::mutex> slock(_streamMutex); _reapStaleStreamsLocked(); } lk.lock(); } } void _storeStreamState(libhttppp::HttpRequest &req, CheckoutPool<libhttppp::HttpClient>::Handle client, size_t sendSize, int tid){ Loading Loading @@ -1991,6 +2035,10 @@ namespace blogi { std::mutex _mdbSetupMutex; std::mutex _streamMutex; std::map<uintptr_t, StreamState> _streams; std::thread _reaperThread; std::mutex _reaperMtx; std::condition_variable _reaperCv; bool _reaperStop = false; // ---- thread-safe resources for the offloadable getimage route ---- Loading Loading
plugins/media/media.cpp +54 −6 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ #include <algorithm> #include <cstdint> #include <cstring> #include <thread> #include <uuidp.h> Loading Loading @@ -152,6 +153,14 @@ namespace blogi { } ~Media(){ if(_reaperThread.joinable()){ { std::lock_guard<std::mutex> lk(_reaperMtx); _reaperStop = true; } _reaperCv.notify_all(); _reaperThread.join(); } } const std::string getName(){ Loading Loading @@ -851,6 +860,10 @@ namespace blogi { for (int i = 0; i < Args->maxthreads; ++i) _dbAvailableIdx.push_back(i); // See _reaperLoop()'s doc comment for why this can't just run // opportunistically from _storeStreamState() alone. _reaperThread = std::thread([this]{ _reaperLoop(); }); } bool Controller(const int tid,libhttppp::HttpRequest &req,libhtmlpp::HtmlElement *page,const std::string &sessionid){ Loading Loading @@ -1862,12 +1875,23 @@ namespace blogi { std::chrono::steady_clock::time_point last_activity = std::chrono::steady_clock::now(); }; // Plugins get no callback when the browser-facing connection dies // mid-stream (no Disconnect hook in PluginApi), so a StreamState // whose owning req is abandoned would otherwise sit in _streams // forever — Response() simply stops being called for it, holding // its pooled mediadb connection open (and un-drained) permanently. // Sweep those out opportunistically whenever a new stream starts. // Disconnect(tid, req) exists on PluginApi, but for H2 it is called // with the real (persistent, multiplexed) connection object — never // with the per-stream `tempreq` that Controller()/Response() actually // see and that _streams below is keyed on (HttpEvent::_dispatchH2Stream // hands each stream its own throwaway HttpRequest; see httpd.cpp). // A browser aborting one image download therefore never matches an // entry here, so a StreamState whose owning req is abandoned would // otherwise sit in _streams indefinitely — Response() simply stops // being called for it, holding its pooled mediadb connection open // (and un-drained) permanently. // // _reaperLoop() below sweeps this on a timer regardless of new // traffic — relying solely on the opportunistic call from // _storeStreamState() meant a quiet period after a burst of aborted // downloads (tab closed, page navigated away mid-load) left those // connections held hostage forever, since nothing else would ever // call _reapStaleStreamsLocked() again to release them back. static constexpr std::chrono::seconds STREAM_IDLE_TIMEOUT{30}; void _reapStaleStreamsLocked(){ Loading @@ -1885,6 +1909,26 @@ namespace blogi { } } // Runs for the plugin's whole lifetime, independent of request // traffic, so orphaned getimage streams get their pooled mediadb // connection back within STREAM_IDLE_TIMEOUT even during a lull // (no new stream to piggyback the opportunistic sweep in // _storeStreamState() on). Woken early by the destructor via // _reaperCv so shutdown doesn't wait out a full sleep interval. void _reaperLoop(){ std::unique_lock<std::mutex> lk(_reaperMtx); while(!_reaperStop){ _reaperCv.wait_for(lk, std::chrono::seconds(5), [this]{ return _reaperStop; }); if(_reaperStop) break; lk.unlock(); { std::unique_lock<std::mutex> slock(_streamMutex); _reapStaleStreamsLocked(); } lk.lock(); } } void _storeStreamState(libhttppp::HttpRequest &req, CheckoutPool<libhttppp::HttpClient>::Handle client, size_t sendSize, int tid){ Loading Loading @@ -1991,6 +2035,10 @@ namespace blogi { std::mutex _mdbSetupMutex; std::mutex _streamMutex; std::map<uintptr_t, StreamState> _streams; std::thread _reaperThread; std::mutex _reaperMtx; std::condition_variable _reaperCv; bool _reaperStop = false; // ---- thread-safe resources for the offloadable getimage route ---- Loading