@planet-matrix/mobius-model 0.5.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 +24 -0
- package/README.md +123 -36
- package/dist/index.js +45 -4
- package/dist/index.js.map +183 -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 -118
- package/src/basic/function.ts +81 -62
- package/src/basic/is.ts +152 -71
- 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 +66 -79
- package/src/encoding/base64.ts +13 -4
- 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 +16 -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 +55 -86
- package/src/random/index.ts +1 -1
- 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/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/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
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import type { InternalWaitQueueEntry } from "./internal/wait-queue.ts"
|
|
2
|
+
import type { ModePermitDetails } from "./permit.ts"
|
|
3
|
+
import type { InternalWaitConstraintsEntry, InternalWaitConstraintsOptions } from "./internal/wait-constraints.ts"
|
|
4
|
+
|
|
5
|
+
import { InternalWaitQueue } from "./internal/wait-queue.ts"
|
|
6
|
+
import { Permit } from "./permit.ts"
|
|
7
|
+
import {
|
|
8
|
+
internalAssertTimeoutOption,
|
|
9
|
+
internalBindWaitConstraints,
|
|
10
|
+
internalCleanupWaitConstraints,
|
|
11
|
+
internalThrowIfAborted,
|
|
12
|
+
} from "./internal/wait-constraints.ts"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 表示读写锁内部使用的两种获取模式。
|
|
16
|
+
*/
|
|
17
|
+
type InternalReadWriteLockAcquireMode = "read" | "write"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 表示读写锁对外暴露的持锁模式。
|
|
21
|
+
*/
|
|
22
|
+
export type ReadWriteLockMode = InternalReadWriteLockAcquireMode
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 表示 `ReadWriteLock` 成功获取后的占用句柄。
|
|
26
|
+
*/
|
|
27
|
+
export type ReadWriteLockPermit = Permit<ModePermitDetails<"read-write-lock", ReadWriteLockMode>>
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 表示读写锁内部等待队列中的节点。
|
|
31
|
+
*/
|
|
32
|
+
interface InternalReadWriteLockQueueEntry extends InternalWaitQueueEntry<InternalReadWriteLockQueueEntry>, InternalWaitConstraintsEntry {
|
|
33
|
+
mode: InternalReadWriteLockAcquireMode
|
|
34
|
+
resolve: (permit: ReadWriteLockPermit) => void
|
|
35
|
+
reject: (reason: unknown) => void
|
|
36
|
+
isSettled: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 表示调用读锁或写锁获取方法时可选的等待约束。
|
|
41
|
+
*/
|
|
42
|
+
export interface ReadWriteLockAcquireOptions extends InternalWaitConstraintsOptions {
|
|
43
|
+
/**
|
|
44
|
+
* 单次等待允许持续的最长时间,单位为毫秒。
|
|
45
|
+
*/
|
|
46
|
+
timeout?: number | undefined
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 用于主动中止本次等待的信号。
|
|
50
|
+
*/
|
|
51
|
+
abortSignal?: AbortSignal | undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const INTERNAL_READ_WRITE_LOCK_DUPLICATE_RELEASE_MESSAGE = "ReadWriteLock permit release was called more than once."
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 表示读写锁在检测到重复释放时使用的处理函数。
|
|
58
|
+
*/
|
|
59
|
+
export type ReadWriteLockDuplicateReleaseHandler = (message: string) => void
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 表示构造 `ReadWriteLock` 时支持的配置。
|
|
63
|
+
*/
|
|
64
|
+
export interface ReadWriteLockOptions {
|
|
65
|
+
/**
|
|
66
|
+
* 重复调用同一个 permit 的 `release()` 时触发的可选处理器。
|
|
67
|
+
*/
|
|
68
|
+
onDuplicateRelease?: ReadWriteLockDuplicateReleaseHandler | undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 表示一个读写锁。
|
|
73
|
+
*
|
|
74
|
+
* 读锁允许多个读取者并发进入,写锁则要求完全独占。
|
|
75
|
+
* 当前实现采用 FIFO 等待队列,并保证一旦队头出现写者,后续读者不会越过该写者插队。
|
|
76
|
+
*/
|
|
77
|
+
export class ReadWriteLock {
|
|
78
|
+
private readonly queue: InternalWaitQueue<InternalReadWriteLockQueueEntry>
|
|
79
|
+
|
|
80
|
+
private internalPendingReaderCount: number
|
|
81
|
+
|
|
82
|
+
private internalPendingWriterCount: number
|
|
83
|
+
|
|
84
|
+
private internalActiveReaderCount: number
|
|
85
|
+
|
|
86
|
+
private internalHasActiveWriter: boolean
|
|
87
|
+
|
|
88
|
+
private readonly onDuplicateRelease: ReadWriteLockDuplicateReleaseHandler | undefined
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 创建一个读写锁实例。
|
|
92
|
+
*/
|
|
93
|
+
constructor(options: ReadWriteLockOptions = {}) {
|
|
94
|
+
this.queue = new InternalWaitQueue<InternalReadWriteLockQueueEntry>()
|
|
95
|
+
this.internalPendingReaderCount = 0
|
|
96
|
+
this.internalPendingWriterCount = 0
|
|
97
|
+
this.internalActiveReaderCount = 0
|
|
98
|
+
this.internalHasActiveWriter = false
|
|
99
|
+
this.onDuplicateRelease = options.onDuplicateRelease
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 判断当前是否至少存在一个活动中的读者。
|
|
104
|
+
*/
|
|
105
|
+
isReadLocked(): boolean {
|
|
106
|
+
return this.internalActiveReaderCount !== 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 判断当前是否存在活动中的写者。
|
|
111
|
+
*/
|
|
112
|
+
isWriteLocked(): boolean {
|
|
113
|
+
return this.internalHasActiveWriter
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 判断当前读写锁是否被任意模式占用。
|
|
118
|
+
*/
|
|
119
|
+
isLocked(): boolean {
|
|
120
|
+
return this.internalHasActiveWriter || this.internalActiveReaderCount !== 0
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 读取等待队列中的总任务数。
|
|
125
|
+
*/
|
|
126
|
+
getPendingCount(): number {
|
|
127
|
+
return this.queue.getCount()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 读取等待中的读者数量。
|
|
132
|
+
*/
|
|
133
|
+
getPendingReaderCount(): number {
|
|
134
|
+
return this.internalPendingReaderCount
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 读取等待中的写者数量。
|
|
139
|
+
*/
|
|
140
|
+
getPendingWriterCount(): number {
|
|
141
|
+
return this.internalPendingWriterCount
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 读取当前已进入临界区的读者数量。
|
|
146
|
+
*/
|
|
147
|
+
getActiveReaderCount(): number {
|
|
148
|
+
return this.internalActiveReaderCount
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 把等待节点加入队列,并同步更新按模式拆分的统计计数。
|
|
153
|
+
*/
|
|
154
|
+
private enqueueEntry(entry: InternalReadWriteLockQueueEntry): void {
|
|
155
|
+
this.queue.enqueue(entry)
|
|
156
|
+
|
|
157
|
+
if (entry.mode === "read") {
|
|
158
|
+
this.internalPendingReaderCount = this.internalPendingReaderCount + 1
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this.internalPendingWriterCount = this.internalPendingWriterCount + 1
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 从等待队列中移除指定节点,并同步回收对应的统计计数。
|
|
167
|
+
*/
|
|
168
|
+
private removeEntry(entry: InternalReadWriteLockQueueEntry): void {
|
|
169
|
+
if (entry.isQueued === false) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.queue.remove(entry)
|
|
174
|
+
|
|
175
|
+
if (entry.mode === "read") {
|
|
176
|
+
this.internalPendingReaderCount = this.internalPendingReaderCount - 1
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this.internalPendingWriterCount = this.internalPendingWriterCount - 1
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 构造一个读锁 permit。
|
|
185
|
+
*/
|
|
186
|
+
private buildReadPermit(): ReadWriteLockPermit {
|
|
187
|
+
return new Permit<ModePermitDetails<"read-write-lock", ReadWriteLockMode>>({
|
|
188
|
+
details: { coordination: "read-write-lock", mode: "read" },
|
|
189
|
+
duplicateReleaseMessage: INTERNAL_READ_WRITE_LOCK_DUPLICATE_RELEASE_MESSAGE,
|
|
190
|
+
onDuplicateRelease: this.onDuplicateRelease,
|
|
191
|
+
onRelease: (): void => {
|
|
192
|
+
this.internalActiveReaderCount = this.internalActiveReaderCount - 1
|
|
193
|
+
this.dispatch()
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 构造一个写锁 permit。
|
|
200
|
+
*/
|
|
201
|
+
private buildWritePermit(): ReadWriteLockPermit {
|
|
202
|
+
return new Permit<ModePermitDetails<"read-write-lock", ReadWriteLockMode>>({
|
|
203
|
+
details: { coordination: "read-write-lock", mode: "write" },
|
|
204
|
+
duplicateReleaseMessage: INTERNAL_READ_WRITE_LOCK_DUPLICATE_RELEASE_MESSAGE,
|
|
205
|
+
onDuplicateRelease: this.onDuplicateRelease,
|
|
206
|
+
onRelease: (): void => {
|
|
207
|
+
this.internalHasActiveWriter = false
|
|
208
|
+
this.dispatch()
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 尝试按 FIFO 规则调度队头等待者。
|
|
215
|
+
*
|
|
216
|
+
* 规则如下:
|
|
217
|
+
* 1. 若已有活动写者,则任何人都不能继续进入。
|
|
218
|
+
* 2. 若队头是写者,则必须等到所有活动读者结束后才可授予写锁。
|
|
219
|
+
* 3. 若队头是读者,则会批量放行连续的读者,直到遇到写者或队列结束。
|
|
220
|
+
*/
|
|
221
|
+
private dispatch(): void {
|
|
222
|
+
if (this.internalHasActiveWriter === true) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
while (true) {
|
|
227
|
+
const head = this.queue.peek()
|
|
228
|
+
if (head === undefined) {
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (head.mode === "write") {
|
|
233
|
+
if (this.internalActiveReaderCount !== 0) {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const writerEntry = this.queue.shift()
|
|
238
|
+
if (writerEntry === undefined) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.internalPendingWriterCount = this.internalPendingWriterCount - 1
|
|
243
|
+
|
|
244
|
+
if (writerEntry.isSettled === true) {
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this.internalHasActiveWriter = true
|
|
249
|
+
writerEntry.isSettled = true
|
|
250
|
+
internalCleanupWaitConstraints(writerEntry)
|
|
251
|
+
writerEntry.resolve(this.buildWritePermit())
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
while (true) {
|
|
256
|
+
const readerHead = this.queue.peek()
|
|
257
|
+
if (readerHead === undefined || readerHead.mode !== "read") {
|
|
258
|
+
break
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const readerEntry = this.queue.shift()
|
|
262
|
+
if (readerEntry === undefined) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.internalPendingReaderCount = this.internalPendingReaderCount - 1
|
|
267
|
+
|
|
268
|
+
if (readerEntry.isSettled === true) {
|
|
269
|
+
continue
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this.internalActiveReaderCount = this.internalActiveReaderCount + 1
|
|
273
|
+
readerEntry.isSettled = true
|
|
274
|
+
internalCleanupWaitConstraints(readerEntry)
|
|
275
|
+
readerEntry.resolve(this.buildReadPermit())
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (this.internalActiveReaderCount !== 0) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 以失败状态结束一个等待节点,并尝试继续调度后继等待者。
|
|
286
|
+
*/
|
|
287
|
+
private rejectEntry(entry: InternalReadWriteLockQueueEntry, reason: unknown): void {
|
|
288
|
+
if (entry.isSettled === true) {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
entry.isSettled = true
|
|
293
|
+
internalCleanupWaitConstraints(entry)
|
|
294
|
+
this.removeEntry(entry)
|
|
295
|
+
entry.reject(reason)
|
|
296
|
+
this.dispatch()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 尝试立即获取读锁;若当前已有写者或队列中存在排队任务,则返回 `undefined`。
|
|
301
|
+
*/
|
|
302
|
+
tryAcquireRead(): ReadWriteLockPermit | undefined {
|
|
303
|
+
if (this.internalHasActiveWriter === true || this.queue.isEmpty() === false) {
|
|
304
|
+
return undefined
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.internalActiveReaderCount = this.internalActiveReaderCount + 1
|
|
308
|
+
return this.buildReadPermit()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 尝试立即获取写锁;只有完全空闲时才会成功。
|
|
313
|
+
*/
|
|
314
|
+
tryAcquireWrite(): ReadWriteLockPermit | undefined {
|
|
315
|
+
if (
|
|
316
|
+
this.internalHasActiveWriter === true
|
|
317
|
+
|| this.internalActiveReaderCount !== 0
|
|
318
|
+
|| this.queue.isEmpty() === false
|
|
319
|
+
) {
|
|
320
|
+
return undefined
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this.internalHasActiveWriter = true
|
|
324
|
+
return this.buildWritePermit()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 等待直到成功获取读锁。
|
|
329
|
+
*/
|
|
330
|
+
async acquireRead(options: ReadWriteLockAcquireOptions = {}): Promise<ReadWriteLockPermit> {
|
|
331
|
+
internalAssertTimeoutOption("ReadWriteLock read acquire", options.timeout)
|
|
332
|
+
|
|
333
|
+
internalThrowIfAborted("ReadWriteLock read acquire", options.abortSignal)
|
|
334
|
+
|
|
335
|
+
const immediatePermit = this.tryAcquireRead()
|
|
336
|
+
if (immediatePermit !== undefined) {
|
|
337
|
+
return immediatePermit
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const permit = await new Promise<ReadWriteLockPermit>((resolve, reject) => {
|
|
341
|
+
const entry: InternalReadWriteLockQueueEntry = {
|
|
342
|
+
mode: "read",
|
|
343
|
+
resolve,
|
|
344
|
+
reject,
|
|
345
|
+
isSettled: false,
|
|
346
|
+
isQueued: false,
|
|
347
|
+
previous: undefined,
|
|
348
|
+
next: undefined,
|
|
349
|
+
timeoutId: undefined,
|
|
350
|
+
abortSignal: undefined,
|
|
351
|
+
abortListener: undefined,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
internalBindWaitConstraints(entry, options, {
|
|
355
|
+
abortOperation: "ReadWriteLock read acquire",
|
|
356
|
+
timeoutOperation: "ReadWriteLock read acquire",
|
|
357
|
+
onAbort: error => {
|
|
358
|
+
this.rejectEntry(entry, error)
|
|
359
|
+
},
|
|
360
|
+
onTimeout: error => {
|
|
361
|
+
this.rejectEntry(entry, error)
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
this.enqueueEntry(entry)
|
|
365
|
+
this.dispatch()
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
return permit
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 等待直到成功获取写锁。
|
|
373
|
+
*/
|
|
374
|
+
async acquireWrite(options: ReadWriteLockAcquireOptions = {}): Promise<ReadWriteLockPermit> {
|
|
375
|
+
internalAssertTimeoutOption("ReadWriteLock write acquire", options.timeout)
|
|
376
|
+
|
|
377
|
+
internalThrowIfAborted("ReadWriteLock write acquire", options.abortSignal)
|
|
378
|
+
|
|
379
|
+
const immediatePermit = this.tryAcquireWrite()
|
|
380
|
+
if (immediatePermit !== undefined) {
|
|
381
|
+
return immediatePermit
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const permit = await new Promise<ReadWriteLockPermit>((resolve, reject) => {
|
|
385
|
+
const entry: InternalReadWriteLockQueueEntry = {
|
|
386
|
+
mode: "write",
|
|
387
|
+
resolve,
|
|
388
|
+
reject,
|
|
389
|
+
isSettled: false,
|
|
390
|
+
isQueued: false,
|
|
391
|
+
previous: undefined,
|
|
392
|
+
next: undefined,
|
|
393
|
+
timeoutId: undefined,
|
|
394
|
+
abortSignal: undefined,
|
|
395
|
+
abortListener: undefined,
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
internalBindWaitConstraints(entry, options, {
|
|
399
|
+
abortOperation: "ReadWriteLock write acquire",
|
|
400
|
+
timeoutOperation: "ReadWriteLock write acquire",
|
|
401
|
+
onAbort: error => {
|
|
402
|
+
this.rejectEntry(entry, error)
|
|
403
|
+
},
|
|
404
|
+
onTimeout: error => {
|
|
405
|
+
this.rejectEntry(entry, error)
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
this.enqueueEntry(entry)
|
|
409
|
+
this.dispatch()
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
return permit
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 在读锁保护下执行回调。
|
|
417
|
+
*/
|
|
418
|
+
async runExclusiveRead<T>(callback: () => Promise<T>, options?: ReadWriteLockAcquireOptions): Promise<T>
|
|
419
|
+
async runExclusiveRead<T>(callback: () => T, options?: ReadWriteLockAcquireOptions): Promise<T>
|
|
420
|
+
async runExclusiveRead<T>(callback: () => Promise<T> | T, options: ReadWriteLockAcquireOptions = {}): Promise<T> {
|
|
421
|
+
const permit = await this.acquireRead(options)
|
|
422
|
+
try {
|
|
423
|
+
return await callback()
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
permit.release()
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* 在写锁保护下执行回调。
|
|
432
|
+
*/
|
|
433
|
+
async runExclusiveWrite<T>(callback: () => Promise<T>, options?: ReadWriteLockAcquireOptions): Promise<T>
|
|
434
|
+
async runExclusiveWrite<T>(callback: () => T, options?: ReadWriteLockAcquireOptions): Promise<T>
|
|
435
|
+
async runExclusiveWrite<T>(callback: () => Promise<T> | T, options: ReadWriteLockAcquireOptions = {}): Promise<T> {
|
|
436
|
+
const permit = await this.acquireWrite(options)
|
|
437
|
+
try {
|
|
438
|
+
return await callback()
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
permit.release()
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { CoordinationPermitDetails } from "./permit.ts"
|
|
2
|
+
import type { InternalWaitQueueEntry } from "./internal/wait-queue.ts"
|
|
3
|
+
import type { InternalWaitConstraintsEntry, InternalWaitConstraintsOptions } from "./internal/wait-constraints.ts"
|
|
4
|
+
|
|
5
|
+
import { InternalWaitQueue } from "./internal/wait-queue.ts"
|
|
6
|
+
import { Permit } from "./permit.ts"
|
|
7
|
+
import {
|
|
8
|
+
internalAssertTimeoutOption,
|
|
9
|
+
internalBindWaitConstraints,
|
|
10
|
+
internalCleanupWaitConstraints,
|
|
11
|
+
internalThrowIfAborted,
|
|
12
|
+
} from "./internal/wait-constraints.ts"
|
|
13
|
+
|
|
14
|
+
const internalAssertMaxConcurrency = (maxConcurrency: number): void => {
|
|
15
|
+
if (
|
|
16
|
+
Number.isFinite(maxConcurrency) === false
|
|
17
|
+
|| Number.isInteger(maxConcurrency) === false
|
|
18
|
+
|| maxConcurrency <= 0
|
|
19
|
+
) {
|
|
20
|
+
throw new RangeError("Semaphore maxConcurrency must be a finite integer greater than 0.")
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 表示 `Semaphore` 成功获取后的占用句柄。
|
|
26
|
+
*/
|
|
27
|
+
export type SemaphorePermit = Permit<CoordinationPermitDetails<"semaphore">>
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 表示信号量等待队列中的单个节点。
|
|
31
|
+
*/
|
|
32
|
+
interface InternalSemaphoreQueueEntry extends InternalWaitQueueEntry<InternalSemaphoreQueueEntry>, InternalWaitConstraintsEntry {
|
|
33
|
+
resolve: (permit: SemaphorePermit) => void
|
|
34
|
+
reject: (reason: unknown) => void
|
|
35
|
+
isSettled: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 表示调用 `Semaphore.acquire()` 时可选的等待约束。
|
|
40
|
+
*/
|
|
41
|
+
export interface SemaphoreAcquireOptions extends InternalWaitConstraintsOptions {
|
|
42
|
+
/**
|
|
43
|
+
* 单次等待允许持续的最长时间,单位为毫秒。
|
|
44
|
+
*/
|
|
45
|
+
timeout?: number | undefined
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 用于主动中止本次等待的信号。
|
|
49
|
+
*/
|
|
50
|
+
abortSignal?: AbortSignal | undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const INTERNAL_SEMAPHORE_DUPLICATE_RELEASE_MESSAGE = "Semaphore permit release was called more than once."
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 表示信号量在检测到重复释放时使用的处理函数。
|
|
57
|
+
*/
|
|
58
|
+
export type SemaphoreDuplicateReleaseHandler = (message: string) => void
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 表示构造 `Semaphore` 时支持的配置。
|
|
62
|
+
*/
|
|
63
|
+
export interface SemaphoreOptions {
|
|
64
|
+
/**
|
|
65
|
+
* 重复调用同一个 permit 的 `release()` 时触发的可选处理器。
|
|
66
|
+
*/
|
|
67
|
+
onDuplicateRelease?: SemaphoreDuplicateReleaseHandler | undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 表示用于限制并发进入数量的信号量。
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```
|
|
75
|
+
* const semaphore = new Semaphore(2)
|
|
76
|
+
* let activeCount = 0
|
|
77
|
+
* let peakCount = 0
|
|
78
|
+
*
|
|
79
|
+
* await Promise.all([
|
|
80
|
+
* semaphore.runExclusive(async () => {
|
|
81
|
+
* activeCount = activeCount + 1
|
|
82
|
+
* peakCount = Math.max(peakCount, activeCount)
|
|
83
|
+
* await Promise.resolve()
|
|
84
|
+
* activeCount = activeCount - 1
|
|
85
|
+
* }),
|
|
86
|
+
* semaphore.runExclusive(async () => {
|
|
87
|
+
* activeCount = activeCount + 1
|
|
88
|
+
* peakCount = Math.max(peakCount, activeCount)
|
|
89
|
+
* await Promise.resolve()
|
|
90
|
+
* activeCount = activeCount - 1
|
|
91
|
+
* }),
|
|
92
|
+
* semaphore.runExclusive(async () => {
|
|
93
|
+
* activeCount = activeCount + 1
|
|
94
|
+
* peakCount = Math.max(peakCount, activeCount)
|
|
95
|
+
* await Promise.resolve()
|
|
96
|
+
* activeCount = activeCount - 1
|
|
97
|
+
* }),
|
|
98
|
+
* ])
|
|
99
|
+
*
|
|
100
|
+
* // Expect: 2
|
|
101
|
+
* const example1 = peakCount
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export class Semaphore {
|
|
105
|
+
private readonly queue: InternalWaitQueue<InternalSemaphoreQueueEntry>
|
|
106
|
+
|
|
107
|
+
private internalActiveCount: number
|
|
108
|
+
|
|
109
|
+
private readonly maxConcurrency: number
|
|
110
|
+
|
|
111
|
+
private readonly onDuplicateRelease: SemaphoreDuplicateReleaseHandler | undefined
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 使用给定最大并发数创建信号量。
|
|
115
|
+
*/
|
|
116
|
+
constructor(maxConcurrency: number, options: SemaphoreOptions = {}) {
|
|
117
|
+
internalAssertMaxConcurrency(maxConcurrency)
|
|
118
|
+
|
|
119
|
+
this.queue = new InternalWaitQueue<InternalSemaphoreQueueEntry>()
|
|
120
|
+
this.internalActiveCount = 0
|
|
121
|
+
this.maxConcurrency = maxConcurrency
|
|
122
|
+
this.onDuplicateRelease = options.onDuplicateRelease
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 判断当前是否已经达到并发上限。
|
|
127
|
+
*/
|
|
128
|
+
isSaturated(): boolean {
|
|
129
|
+
return this.internalActiveCount >= this.maxConcurrency
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 读取当前正在占用中的数量。
|
|
134
|
+
*/
|
|
135
|
+
getActiveCount(): number {
|
|
136
|
+
return this.internalActiveCount
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 读取当前还剩多少可立即分配的许可。
|
|
141
|
+
*/
|
|
142
|
+
getAvailableCount(): number {
|
|
143
|
+
return this.maxConcurrency - this.internalActiveCount
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 读取当前等待中的任务数量。
|
|
148
|
+
*/
|
|
149
|
+
getPendingCount(): number {
|
|
150
|
+
return this.queue.getCount()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 读取信号量的最大并发容量。
|
|
155
|
+
*/
|
|
156
|
+
getMaxConcurrency(): number {
|
|
157
|
+
return this.maxConcurrency
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 在仍有可用容量时,按 FIFO 顺序持续放行等待者。
|
|
162
|
+
*/
|
|
163
|
+
private dispatch(): void {
|
|
164
|
+
while (this.internalActiveCount < this.maxConcurrency && this.queue.isEmpty() === false) {
|
|
165
|
+
const nextEntry = this.queue.shift()
|
|
166
|
+
if (nextEntry === undefined) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (nextEntry.isSettled === true) {
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.internalActiveCount = this.internalActiveCount + 1
|
|
175
|
+
nextEntry.isSettled = true
|
|
176
|
+
internalCleanupWaitConstraints(nextEntry)
|
|
177
|
+
nextEntry.resolve(this.buildPermit())
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 构造一个信号量 permit,并在释放时归还一个并发槽位。
|
|
183
|
+
*/
|
|
184
|
+
private buildPermit(): SemaphorePermit {
|
|
185
|
+
return new Permit<CoordinationPermitDetails<"semaphore">>({
|
|
186
|
+
details: { coordination: "semaphore" },
|
|
187
|
+
duplicateReleaseMessage: INTERNAL_SEMAPHORE_DUPLICATE_RELEASE_MESSAGE,
|
|
188
|
+
onDuplicateRelease: this.onDuplicateRelease,
|
|
189
|
+
onRelease: (): void => {
|
|
190
|
+
this.internalActiveCount = this.internalActiveCount - 1
|
|
191
|
+
this.dispatch()
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 以失败状态结束一个等待节点,并尝试继续调度后继等待者。
|
|
198
|
+
*/
|
|
199
|
+
private rejectEntry(entry: InternalSemaphoreQueueEntry, reason: unknown): void {
|
|
200
|
+
if (entry.isSettled === true) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
entry.isSettled = true
|
|
205
|
+
internalCleanupWaitConstraints(entry)
|
|
206
|
+
this.queue.remove(entry)
|
|
207
|
+
entry.reject(reason)
|
|
208
|
+
this.dispatch()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 尝试立即获取一个许可;若没有剩余容量或前面已有排队任务,则返回 `undefined`。
|
|
213
|
+
*/
|
|
214
|
+
tryAcquire(): SemaphorePermit | undefined {
|
|
215
|
+
if (this.internalActiveCount >= this.maxConcurrency || this.queue.isEmpty() === false) {
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.internalActiveCount = this.internalActiveCount + 1
|
|
220
|
+
return this.buildPermit()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 等待直到成功获取一个许可。
|
|
225
|
+
*/
|
|
226
|
+
async acquire(options: SemaphoreAcquireOptions = {}): Promise<SemaphorePermit> {
|
|
227
|
+
internalAssertTimeoutOption("Semaphore acquire", options.timeout)
|
|
228
|
+
|
|
229
|
+
internalThrowIfAborted("Semaphore acquire", options.abortSignal)
|
|
230
|
+
|
|
231
|
+
const immediatePermit = this.tryAcquire()
|
|
232
|
+
if (immediatePermit !== undefined) {
|
|
233
|
+
return immediatePermit
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const permit = await new Promise<SemaphorePermit>((resolve, reject) => {
|
|
237
|
+
const entry: InternalSemaphoreQueueEntry = {
|
|
238
|
+
resolve,
|
|
239
|
+
reject,
|
|
240
|
+
isSettled: false,
|
|
241
|
+
isQueued: false,
|
|
242
|
+
previous: undefined,
|
|
243
|
+
next: undefined,
|
|
244
|
+
timeoutId: undefined,
|
|
245
|
+
abortSignal: undefined,
|
|
246
|
+
abortListener: undefined,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
internalBindWaitConstraints(entry, options, {
|
|
250
|
+
abortOperation: "Semaphore acquire",
|
|
251
|
+
timeoutOperation: "Semaphore acquire",
|
|
252
|
+
onAbort: error => {
|
|
253
|
+
this.rejectEntry(entry, error)
|
|
254
|
+
},
|
|
255
|
+
onTimeout: error => {
|
|
256
|
+
this.rejectEntry(entry, error)
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
this.queue.enqueue(entry)
|
|
260
|
+
this.dispatch()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
return permit
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 在信号量许可保护下执行回调。
|
|
268
|
+
*/
|
|
269
|
+
async runExclusive<T>(callback: () => Promise<T>, options?: SemaphoreAcquireOptions): Promise<T>
|
|
270
|
+
async runExclusive<T>(callback: () => T, options?: SemaphoreAcquireOptions): Promise<T>
|
|
271
|
+
async runExclusive<T>(callback: () => Promise<T> | T, options: SemaphoreAcquireOptions = {}): Promise<T> {
|
|
272
|
+
const permit = await this.acquire(options)
|
|
273
|
+
try {
|
|
274
|
+
return await callback()
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
permit.release()
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|