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.
- package/README.md +2 -1
- package/lib/index.js.map +1 -0
- package/lib/{es2015/instance.d.ts → instance.d.ts} +5 -3
- package/lib/{es2018/instance.js → instance.js} +35 -19
- package/lib/instance.js.map +1 -0
- package/lib/{es2015/pool.d.ts → pool.d.ts} +5 -3
- package/lib/{es2018/pool.js → pool.js} +46 -35
- package/lib/pool.js.map +1 -0
- package/package.json +26 -41
- package/src/index.ts +1 -0
- package/src/instance.ts +133 -0
- package/src/pool.ts +194 -0
- package/dist/es2015/index.min.mjs +0 -2
- package/dist/es2015/index.min.mjs.map +0 -1
- package/dist/es2015/index.mjs +0 -16672
- package/dist/es2015/index.mjs.map +0 -1
- package/dist/es2015/index.umd.js +0 -16683
- package/dist/es2015/index.umd.js.map +0 -1
- package/dist/es2015/index.umd.min.js +0 -2
- package/dist/es2015/index.umd.min.js.map +0 -1
- package/dist/es2018/index.min.mjs +0 -2
- package/dist/es2018/index.min.mjs.map +0 -1
- package/dist/es2018/index.mjs +0 -16648
- package/dist/es2018/index.mjs.map +0 -1
- package/dist/es2018/index.umd.js +0 -16659
- package/dist/es2018/index.umd.js.map +0 -1
- package/dist/es2018/index.umd.min.js +0 -2
- package/dist/es2018/index.umd.min.js.map +0 -1
- package/lib/es2015/index.js.map +0 -1
- package/lib/es2015/instance.js +0 -97
- package/lib/es2015/instance.js.map +0 -1
- package/lib/es2015/pool.js +0 -129
- package/lib/es2015/pool.js.map +0 -1
- package/lib/es2018/index.d.ts +0 -1
- package/lib/es2018/index.js +0 -18
- package/lib/es2018/index.js.map +0 -1
- package/lib/es2018/instance.d.ts +0 -19
- package/lib/es2018/instance.js.map +0 -1
- package/lib/es2018/pool.d.ts +0 -26
- package/lib/es2018/pool.js.map +0 -1
- /package/lib/{es2015/index.d.ts → index.d.ts} +0 -0
- /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 {}
|