| … | … | @@ -2,7 +2,9 @@ |
| 2 | 2 | |
| 3 | 3 | #include <algorithm> |
| 4 | 4 | #include <chrono> |
| 5 | #include <condition_variable> |
| 5 | 6 | #include <cstdio> |
| 7 | #include <mutex> |
| 6 | 8 | |
| 7 | 9 | namespace swarmd { |
| 8 | 10 | |
| … | … | @@ -68,18 +70,39 @@ bool Content::open(const std::string &storage_dir, UdxTransport *transport) { |
| 68 | 70 | void Content::close() { |
| 69 | 71 | if (!open_) return; |
| 70 | 72 | open_ = false; |
| 71 | | // The channels belong to the UDX loop and must be torn down there. If the |
| 72 | | // transport is already stopped there is no loop to marshal to, and nothing |
| 73 | | // else is touching them either. |
| 73 | |
| 74 | // ⛔ WAIT FOR THE LOOP, DO NOT JUST POST TO IT. |
| 75 | // |
| 76 | // Every command — serve, fetch, cancel, the progress sample — is a lambda |
| 77 | // queued onto the UDX loop that captures `this` and reads index_ and proto_. |
| 78 | // Posting the teardown and then resetting them here races those: a serve |
| 79 | // posted a moment earlier runs afterwards, against an index that has been |
| 80 | // destroyed. Measured, as an intermittent SEGV on the UDX thread inside |
| 81 | // Content::serve's lambda, in two runs out of three. |
| 82 | // |
| 83 | // run_on_loop is FIFO, so a teardown posted last runs last — but only if we |
| 84 | // wait for it before freeing anything. That is what HyperOverlay::detach |
| 85 | // does (src/overlay_node.cpp) and this is the same hazard. |
| 74 | 86 | if (t_) { |
| 75 | | t_->run_on_loop([this]() { |
| 87 | std::mutex m; |
| 88 | std::condition_variable cv; |
| 89 | bool done = false; |
| 90 | t_->run_on_loop([this, &m, &cv, &done]() { |
| 76 | 91 | for (auto &kv : peers_) { |
| 77 | 92 | if (proto_) proto_->remove_peer(kv.second); |
| 78 | 93 | delete kv.second; |
| 79 | 94 | } |
| 80 | 95 | peers_.clear(); |
| 96 | { std::lock_guard<std::mutex> lk(m); done = true; } |
| 97 | cv.notify_one(); |
| 81 | 98 | }); |
| 99 | std::unique_lock<std::mutex> lk(m); |
| 100 | // Bounded: a transport already stopped will never run this, and hanging |
| 101 | // a shutdown on it would be worse than the leak of not running it. |
| 102 | cv.wait_for(lk, std::chrono::seconds(2), [&done]() { return done; }); |
| 82 | 103 | } |
| 104 | |
| 105 | // Only now is nothing on the loop pointing at either of these. |
| 83 | 106 | proto_.reset(); |
| 84 | 107 | if (index_) index_->close(); |
| 85 | 108 | index_.reset(); |