-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathlib.js
More file actions
173 lines (151 loc) · 5.57 KB
/
Copy pathlib.js
File metadata and controls
173 lines (151 loc) · 5.57 KB
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
import http from "node:http";
export function url(port, scenario) {
return `http://localhost:${port}/${scenario}`
}
// races promises
// losers are cancelled
// winner is returned
export async function raceWithCancellation(racers) {
const controllers = racers.map(() => new AbortController())
const promises = racers.map((racer, i) => racer(controllers[i].signal))
return new Promise((resolve, reject) => {
let settled = false
promises.forEach((promise, winnerIndex) => {
promise.then((value) => {
if (!settled) {
settled = true
// Cancel all losers
controllers.forEach((controller, i) => {
if (i !== winnerIndex) {
controller.abort()
}
})
resolve(value)
}
}).catch((err) => {
// Ignore errors from cancelled promises
})
})
})
}
// make a cancelable http request
// use the node http module
// returned promise has statusCode and text (not a promise)
// cancelled promises are not rejected until the connection is closed
// request or response errors are rejected
export async function httpGet(url, signal) {
return new Promise((resolve, reject) => {
const req = http.get(url, (res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
resolve({statusCode: res.statusCode, text: data})
})
res.on('error', reject)
})
req.on('error', reject)
if (signal) {
const onAbort = () => {
req.destroy(new Error('Request aborted'))
}
signal.addEventListener('abort', onAbort)
// Clean up the listener when request completes
req.on('close', () => {
signal.removeEventListener('abort', onAbort)
})
}
})
}
export async function scenario1(port) {
const req = async (signal) => httpGet(url(port, 1), signal).then(resp => resp.text)
return raceWithCancellation([req, req])
}
export async function scenario2(port) {
const req = async (signal) => httpGet(url(port, 2), signal).then(resp => resp.text)
return raceWithCancellation([req, req])
}
export async function scenario3(port) {
const req = async (signal) => httpGet(url(port, 3), signal).then(resp => resp.text)
const reqs = Array.from(new Array(10000), () => req)
return raceWithCancellation(reqs)
}
export async function scenario4(port) {
const req = async (signal) => httpGet(url(port, 4), signal).then(resp => resp.text)
const reqWithTimeout = async (signal) => {
const timeoutController = new AbortController()
const timeout = setTimeout(() => timeoutController.abort(), 1000)
signal?.addEventListener('abort', () => timeoutController.abort())
try {
return await httpGet(url(port, 4), timeoutController.signal).then(resp => resp.text)
} finally {
clearTimeout(timeout)
}
}
return raceWithCancellation([req, reqWithTimeout])
}
export async function scenario5(port) {
const req = async (signal) => {
const resp = await httpGet(url(port, 5), signal)
if (resp.statusCode !== 200) {
throw new Error(`Non-200 response: ${resp.statusCode}`)
}
return resp.text
}
return raceWithCancellation([req, req])
}
export async function scenario6(port) {
const req = async (signal) => {
const resp = await httpGet(url(port, 6), signal)
if (resp.statusCode !== 200) {
throw new Error(`Non-200 response: ${resp.statusCode}`)
}
return resp.text
}
return raceWithCancellation([req, req, req])
}
export async function scenario7(port) {
const req = async (signal) => httpGet(url(port, 7), signal).then(resp => resp.text)
const delayedReq = async (signal) => {
await new Promise(resolve => setTimeout(resolve, 3000))
if (signal?.aborted) throw new Error('Aborted')
return httpGet(url(port, 7), signal).then(resp => resp.text)
}
return raceWithCancellation([req, delayedReq])
}
export async function scenario8(port) {
const req = async (params, signal) => {
const resp = await httpGet(url(port, 8) + `?${params}`, signal)
if (resp.statusCode >= 300) {
throw new Error("Not a successful response")
}
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 httpGet(url(port, 9))
if (resp.statusCode >= 300) {
throw new Error("Not a successful response")
}
return {now: performance.now(), text: 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, "")
}