Skip to content

Commit 98636d3

Browse files
committed
autoconfig: abort slower routers after fastest routers are connected
1 parent fcdcf4b commit 98636d3

5 files changed

Lines changed: 78 additions & 15 deletions

File tree

pkg/autoconfig/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@
3232
"@ndn/packet": "workspace:*",
3333
"@ndn/quic-transport": "workspace:*",
3434
"@ndn/ws-transport": "workspace:*",
35+
"@ndn/util": "workspace:*",
3536
"default-gateway": "^7.2.2",
36-
"tslib": "^2.8.1"
37+
"p-event": "^7.1.0",
38+
"tslib": "^2.8.1",
39+
"type-fest": "^5.6.0"
3740
},
3841
"devDependencies": {
39-
"@ndn/util": "workspace:*",
4042
"@types/default-gateway": "^7.2.2",
4143
"@types/koa": "^3.0.3",
42-
"koa": "^3.2.1",
43-
"type-fest": "^5.6.0"
44+
"koa": "^3.2.1"
4445
}
4546
}

pkg/autoconfig/src/network.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ export interface ConnectNetworkOptions extends ConnectRouterOptions {
3939
* @defaultValue 1
4040
*/
4141
fastest?: number;
42+
43+
/***
44+
* After `fastest` faces have been created and finished testConnection,
45+
* how much longer to wait for other faces, in milliseconds.
46+
* @defaultValue 1000
47+
*/
48+
waitAfterFastest?: number;
4249
}
4350

4451
/** Connect to an NDN network. */
@@ -49,7 +56,16 @@ export async function connectToNetwork(opts: ConnectNetworkOptions = {}): Promis
4956
tryDefaultGateway = true,
5057
fallback = [],
5158
fastest = 1,
59+
waitAfterFastest = 1000,
60+
signal: parentSignal,
5261
} = opts;
62+
parentSignal?.throwIfAborted();
63+
64+
const fastestAbort = new AbortController();
65+
const routerOpts: ConnectRouterOptions = {
66+
...opts,
67+
signal: parentSignal ? AbortSignal.any([parentSignal, fastestAbort.signal]) : fastestAbort.signal,
68+
};
5369

