extra-pool 0.1.0 → 0.1.2

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 (42) hide show
  1. package/README.md +2 -1
  2. package/lib/index.js.map +1 -0
  3. package/lib/{es2015/instance.d.ts → instance.d.ts} +5 -3
  4. package/lib/{es2018/instance.js → instance.js} +35 -19
  5. package/lib/instance.js.map +1 -0
  6. package/lib/{es2015/pool.d.ts → pool.d.ts} +5 -3
  7. package/lib/{es2018/pool.js → pool.js} +46 -35
  8. package/lib/pool.js.map +1 -0
  9. package/package.json +26 -41
  10. package/src/index.ts +1 -0
  11. package/src/instance.ts +133 -0
  12. package/src/pool.ts +194 -0
  13. package/dist/es2015/index.min.mjs +0 -2
  14. package/dist/es2015/index.min.mjs.map +0 -1
  15. package/dist/es2015/index.mjs +0 -16672
  16. package/dist/es2015/index.mjs.map +0 -1
  17. package/dist/es2015/index.umd.js +0 -16683
  18. package/dist/es2015/index.umd.js.map +0 -1
  19. package/dist/es2015/index.umd.min.js +0 -2
  20. package/dist/es2015/index.umd.min.js.map +0 -1
  21. package/dist/es2018/index.min.mjs +0 -2
  22. package/dist/es2018/index.min.mjs.map +0 -1
  23. package/dist/es2018/index.mjs +0 -16648
  24. package/dist/es2018/index.mjs.map +0 -1
  25. package/dist/es2018/index.umd.js +0 -16659
  26. package/dist/es2018/index.umd.js.map +0 -1
  27. package/dist/es2018/index.umd.min.js +0 -2
  28. package/dist/es2018/index.umd.min.js.map +0 -1
  29. package/lib/es2015/index.js.map +0 -1
  30. package/lib/es2015/instance.js +0 -97
  31. package/lib/es2015/instance.js.map +0 -1
  32. package/lib/es2015/pool.js +0 -129
  33. package/lib/es2015/pool.js.map +0 -1
  34. package/lib/es2018/index.d.ts +0 -1
  35. package/lib/es2018/index.js +0 -18
  36. package/lib/es2018/index.js.map +0 -1
  37. package/lib/es2018/instance.d.ts +0 -19
  38. package/lib/es2018/instance.js.map +0 -1
  39. package/lib/es2018/pool.d.ts +0 -26
  40. package/lib/es2018/pool.js.map +0 -1
  41. /package/lib/{es2015/index.d.ts → index.d.ts} +0 -0
  42. /package/lib/{es2015/index.js → index.js} +0 -0
