fib-pool 1.6.1 → 1.7.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/Readme.md +86 -3
- package/package.json +4 -33
- package/src/index.ts +416 -0
- package/tsconfig.json +10 -10
- package/.travis.yml +0 -99
- package/appveyor.yml +0 -26
- package/lib/index.js +0 -164
- package/typings/index.d.ts +0 -41
package/Readme.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
## Generic resource pooling for fibjs
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.org/package/fib-pool)
|
|
4
|
-
[](https://travis-ci.org/fibjs/fib-pool)
|
|
5
|
-
[](https://ci.appveyor.com/project/richardo2016/fib-pool)
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
@@ -13,7 +11,8 @@ npm install fib-pool [--save]
|
|
|
13
11
|
## Test
|
|
14
12
|
|
|
15
13
|
```sh
|
|
16
|
-
npm
|
|
14
|
+
npm test # fibjs test
|
|
15
|
+
npm run typecheck # fibjs --check
|
|
17
16
|
```
|
|
18
17
|
|
|
19
18
|
## Creating a pool
|
|
@@ -103,6 +102,90 @@ var res = p("test", (conn) => {
|
|
|
103
102
|
|
|
104
103
|
```
|
|
105
104
|
|
|
105
|
+
## Options
|
|
106
|
+
|
|
107
|
+
| option | type | default | notes |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `create` | `(name) => item` | — | required; throwing is a failure, retried up to `retry` times |
|
|
110
|
+
| `destroy` | `(item) => void` | close/destroy/dispose | defaults to calling whichever of those three the item provides |
|
|
111
|
+
| `maxsize` | positive integer | `10` | caps both concurrency and the number of idle items |
|
|
112
|
+
| `timeout` | positive number (ms) | `60000` | how long an idle item is kept |
|
|
113
|
+
| `retry` | positive integer | `1` | how many times `create` is attempted before giving up |
|
|
114
|
+
| `strict` | boolean | `true` | see below |
|
|
115
|
+
|
|
116
|
+
Invalid `maxsize`, `timeout` and `retry` values throw a `TypeError` when the
|
|
117
|
+
pool is created rather than being silently coerced:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
Pool({ create: () => ({}), maxsize: 0 }); // TypeError: maxsize must be a positive integer, received 0
|
|
121
|
+
Pool({ create: () => ({}), timeout: -5 }); // TypeError: timeout must be a positive number, received -5
|
|
122
|
+
Pool({ create: () => ({}), retry: true }); // TypeError: retry must be a positive integer, received true
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The idle list is capped by `maxsize` as well: a pool used with more names than
|
|
126
|
+
that evicts the least recently used item (and destroys it) instead of keeping
|
|
127
|
+
one idle item per name.
|
|
128
|
+
|
|
129
|
+
## Reentrancy
|
|
130
|
+
|
|
131
|
+
A pool cannot be acquired twice from the same fiber. The inner call would wait
|
|
132
|
+
for a permit that the outer call is holding, which never arrives, so it is
|
|
133
|
+
rejected instead of deadlocking:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
var p = Pool({ create: () => ({}), maxsize: 1 });
|
|
137
|
+
|
|
138
|
+
p(() => {
|
|
139
|
+
p(() => {}); // Error: Cannot pool again from the same fiber
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Nesting different pools, or acquiring the same pool from another fiber, works as
|
|
144
|
+
expected.
|
|
145
|
+
|
|
146
|
+
## Strict mode (default: enabled)
|
|
147
|
+
|
|
148
|
+
By default, the pool uses strict mode to prevent accessing pooled objects outside of the callback function. This helps prevent resource leaks and ensures proper resource management.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
var Pool = require("fib-pool");
|
|
152
|
+
|
|
153
|
+
var p = Pool({
|
|
154
|
+
create: () => {
|
|
155
|
+
return { value: 1, getValue: function() { return this.value; } };
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
var leaked;
|
|
160
|
+
p((obj) => {
|
|
161
|
+
leaked = obj;
|
|
162
|
+
// This works inside the pool callback
|
|
163
|
+
console.log(obj.getValue()); // OK
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// This will throw an error: "access object outside of pool scope"
|
|
167
|
+
leaked.getValue(); // Error!
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The guarded object behaves like the item for reading and inspection —
|
|
171
|
+
`key in obj`, `Object.keys(obj)`, `for...in`, `JSON.stringify(obj)` and
|
|
172
|
+
`util.inspect(obj)` all report the item's own members — while the guards remain:
|
|
173
|
+
|
|
174
|
+
- Setting properties on pooled objects
|
|
175
|
+
- Calling `close()`, `destroy()`, or `dispose()` methods directly
|
|
176
|
+
- Extracting a method and calling it after the callback returned
|
|
177
|
+
|
|
178
|
+
To disable strict mode:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
var p = Pool({
|
|
182
|
+
create: () => {
|
|
183
|
+
return { value: 1 };
|
|
184
|
+
},
|
|
185
|
+
strict: false
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
106
189
|
## Clear a pool
|
|
107
190
|
|
|
108
191
|
Simple example.
|
package/package.json
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fib-pool",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Generic resource pooling for fibjs",
|
|
5
|
-
"main": "./
|
|
6
|
-
"
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
7
|
"repository": "git://github.com/fibjs/fib-pool.git",
|
|
8
8
|
"author": "Liu Hu <lion@9465.net>",
|
|
9
|
-
"contributors": [
|
|
10
|
-
{
|
|
11
|
-
"name": "Richard",
|
|
12
|
-
"email": "richardo2016@gmail.com"
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
9
|
"homepage": "https://github.com/fibjs/fib-pool",
|
|
16
10
|
"license": "MIT",
|
|
17
11
|
"keywords": [
|
|
@@ -20,30 +14,7 @@
|
|
|
20
14
|
"fibjs"
|
|
21
15
|
],
|
|
22
16
|
"scripts": {
|
|
23
|
-
"build": "ftsc ./src/* --outDir lib",
|
|
24
17
|
"test": "fibjs test",
|
|
25
|
-
"
|
|
26
|
-
"prepublishOnly": "npm run build"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"@fibjs/ci": "^2.3.0",
|
|
30
|
-
"@fibjs/types": "^0.27.1",
|
|
31
|
-
"fib-typify": "^0.8.4"
|
|
32
|
-
},
|
|
33
|
-
"ci": {
|
|
34
|
-
"type": "travis, appveyor",
|
|
35
|
-
"version": [
|
|
36
|
-
"0.21.0",
|
|
37
|
-
"0.22.0",
|
|
38
|
-
"0.23.0",
|
|
39
|
-
"0.24.0",
|
|
40
|
-
"0.25.0",
|
|
41
|
-
"0.26.0",
|
|
42
|
-
"0.26.1",
|
|
43
|
-
"0.27.0",
|
|
44
|
-
"0.28.0",
|
|
45
|
-
"0.29.0",
|
|
46
|
-
"0.30.0"
|
|
47
|
-
]
|
|
18
|
+
"typecheck": "fibjs --check"
|
|
48
19
|
}
|
|
49
20
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import * as coroutine from 'coroutine';
|
|
2
|
+
import * as util from 'util';
|
|
3
|
+
|
|
4
|
+
declare namespace FibPoolNS {
|
|
5
|
+
/**
|
|
6
|
+
* The shape `create` must return. All three members are optional: the
|
|
7
|
+
* default destroy calls whichever of them the item actually provides, and
|
|
8
|
+
* a non-function value (a plain property) is ignored.
|
|
9
|
+
*/
|
|
10
|
+
interface DippedItemStruct {
|
|
11
|
+
close?: unknown;
|
|
12
|
+
destroy?: unknown;
|
|
13
|
+
dispose?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface FibPoolOptionArgs<T extends DippedItemStruct = {}> {
|
|
17
|
+
create: (name: string) => T;
|
|
18
|
+
destroy?: (item?: T) => any;
|
|
19
|
+
maxsize?: number;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
retry?: number;
|
|
22
|
+
strict?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface FibPoolDipperFn<T_CREATED_ITEM, T_POOL_FUNC_RETURN = any> {
|
|
26
|
+
(o: T_CREATED_ITEM): T_POOL_FUNC_RETURN
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type FibPool<T_CREATED_ITEM, T_POOL_FUNC_RETURN = any> = {
|
|
30
|
+
<T_RETURN = T_POOL_FUNC_RETURN>(name: string, func: FibPoolDipperFn<T_CREATED_ITEM, T_RETURN>): T_RETURN
|
|
31
|
+
<T_RETURN = T_POOL_FUNC_RETURN>(func: FibPoolDipperFn<T_CREATED_ITEM, T_RETURN>): T_RETURN
|
|
32
|
+
} & {
|
|
33
|
+
connections(): number;
|
|
34
|
+
info(): {
|
|
35
|
+
maxsize: number;
|
|
36
|
+
count: number;
|
|
37
|
+
running: number;
|
|
38
|
+
wait: number;
|
|
39
|
+
timeout: number;
|
|
40
|
+
};
|
|
41
|
+
clear(): void;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Calls the value when it is a function and ignores it otherwise: a pooled item
|
|
47
|
+
* may expose a plain property named close/destroy/dispose.
|
|
48
|
+
*/
|
|
49
|
+
function callIfFunction(v: unknown) {
|
|
50
|
+
if (util.isFunction(v))
|
|
51
|
+
(v as (...args: any[]) => any)();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `maxsize` and `retry` are counts, `timeout` is a duration. Both are rejected
|
|
56
|
+
* up front instead of being silently coerced into a default or handed to the
|
|
57
|
+
* semaphore, which would fail far from the option that is wrong.
|
|
58
|
+
*/
|
|
59
|
+
function toPositiveInteger(value: unknown, name: string): number {
|
|
60
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || Math.floor(value) !== value)
|
|
61
|
+
throw new TypeError(`${name} must be a positive integer, received ${String(value)}`);
|
|
62
|
+
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function toPositiveNumber(value: unknown, name: string): number {
|
|
67
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
68
|
+
throw new TypeError(`${name} must be a positive number, received ${String(value)}`);
|
|
69
|
+
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The pool always hands out an Error: `create` or the callback may throw a
|
|
75
|
+
* string, a number, or nothing at all, and the caller expects to be able to
|
|
76
|
+
* read `.message`. A thrown falsy value (0, '', false, null, undefined) is still
|
|
77
|
+
* a failure and must not be mistaken for a successful create, so this returns an
|
|
78
|
+
* Error for every input.
|
|
79
|
+
*/
|
|
80
|
+
function toError(e: unknown): Error {
|
|
81
|
+
if (e instanceof Error)
|
|
82
|
+
return e;
|
|
83
|
+
|
|
84
|
+
if (typeof e === 'string')
|
|
85
|
+
return new Error(e);
|
|
86
|
+
|
|
87
|
+
return new Error(String(e));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Guards a pooled item while the callback runs. The item is read through a
|
|
92
|
+
* getter rather than captured by value: once it goes back to the pool the
|
|
93
|
+
* handle must stop working, which is what the "outside of pool scope" error
|
|
94
|
+
* reports.
|
|
95
|
+
*
|
|
96
|
+
* The reflection traps matter as much as the getter: without them `key in obj`,
|
|
97
|
+
* `Object.keys`, `for...in` and `JSON.stringify` all see the empty proxy target
|
|
98
|
+
* instead of the item.
|
|
99
|
+
*/
|
|
100
|
+
function wrapStrict<T extends object>(item: () => T | undefined): T {
|
|
101
|
+
const current = () => {
|
|
102
|
+
const o = item();
|
|
103
|
+
if (o === undefined)
|
|
104
|
+
throw new Error("access object outside of pool scope");
|
|
105
|
+
|
|
106
|
+
return o;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return new Proxy({}, {
|
|
110
|
+
get(target, prop) {
|
|
111
|
+
if (prop === 'close' || prop === 'destroy' || prop === 'dispose')
|
|
112
|
+
throw new Error("Cannot close/destroy/dispose");
|
|
113
|
+
|
|
114
|
+
const o = current();
|
|
115
|
+
const value = (o as any)[prop];
|
|
116
|
+
if (typeof value === 'function') {
|
|
117
|
+
/* the item is resolved again when the method runs: a method
|
|
118
|
+
extracted inside the callback and called after it returned
|
|
119
|
+
must not keep the item alive outside its scope */
|
|
120
|
+
return function () {
|
|
121
|
+
const self = current();
|
|
122
|
+
return (self as any)[prop].apply(self, arguments);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
},
|
|
127
|
+
has(target, prop) {
|
|
128
|
+
/* no close/destroy/dispose refusal here: the key is reported like
|
|
129
|
+
any other (ownKeys lists it too), only reading or calling those
|
|
130
|
+
members is refused, in `get` */
|
|
131
|
+
return Reflect.has(current() as object, prop);
|
|
132
|
+
},
|
|
133
|
+
ownKeys() {
|
|
134
|
+
return Reflect.ownKeys(current() as object);
|
|
135
|
+
},
|
|
136
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
137
|
+
const d = Reflect.getOwnPropertyDescriptor(current() as object, prop);
|
|
138
|
+
|
|
139
|
+
/* every key of the proxy target is configurable: a descriptor that
|
|
140
|
+
claims otherwise would break the proxy invariants */
|
|
141
|
+
return d && Object.assign(d, { configurable: true });
|
|
142
|
+
},
|
|
143
|
+
set() {
|
|
144
|
+
throw new Error("Cannot set property");
|
|
145
|
+
}
|
|
146
|
+
}) as T;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default function GetPool<
|
|
150
|
+
T_CREATED_ITEM extends FibPoolNS.DippedItemStruct,
|
|
151
|
+
T_POOL_FUNC_RETURN
|
|
152
|
+
> (
|
|
153
|
+
_opt: FibPoolNS.FibPoolOptionArgs<T_CREATED_ITEM> | FibPoolNS.FibPoolOptionArgs<T_CREATED_ITEM>['create'],
|
|
154
|
+
maxsize?: number,
|
|
155
|
+
timeout?: number
|
|
156
|
+
): FibPoolNS.FibPool<T_CREATED_ITEM, T_POOL_FUNC_RETURN> {
|
|
157
|
+
/* clean input arguments :start */
|
|
158
|
+
let opt: FibPoolNS.FibPoolOptionArgs<T_CREATED_ITEM>;
|
|
159
|
+
|
|
160
|
+
if (util.isFunction(_opt)) {
|
|
161
|
+
opt = {
|
|
162
|
+
create: _opt as (name: string) => T_CREATED_ITEM,
|
|
163
|
+
maxsize: maxsize,
|
|
164
|
+
timeout: timeout
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
opt = _opt as FibPoolNS.FibPoolOptionArgs<T_CREATED_ITEM>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const create = opt.create;
|
|
171
|
+
const destroy = opt.destroy || ((o: FibPoolNS.DippedItemStruct | undefined) => {
|
|
172
|
+
/* create is allowed to return null/undefined: there is nothing to
|
|
173
|
+
destroy then */
|
|
174
|
+
if (o === null || o === undefined)
|
|
175
|
+
return;
|
|
176
|
+
|
|
177
|
+
callIfFunction(o.close);
|
|
178
|
+
callIfFunction(o.destroy);
|
|
179
|
+
callIfFunction(o.dispose);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const maxsizeValue = opt.maxsize === undefined ? 10 : toPositiveInteger(opt.maxsize, 'maxsize');
|
|
183
|
+
let timeoutValue = opt.timeout === undefined ? 60000 : toPositiveNumber(opt.timeout, 'timeout');
|
|
184
|
+
|
|
185
|
+
/* the idle scan runs at a tenth of the timeout, but never more often than
|
|
186
|
+
every 10ms */
|
|
187
|
+
let tm = timeoutValue / 10;
|
|
188
|
+
if (tm < 10)
|
|
189
|
+
tm = 10;
|
|
190
|
+
|
|
191
|
+
const retry = opt.retry === undefined ? 1 : toPositiveInteger(opt.retry, 'retry');
|
|
192
|
+
const strictMode = opt.strict === undefined ? true : opt.strict;
|
|
193
|
+
/* clean input arguments :end */
|
|
194
|
+
|
|
195
|
+
let pools = <{
|
|
196
|
+
o: T_CREATED_ITEM;
|
|
197
|
+
name: string;
|
|
198
|
+
time: Date;
|
|
199
|
+
}[]>[];
|
|
200
|
+
|
|
201
|
+
const jobs = <{
|
|
202
|
+
name: string;
|
|
203
|
+
ev: Class_Event;
|
|
204
|
+
o?: T_CREATED_ITEM;
|
|
205
|
+
e?: Error;
|
|
206
|
+
}[]>[];
|
|
207
|
+
|
|
208
|
+
let count = 0;
|
|
209
|
+
let running = 0;
|
|
210
|
+
|
|
211
|
+
const sem = new coroutine.Semaphore(maxsizeValue);
|
|
212
|
+
let clearTimer: Class_Timer | null = null;
|
|
213
|
+
|
|
214
|
+
function clearPool() {
|
|
215
|
+
const d = new Date().getTime();
|
|
216
|
+
|
|
217
|
+
while (count) {
|
|
218
|
+
const c = pools[0];
|
|
219
|
+
|
|
220
|
+
if (d - c.time.getTime() > timeoutValue) {
|
|
221
|
+
pools = pools.slice(1);
|
|
222
|
+
count--;
|
|
223
|
+
|
|
224
|
+
if (c.o !== undefined)
|
|
225
|
+
coroutine.start(destroy, c.o);
|
|
226
|
+
} else
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!count) {
|
|
231
|
+
if (clearTimer) {
|
|
232
|
+
clearTimer.clear();
|
|
233
|
+
clearTimer = null;
|
|
234
|
+
}
|
|
235
|
+
} else if (!clearTimer) {
|
|
236
|
+
/* the timer must not hold the process alive: the pool is idle
|
|
237
|
+
between uses */
|
|
238
|
+
clearTimer = setInterval(clearPool, tm).unref();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Hand the item back: wake the first waiter bound to `name`, or park the
|
|
244
|
+
* item in the idle list. An errored item is never parked, but a successful
|
|
245
|
+
* create may legitimately produce `undefined` and is parked as is.
|
|
246
|
+
*/
|
|
247
|
+
function putback(
|
|
248
|
+
name: string,
|
|
249
|
+
o: T_CREATED_ITEM | undefined,
|
|
250
|
+
e?: Error
|
|
251
|
+
) {
|
|
252
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
253
|
+
const job = jobs[i];
|
|
254
|
+
if (job.name === name) {
|
|
255
|
+
jobs.splice(i, 1);
|
|
256
|
+
job.o = o;
|
|
257
|
+
job.e = e;
|
|
258
|
+
job.ev.set();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (e === undefined) {
|
|
264
|
+
/* The idle list is capped by maxsize: a pool used with more names
|
|
265
|
+
than that would otherwise keep one idle item per name and grow
|
|
266
|
+
without bound. The oldest idle item gives way to the one just
|
|
267
|
+
returned, so the most recently used names stay cached. */
|
|
268
|
+
if (count >= maxsizeValue && count) {
|
|
269
|
+
const evicted = pools[0];
|
|
270
|
+
pools = pools.slice(1);
|
|
271
|
+
count--;
|
|
272
|
+
coroutine.start(destroy, evicted.o);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
pools[count++] = {
|
|
276
|
+
o: o as T_CREATED_ITEM,
|
|
277
|
+
name: name,
|
|
278
|
+
time: new Date()
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function connect(name: string) {
|
|
284
|
+
let o: T_CREATED_ITEM | undefined;
|
|
285
|
+
let err: Error | undefined;
|
|
286
|
+
|
|
287
|
+
for (let cn = 1; ; cn++) {
|
|
288
|
+
try {
|
|
289
|
+
o = create(name);
|
|
290
|
+
break;
|
|
291
|
+
} catch (e) {
|
|
292
|
+
/* any thrown value is a failure, including 0 or '' */
|
|
293
|
+
if (cn >= retry) {
|
|
294
|
+
err = toError(e);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
putback(name, o, err);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* The fiber is used as fiber-local storage: it is the only place a marker
|
|
304
|
+
can live without leaking between fibers. A fiber that already holds an
|
|
305
|
+
item from this pool would wait on itself when it asks again, which the
|
|
306
|
+
semaphore can never satisfy once every permit is out. */
|
|
307
|
+
const fiberDepth = Symbol('fib-pool depth');
|
|
308
|
+
|
|
309
|
+
type FiberState = Class_Fiber & { [fiberDepth]?: true };
|
|
310
|
+
|
|
311
|
+
function enterFiber(): FiberState {
|
|
312
|
+
const fiber = coroutine.current() as FiberState;
|
|
313
|
+
|
|
314
|
+
if (fiber[fiberDepth])
|
|
315
|
+
throw new Error("Cannot pool again from the same fiber: the pool is already held by this callback");
|
|
316
|
+
|
|
317
|
+
fiber[fiberDepth] = true;
|
|
318
|
+
|
|
319
|
+
return fiber;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function leaveFiber(fiber: FiberState) {
|
|
323
|
+
delete fiber[fiberDepth];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const pool = ((
|
|
327
|
+
name: string | FibPoolNS.FibPoolDipperFn<T_CREATED_ITEM, T_POOL_FUNC_RETURN>,
|
|
328
|
+
func?: FibPoolNS.FibPoolDipperFn<T_CREATED_ITEM, T_POOL_FUNC_RETURN>
|
|
329
|
+
) => {
|
|
330
|
+
if (typeof name === 'function') {
|
|
331
|
+
func = name;
|
|
332
|
+
name = "";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const dip = func as FibPoolNS.FibPoolDipperFn<T_CREATED_ITEM, T_POOL_FUNC_RETURN>;
|
|
336
|
+
|
|
337
|
+
let r: T_POOL_FUNC_RETURN;
|
|
338
|
+
let o: T_CREATED_ITEM | undefined;
|
|
339
|
+
let reused = false;
|
|
340
|
+
const fiber = enterFiber();
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
clearPool();
|
|
344
|
+
sem.acquire();
|
|
345
|
+
|
|
346
|
+
if (count) {
|
|
347
|
+
for (let i = count - 1; i >= 0; i--)
|
|
348
|
+
if (pools[i].name === name) {
|
|
349
|
+
reused = true
|
|
350
|
+
o = pools[i].o;
|
|
351
|
+
pools.splice(i, 1);
|
|
352
|
+
count--;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!reused) {
|
|
358
|
+
coroutine.start(connect, name);
|
|
359
|
+
|
|
360
|
+
const job: typeof jobs[any] = {
|
|
361
|
+
name: name,
|
|
362
|
+
ev: new coroutine.Event()
|
|
363
|
+
};
|
|
364
|
+
jobs.push(job);
|
|
365
|
+
|
|
366
|
+
job.ev.wait();
|
|
367
|
+
if (job.e) {
|
|
368
|
+
sem.post();
|
|
369
|
+
throw job.e;
|
|
370
|
+
}
|
|
371
|
+
o = job.o;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
running++;
|
|
375
|
+
try {
|
|
376
|
+
const po = (strictMode && o !== null && typeof o === 'object')
|
|
377
|
+
? wrapStrict<T_CREATED_ITEM & object>(() => o as T_CREATED_ITEM & object)
|
|
378
|
+
: o as T_CREATED_ITEM;
|
|
379
|
+
|
|
380
|
+
r = dip(po);
|
|
381
|
+
putback(name, o);
|
|
382
|
+
o = undefined;
|
|
383
|
+
} catch (e) {
|
|
384
|
+
if (o !== undefined)
|
|
385
|
+
coroutine.start(destroy, o);
|
|
386
|
+
o = undefined;
|
|
387
|
+
throw toError(e);
|
|
388
|
+
} finally {
|
|
389
|
+
running--;
|
|
390
|
+
sem.post();
|
|
391
|
+
clearPool();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return r;
|
|
395
|
+
} finally {
|
|
396
|
+
leaveFiber(fiber);
|
|
397
|
+
}
|
|
398
|
+
}) as FibPoolNS.FibPool<T_CREATED_ITEM, T_POOL_FUNC_RETURN>;
|
|
399
|
+
|
|
400
|
+
pool.connections = () => count
|
|
401
|
+
|
|
402
|
+
pool.info = () => ({
|
|
403
|
+
maxsize: maxsizeValue,
|
|
404
|
+
count: count,
|
|
405
|
+
running: running,
|
|
406
|
+
wait: sem.count(),
|
|
407
|
+
timeout: timeoutValue
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
pool.clear = () => {
|
|
411
|
+
timeoutValue = -1;
|
|
412
|
+
clearPool();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return pool;
|
|
416
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
|
|
3
|
+
// `fibjs --check` derives its default lib from the target: with es6
|
|
4
|
+
// that is lib.dom, which collides with the fibjs built-in types.
|
|
5
|
+
// es2020 is the lowest target whose default lib stays es-only.
|
|
6
|
+
"target": "es2020",
|
|
4
7
|
"module": "commonjs",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
}
|
|
8
|
+
"noEmit": true
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"node_modules"
|
|
12
|
+
]
|
|
13
|
+
}
|
package/.travis.yml
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
language: node_js
|
|
2
|
-
node_js: '6'
|
|
3
|
-
matrix:
|
|
4
|
-
include:
|
|
5
|
-
- os: linux
|
|
6
|
-
env:
|
|
7
|
-
- VERSION=0.21.0
|
|
8
|
-
- os: linux
|
|
9
|
-
env:
|
|
10
|
-
- VERSION=0.22.0
|
|
11
|
-
- os: linux
|
|
12
|
-
env:
|
|
13
|
-
- VERSION=0.23.0
|
|
14
|
-
- os: linux
|
|
15
|
-
env:
|
|
16
|
-
- VERSION=0.24.0
|
|
17
|
-
- os: linux
|
|
18
|
-
env:
|
|
19
|
-
- VERSION=0.25.0
|
|
20
|
-
- os: linux
|
|
21
|
-
env:
|
|
22
|
-
- VERSION=0.26.0
|
|
23
|
-
- os: linux
|
|
24
|
-
env:
|
|
25
|
-
- VERSION=0.26.1
|
|
26
|
-
- os: linux
|
|
27
|
-
env:
|
|
28
|
-
- VERSION=0.27.0
|
|
29
|
-
- os: linux
|
|
30
|
-
env:
|
|
31
|
-
- VERSION=0.28.0
|
|
32
|
-
- os: linux
|
|
33
|
-
env:
|
|
34
|
-
- VERSION=0.29.0
|
|
35
|
-
- os: linux
|
|
36
|
-
env:
|
|
37
|
-
- VERSION=0.30.0
|
|
38
|
-
- os: osx
|
|
39
|
-
osx_image: xcode9
|
|
40
|
-
env:
|
|
41
|
-
- VERSION=0.21.0
|
|
42
|
-
- os: osx
|
|
43
|
-
osx_image: xcode9
|
|
44
|
-
env:
|
|
45
|
-
- VERSION=0.22.0
|
|
46
|
-
- os: osx
|
|
47
|
-
osx_image: xcode9
|
|
48
|
-
env:
|
|
49
|
-
- VERSION=0.23.0
|
|
50
|
-
- os: osx
|
|
51
|
-
osx_image: xcode9
|
|
52
|
-
env:
|
|
53
|
-
- VERSION=0.24.0
|
|
54
|
-
- os: osx
|
|
55
|
-
osx_image: xcode9
|
|
56
|
-
env:
|
|
57
|
-
- VERSION=0.25.0
|
|
58
|
-
- os: osx
|
|
59
|
-
osx_image: xcode9
|
|
60
|
-
env:
|
|
61
|
-
- VERSION=0.26.0
|
|
62
|
-
- os: osx
|
|
63
|
-
osx_image: xcode9
|
|
64
|
-
env:
|
|
65
|
-
- VERSION=0.26.1
|
|
66
|
-
- os: osx
|
|
67
|
-
osx_image: xcode9
|
|
68
|
-
env:
|
|
69
|
-
- VERSION=0.27.0
|
|
70
|
-
- os: osx
|
|
71
|
-
osx_image: xcode9
|
|
72
|
-
env:
|
|
73
|
-
- VERSION=0.28.0
|
|
74
|
-
- os: osx
|
|
75
|
-
osx_image: xcode9
|
|
76
|
-
env:
|
|
77
|
-
- VERSION=0.29.0
|
|
78
|
-
- os: osx
|
|
79
|
-
osx_image: xcode9
|
|
80
|
-
env:
|
|
81
|
-
- VERSION=0.30.0
|
|
82
|
-
before_install:
|
|
83
|
-
- OS=linux
|
|
84
|
-
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
|
85
|
-
rvm install ruby-2.4.0;
|
|
86
|
-
rvm --default use 2.4.0;
|
|
87
|
-
ruby -v;
|
|
88
|
-
brew update;
|
|
89
|
-
brew install xz;
|
|
90
|
-
OS=darwin;
|
|
91
|
-
fi
|
|
92
|
-
script:
|
|
93
|
-
- npm i
|
|
94
|
-
- mkdir -p ./node_modules/.bin
|
|
95
|
-
- rm -rf ./node_modules/.bin/fibjs
|
|
96
|
-
- curl -SL "https://github.com/fibjs/fibjs/releases/download/v${VERSION}/fibjs-v${VERSION}-${OS}-x64.xz" -o ./node_modules/.bin/fibjs.xz
|
|
97
|
-
- xz -d ./node_modules/.bin/fibjs.xz
|
|
98
|
-
- chmod a+x ./node_modules/.bin/fibjs
|
|
99
|
-
- npm run ci
|
package/appveyor.yml
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
environment:
|
|
2
|
-
nodejs_version: '6'
|
|
3
|
-
matrix:
|
|
4
|
-
- VERSION: 0.21.0
|
|
5
|
-
- VERSION: 0.22.0
|
|
6
|
-
- VERSION: 0.23.0
|
|
7
|
-
- VERSION: 0.24.0
|
|
8
|
-
- VERSION: 0.25.0
|
|
9
|
-
- VERSION: 0.26.0
|
|
10
|
-
- VERSION: 0.26.1
|
|
11
|
-
- VERSION: 0.27.0
|
|
12
|
-
- VERSION: 0.28.0
|
|
13
|
-
- VERSION: 0.29.0
|
|
14
|
-
- VERSION: 0.30.0
|
|
15
|
-
|
|
16
|
-
install:
|
|
17
|
-
- ps: Install-Product node $env:nodejs_version
|
|
18
|
-
- npm i
|
|
19
|
-
- If Not Exist "node_modules\.bin" (mkdir node_modules\.bin)
|
|
20
|
-
- curl -fsSL -o node_modules\.bin\fibjs.cab "https://github.com/fibjs/fibjs/releases/download/v%VERSION%/fibjs-v%VERSION%-windows-x64.cab"
|
|
21
|
-
- expand node_modules\.bin\fibjs.cab node_modules\.bin\fibjs.exe
|
|
22
|
-
|
|
23
|
-
test_script:
|
|
24
|
-
- npm run ci
|
|
25
|
-
|
|
26
|
-
build: off
|
package/lib/index.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const coroutine = require("coroutine");
|
|
3
|
-
const util = require("util");
|
|
4
|
-
function ensureError(e) {
|
|
5
|
-
if (typeof e === 'string')
|
|
6
|
-
e = new Error(e);
|
|
7
|
-
return e;
|
|
8
|
-
}
|
|
9
|
-
module.exports = function GetPool(_opt, maxsize, timeout) {
|
|
10
|
-
let opt = _opt;
|
|
11
|
-
if (util.isFunction(_opt)) {
|
|
12
|
-
opt = {
|
|
13
|
-
create: _opt,
|
|
14
|
-
maxsize: maxsize,
|
|
15
|
-
timeout: timeout
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const create = opt.create;
|
|
19
|
-
const destroy = opt.destroy || ((o) => {
|
|
20
|
-
if (util.isFunction(o.close))
|
|
21
|
-
o.close();
|
|
22
|
-
if (util.isFunction(o.destroy))
|
|
23
|
-
o.destroy();
|
|
24
|
-
if (util.isFunction(o.dispose))
|
|
25
|
-
o.dispose();
|
|
26
|
-
});
|
|
27
|
-
maxsize = opt.maxsize || 10;
|
|
28
|
-
timeout = opt.timeout || 60000;
|
|
29
|
-
let tm = timeout / 10;
|
|
30
|
-
if (tm < 10)
|
|
31
|
-
tm = 10;
|
|
32
|
-
const retry = opt.retry || 1;
|
|
33
|
-
let pools = [];
|
|
34
|
-
const jobs = [];
|
|
35
|
-
let count = 0;
|
|
36
|
-
let running = 0;
|
|
37
|
-
const sem = new coroutine.Semaphore(maxsize);
|
|
38
|
-
let clearTimer;
|
|
39
|
-
function clearPool() {
|
|
40
|
-
let c;
|
|
41
|
-
const d = new Date().getTime();
|
|
42
|
-
while (count) {
|
|
43
|
-
c = pools[0];
|
|
44
|
-
if (d - c.time.getTime() > timeout) {
|
|
45
|
-
pools = pools.slice(1);
|
|
46
|
-
count--;
|
|
47
|
-
if (c.o !== undefined)
|
|
48
|
-
coroutine.start(destroy, c.o);
|
|
49
|
-
}
|
|
50
|
-
else
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
if (!count) {
|
|
54
|
-
if (clearTimer) {
|
|
55
|
-
clearTimer.clear();
|
|
56
|
-
clearTimer = null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else if (!clearTimer)
|
|
60
|
-
clearTimer = setInterval(clearPool, tm);
|
|
61
|
-
}
|
|
62
|
-
function putback(name, o, e) {
|
|
63
|
-
e = ensureError(e);
|
|
64
|
-
for (let i = 0; i < jobs.length; i++) {
|
|
65
|
-
const job = jobs[i];
|
|
66
|
-
if (job.name === name) {
|
|
67
|
-
jobs.splice(i, 1);
|
|
68
|
-
job.o = o;
|
|
69
|
-
job.e = e;
|
|
70
|
-
job.ev.set();
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (e === undefined)
|
|
75
|
-
pools[count++] = {
|
|
76
|
-
o: o,
|
|
77
|
-
name: name,
|
|
78
|
-
time: new Date()
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function connect(name) {
|
|
82
|
-
let o;
|
|
83
|
-
let cn = 0;
|
|
84
|
-
let err;
|
|
85
|
-
while (true) {
|
|
86
|
-
try {
|
|
87
|
-
o = create(name);
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
catch (e) {
|
|
91
|
-
console.log(e);
|
|
92
|
-
if (++cn >= retry) {
|
|
93
|
-
err = e;
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
putback(name, o, err);
|
|
99
|
-
}
|
|
100
|
-
const pool = (name, func) => {
|
|
101
|
-
if (typeof name === 'function') {
|
|
102
|
-
func = name;
|
|
103
|
-
name = "";
|
|
104
|
-
}
|
|
105
|
-
let r;
|
|
106
|
-
let o;
|
|
107
|
-
let p = false;
|
|
108
|
-
clearPool();
|
|
109
|
-
sem.acquire();
|
|
110
|
-
if (count) {
|
|
111
|
-
for (let i = count - 1; i >= 0; i--)
|
|
112
|
-
if (pools[i].name === name) {
|
|
113
|
-
p = true;
|
|
114
|
-
o = pools[i].o;
|
|
115
|
-
pools.splice(i, 1);
|
|
116
|
-
count--;
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (!p) {
|
|
121
|
-
coroutine.start(connect, name);
|
|
122
|
-
const job = {
|
|
123
|
-
name: name,
|
|
124
|
-
ev: new coroutine.Event()
|
|
125
|
-
};
|
|
126
|
-
jobs.push(job);
|
|
127
|
-
job.ev.wait();
|
|
128
|
-
if (job.e) {
|
|
129
|
-
sem.post();
|
|
130
|
-
throw ensureError(job.e);
|
|
131
|
-
}
|
|
132
|
-
o = job.o;
|
|
133
|
-
}
|
|
134
|
-
running++;
|
|
135
|
-
try {
|
|
136
|
-
r = func(o);
|
|
137
|
-
putback(name, o);
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
if (o !== undefined)
|
|
141
|
-
coroutine.start(destroy, o);
|
|
142
|
-
throw ensureError(e);
|
|
143
|
-
}
|
|
144
|
-
finally {
|
|
145
|
-
running--;
|
|
146
|
-
sem.post();
|
|
147
|
-
clearPool();
|
|
148
|
-
}
|
|
149
|
-
return r;
|
|
150
|
-
};
|
|
151
|
-
pool.connections = () => count;
|
|
152
|
-
pool.info = () => ({
|
|
153
|
-
maxsize: maxsize,
|
|
154
|
-
count: count,
|
|
155
|
-
running: running,
|
|
156
|
-
wait: sem.count(),
|
|
157
|
-
timeout: timeout
|
|
158
|
-
});
|
|
159
|
-
pool.clear = () => {
|
|
160
|
-
timeout = -1;
|
|
161
|
-
clearPool();
|
|
162
|
-
};
|
|
163
|
-
return pool;
|
|
164
|
-
};
|
package/typings/index.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
declare namespace FibPoolNS {
|
|
2
|
-
type FirstParameter<T> = T extends (arg: infer T, ...args: any[]) => any ? T : never;
|
|
3
|
-
interface DippedItemStruct {
|
|
4
|
-
close?: FibPoolOptionResult['destroy'];
|
|
5
|
-
destroy?: FibPoolOptionResult['destroy'];
|
|
6
|
-
dispose?: FibPoolOptionResult['destroy'];
|
|
7
|
-
}
|
|
8
|
-
interface FibPoolOptionArgs<T extends DippedItemStruct = {}> {
|
|
9
|
-
create: (name: string) => T;
|
|
10
|
-
destroy?: (item?: T) => any;
|
|
11
|
-
maxsize?: number;
|
|
12
|
-
timeout?: number;
|
|
13
|
-
retry?: number | boolean;
|
|
14
|
-
}
|
|
15
|
-
interface FibPoolOptionResult<T extends DippedItemStruct = {}> {
|
|
16
|
-
create: FibPoolOptionArgs<T>['create'];
|
|
17
|
-
destroy: FibPoolOptionArgs<T>['destroy'];
|
|
18
|
-
maxsize: FibPoolOptionArgs<T>['maxsize'];
|
|
19
|
-
timeout: FibPoolOptionArgs<T>['timeout'];
|
|
20
|
-
retry: FibPoolOptionArgs<T>['retry'];
|
|
21
|
-
}
|
|
22
|
-
interface FibPoolDipperFn<T_CREATED_ITEM, T_POOL_FUNC_RETURN = any> {
|
|
23
|
-
(o: T_CREATED_ITEM): T_POOL_FUNC_RETURN;
|
|
24
|
-
}
|
|
25
|
-
type FibPool<T_CREATED_ITEM, T_POOL_FUNC_RETURN = any> = {
|
|
26
|
-
<T_RETURN = T_POOL_FUNC_RETURN>(name: string, func: FibPoolDipperFn<T_CREATED_ITEM, T_RETURN>): T_RETURN;
|
|
27
|
-
<T_RETURN = T_POOL_FUNC_RETURN>(func: FibPoolDipperFn<T_CREATED_ITEM, T_RETURN>): T_RETURN;
|
|
28
|
-
} & {
|
|
29
|
-
connections?(): number;
|
|
30
|
-
info?(): {
|
|
31
|
-
maxsize: number;
|
|
32
|
-
count: number;
|
|
33
|
-
running: number;
|
|
34
|
-
wait: number;
|
|
35
|
-
timeout: number;
|
|
36
|
-
};
|
|
37
|
-
clear?(): void;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
declare const _default: <T_CREATED_ITEM extends FibPoolNS.DippedItemStruct, T_POOL_FUNC_RETURN>(_opt: FibPoolNS.FibPoolOptionArgs<T_CREATED_ITEM> | ((name: string) => T_CREATED_ITEM), maxsize?: number, timeout?: number) => FibPoolNS.FibPool<T_CREATED_ITEM, T_POOL_FUNC_RETURN>;
|
|
41
|
-
export = _default;
|