@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.
Files changed (175) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +123 -36
  3. package/dist/index.js +45 -4
  4. package/dist/index.js.map +183 -11
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +16 -10
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/basic/README.md +69 -118
  12. package/src/basic/function.ts +81 -62
  13. package/src/basic/is.ts +152 -71
  14. package/src/basic/promise.ts +29 -8
  15. package/src/basic/string.ts +2 -33
  16. package/src/color/README.md +105 -0
  17. package/src/color/index.ts +3 -0
  18. package/src/color/internal.ts +42 -0
  19. package/src/color/rgb/analyze.ts +236 -0
  20. package/src/color/rgb/construct.ts +130 -0
  21. package/src/color/rgb/convert.ts +227 -0
  22. package/src/color/rgb/derive.ts +303 -0
  23. package/src/color/rgb/index.ts +6 -0
  24. package/src/color/rgb/internal.ts +208 -0
  25. package/src/color/rgb/parse.ts +302 -0
  26. package/src/color/rgb/serialize.ts +144 -0
  27. package/src/color/types.ts +57 -0
  28. package/src/color/xyz/analyze.ts +80 -0
  29. package/src/color/xyz/construct.ts +19 -0
  30. package/src/color/xyz/convert.ts +71 -0
  31. package/src/color/xyz/index.ts +3 -0
  32. package/src/color/xyz/internal.ts +23 -0
  33. package/src/css/README.md +93 -0
  34. package/src/css/class.ts +559 -0
  35. package/src/css/index.ts +1 -0
  36. package/src/encoding/README.md +66 -79
  37. package/src/encoding/base64.ts +13 -4
  38. package/src/environment/README.md +97 -0
  39. package/src/environment/basic.ts +26 -0
  40. package/src/environment/device.ts +311 -0
  41. package/src/environment/feature.ts +285 -0
  42. package/src/environment/geo.ts +337 -0
  43. package/src/environment/index.ts +7 -0
  44. package/src/environment/runtime.ts +400 -0
  45. package/src/environment/snapshot.ts +60 -0
  46. package/src/environment/variable.ts +239 -0
  47. package/src/event/README.md +90 -0
  48. package/src/event/class-event-proxy.ts +228 -0
  49. package/src/event/common.ts +19 -0
  50. package/src/event/event-manager.ts +203 -0
  51. package/src/event/index.ts +4 -0
  52. package/src/event/instance-event-proxy.ts +186 -0
  53. package/src/event/internal.ts +24 -0
  54. package/src/exception/README.md +96 -0
  55. package/src/exception/browser.ts +219 -0
  56. package/src/exception/index.ts +4 -0
  57. package/src/exception/nodejs.ts +169 -0
  58. package/src/exception/normalize.ts +106 -0
  59. package/src/exception/types.ts +99 -0
  60. package/src/identifier/README.md +92 -0
  61. package/src/identifier/id.ts +119 -0
  62. package/src/identifier/index.ts +2 -0
  63. package/src/identifier/uuid.ts +187 -0
  64. package/src/index.ts +16 -1
  65. package/src/log/README.md +79 -0
  66. package/src/log/index.ts +5 -0
  67. package/src/log/log-emitter.ts +72 -0
  68. package/src/log/log-record.ts +10 -0
  69. package/src/log/log-scheduler.ts +74 -0
  70. package/src/log/log-type.ts +8 -0
  71. package/src/log/logger.ts +543 -0
  72. package/src/orchestration/README.md +89 -0
  73. package/src/orchestration/coordination/barrier.ts +214 -0
  74. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  75. package/src/orchestration/coordination/errors.ts +98 -0
  76. package/src/orchestration/coordination/index.ts +16 -0
  77. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  78. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  79. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  80. package/src/orchestration/coordination/mutex.ts +257 -0
  81. package/src/orchestration/coordination/permit.ts +127 -0
  82. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  83. package/src/orchestration/coordination/semaphore.ts +280 -0
  84. package/src/orchestration/index.ts +1 -0
  85. package/src/random/README.md +55 -86
  86. package/src/random/index.ts +1 -1
  87. package/src/random/string.ts +35 -0
  88. package/src/reactor/README.md +4 -0
  89. package/src/reactor/reactor-core/primitive.ts +9 -9
  90. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  91. package/src/singleton/README.md +79 -0
  92. package/src/singleton/factory.ts +55 -0
  93. package/src/singleton/index.ts +2 -0
  94. package/src/singleton/manager.ts +204 -0
  95. package/src/storage/README.md +107 -0
  96. package/src/storage/index.ts +1 -0
  97. package/src/storage/table.ts +449 -0
  98. package/src/timer/README.md +86 -0
  99. package/src/timer/expiration/expiration-manager.ts +594 -0
  100. package/src/timer/expiration/index.ts +3 -0
  101. package/src/timer/expiration/min-heap.ts +208 -0
  102. package/src/timer/expiration/remaining-manager.ts +241 -0
  103. package/src/timer/index.ts +1 -0
  104. package/src/type/README.md +54 -307
  105. package/src/type/class.ts +2 -2
  106. package/src/type/index.ts +14 -14
  107. package/src/type/is.ts +265 -2
  108. package/src/type/object.ts +37 -0
  109. package/src/type/string.ts +7 -2
  110. package/src/type/tuple.ts +6 -6
  111. package/src/type/union.ts +16 -0
  112. package/src/web/README.md +77 -0
  113. package/src/web/capture.ts +35 -0
  114. package/src/web/clipboard.ts +97 -0
  115. package/src/web/dom.ts +117 -0
  116. package/src/web/download.ts +16 -0
  117. package/src/web/event.ts +46 -0
  118. package/src/web/index.ts +10 -0
  119. package/src/web/local-storage.ts +113 -0
  120. package/src/web/location.ts +28 -0
  121. package/src/web/permission.ts +172 -0
  122. package/src/web/script-loader.ts +432 -0
  123. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  124. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  125. package/tests/unit/basic/array.spec.ts +1 -1
  126. package/tests/unit/basic/stream.spec.ts +1 -1
  127. package/tests/unit/basic/string.spec.ts +0 -9
  128. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  129. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  130. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  131. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  132. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  133. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  134. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  135. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  136. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  137. package/tests/unit/css/class.spec.ts +157 -0
  138. package/tests/unit/environment/basic.spec.ts +20 -0
  139. package/tests/unit/environment/device.spec.ts +146 -0
  140. package/tests/unit/environment/feature.spec.ts +388 -0
  141. package/tests/unit/environment/geo.spec.ts +111 -0
  142. package/tests/unit/environment/runtime.spec.ts +364 -0
  143. package/tests/unit/environment/snapshot.spec.ts +4 -0
  144. package/tests/unit/environment/variable.spec.ts +190 -0
  145. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  146. package/tests/unit/event/event-manager.spec.ts +246 -0
  147. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  148. package/tests/unit/exception/browser.spec.ts +213 -0
  149. package/tests/unit/exception/nodejs.spec.ts +144 -0
  150. package/tests/unit/exception/normalize.spec.ts +57 -0
  151. package/tests/unit/identifier/id.spec.ts +71 -0
  152. package/tests/unit/identifier/uuid.spec.ts +85 -0
  153. package/tests/unit/log/log-emitter.spec.ts +33 -0
  154. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  155. package/tests/unit/log/log-type.spec.ts +7 -0
  156. package/tests/unit/log/logger.spec.ts +222 -0
  157. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  158. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  159. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  160. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  161. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  162. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  163. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  164. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  165. package/tests/unit/random/string.spec.ts +11 -0
  166. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  167. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  168. package/tests/unit/singleton/singleton.spec.ts +49 -0
  169. package/tests/unit/storage/table.spec.ts +620 -0
  170. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  171. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  172. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  173. package/.oxlintrc.json +0 -5
  174. package/src/random/uuid.ts +0 -103
  175. 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
+ }