@abtnode/util 1.16.44 → 1.16.45-beta-20250609-025419-7fd1f86c
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/package.json +11 -11
- package/lib/lock-with-file.js +0 -142
- package/lib/single-flight-lru-cache.js +0 -80
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.
|
|
6
|
+
"version": "1.16.45-beta-20250609-025419-7fd1f86c",
|
|
7
7
|
"description": "ArcBlock's JavaScript utility",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -18,17 +18,18 @@
|
|
|
18
18
|
"author": "polunzh <polunzh@gmail.com> (http://github.com/polunzh)",
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@abtnode/constant": "1.16.
|
|
22
|
-
"@
|
|
21
|
+
"@abtnode/constant": "1.16.45-beta-20250609-025419-7fd1f86c",
|
|
22
|
+
"@abtnode/db-cache": "1.16.45-beta-20250609-025419-7fd1f86c",
|
|
23
|
+
"@arcblock/did": "1.20.14",
|
|
23
24
|
"@arcblock/pm2": "^5.4.0",
|
|
24
|
-
"@blocklet/constant": "1.16.
|
|
25
|
+
"@blocklet/constant": "1.16.45-beta-20250609-025419-7fd1f86c",
|
|
25
26
|
"@blocklet/error": "^0.2.5",
|
|
26
|
-
"@blocklet/meta": "1.16.
|
|
27
|
+
"@blocklet/meta": "1.16.45-beta-20250609-025419-7fd1f86c",
|
|
27
28
|
"@blocklet/xss": "^0.1.36",
|
|
28
|
-
"@ocap/client": "1.20.
|
|
29
|
-
"@ocap/mcrypto": "1.20.
|
|
30
|
-
"@ocap/util": "1.20.
|
|
31
|
-
"@ocap/wallet": "1.20.
|
|
29
|
+
"@ocap/client": "1.20.14",
|
|
30
|
+
"@ocap/mcrypto": "1.20.14",
|
|
31
|
+
"@ocap/util": "1.20.14",
|
|
32
|
+
"@ocap/wallet": "1.20.14",
|
|
32
33
|
"archiver": "^7.0.1",
|
|
33
34
|
"axios": "^1.7.9",
|
|
34
35
|
"axios-mock-adapter": "^2.1.0",
|
|
@@ -55,7 +56,6 @@
|
|
|
55
56
|
"is-url": "^1.2.4",
|
|
56
57
|
"json-stable-stringify": "^1.0.1",
|
|
57
58
|
"lodash": "^4.17.21",
|
|
58
|
-
"lru-cache": "^11.0.2",
|
|
59
59
|
"minimatch": "^10.0.1",
|
|
60
60
|
"multiformats": "9.9.0",
|
|
61
61
|
"npm-packlist": "^7.0.4",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"fs-extra": "^11.2.0",
|
|
89
89
|
"jest": "^29.7.0"
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "0457b82da528653a1b903149ea1fb1966c31bee3"
|
|
92
92
|
}
|
package/lib/lock-with-file.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const debug = require('debug')('@abtnode/util:lock-with-file');
|
|
4
|
-
|
|
5
|
-
class LockFile {
|
|
6
|
-
constructor(lockDir, timeout = 1000 * 60 * 2) {
|
|
7
|
-
this.lockDir = lockDir;
|
|
8
|
-
this.timeout = timeout;
|
|
9
|
-
if (lockDir && !fs.existsSync(lockDir)) {
|
|
10
|
-
try {
|
|
11
|
-
fs.mkdirSync(lockDir, { recursive: true });
|
|
12
|
-
} catch (err) {
|
|
13
|
-
debug('create lock dir failed', { lockDir, err });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 创建锁文件(原子操作)
|
|
20
|
-
* @param {string} lockName 锁文件名称
|
|
21
|
-
* @returns {boolean} 是否成功创建锁
|
|
22
|
-
*/
|
|
23
|
-
createLock(lockName) {
|
|
24
|
-
if (!this.lockDir) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
const lockFile = path.join(this.lockDir, `${lockName}.lock`);
|
|
28
|
-
try {
|
|
29
|
-
debug('create lock file', { lockFile });
|
|
30
|
-
if (this.lockDir && !fs.existsSync(this.lockDir)) {
|
|
31
|
-
fs.mkdirSync(this.lockDir, { recursive: true });
|
|
32
|
-
}
|
|
33
|
-
const fd = fs.openSync(lockFile, 'wx'); // 原子操作创建文件, 如果文件已存在则失败
|
|
34
|
-
fs.writeSync(fd, Date.now().toString());
|
|
35
|
-
fs.closeSync(fd);
|
|
36
|
-
return true;
|
|
37
|
-
} catch (err) {
|
|
38
|
-
debug('create lock file failed', { lockName, err });
|
|
39
|
-
if (err.code === 'EEXIST') {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
throw err;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 检查锁文件是否过期
|
|
48
|
-
* @param {string} lockName 锁文件名称
|
|
49
|
-
* @returns {boolean} 是否已过期
|
|
50
|
-
*/
|
|
51
|
-
isExpired(lockName) {
|
|
52
|
-
if (!this.lockDir) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
const lockFile = path.join(this.lockDir, `${lockName}.lock`);
|
|
56
|
-
if (!fs.existsSync(lockFile)) {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const lockTime = parseInt(fs.readFileSync(lockFile, 'utf8'), 10);
|
|
61
|
-
debug('check lock file expired', { lockName, lockTime });
|
|
62
|
-
return Date.now() - lockTime > this.timeout;
|
|
63
|
-
} catch (err) {
|
|
64
|
-
debug('check lock file expired failed', { lockName, err });
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 释放锁文件
|
|
71
|
-
* @param {string} lockName 锁文件名称
|
|
72
|
-
*/
|
|
73
|
-
releaseLock(lockName) {
|
|
74
|
-
if (!this.lockDir) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const lockFile = path.join(this.lockDir, `${lockName}.lock`);
|
|
78
|
-
try {
|
|
79
|
-
if (fs.existsSync(lockFile)) {
|
|
80
|
-
debug('release lock file', { lockName });
|
|
81
|
-
fs.rmSync(lockFile, { force: true });
|
|
82
|
-
}
|
|
83
|
-
} catch (err) {
|
|
84
|
-
debug('release lock file failed', { lockName, err });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* 尝试获取锁文件(自动处理过期锁)
|
|
90
|
-
* @param {string} lockName 锁文件名称
|
|
91
|
-
* @returns {boolean} 是否成功获取锁
|
|
92
|
-
*/
|
|
93
|
-
tryLock(lockName) {
|
|
94
|
-
if (!this.lockDir) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
if (this.createLock(lockName)) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
if (this.isExpired(lockName)) {
|
|
101
|
-
this.releaseLock(lockName);
|
|
102
|
-
return this.createLock(lockName);
|
|
103
|
-
}
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
waitUnLock(lockName, timeout = 1000 * 60 * 2) {
|
|
108
|
-
if (!this.lockDir) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
const start = Date.now();
|
|
112
|
-
const lockFile = path.join(this.lockDir, `${lockName}.lock`);
|
|
113
|
-
debug('wait unlock', { lockName });
|
|
114
|
-
|
|
115
|
-
return new Promise((resolve) => {
|
|
116
|
-
const interval = setInterval(() => {
|
|
117
|
-
debug('check unlock', { lockName });
|
|
118
|
-
if (!fs.existsSync(lockFile) || this.isExpired(lockName)) {
|
|
119
|
-
clearInterval(interval);
|
|
120
|
-
resolve(true);
|
|
121
|
-
} else if (Date.now() - start >= timeout) {
|
|
122
|
-
clearInterval(interval);
|
|
123
|
-
resolve(false);
|
|
124
|
-
}
|
|
125
|
-
}, 200);
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async acquire(lockName) {
|
|
130
|
-
if (!this.tryLock(lockName)) {
|
|
131
|
-
await this.waitUnLock(lockName);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
this.tryLock(lockName);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
release(lockName) {
|
|
138
|
-
this.releaseLock(lockName);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
module.exports = LockFile;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
const { LRUCache } = require('lru-cache');
|
|
2
|
-
const pWaitFor = require('p-wait-for');
|
|
3
|
-
|
|
4
|
-
const PADDING_VALUE = '__padding__';
|
|
5
|
-
|
|
6
|
-
// Single flight lru cache
|
|
7
|
-
// ref: https://www.codingexplorations.com/blog/understanding-singleflight-in-golang-a-solution-for-eliminating-redundant-work
|
|
8
|
-
class SingleFlightLRUCache extends LRUCache {
|
|
9
|
-
paddingInterval = 100;
|
|
10
|
-
|
|
11
|
-
paddingTimeout = 10 * 1000;
|
|
12
|
-
|
|
13
|
-
setPadding(key) {
|
|
14
|
-
this.set(key, PADDING_VALUE);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
isPadding(key) {
|
|
18
|
-
return this.get(key) === PADDING_VALUE;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async waitPending(key) {
|
|
22
|
-
if (this.get(key) === PADDING_VALUE) {
|
|
23
|
-
try {
|
|
24
|
-
await pWaitFor(() => this.get(key) !== PADDING_VALUE, {
|
|
25
|
-
interval: this.paddingInterval,
|
|
26
|
-
timeout: this.paddingTimeout,
|
|
27
|
-
});
|
|
28
|
-
} catch (_) {
|
|
29
|
-
if (this.get(key) === PADDING_VALUE) {
|
|
30
|
-
this.delete(key);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return this.get(key);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async wrapPending(key, fn) {
|
|
38
|
-
if (!key) {
|
|
39
|
-
return Promise.resolve(fn());
|
|
40
|
-
}
|
|
41
|
-
const old = this.get(key);
|
|
42
|
-
if (old) {
|
|
43
|
-
if (old === PADDING_VALUE) {
|
|
44
|
-
const result = await this.waitPending(key);
|
|
45
|
-
if (result && result !== PADDING_VALUE) {
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
return old;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
this.setPadding(key);
|
|
53
|
-
try {
|
|
54
|
-
const result = await Promise.resolve(fn());
|
|
55
|
-
return result;
|
|
56
|
-
} finally {
|
|
57
|
-
if (this.get(key) === PADDING_VALUE) {
|
|
58
|
-
this.delete(key);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* autoCache will set the result to the cache if the key is not null
|
|
65
|
-
* if the key is null, it will return the result of the function
|
|
66
|
-
* if the key is not null, it will return the cached result
|
|
67
|
-
* if padding, it will wait for the padding to finish
|
|
68
|
-
* if the key is not in the cache, it will set the result to the cache
|
|
69
|
-
* if the key is in the cache, it will return the cached result
|
|
70
|
-
*/
|
|
71
|
-
async autoCache(key, fn) {
|
|
72
|
-
const result = await this.wrapPending(key, fn);
|
|
73
|
-
if (key) {
|
|
74
|
-
this.set(key, result);
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
module.exports = SingleFlightLRUCache;
|