@planet-matrix/mobius-model 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +134 -21
- package/dist/index.js +45 -4
- package/dist/index.js.map +186 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/basic/README.md +69 -117
- package/src/basic/enhance.ts +10 -0
- package/src/basic/function.ts +81 -62
- package/src/basic/index.ts +2 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/object.ts +82 -0
- package/src/basic/promise.ts +29 -8
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/encoding/README.md +92 -0
- package/src/encoding/base64.ts +107 -0
- package/src/encoding/index.ts +1 -0
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +18 -1
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +543 -0
- package/src/orchestration/README.md +89 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/index.ts +1 -0
- package/src/random/README.md +78 -0
- package/src/random/index.ts +1 -0
- package/src/random/string.ts +35 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/object.spec.ts +32 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/encoding/base64.spec.ts +40 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +222 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/random/string.spec.ts +11 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/.oxlintrc.json +0 -5
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { InternalWaitQueueEntry } from "./internal/wait-queue.ts"
|
|
2
|
+
import type { InternalWaitConstraintsEntry, InternalWaitConstraintsOptions } from "./internal/wait-constraints.ts"
|
|
3
|
+
|
|
4
|
+
import { InternalWaitQueue } from "./internal/wait-queue.ts"
|
|
5
|
+
import { BrokenBarrierError, CoordinationAbortError } from "./errors.ts"
|
|
6
|
+
import { internalAssertTimeoutOption, internalBindWaitConstraints, internalCleanupWaitConstraints } from "./internal/wait-constraints.ts"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 断言栅栏的参与者数量是一个有效的正整数。
|
|
10
|
+
*/
|
|
11
|
+
const internalAssertParticipantCount = (participantCount: number): void => {
|
|
12
|
+
if (
|
|
13
|
+
Number.isFinite(participantCount) === false
|
|
14
|
+
|| Number.isInteger(participantCount) === false
|
|
15
|
+
|| participantCount <= 0
|
|
16
|
+
) {
|
|
17
|
+
throw new RangeError("Barrier participantCount must be a finite integer greater than 0.")
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 表示某一代栅栏中单个等待者对应的内部节点。
|
|
23
|
+
*
|
|
24
|
+
* 每个节点既要能挂入等待队列,也要能绑定超时与中止约束,最终在整代完成或整代破坏时统一结算。
|
|
25
|
+
*/
|
|
26
|
+
interface InternalBarrierQueueEntry extends InternalWaitQueueEntry<InternalBarrierQueueEntry>, InternalWaitConstraintsEntry {
|
|
27
|
+
resolve: (generation: number) => void
|
|
28
|
+
reject: (reason: unknown) => void
|
|
29
|
+
isSettled: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 表示调用 `Barrier.signalAndWait()` 时可选的等待约束。
|
|
34
|
+
*/
|
|
35
|
+
export interface BarrierWaitOptions extends InternalWaitConstraintsOptions {
|
|
36
|
+
/**
|
|
37
|
+
* 单次等待允许持续的最长时间,单位为毫秒。
|
|
38
|
+
*/
|
|
39
|
+
timeout?: number | undefined
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 用于主动中止当前等待的信号。
|
|
43
|
+
*/
|
|
44
|
+
abortSignal?: AbortSignal | undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 表示一个循环栅栏,用于让固定数量的参与者在同一同步点会合。
|
|
49
|
+
*
|
|
50
|
+
* 每次当所有参与者都调用 `signalAndWait()` 后,当前这一代会被一次性完成,
|
|
51
|
+
* 所有等待者都会收到相同的代号,然后栅栏自动进入下一代继续复用。
|
|
52
|
+
*/
|
|
53
|
+
export class Barrier {
|
|
54
|
+
private readonly queue: InternalWaitQueue<InternalBarrierQueueEntry>
|
|
55
|
+
|
|
56
|
+
private internalArrivedCount: number
|
|
57
|
+
|
|
58
|
+
private internalGeneration: number
|
|
59
|
+
|
|
60
|
+
private readonly participantCount: number
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 创建一个需要固定参与者数量才能完成一代的栅栏。
|
|
64
|
+
*/
|
|
65
|
+
constructor(participantCount: number) {
|
|
66
|
+
internalAssertParticipantCount(participantCount)
|
|
67
|
+
|
|
68
|
+
this.queue = new InternalWaitQueue<InternalBarrierQueueEntry>()
|
|
69
|
+
this.internalArrivedCount = 0
|
|
70
|
+
this.internalGeneration = 0
|
|
71
|
+
this.participantCount = participantCount
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 读取每一代完成所需的参与者总数。
|
|
76
|
+
*/
|
|
77
|
+
getParticipantCount(): number {
|
|
78
|
+
return this.participantCount
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 读取当前所处的代号。
|
|
83
|
+
*
|
|
84
|
+
* 该值会在一代正常完成或被破坏后递增。
|
|
85
|
+
*/
|
|
86
|
+
getGeneration(): number {
|
|
87
|
+
return this.internalGeneration
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 读取当前这一代中仍在等待其余参与者的任务数量。
|
|
92
|
+
*/
|
|
93
|
+
getPendingCount(): number {
|
|
94
|
+
return this.queue.getCount()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 读取当前这一代距离完成还差多少参与者。
|
|
99
|
+
*/
|
|
100
|
+
getRemainingCount(): number {
|
|
101
|
+
return this.participantCount - this.internalArrivedCount
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 以成功状态完成当前代。
|
|
106
|
+
*
|
|
107
|
+
* 完成时会先重置计数并推进代号,再唤醒所有尚未结算的等待者,确保下一代可以立即开始接收新的参与者。
|
|
108
|
+
*/
|
|
109
|
+
private completeGeneration(completedGeneration: number): void {
|
|
110
|
+
this.internalArrivedCount = 0
|
|
111
|
+
this.internalGeneration = this.internalGeneration + 1
|
|
112
|
+
|
|
113
|
+
while (this.queue.isEmpty() === false) {
|
|
114
|
+
const entry = this.queue.shift()
|
|
115
|
+
if (entry === undefined) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (entry.isSettled === true) {
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
entry.isSettled = true
|
|
124
|
+
internalCleanupWaitConstraints(entry)
|
|
125
|
+
entry.resolve(completedGeneration)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 以失败状态破坏当前代。
|
|
131
|
+
*
|
|
132
|
+
* 触发 abort 或 timeout 的参与者收到其原始错误,其余同代等待者统一收到 `BrokenBarrierError`,
|
|
133
|
+
* 这样调用方可以区分“我自己失败了”与“别人导致这一代失效”。
|
|
134
|
+
*/
|
|
135
|
+
private breakGeneration(failingEntry: InternalBarrierQueueEntry, reason: unknown): void {
|
|
136
|
+
if (failingEntry.isSettled === true) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const brokenReason = new BrokenBarrierError(reason)
|
|
141
|
+
this.internalArrivedCount = 0
|
|
142
|
+
this.internalGeneration = this.internalGeneration + 1
|
|
143
|
+
|
|
144
|
+
while (this.queue.isEmpty() === false) {
|
|
145
|
+
const entry = this.queue.shift()
|
|
146
|
+
if (entry === undefined) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (entry.isSettled === true) {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
entry.isSettled = true
|
|
155
|
+
internalCleanupWaitConstraints(entry)
|
|
156
|
+
|
|
157
|
+
if (entry === failingEntry) {
|
|
158
|
+
entry.reject(reason)
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
entry.reject(brokenReason)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 声明当前参与者已经到达同步点,并等待整代完成。
|
|
168
|
+
*
|
|
169
|
+
* 返回值是本次完成时对应的代号;如果本调用导致最后一个参与者到达,则会立即完成整代并同步返回。
|
|
170
|
+
*/
|
|
171
|
+
async signalAndWait(options: BarrierWaitOptions = {}): Promise<number> {
|
|
172
|
+
internalAssertTimeoutOption("Barrier wait", options.timeout)
|
|
173
|
+
|
|
174
|
+
if (options.abortSignal?.aborted === true) {
|
|
175
|
+
throw new CoordinationAbortError("Barrier wait", options.abortSignal.reason)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const currentGeneration = this.internalGeneration
|
|
179
|
+
this.internalArrivedCount = this.internalArrivedCount + 1
|
|
180
|
+
|
|
181
|
+
if (this.internalArrivedCount === this.participantCount) {
|
|
182
|
+
this.completeGeneration(currentGeneration)
|
|
183
|
+
return currentGeneration
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const generation = await new Promise<number>((resolve, reject) => {
|
|
187
|
+
const entry: InternalBarrierQueueEntry = {
|
|
188
|
+
resolve,
|
|
189
|
+
reject,
|
|
190
|
+
isSettled: false,
|
|
191
|
+
isQueued: false,
|
|
192
|
+
previous: undefined,
|
|
193
|
+
next: undefined,
|
|
194
|
+
timeoutId: undefined,
|
|
195
|
+
abortSignal: undefined,
|
|
196
|
+
abortListener: undefined,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
internalBindWaitConstraints(entry, options, {
|
|
200
|
+
abortOperation: "Barrier wait",
|
|
201
|
+
timeoutOperation: "Barrier wait",
|
|
202
|
+
onAbort: error => {
|
|
203
|
+
this.breakGeneration(entry, error)
|
|
204
|
+
},
|
|
205
|
+
onTimeout: error => {
|
|
206
|
+
this.breakGeneration(entry, error)
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
this.queue.enqueue(entry)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return generation
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { InternalWaitQueueEntry } from "./internal/wait-queue.ts"
|
|
2
|
+
import type { InternalWaitConstraintsEntry, InternalWaitConstraintsOptions } from "./internal/wait-constraints.ts"
|
|
3
|
+
|
|
4
|
+
import { InternalWaitQueue } from "./internal/wait-queue.ts"
|
|
5
|
+
import {
|
|
6
|
+
internalAssertTimeoutOption,
|
|
7
|
+
internalBindWaitConstraints,
|
|
8
|
+
internalCleanupWaitConstraints,
|
|
9
|
+
internalThrowIfAborted,
|
|
10
|
+
} from "./internal/wait-constraints.ts"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 断言闭锁初始计数为合法的非负整数。
|
|
14
|
+
*/
|
|
15
|
+
const internalAssertInitialCount = (count: number): void => {
|
|
16
|
+
if (
|
|
17
|
+
Number.isFinite(count) === false
|
|
18
|
+
|| Number.isInteger(count) === false
|
|
19
|
+
|| count < 0
|
|
20
|
+
) {
|
|
21
|
+
throw new RangeError("CountDownLatch initialCount must be a finite integer greater than or equal to 0.")
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 断言单次递减步长为合法的正整数。
|
|
27
|
+
*/
|
|
28
|
+
const internalAssertDelta = (count: number): void => {
|
|
29
|
+
if (
|
|
30
|
+
Number.isFinite(count) === false
|
|
31
|
+
|| Number.isInteger(count) === false
|
|
32
|
+
|| count <= 0
|
|
33
|
+
) {
|
|
34
|
+
throw new RangeError("CountDownLatch count must be a finite integer greater than 0.")
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 表示 `CountDownLatch` 等待者在内部队列中的节点。
|
|
40
|
+
*/
|
|
41
|
+
interface InternalCountDownLatchQueueEntry extends InternalWaitQueueEntry<InternalCountDownLatchQueueEntry>, InternalWaitConstraintsEntry {
|
|
42
|
+
resolve: () => void
|
|
43
|
+
reject: (reason: unknown) => void
|
|
44
|
+
isSettled: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 表示调用 `CountDownLatch.wait()` 时可选的等待约束。
|
|
49
|
+
*/
|
|
50
|
+
export interface CountDownLatchWaitOptions extends InternalWaitConstraintsOptions {
|
|
51
|
+
/**
|
|
52
|
+
* 等待闭锁打开的最长时间,单位为毫秒。
|
|
53
|
+
*/
|
|
54
|
+
timeout?: number | undefined
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 用于主动取消等待的信号。
|
|
58
|
+
*/
|
|
59
|
+
abortSignal?: AbortSignal | undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 表示一个倒计时闭锁。
|
|
64
|
+
*
|
|
65
|
+
* 闭锁在剩余计数降到 0 前保持关闭,所有等待者都会被挂起;
|
|
66
|
+
* 一旦打开,当前及后续等待都会立即通过,且该实例不会再次关闭。
|
|
67
|
+
*/
|
|
68
|
+
export class CountDownLatch {
|
|
69
|
+
private readonly queue: InternalWaitQueue<InternalCountDownLatchQueueEntry>
|
|
70
|
+
|
|
71
|
+
private internalRemainingCount: number
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 使用给定初始计数创建闭锁。
|
|
75
|
+
*/
|
|
76
|
+
constructor(initialCount: number) {
|
|
77
|
+
internalAssertInitialCount(initialCount)
|
|
78
|
+
|
|
79
|
+
this.queue = new InternalWaitQueue<InternalCountDownLatchQueueEntry>()
|
|
80
|
+
this.internalRemainingCount = initialCount
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 判断闭锁是否已经打开。
|
|
85
|
+
*/
|
|
86
|
+
isOpen(): boolean {
|
|
87
|
+
return this.internalRemainingCount === 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 以同步方式检测当前是否可以继续执行而无需等待。
|
|
92
|
+
*/
|
|
93
|
+
tryWait(): boolean {
|
|
94
|
+
return this.internalRemainingCount === 0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 读取距离打开闭锁还差多少次 `countDown()`。
|
|
99
|
+
*/
|
|
100
|
+
getRemainingCount(): number {
|
|
101
|
+
return this.internalRemainingCount
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 读取当前挂起的等待者数量。
|
|
106
|
+
*/
|
|
107
|
+
getPendingCount(): number {
|
|
108
|
+
return this.queue.getCount()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 在闭锁打开时一次性唤醒全部等待者。
|
|
113
|
+
*/
|
|
114
|
+
private resolveAll(): void {
|
|
115
|
+
while (this.queue.isEmpty() === false) {
|
|
116
|
+
const entry = this.queue.shift()
|
|
117
|
+
if (entry === undefined) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (entry.isSettled === true) {
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
entry.isSettled = true
|
|
126
|
+
internalCleanupWaitConstraints(entry)
|
|
127
|
+
entry.resolve()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 以失败状态结束指定等待者,并从队列中摘除。
|
|
133
|
+
*/
|
|
134
|
+
private rejectEntry(entry: InternalCountDownLatchQueueEntry, reason: unknown): void {
|
|
135
|
+
if (entry.isSettled === true) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
entry.isSettled = true
|
|
140
|
+
internalCleanupWaitConstraints(entry)
|
|
141
|
+
this.queue.remove(entry)
|
|
142
|
+
entry.reject(reason)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 将闭锁剩余计数减少指定值。
|
|
147
|
+
*
|
|
148
|
+
* 当剩余计数降到 0 时,所有等待者会立即被放行。
|
|
149
|
+
*/
|
|
150
|
+
countDown(count: number = 1): number {
|
|
151
|
+
internalAssertDelta(count)
|
|
152
|
+
|
|
153
|
+
if (this.internalRemainingCount === 0) {
|
|
154
|
+
return 0
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.internalRemainingCount = Math.max(0, this.internalRemainingCount - count)
|
|
158
|
+
|
|
159
|
+
if (this.internalRemainingCount === 0) {
|
|
160
|
+
this.resolveAll()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return this.internalRemainingCount
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* `countDown()` 的语义化别名,用于强调“到达一次事件”。
|
|
168
|
+
*/
|
|
169
|
+
arrive(count: number = 1): number {
|
|
170
|
+
return this.countDown(count)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 等待直到闭锁打开。
|
|
175
|
+
*/
|
|
176
|
+
async wait(options: CountDownLatchWaitOptions = {}): Promise<void> {
|
|
177
|
+
internalAssertTimeoutOption("CountDownLatch wait", options.timeout)
|
|
178
|
+
|
|
179
|
+
internalThrowIfAborted("CountDownLatch wait", options.abortSignal)
|
|
180
|
+
|
|
181
|
+
if (this.internalRemainingCount === 0) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await new Promise<void>((resolve, reject) => {
|
|
186
|
+
const entry: InternalCountDownLatchQueueEntry = {
|
|
187
|
+
resolve,
|
|
188
|
+
reject,
|
|
189
|
+
isSettled: false,
|
|
190
|
+
isQueued: false,
|
|
191
|
+
previous: undefined,
|
|
192
|
+
next: undefined,
|
|
193
|
+
timeoutId: undefined,
|
|
194
|
+
abortSignal: undefined,
|
|
195
|
+
abortListener: undefined,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
internalBindWaitConstraints(entry, options, {
|
|
199
|
+
abortOperation: "CountDownLatch wait",
|
|
200
|
+
timeoutOperation: "CountDownLatch wait",
|
|
201
|
+
onAbort: error => {
|
|
202
|
+
this.rejectEntry(entry, error)
|
|
203
|
+
},
|
|
204
|
+
onTimeout: error => {
|
|
205
|
+
this.rejectEntry(entry, error)
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
this.queue.enqueue(entry)
|
|
209
|
+
|
|
210
|
+
if (this.internalRemainingCount === 0) {
|
|
211
|
+
this.resolveAll()
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把可读的失败原因拼接到错误消息中。
|
|
3
|
+
*
|
|
4
|
+
* 这里尽量保留基础类型与 `Error.message`,避免把复杂对象直接序列化成噪音文本。
|
|
5
|
+
*/
|
|
6
|
+
const internalAppendReasonMessage = (message: string, reason: unknown): string => {
|
|
7
|
+
if (reason === undefined) {
|
|
8
|
+
return message
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (reason instanceof Error && reason.message !== "") {
|
|
12
|
+
return `${message} ${reason.message}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
typeof reason === "string"
|
|
17
|
+
|| typeof reason === "number"
|
|
18
|
+
|| typeof reason === "boolean"
|
|
19
|
+
|| typeof reason === "bigint"
|
|
20
|
+
|| typeof reason === "symbol"
|
|
21
|
+
) {
|
|
22
|
+
return `${message} ${String(reason)}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return message
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 表示协调原语等待过程中的中止错误。
|
|
30
|
+
*/
|
|
31
|
+
export class CoordinationAbortError extends Error {
|
|
32
|
+
/**
|
|
33
|
+
* 触发中止的操作名称。
|
|
34
|
+
*/
|
|
35
|
+
readonly operation: string
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 中止信号携带的原始原因。
|
|
39
|
+
*/
|
|
40
|
+
readonly reason: unknown
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 创建一个表示等待被主动中止的错误。
|
|
44
|
+
*/
|
|
45
|
+
constructor(operation: string, reason: unknown) {
|
|
46
|
+
super(internalAppendReasonMessage(`${operation} aborted.`, reason))
|
|
47
|
+
this.name = "CoordinationAbortError"
|
|
48
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
49
|
+
this.operation = operation
|
|
50
|
+
this.reason = reason
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 表示协调原语等待过程中的超时错误。
|
|
56
|
+
*/
|
|
57
|
+
export class CoordinationTimeoutError extends Error {
|
|
58
|
+
/**
|
|
59
|
+
* 发生超时的操作名称。
|
|
60
|
+
*/
|
|
61
|
+
readonly operation: string
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 触发超时时等待的毫秒数。
|
|
65
|
+
*/
|
|
66
|
+
readonly timeout: number
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 创建一个表示等待超时的错误。
|
|
70
|
+
*/
|
|
71
|
+
constructor(operation: string, timeout: number) {
|
|
72
|
+
super(`${operation} timeout after ${timeout}ms.`)
|
|
73
|
+
this.name = "CoordinationTimeoutError"
|
|
74
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
75
|
+
this.operation = operation
|
|
76
|
+
this.timeout = timeout
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 表示栅栏某一代被参与者破坏后的错误。
|
|
82
|
+
*/
|
|
83
|
+
export class BrokenBarrierError extends Error {
|
|
84
|
+
/**
|
|
85
|
+
* 导致当前栅栏代失效的原始原因。
|
|
86
|
+
*/
|
|
87
|
+
readonly reason: unknown
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 创建一个表示“当前代已被其他参与者破坏”的错误。
|
|
91
|
+
*/
|
|
92
|
+
constructor(reason: unknown) {
|
|
93
|
+
super(internalAppendReasonMessage("Barrier generation was broken by another participant.", reason))
|
|
94
|
+
this.name = "BrokenBarrierError"
|
|
95
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
96
|
+
this.reason = reason
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./errors.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 栅栏与闭锁类原语。
|
|
5
|
+
*/
|
|
6
|
+
export * from "./barrier.ts"
|
|
7
|
+
export * from "./count-down-latch.ts"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* permit 与各类锁/信号量原语。
|
|
11
|
+
*/
|
|
12
|
+
export * from "./permit.ts"
|
|
13
|
+
export * from "./mutex.ts"
|
|
14
|
+
export * from "./semaphore.ts"
|
|
15
|
+
export * from "./keyed-lock.ts"
|
|
16
|
+
export * from "./read-write-lock.ts"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CoordinationAbortError, CoordinationTimeoutError } from "../errors.ts"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 表示等待节点上与 timeout / abort 绑定相关的内部状态。
|
|
5
|
+
*/
|
|
6
|
+
export interface InternalWaitConstraintsEntry {
|
|
7
|
+
timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
8
|
+
abortSignal: AbortSignal | undefined
|
|
9
|
+
abortListener: (() => void) | undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 表示等待约束的输入参数。
|
|
14
|
+
*/
|
|
15
|
+
export interface InternalWaitConstraintsOptions {
|
|
16
|
+
timeout?: number | undefined
|
|
17
|
+
abortSignal?: AbortSignal | undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 表示将等待约束绑定到某个节点时需要提供的回调集合。
|
|
22
|
+
*/
|
|
23
|
+
export interface InternalWaitConstraintBindings {
|
|
24
|
+
abortOperation: string
|
|
25
|
+
timeoutOperation: string
|
|
26
|
+
onAbort: (error: CoordinationAbortError) => void
|
|
27
|
+
onTimeout: (error: CoordinationTimeoutError) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 断言 timeout 参数是合法的非负有限数值。
|
|
32
|
+
*/
|
|
33
|
+
export const internalAssertTimeoutOption = (operation: string, timeout: number | undefined): void => {
|
|
34
|
+
if (timeout === undefined) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (Number.isFinite(timeout) === false || timeout < 0) {
|
|
39
|
+
throw new RangeError(`${operation} timeout must be a finite number greater than or equal to 0.`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 如果传入的 `AbortSignal` 已经处于中止状态,则立即抛出中止错误。
|
|
45
|
+
*/
|
|
46
|
+
export const internalThrowIfAborted = (operation: string, abortSignal: AbortSignal | undefined): void => {
|
|
47
|
+
if (abortSignal?.aborted === true) {
|
|
48
|
+
throw new CoordinationAbortError(operation, abortSignal.reason)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 清理已经绑定到等待节点上的超时器与中止监听器。
|
|
54
|
+
*
|
|
55
|
+
* 该函数应当在节点成功结算或失败结算时都被调用,以避免悬挂的定时器与事件监听器泄漏。
|
|
56
|
+
*/
|
|
57
|
+
export const internalCleanupWaitConstraints = (entry: InternalWaitConstraintsEntry): void => {
|
|
58
|
+
if (entry.timeoutId !== undefined) {
|
|
59
|
+
clearTimeout(entry.timeoutId)
|
|
60
|
+
entry.timeoutId = undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (entry.abortSignal !== undefined && entry.abortListener !== undefined) {
|
|
64
|
+
entry.abortSignal.removeEventListener("abort", entry.abortListener)
|
|
65
|
+
entry.abortSignal = undefined
|
|
66
|
+
entry.abortListener = undefined
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 把 timeout 与 abort 约束绑定到某个等待节点。
|
|
72
|
+
*
|
|
73
|
+
* 绑定本身只负责注册外部约束,真正如何让节点失败由调用方通过 `bindings` 中的回调决定。
|
|
74
|
+
*/
|
|
75
|
+
export const internalBindWaitConstraints = (
|
|
76
|
+
entry: InternalWaitConstraintsEntry,
|
|
77
|
+
options: InternalWaitConstraintsOptions,
|
|
78
|
+
bindings: InternalWaitConstraintBindings,
|
|
79
|
+
): void => {
|
|
80
|
+
const { abortSignal, timeout } = options
|
|
81
|
+
|
|
82
|
+
if (abortSignal !== undefined) {
|
|
83
|
+
entry.abortSignal = abortSignal
|
|
84
|
+
entry.abortListener = (): void => {
|
|
85
|
+
bindings.onAbort(new CoordinationAbortError(bindings.abortOperation, abortSignal.reason))
|
|
86
|
+
}
|
|
87
|
+
abortSignal.addEventListener("abort", entry.abortListener, { once: true })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (timeout !== undefined) {
|
|
91
|
+
entry.timeoutId = setTimeout(() => {
|
|
92
|
+
bindings.onTimeout(new CoordinationTimeoutError(bindings.timeoutOperation, timeout))
|
|
93
|
+
}, timeout)
|
|
94
|
+
}
|
|
95
|
+
}
|