-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathlib.js
184 lines (157 loc) · 5.58 KB
/
lib.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import * as net from "net";
function url(port, scenario) {
return `http://localhost:${port}/${scenario}`
}
async function raceWithCancellation(racers) {
const racersAndSignals = racers.map((racer) => {
const controller = new AbortController()
const signal = controller.signal
return {
promise: racer(signal).then((result) => {
controller.isFinished = true
return result
}),
controller: controller
}
})
const racerPromises = racersAndSignals.map(racer => racer.promise)
return Promise.any(racerPromises).then((winner) => {
racersAndSignals.forEach((racer) => {
if (racer.controller.isFinished === undefined) {
racer.controller.abort()
}
})
// wait for all the promises to settle before completing the winner
// note that this doesn't actually wait for all the loser connections to close
return Promise.allSettled(racerPromises).then(() => winner)
})
}
export async function scenario1(port) {
const req = async (signal) => {
const resp = await fetch(url(port, 1), { signal })
return resp.text()
}
return raceWithCancellation([req, req])
}
export async function scenario2(port) {
const req = async (signal) => {
const resp = await fetch(url(port, 2), { signal })
return resp.text()
}
return raceWithCancellation([req, req])
}
export async function scenario3(port) {
const req = async (signal) => {
const resp = await fetch(url(port, 3), { signal })
return resp.text()
}
const reqs = Array.from(new Array(10000), () => req)
return raceWithCancellation(reqs)
}
/*
On slow machines this one can't use fetch for the timeout'd request because:
scenario3 finishes and the connections are still being closed (there doesn't seem to be a way to wait until they are all closed to continue).
Then when scenario4 runs, fetch doesn't immediately make a connection (because the libuv thread pool is exhausted).
This causes the timeout to stop making the second connection before it actually makes the connection.
So instead of using a fetch for the timeout'd request, use a Socket and do not start the timeout until the request connects.
*/
export async function scenario4(port) {
const req = async () => {
const resp = await fetch(url(port, 4))
return resp.text()
}
const reqWithTimeout = () => {
const client = net.createConnection({ port }, () => {
client.write('GET /4 HTTP/1.1\n\n');
})
// don't worry about this request returning a result, cause it will always be the loser
return new Promise((_, reject) => {
client.on("connect", () => {
setTimeout(() => {
client.end()
reject(new Error("Request timed out"))
}, 1000)
})
})
}
return Promise.any([req(), reqWithTimeout()])
}
export async function scenario5(port) {
const req = async (signal) => {
const resp = await fetch(url(port, 5), { signal })
if (resp.status >= 300) {
return Promise.reject(new Error("Not a successful response"))
}
else {
return resp.text()
}
}
return raceWithCancellation([req, req])
}
export async function scenario6(port) {
const req = async (signal) => {
const resp = await fetch(url(port, 6), { signal })
if (resp.status >= 300) {
return Promise.reject(new Error("Not a successful response"))
}
else {
return resp.text()
}
}
return raceWithCancellation([req, req, req])
}
export async function scenario7(port) {
const req = async (signal) => {
return fetch(url(port, 7) , { signal })
.then((resp) => resp.text())
}
const hedge = async (signal) => {
await new Promise((_, reject) => setTimeout(() => {
// not sure why I need the catch & reject here
req(signal).catch((error) => {
reject(error)
})
}, 3000))
}
return raceWithCancellation([req, hedge])
}
export async function scenario8(port) {
const req = async (params, signal) => {
const resp = await fetch(url(port, 8) + `?${params}`, { signal })
if (resp.status >= 300) {
return Promise.reject(new Error("Not a successful response"))
}
else {
return resp.text()
}
}
const open = (signal) => req("open", signal)
const use = (id, signal) => req(`use=${id}`, signal)
const close = (id, signal) => req(`close=${id}`, signal)
const reqRes = async (signal) => {
const id = await open(signal)
try {
return await use(id, signal)
}
finally {
await close(id, signal)
}
}
return raceWithCancellation([reqRes, reqRes])
}
export async function scenario9(port) {
const req = async () => {
const resp = await fetch(url(port, 9))
if (resp.status >= 300) {
return Promise.reject(new Error("Not a successful response"))
}
else {
return { now: performance.now(), text: await resp.text() }
}
}
const reqs = Array.from(new Array(10), req)
const responses = await Promise.allSettled(reqs)
const successes = responses.filter((result) => result.status === "fulfilled")
const ordered = successes.sort((a, b) => a.value.now - b.value.now)
return ordered.reduce((acc, resp) => acc + resp.value.text, "")
}