5470
const connected: ConnectRouterResult[] = [];
5571
const errs: Record<string, unknown> = {};
@@ -75,7 +91,13 @@ export async function connectToNetwork(opts: ConnectNetworkOptions = {}): Promis
7591
) {
7692
await Promise.all(routers.map(async (router) => {
7793
try {
78-
connected.push(await connectToRouter(router, opts));
94+
connected.push(await connectToRouter(router, routerOpts));
95+
if (connected.length === fastest) {
96+
setTimeout(
97+
() => fastestAbort.abort(new Error("sufficient number of faces have been established")),
98+
waitAfterFastest,
99+
);
100+
}
79101
} catch (err: unknown) {
80102
errs[router] = err;
81103
}

pkg/autoconfig/src/router.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { consume, type ConsumerOptions } from "@ndn/endpoint";
22
import { type Forwarder, type FwFace, TapFace } from "@ndn/fw";
33
import { Interest, Name, type NameLike } from "@ndn/packet";
44
import type { H3Transport } from "@ndn/quic-transport";
5+
import { assert } from "@ndn/util";
6+
import { pEvent } from "p-event";
7+
import type { Arrayable } from "type-fest";
58

69
import { createFace } from "./platform_node";
710

@@ -50,7 +53,7 @@ export interface ConnectRouterOptions {
5053
* If string ends with "/*", it's replaced with a random component.
5154
* - function: execute the custom tester function.
5255
*/
53-
testConnection?: false | TestConnectionPacket | TestConnectionPacket[] |
56+
testConnection?: false | Arrayable<TestConnectionPacket> |
5457
((face: FwFace) => Promise<unknown>);
5558

5659
/**
@@ -66,7 +69,10 @@ export interface ConnectRouterOptions {
6669
* Routes to be added on the created face.
6770
* @defaultValue `["/"]`
6871
*/
69-
addRoutes?: NameLike[];
72+
addRoutes?: readonly NameLike[];
73+
74+
/** AbortSignal that allows canceling the attempt via AbortController. */
75+
signal?: AbortSignal;
7076
}
7177

7278
/** {@link connectToRouter} result. */
@@ -83,14 +89,34 @@ export interface ConnectRouterResult {
8389

8490
/** Connect to a router and test the connection. */
8591
export async function connectToRouter(router: string, opts: ConnectRouterOptions = {}): Promise<ConnectRouterResult> {
86-
const face = await createFace(router, opts);
92+
const { signal } = opts;
93+
let face: FwFace | undefined;
94+
const promises: Array<Promise<void>> = [
95+
(async () => {
96+
// createFace does not take AbortSignal, but clear it to protect against future changes
97+
face = await createFace(router, { ...opts, signal: undefined });
98+
})(),
99+
];
100+
if (signal) {
101+
promises.push((async () => {
102+
if (!signal.aborted) {
103+
await pEvent(signal, "abort");
104+
}
105+
})());
106+
}
107+
await Promise.race(promises);
108+
if (!face) {
109+
assert(signal?.aborted);
110+
throw signal.reason; // eslint-disable-line @typescript-eslint/only-throw-error
111+
}
87112

88113
const testConnectionStart = performance.now();
89114
let testConnectionDuration: number;
90115
let testConnectionResult: unknown;
91116
try {
92117
testConnectionResult = await testConnection(face, opts);
93118
testConnectionDuration = performance.now() - testConnectionStart;
119+
signal?.throwIfAborted();
94120
} catch (err: unknown) {
95121
face.close();
96122
throw err;
@@ -103,8 +129,11 @@ async function testConnection(
103129
{
104130
testConnection: tc = new Name("/localhop/nfd/rib/list"),
105131
testConnectionTimeout = 2000,
132+
signal: parentSignal,
106133
}: ConnectRouterOptions,
107134
): Promise<unknown> {
135+
parentSignal?.throwIfAborted();
136+
108137
if (tc === false) {
109138
return undefined;
110139
}
@@ -117,20 +146,23 @@ async function testConnection(
117146

118147
const tapFace = TapFace.create(face);
119148
tapFace.addRoute("/");
120-
const abort = new AbortController();
121-
const cOpts: ConsumerOptions = { fw: tapFace.fw, signal: abort.signal };
149+
const raceAbort = new AbortController();
150+
const cOpts: ConsumerOptions = {
151+
fw: tapFace.fw,
152+
signal: parentSignal ? AbortSignal.any([parentSignal, raceAbort.signal]) : raceAbort.signal,
153+
};
122154
try {
123-
await Promise.any(tc.map((pkt) => {
155+
return await Promise.any(tc.map(async (pkt, i) => {
124156
if (typeof pkt === "string" && pkt.endsWith("/*")) {
125157
pkt = new Name(pkt.slice(0, -2)).append(Math.trunc(Math.random() * 1e8).toString().padStart(8, "0"));
126158
}
127159
const interest = pkt instanceof Interest ? pkt :
128160
new Interest(pkt, Interest.CanBePrefix, Interest.Lifetime(testConnectionTimeout));
129-
return consume(interest, cOpts);
161+
const data = await consume(interest, cOpts);
162+
return { i, interest, data };
130163
}));
131164
} finally {
132-
abort.abort();
165+
raceAbort.abort();
133166
tapFace.close();
134167
}
135-
return undefined;
136168
}

pkg/autoconfig/tests/network.t.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,18 @@ test("connectToNetwork", async () => {
4444
...await addClosedServers(),
4545
];
4646

47+
const t0 = Date.now();
4748
const faces = await connectToNetwork({
4849
fch: false,
4950
tryDefaultGateway: false,
5051
fallback: servers,
52+
connectTimeout: 5000,
5153
testConnection: "/localhop/test-connection/*",
52-
testConnectionTimeout: 1500,
54+
testConnectionTimeout: 5000,
55+
waitAfterFastest: 100,
5356
});
57+
const t1 = Date.now();
58+
expect(t1 - t0).toBeLessThanOrEqual(2000);
5459
closers.push(...faces);
5560
expect(faces).toHaveLength(1);
5661
expect(faces[0]!.toString()).toContain(servers[1]);
@@ -62,6 +67,7 @@ test("connectToNetwork", async () => {
6267
tryDefaultGateway: false,
6368
fallback: servers,
6469
fastest: 2,
70+
connectTimeout: 1500,
6571
testConnection: ["/localhop/test-connection/*", new Name("/unreachable")],
6672
testConnectionTimeout: 1500,
6773
});

pkg/endpoint/src/consumer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ function makeConsumer(
8181
const retxGen = makeRetxGenerator(retx)(interest.lifetime)[Symbol.iterator]();
8282

8383
const promise = new Promise<Data>((resolve, reject) => {
84+
signal?.throwIfAborted();
85+
8486
const rx = pushable<FwPacket>();
8587

8688
let timer: NodeJS.Timeout | number | undefined;

0 commit comments

Comments
 (0)