Skip to content

Commit 903de03

Browse files
authored
Merge pull request #53 from MatrixAI/feature-events
Events Refactoring (integrating `js-events`)
2 parents 67a73f3 + 0a5f4bd commit 903de03

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+19966
-14369
lines changed

.eslintrc

+6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
"message": "Use `globalThis` instead"
4040
}
4141
],
42+
"prefer-rest-params": 0,
43+
"prefer-const": [
44+
"error", {
45+
"destructuring": "all"
46+
}
47+
],
4248
"require-yield": 0,
4349
"eqeqeq": ["error", "smart"],
4450
"spaced-comment": [

.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
/tests
1111
/tmp
1212
/docs
13+
/images
1314
/benches
1415
/build
1516
/builds

Cargo.lock

+68-17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ path = "src/native/napi/lib.rs"
1414
napi = { version = "2", features = ["async", "napi6", "serde-json"] }
1515
serde = { version = "1.0", features = ["derive"] }
1616
napi-derive = { version = "2", default-features = false, features = ["strict", "compat-mode"] }
17-
quiche = { version = "0.17.1", features = ["boringssl-boring-crate", "boringssl-vendored"] }
18-
boring = "2.1.0"
17+
quiche = { version = "0.18.0", features = ["boringssl-boring-crate", "boringssl-vendored"] }
18+
boring = "3"
1919

2020
[build-dependencies]
2121
napi-build = "2"

README.md

+51
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,57 @@ x86_64-apple-darwin
108108

109109
The available target list is in `rustc --print target-list`.
110110

111+
### Structure
112+
113+
It is possible to structure the QUIC system in the encapsulated way or the injected way.
114+
115+
When using the encapsulated way, the `QUICSocket` is separated between client and server.
116+
117+
When using the injected way, the `QUICSocket` is shared between client and server.
118+
119+
![](/images/quic_structure_encapsulated.svg)
120+
121+
If you are building a peer to peer network, you must use the injected way. This is the only way to ensure that hole-punching works because both the client and server for any given peer must share the same UDP socket and thus share the `QUICSocket`. When done in this way, the `QUICSocket` lifecycle is managed outside of both the `QUICClient` and `QUICServer`.
122+
123+
![](/images/quic_structure_injected.svg)
124+
125+
This also means both `QUICClient` and `QUICServer` must share the same connection map. In order to allow the `QUICSocket` to dispatch data into the correct connection, the connection map is constructed in the `QUICSocket`, however setting and unsetting connections is managed by `QUICClient` and `QUICServer`.
126+
127+
## Dataflow
128+
129+
The data flow of the QUIC system is a bidirectional graph.
130+
131+
Data received from the outside world is received on the UDP socket. It is parsed and then dispatched to each `QUICConnection`. Each connection further parses the data and then dispatches to the `QUICStream`. Each `QUICStream` presents the data on the `ReadableStream` interface, which can be read by a caller.
132+
133+
Data sent to the outside world is written to a `WritableStream` interface of a `QUICStream`. This data is buffered up in the underlying Quiche stream. A send procedure is triggered on the associated `QUICConnection` which takes all the buffered data to be sent for that connection, and sends it to the `QUICSocket`, which then sends it to the underlying UDP socket.
134+
135+
![](/images/quic_dataflow.svg)
136+
137+
Buffering occurs at the connection level and at the stream level. Each connection has a global buffer for all streams, and each stream has its own buffer. Note that connection buffering and stream buffering all occur within the Quiche library. The web streams `ReadableStream` and `WritableStream` do not do any buffering at all.
138+
139+
### Connection Negotiation
140+
141+
The connection negotiation process involves several exchanges of QUIC packets before the `QUICConnection` is constructed.
142+
143+
The primary reason to do this is for both sides to determine their respective connection IDs.
144+
145+
![](/images/quic_connection_negotiation.svg)
146+
147+
### Push & Pull
148+
149+
The `QUICSocket`, `QUICClient`, `QUICServer`, `QUICConnection` and `QUICStream` are independent state machines that exposes methods that can be called as well as events that may be emitted between them.
150+
151+
This creates a concurrent decentralised state machine system where there are multiple entrypoints of change.
152+
153+
Users may call methods which causes state transitions internally that trigger event emissions. However some methods are considered internal to the library, this means these methods are not intended to be called by the end user. They are however public relative to the other components in the system. These methods should be marked with `@internal` documentation annotation.
154+
155+
External events may also trigger event handlers that will call methods which perform state transitions and event emission.
156+
157+
Keeping track of how the system works is therefore quite complex and must follow a set of rules.
158+
159+
* Pull methods - these are either synchronous or asynchronous methods that may throw exceptions.
160+
* Push handlers - these are event handlers that can initiate pull methods, if these pull handlers throw exceptions, these exceptions must be caught, and expected runtime exceptions are to be converted to error events, all other exceptions will be considered to be software bugs and will be bubbled up to the program boundary as unhandled exceptions or unhandled promise rejections. Generally the only exceptions that are expected runtime exceptions are those that arise from perform IO with the operating system.
161+
111162
## Benchmarks
112163

113164
```sh

benches/index.ts

+34-22
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,43 @@
11
#!/usr/bin/env ts-node
22

3+
import type { Summary } from 'benny/lib/internal/common-types';
34
import fs from 'fs';
45
import path from 'path';
56
import si from 'systeminformation';
6-
import Stream1KB from './stream_1KB';
7+
import { fsWalk, resultsPath, suitesPath } from './utils';
78

89
async function main(): Promise<void> {
910
await fs.promises.mkdir(path.join(__dirname, 'results'), { recursive: true });
10-
// Running benches
11-
await Stream1KB();
12-
const resultFilenames = await fs.promises.readdir(
13-
path.join(__dirname, 'results'),
14-
);
15-
const metricsFile = await fs.promises.open(
16-
path.join(__dirname, 'results', 'metrics.txt'),
17-
'w',
18-
);
11+
// Running all suites
12+
for await (const suitePath of fsWalk(suitesPath)) {
13+
// Skip over non-ts and non-js files
14+
const ext = path.extname(suitePath);
15+
if (ext !== '.ts' && ext !== '.js') {
16+
continue;
17+
}
18+
const suite: () => Promise<Summary> = (await import(suitePath)).default;
19+
// Skip default exports that are not functions and are not called "main"
20+
// They might be utility files
21+
if (typeof suite === 'function' && suite.name === 'main') {
22+
await suite();
23+
}
24+
}
25+
// Concatenating metrics
26+
const metricsPath = path.join(resultsPath, 'metrics.txt');
27+
await fs.promises.rm(metricsPath, { force: true });
1928
let concatenating = false;
20-
for (const resultFilename of resultFilenames) {
21-
if (/.+_metrics\.txt$/.test(resultFilename)) {
22-
const metricsData = await fs.promises.readFile(
23-
path.join(__dirname, 'results', resultFilename),
24-
);
25-
if (concatenating) {
26-
await metricsFile.write('\n');
27-
}
28-
await metricsFile.write(metricsData);
29-
concatenating = true;
29+
for await (const metricPath of fsWalk(resultsPath)) {
30+
// Skip over non-metrics files
31+
if (!metricPath.endsWith('_metrics.txt')) {
32+
continue;
33+
}
34+
const metricData = await fs.promises.readFile(metricPath);
35+
if (concatenating) {
36+
await fs.promises.appendFile(metricsPath, '\n');
3037
}
38+
await fs.promises.appendFile(metricsPath, metricData);
39+
concatenating = true;
3140
}
32-
await metricsFile.close();
3341
const systemData = await si.get({
3442
cpu: '*',
3543
osInfo: 'platform, distro, release, kernel, arch',
@@ -41,4 +49,8 @@ async function main(): Promise<void> {
4149
);
4250
}
4351

44-
void main();
52+
if (require.main === module) {
53+
void main();
54+
}
55+
56+
export default main;

0 commit comments

Comments
 (0)