package/src/pool.ts ADDED
@@ -0,0 +1,194 @@
1
+ import { CustomError } from '@blackglory/errors'
2
+ import { go, Awaitable } from '@blackglory/prelude'
3
+ import { Queue } from '@blackglory/structures'
4
+ import { FiniteStateMachine } from 'extra-fsm'
5
+ import { Deferred } from 'extra-promise'
6
+ import { toArray, filter } from 'iterable-operator'
7
+ import { setTimeout } from 'extra-timers'
8
+ import { Instance, InstanceState } from './instance'
9
+
10
+ interface IPoolOptions<T> {
11
+ create: () => Awaitable<T>
12
+
13
+ /**
14
+ * 该函数用于销毁实例.
15
+ * 它是可选的, 有一些实例可能不需要销毁或不需要手动销毁.
16
+ */
17
+ destroy?: (value: T) => Awaitable<void>
18
+
19
+ /**
20
+ * 池中实例的最高数量, 默认为Infinity.
21
+ */
22
+ maxInstances?: number
23
+
24
+ /**
25
+ * 池中实例的最低数量, 默认为0.
26
+ * 注意, 非零值不意味着最低数量的实例会与池一同被创建,
27
+ * 该值的主要作用是防止空闲实例被全部销毁(导致下次使用时需要重新创建实例).
28
+ */
29
+ minInstances?: number
30
+
31
+ /**
32
+ * 空闲实例的存活时间(毫秒).
33
+ * 默认为0, 空闲实例会被立即销毁.
34
+ */
35
+ idleTimeout?: number
36
+
37
+ /**
38
+ * 每个实例的并发数.
39
+ * 默认为1, 每个实例只能同时有一个用户.
40
+ */
41
+ concurrencyPerInstance?: number
42
+ }
43
+
44
+ interface IPoolItem<T> {
45
+ instance: Instance<T>
46
+
47
+ /**
48
+ * 空闲实例会在idleTimeout之后被删除, 该函数用于取消预定的删除操作.
49
+ */
50
+ cancelScheduledDeletion?: () => void
51
+ }
52
+
53
+ enum PoolState {
54
+ Running = 'running'
55
+ , Destroying = 'destroying'
56
+ , Destroyed = 'destroyed'
57
+ }
58
+
59
+ type PoolEvent =
60
+ | 'destroy'
61
+ | 'destroyed'
62
+
63
+ const poolSchema = {
64
+ [PoolState.Running]: {
65
+ destroy: PoolState.Destroying
66
+ }
67
+ , [PoolState.Destroying]: {
68
+ destroyed: PoolState.Destroyed
69
+ }
70
+ , [PoolState.Destroyed]: {}
71
+ }
72
+
73
+ export class Pool<T> {
74
+ private createInstance: () => Awaitable<T>
75
+ private destroyInstance?: (value: T) => Awaitable<void>
76
+ private fsm = new FiniteStateMachine<PoolState, PoolEvent>(
77
+ poolSchema
78
+ , PoolState.Running
79
+ )
80
+ private waitingUsers: Queue<Deferred<IPoolItem<T>>> = new Queue()
81
+ private items: Set<IPoolItem<T>> = new Set()
82
+ private maxInstances: number
83
+ private minInstances: number
84
+ private idleTimeout: number
85
+ private concurrencyPerInstance: number
86
+
87
+ get size(): number {
88
+ return this.items.size
89
+ }
90
+
91
+ constructor(options: IPoolOptions<T>) {
92
+ this.createInstance = options.create
93
+ this.destroyInstance = options.destroy
94
+ this.maxInstances = options.maxInstances ?? Infinity
95
+ this.minInstances = options.minInstances ?? 0
96
+ this.idleTimeout = options.idleTimeout ?? 0
97
+ this.concurrencyPerInstance = options.concurrencyPerInstance ?? 1
98
+ }
99
+
100
+ /**
101
+ * 如果所有实例都不空闲, 该函数会通过返回Promise来阻塞使用者, 直到有空闲的实例.
102
+ * 函数的使用者应该尊重阻塞, 否则会意外制造大量非必要的Promise.
103
+ */
104
+ async use<U>(fn: (instance: T) => Awaitable<U>): Promise<U> {
105
+ const self = this
106
+
107
+ const item = go(() => {
108
+ const candidateItems = toArray(filter(
109
+ this.items
110
+ , item => item.instance.users < this.concurrencyPerInstance
111
+ ))
112
+
113
+ if (candidateItems.length) {
114
+ return candidateItems.reduce((previous, current) => {
115
+ if (current.instance.users < previous.instance.users) {
116
+ return current
117
+ } else {
118
+ return previous
119
+ }
120
+ })
121
+ }
122
+ })
123
+
124
+ if (item) {
125
+ return await useItem(item)
126
+ } else {
127
+ if (this.items.size < this.maxInstances) {
128
+ const instance = new Instance(this.createInstance, this.destroyInstance)
129
+ const item: IPoolItem<T> = { instance }
130
+ this.items.add(item)
131
+ return await useItem(item)
132
+ } else {
133
+ const waitingUser = new Deferred<IPoolItem<T>>()
134
+ this.waitingUsers.enqueue(waitingUser)
135
+ const item = await waitingUser
136
+ return await useItem(item)
137
+ }
138
+ }
139
+
140
+ async function useItem(item: IPoolItem<T>): Promise<U> {
141
+ // 由于使用该实例, 取消其预定的删除动作.
142
+ if (item.cancelScheduledDeletion) {
143
+ item.cancelScheduledDeletion()
144
+ delete item.cancelScheduledDeletion
145
+ }
146
+
147
+ try {
148
+ return await item.instance.use(fn)
149
+ } finally {
150
+ const waitingUser = self.waitingUsers.dequeue()
151
+ if (waitingUser) {
152
+ waitingUser.resolve(item)
153
+ } else {
154
+ if (
155
+ item.instance.users === 0 &&
156
+ self.items.size > self.minInstances
157
+ ) {
158
+ if (self.idleTimeout > 0) {
159
+ item.cancelScheduledDeletion = setTimeout(
160
+ self.idleTimeout
161
+ , deleteInstance
162
+ )
163
+ } else {
164
+ await deleteInstance()
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ async function deleteInstance(): Promise<void> {
171
+ self.items.delete(item)
172
+ await item.instance.destroy()
173
+ }
174
+ }
175
+ }
176
+
177
+ async destroy(): Promise<void> {
178
+ this.fsm.send('destroy')
179
+
180
+ for (const item of this.items) {
181
+ await item.instance.destroy()
182
+ }
183
+ this.items.clear()
184
+
185
+ let deferred: Deferred<IPoolItem<T>> | undefined
186
+ while (deferred = this.waitingUsers.dequeue()) {
187
+ deferred.reject(new UnavailablePool())
188
+ }
189
+
190
+ this.fsm.send('destroyed')
191
+ }
192
+ }
193
+
194
+ export class UnavailablePool extends CustomError {}