upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/relay/btfrv.cpp
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-02 11:59:12 -0300
committerGitHub <noreply@github.com>2026-08-02 11:59:12 -0300
commit758ff4bc49af78b06e37fc1e99741cb5fa8e43d6 (patch)
tree4e9d4ed63430d7a442260d1be78d63878a2fc455 /relay/btfrv.cpp
parentfe04a2045c3080797c6f93bf7cd2e12192b8f68f (diff)
rendezvous: let a node park more than one registration (#113)
A registration was consumed by the first caller and registering again replaced the previous one, so a node was reachable to one peer at a time and unreachable for the gap between being paired and registering again. Measured on a live node: descriptors resolved 53/54, our own registrations paired 11/12, nothing paired and went silent -- and half of every outbound dial failed, 15 of 30 in eleven minutes. The relay keeps a queue per pubkey capped at four, pairs with the oldest still-alive registration and discards the dead; the node runs three accept threads, each holding its own registration. The wire protocol does not change, so either side can be deployed first. Bench with both relays built from source and waves of concurrent dialers: old+1 65%, new+1 58%, old+3 68%, new+3 98%. Each half alone lands within noise of doing nothing.
Diffstat (limited to 'relay/btfrv.cpp')
-rw-r--r--relay/btfrv.cpp97
1 files changed, 73 insertions, 24 deletions
diff --git a/relay/btfrv.cpp b/relay/btfrv.cpp
index b1eee55..833c47c 100644
--- a/relay/btfrv.cpp
+++ b/relay/btfrv.cpp
@@ -31,6 +31,7 @@ typedef int SOCKET;
31#include <cstring> 31#include <cstring>
32#include <cstdio> 32#include <cstdio>
33#include <string> 33#include <string>
34#include <deque>
34#include <map> 35#include <map>
35#include <thread> 36#include <thread>
36#include <mutex> 37#include <mutex>
@@ -44,7 +45,24 @@ static std::atomic<bool> g_stop(false);
44static SOCKET g_listen = INVALID_SOCKET; 45static SOCKET g_listen = INVALID_SOCKET;
45 46
46// Services that have registered and are waiting for a client, keyed by pubkey. 47// Services that have registered and are waiting for a client, keyed by pubkey.
47static std::map<std::string, SOCKET> g_waiting; 48// More than one registration per node, on purpose.
49//
50// This used to be one socket per pubkey, and a dial *consumed* it: the first
51// caller took the registration and everyone else was told "not found" until the
52// node noticed and registered again. A node re-registers only after its pairing
53// has been handed off, so between the two there is a window where it is
54// unreachable to the whole network. Measured from a node's own counters: half
55// of all outbound dials failed that way -- 15 of 30 in eleven minutes -- while
56// descriptors resolved 53 times out of 54 and the node's own registrations
57// paired 11 times out of 12. The rendezvous was not losing anybody; it was
58// letting one caller in at a time.
59//
60// A queue per key lets a node keep spare registrations parked, so a dial that
61// arrives while another is being paired finds the next one instead of a closed
62// door. Old nodes register exactly as before and simply keep one entry: the
63// wire protocol does not change, so a relay running this serves both.
64static const size_t MAX_WAITING_PER_KEY = 4;
65static std::map<std::string, std::deque<SOCKET> > g_waiting;
48static std::mutex g_mtx; 66static std::mutex g_mtx;
49 67
50// How often the reaper walks the waiting map. 68// How often the reaper walks the waiting map.
@@ -150,16 +168,24 @@ static void ReapDeadRegistrations()
150 int nBefore = 0, nReaped = 0; 168 int nBefore = 0, nReaped = 0;
151 { 169 {
152 std::lock_guard<std::mutex> lk(g_mtx); 170 std::lock_guard<std::mutex> lk(g_mtx);
153 nBefore = (int)g_waiting.size(); 171 std::map<std::string, std::deque<SOCKET> >::iterator it = g_waiting.begin();
154 std::map<std::string, SOCKET>::iterator it = g_waiting.begin();
155 while (it != g_waiting.end()) 172 while (it != g_waiting.end())
156 { 173 {
157 if (RegistrationDead(it->second)) 174 std::deque<SOCKET>& q = it->second;
175 nBefore += (int)q.size();
176 for (std::deque<SOCKET>::iterator jt = q.begin(); jt != q.end();)
158 { 177 {
159 CLOSESOCK(it->second); 178 if (RegistrationDead(*jt))
160 g_waiting.erase(it++); 179 {
161 nReaped++; 180 CLOSESOCK(*jt);
181 jt = q.erase(jt);
182 nReaped++;
183 }
184 else
185 ++jt;
162 } 186 }
187 if (q.empty())
188 g_waiting.erase(it++);
163 else 189 else
164 ++it; 190 ++it;
165 } 191 }
@@ -209,34 +235,57 @@ static void HandleIncoming(SOCKET s)
209 { 235 {
210 // Register and wait; a future client will pair us. 236 // Register and wait; a future client will pair us.
211 std::lock_guard<std::mutex> lk(g_mtx); 237 std::lock_guard<std::mutex> lk(g_mtx);
212 // If a stale service for this key exists, drop it. 238 std::deque<SOCKET>& q = g_waiting[pubkey];
213 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey); 239
214 if (it != g_waiting.end()) CLOSESOCK(it->second); 240 // Drop registrations this node has already lost the far end of, rather
215 g_waiting[pubkey] = s; 241 // than dropping the previous one on principle. The old code closed the
216 // leave the socket open in the map; do not close here. 242 // prior registration on every new one, which is why a node could never
243 // hold a spare.
244 for (std::deque<SOCKET>::iterator it = q.begin(); it != q.end();)
245 {
246 if (RegistrationDead(*it)) { CLOSESOCK(*it); it = q.erase(it); }
247 else ++it;
248 }
249
250 // A ceiling, because a stranger can open these. Oldest goes first: it
251 // is the one most likely to be stale.
252 while (q.size() >= MAX_WAITING_PER_KEY)
253 {
254 CLOSESOCK(q.front());
255 q.pop_front();
256 }
257 q.push_back(s);
258 // leave the socket open in the queue; do not close here.
217 } 259 }
218 else if (role == 'C') 260 else if (role == 'C')
219 { 261 {
220 SOCKET svc = INVALID_SOCKET; 262 SOCKET svc = INVALID_SOCKET;
221 { 263 {
222 std::lock_guard<std::mutex> lk(g_mtx); 264 std::lock_guard<std::mutex> lk(g_mtx);
223 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey); 265 std::map<std::string, std::deque<SOCKET> >::iterator it = g_waiting.find(pubkey);
224 if (it != g_waiting.end()) 266 if (it != g_waiting.end())
225 { 267 {
226 // Check before handing it over. Pairing a client with a dead 268 std::deque<SOCKET>& q = it->second;
227 // registration produces a tunnel that fails after both sides 269 // Take the oldest one that is still alive, discarding the dead
228 // think they succeeded, which is the failure the dialling node 270 // as we go. Checking before handing it over matters: pairing a
229 // reports as "tunnel to X failed" with nothing to explain it. 271 // client with a dead registration produces a tunnel that fails
230 if (RegistrationDead(it->second)) 272 // after both sides think they succeeded, which is the failure
273 // the dialling node reports as "tunnel to X failed" with
274 // nothing to explain it.
275 while (!q.empty())
231 { 276 {
232 CLOSESOCK(it->second); 277 SOCKET candidate = q.front();
233 g_waiting.erase(it); 278 q.pop_front();
279 if (RegistrationDead(candidate))
280 {
281 CLOSESOCK(candidate);
282 continue;
283 }
284 svc = candidate;
285 break;
234 } 286 }
235 else 287 if (q.empty())
236 {
237 svc = it->second;
238 g_waiting.erase(it); 288 g_waiting.erase(it);
239 }
240 } 289 }
241 } 290 }
242 if (svc == INVALID_SOCKET) 291 if (svc == INVALID_SOCKET)