@atlaspack/cache 3.1.1-canary.9 → 3.1.1-dev.55
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/CHANGELOG.md +61 -0
- package/lib/FSCache.js +21 -0
- package/lib/LMDBLiteCache.js +110 -42
- package/package.json +10 -9
- package/src/FSCache.js +9 -0
- package/src/LMDBLiteCache.js +96 -37
- package/test/LMDBLiteCache.test.js +193 -4
- package/test/workerThreadsTest.js +42 -0
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,66 @@
|
|
1
1
|
# @atlaspack/cache
|
2
2
|
|
3
|
+
## 3.2.4
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- [#583](https://github.com/atlassian-labs/atlaspack/pull/583) [`124b7ff`](https://github.com/atlassian-labs/atlaspack/commit/124b7fff44f71aac9fbad289a9a9509b3dfc9aaa) Thanks [@yamadapc](https://github.com/yamadapc)! - Fix problem where cache writes could start to fail during a V3 build
|
8
|
+
|
9
|
+
- Updated dependencies [[`124b7ff`](https://github.com/atlassian-labs/atlaspack/commit/124b7fff44f71aac9fbad289a9a9509b3dfc9aaa), [`e052521`](https://github.com/atlassian-labs/atlaspack/commit/e0525210850ed1606146eb86991049cf567c5dec), [`15c6d70`](https://github.com/atlassian-labs/atlaspack/commit/15c6d7000bd89da876bc590aa75b17a619a41896), [`e4d966c`](https://github.com/atlassian-labs/atlaspack/commit/e4d966c3c9c4292c5013372ae65b10d19d4bacc6), [`209692f`](https://github.com/atlassian-labs/atlaspack/commit/209692ffb11eae103a0d65c5e1118a5aa1625818), [`42a775d`](https://github.com/atlassian-labs/atlaspack/commit/42a775de8eec638ad188f3271964170d8c04d84b), [`29c2f10`](https://github.com/atlassian-labs/atlaspack/commit/29c2f106de9679adfb5afa04e1910471dc65a427), [`f4da1e1`](https://github.com/atlassian-labs/atlaspack/commit/f4da1e120e73eeb5e8b8927f05e88f04d6148c7b), [`1ef91fc`](https://github.com/atlassian-labs/atlaspack/commit/1ef91fcc863fdd2831511937083dbbc1263b3d9d)]:
|
10
|
+
- @atlaspack/rust@3.3.4
|
11
|
+
- @atlaspack/fs@2.15.4
|
12
|
+
- @atlaspack/feature-flags@2.16.0
|
13
|
+
- @atlaspack/logger@2.14.9
|
14
|
+
- @atlaspack/utils@2.14.9
|
15
|
+
|
16
|
+
## 3.2.3
|
17
|
+
|
18
|
+
### Patch Changes
|
19
|
+
|
20
|
+
- Updated dependencies [[`30f6017`](https://github.com/atlassian-labs/atlaspack/commit/30f60175ba4d272c5fc193973c63bc298584775b), [`3a3e8e7`](https://github.com/atlassian-labs/atlaspack/commit/3a3e8e7be9e2dffd7304436d792f0f595d59665a), [`1ab0a27`](https://github.com/atlassian-labs/atlaspack/commit/1ab0a275aeca40350415e2b03e7440d1dddc6228), [`b8a4ae8`](https://github.com/atlassian-labs/atlaspack/commit/b8a4ae8f83dc0a83d8b145c5f729936ce52080a3)]:
|
21
|
+
- @atlaspack/feature-flags@2.15.1
|
22
|
+
- @atlaspack/fs@2.15.3
|
23
|
+
- @atlaspack/rust@3.3.3
|
24
|
+
- @atlaspack/utils@2.14.8
|
25
|
+
- @atlaspack/logger@2.14.8
|
26
|
+
|
27
|
+
## 3.2.2
|
28
|
+
|
29
|
+
### Patch Changes
|
30
|
+
|
31
|
+
- Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
|
32
|
+
- @atlaspack/feature-flags@2.15.0
|
33
|
+
- @atlaspack/logger@2.14.7
|
34
|
+
- @atlaspack/rust@3.3.2
|
35
|
+
- @atlaspack/fs@2.15.2
|
36
|
+
- @atlaspack/utils@2.14.7
|
37
|
+
|
38
|
+
## 3.2.1
|
39
|
+
|
40
|
+
### Patch Changes
|
41
|
+
|
42
|
+
- Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
|
43
|
+
- @atlaspack/feature-flags@2.14.4
|
44
|
+
- @atlaspack/rust@3.3.1
|
45
|
+
- @atlaspack/fs@2.15.1
|
46
|
+
- @atlaspack/utils@2.14.6
|
47
|
+
- @atlaspack/logger@2.14.6
|
48
|
+
|
49
|
+
## 3.2.0
|
50
|
+
|
51
|
+
### Minor Changes
|
52
|
+
|
53
|
+
- [#531](https://github.com/atlassian-labs/atlaspack/pull/531) [`d2c50c2`](https://github.com/atlassian-labs/atlaspack/commit/d2c50c2c020888b33bb25b8690d9320c2b69e2a6) Thanks [@yamadapc](https://github.com/yamadapc)! - Add way to iterate LMDB cache keys
|
54
|
+
|
55
|
+
### Patch Changes
|
56
|
+
|
57
|
+
- Updated dependencies [[`11d6f16`](https://github.com/atlassian-labs/atlaspack/commit/11d6f16b6397dee2f217167e5c98b39edb63f7a7), [`e2ba0f6`](https://github.com/atlassian-labs/atlaspack/commit/e2ba0f69702656f3d1ce95ab1454e35062b13b39), [`d2c50c2`](https://github.com/atlassian-labs/atlaspack/commit/d2c50c2c020888b33bb25b8690d9320c2b69e2a6), [`46a90dc`](https://github.com/atlassian-labs/atlaspack/commit/46a90dccd019a26b222c878a92d23acc75dc67c5), [`4c17141`](https://github.com/atlassian-labs/atlaspack/commit/4c1714103dab2aa9039c488f381551d2b65d1d01)]:
|
58
|
+
- @atlaspack/feature-flags@2.14.3
|
59
|
+
- @atlaspack/rust@3.3.0
|
60
|
+
- @atlaspack/fs@2.15.0
|
61
|
+
- @atlaspack/utils@2.14.5
|
62
|
+
- @atlaspack/logger@2.14.5
|
63
|
+
|
3
64
|
## 3.1.0
|
4
65
|
|
5
66
|
### Minor Changes
|
package/lib/FSCache.js
CHANGED
@@ -25,6 +25,20 @@ function _util() {
|
|
25
25
|
};
|
26
26
|
return data;
|
27
27
|
}
|
28
|
+
function _rust() {
|
29
|
+
const data = require("@atlaspack/rust");
|
30
|
+
_rust = function () {
|
31
|
+
return data;
|
32
|
+
};
|
33
|
+
return data;
|
34
|
+
}
|
35
|
+
function _featureFlags() {
|
36
|
+
const data = require("@atlaspack/feature-flags");
|
37
|
+
_featureFlags = function () {
|
38
|
+
return data;
|
39
|
+
};
|
40
|
+
return data;
|
41
|
+
}
|
28
42
|
function _logger() {
|
29
43
|
const data = _interopRequireDefault(require("@atlaspack/logger"));
|
30
44
|
_logger = function () {
|
@@ -62,6 +76,10 @@ class FSCache {
|
|
62
76
|
await Promise.all(dirPromises);
|
63
77
|
}
|
64
78
|
_getCachePath(cacheId) {
|
79
|
+
if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
80
|
+
const cleanId = (0, _rust().hashString)(cacheId);
|
81
|
+
return _path().default.join(this.dir, cleanId.slice(0, 2), cleanId.slice(2));
|
82
|
+
}
|
65
83
|
return _path().default.join(this.dir, cacheId.slice(0, 2), cacheId.slice(2));
|
66
84
|
}
|
67
85
|
getStream(key) {
|
@@ -91,6 +109,9 @@ class FSCache {
|
|
91
109
|
}
|
92
110
|
}
|
93
111
|
#getFilePath(key, index) {
|
112
|
+
if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
113
|
+
return _path().default.join(this.dir, `${(0, _rust().hashString)(key)}-${index}`);
|
114
|
+
}
|
94
115
|
return _path().default.join(this.dir, `${key}-${index}`);
|
95
116
|
}
|
96
117
|
async #unlinkChunks(key, index) {
|
package/lib/LMDBLiteCache.js
CHANGED
@@ -26,16 +26,16 @@ function _rust() {
|
|
26
26
|
};
|
27
27
|
return data;
|
28
28
|
}
|
29
|
-
function
|
30
|
-
const data = _interopRequireDefault(require("
|
31
|
-
|
29
|
+
function _fs() {
|
30
|
+
const data = _interopRequireDefault(require("fs"));
|
31
|
+
_fs = function () {
|
32
32
|
return data;
|
33
33
|
};
|
34
34
|
return data;
|
35
35
|
}
|
36
|
-
function
|
37
|
-
const data = _interopRequireDefault(require("
|
38
|
-
|
36
|
+
function _ncp() {
|
37
|
+
const data = _interopRequireDefault(require("ncp"));
|
38
|
+
_ncp = function () {
|
39
39
|
return data;
|
40
40
|
};
|
41
41
|
return data;
|
@@ -47,25 +47,42 @@ function _util() {
|
|
47
47
|
};
|
48
48
|
return data;
|
49
49
|
}
|
50
|
-
function
|
50
|
+
function _stream() {
|
51
|
+
const data = _interopRequireDefault(require("stream"));
|
52
|
+
_stream = function () {
|
53
|
+
return data;
|
54
|
+
};
|
55
|
+
return data;
|
56
|
+
}
|
57
|
+
function _path() {
|
58
|
+
const data = _interopRequireDefault(require("path"));
|
59
|
+
_path = function () {
|
60
|
+
return data;
|
61
|
+
};
|
62
|
+
return data;
|
63
|
+
}
|
64
|
+
function _fs2() {
|
51
65
|
const data = require("@atlaspack/fs");
|
52
|
-
|
66
|
+
_fs2 = function () {
|
53
67
|
return data;
|
54
68
|
};
|
55
69
|
return data;
|
56
70
|
}
|
57
71
|
var _package = _interopRequireDefault(require("../package.json"));
|
58
72
|
var _FSCache = require("./FSCache");
|
73
|
+
function _logger() {
|
74
|
+
const data = require("@atlaspack/logger");
|
75
|
+
_logger = function () {
|
76
|
+
return data;
|
77
|
+
};
|
78
|
+
return data;
|
79
|
+
}
|
59
80
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
60
81
|
// $FlowFixMe
|
82
|
+
const ncpAsync = (0, _util().promisify)(_ncp().default);
|
61
83
|
class LmdbWrapper {
|
62
84
|
constructor(lmdb) {
|
63
85
|
this.lmdb = lmdb;
|
64
|
-
|
65
|
-
// $FlowFixMe
|
66
|
-
this[Symbol.dispose] = () => {
|
67
|
-
this.lmdb.close();
|
68
|
-
};
|
69
86
|
}
|
70
87
|
has(key) {
|
71
88
|
return this.lmdb.hasSync(key);
|
@@ -90,23 +107,30 @@ class LmdbWrapper {
|
|
90
107
|
currentKeys = this.lmdb.keysSync(currentKeys.length, PAGE_SIZE);
|
91
108
|
}
|
92
109
|
}
|
93
|
-
|
110
|
+
compact(targetPath) {
|
111
|
+
this.lmdb.compact(targetPath);
|
112
|
+
}
|
94
113
|
}
|
95
114
|
exports.LmdbWrapper = LmdbWrapper;
|
96
|
-
function open(directory
|
115
|
+
function open(directory,
|
97
116
|
// eslint-disable-next-line no-unused-vars
|
98
|
-
) {
|
117
|
+
openOptions) {
|
99
118
|
return new LmdbWrapper(new (_rust().Lmdb)({
|
100
119
|
path: directory,
|
101
120
|
asyncWrites: true,
|
102
|
-
mapSize: 1024 * 1024 * 1024 * 15
|
121
|
+
mapSize: process.env.ATLASPACK_BUILD_ENV === 'test' ? 1024 * 1024 * 1024 : 1024 * 1024 * 1024 * 15
|
103
122
|
}));
|
104
123
|
}
|
105
124
|
const pipeline = (0, _util().promisify)(_stream().default.pipeline);
|
106
125
|
class LMDBLiteCache {
|
126
|
+
/**
|
127
|
+
* Directory where we store raw files.
|
128
|
+
*/
|
129
|
+
|
107
130
|
constructor(cacheDir) {
|
108
|
-
this.fs = new (
|
131
|
+
this.fs = new (_fs2().NodeFS)();
|
109
132
|
this.dir = cacheDir;
|
133
|
+
this.cacheFilesDirectory = _path().default.join(cacheDir, 'files');
|
110
134
|
this.fsCache = new _FSCache.FSCache(this.fs, cacheDir);
|
111
135
|
this.store = open(cacheDir, {
|
112
136
|
name: 'parcel-cache',
|
@@ -125,6 +149,7 @@ class LMDBLiteCache {
|
|
125
149
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
126
150
|
await this.fsCache.ensure();
|
127
151
|
}
|
152
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
128
153
|
return Promise.resolve();
|
129
154
|
}
|
130
155
|
serialize() {
|
@@ -149,10 +174,20 @@ class LMDBLiteCache {
|
|
149
174
|
await this.setBlob(key, (0, _buildCache().serialize)(value));
|
150
175
|
}
|
151
176
|
getStream(key) {
|
152
|
-
|
177
|
+
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
178
|
+
return this.fs.createReadStream(_path().default.join(this.dir, key));
|
179
|
+
}
|
180
|
+
return _fs().default.createReadStream(this.getFileKey(key));
|
153
181
|
}
|
154
|
-
setStream(key, stream) {
|
155
|
-
|
182
|
+
async setStream(key, stream) {
|
183
|
+
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
184
|
+
return pipeline(stream, this.fs.createWriteStream(_path().default.join(this.dir, key)));
|
185
|
+
}
|
186
|
+
const filePath = this.getFileKey(key);
|
187
|
+
await _fs().default.promises.mkdir(_path().default.dirname(filePath), {
|
188
|
+
recursive: true
|
189
|
+
});
|
190
|
+
return pipeline(stream, _fs().default.createWriteStream(filePath));
|
156
191
|
}
|
157
192
|
|
158
193
|
// eslint-disable-next-line require-await
|
@@ -172,34 +207,27 @@ class LMDBLiteCache {
|
|
172
207
|
getBuffer(key) {
|
173
208
|
return Promise.resolve(this.store.get(key));
|
174
209
|
}
|
175
|
-
#getFilePath(key, index) {
|
176
|
-
return _path().default.join(this.dir, `${key}-${index}`);
|
177
|
-
}
|
178
210
|
hasLargeBlob(key) {
|
179
211
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
180
212
|
return this.fsCache.hasLargeBlob(key);
|
181
213
|
}
|
182
|
-
return this.
|
214
|
+
return _fs().default.promises.access(this.getFileKey(key), _fs().default.constants.F_OK).then(() => true).catch(() => false);
|
183
215
|
}
|
184
|
-
|
185
|
-
/**
|
186
|
-
* @deprecated Use getBlob instead.
|
187
|
-
*/
|
188
216
|
getLargeBlob(key) {
|
189
217
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
190
218
|
return this.fsCache.getLargeBlob(key);
|
191
219
|
}
|
192
|
-
return
|
220
|
+
return _fs().default.promises.readFile(this.getFileKey(key));
|
193
221
|
}
|
194
|
-
|
195
|
-
/**
|
196
|
-
* @deprecated Use setBlob instead.
|
197
|
-
*/
|
198
|
-
setLargeBlob(key, contents, options) {
|
222
|
+
async setLargeBlob(key, contents, options) {
|
199
223
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
200
224
|
return this.fsCache.setLargeBlob(key, contents, options);
|
201
225
|
}
|
202
|
-
|
226
|
+
const targetPath = this.getFileKey(key);
|
227
|
+
await _fs().default.promises.mkdir(_path().default.dirname(targetPath), {
|
228
|
+
recursive: true
|
229
|
+
});
|
230
|
+
return _fs().default.promises.writeFile(targetPath, contents);
|
203
231
|
}
|
204
232
|
|
205
233
|
/**
|
@@ -214,12 +242,52 @@ class LMDBLiteCache {
|
|
214
242
|
keys() {
|
215
243
|
return this.store.keys();
|
216
244
|
}
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
245
|
+
async compact(targetPath) {
|
246
|
+
await _fs().default.promises.mkdir(targetPath, {
|
247
|
+
recursive: true
|
248
|
+
});
|
249
|
+
const files = await _fs().default.promises.readdir(this.dir);
|
250
|
+
// copy all files except data.mdb and lock.mdb to the target path (recursive)
|
251
|
+
for (const file of files) {
|
252
|
+
const filePath = _path().default.join(this.dir, file);
|
253
|
+
if (file === 'data.mdb' || file === 'lock.mdb') {
|
254
|
+
continue;
|
255
|
+
}
|
256
|
+
await ncpAsync(filePath, _path().default.join(targetPath, file));
|
257
|
+
}
|
258
|
+
this.store.compact(_path().default.join(targetPath, 'data.mdb'));
|
259
|
+
}
|
260
|
+
refresh() {}
|
261
|
+
|
262
|
+
/**
|
263
|
+
* Streams, packages are stored in files instead of LMDB.
|
264
|
+
*
|
265
|
+
* On this case, if a cache key happens to have a parent traversal, ../..
|
266
|
+
* it is treated specially
|
267
|
+
*
|
268
|
+
* That is, something/../something and something are meant to be different
|
269
|
+
* keys.
|
270
|
+
*
|
271
|
+
* Plus we do not want to store values outside of the cache directory.
|
272
|
+
*/
|
273
|
+
getFileKey(key) {
|
274
|
+
const cleanKey = key.split('/').map(part => {
|
275
|
+
if (part === '..') {
|
276
|
+
return '$$__parent_dir$$';
|
277
|
+
}
|
278
|
+
return part;
|
279
|
+
}).join('/');
|
280
|
+
return _path().default.join(this.cacheFilesDirectory, cleanKey);
|
281
|
+
}
|
282
|
+
async clear() {
|
283
|
+
await (0, _logger().instrumentAsync)('LMDBLiteCache::clear', async () => {
|
284
|
+
const keys = await this.keys();
|
285
|
+
for (const key of keys) {
|
286
|
+
await this.store.delete(key);
|
287
|
+
}
|
288
|
+
await this.fs.rimraf(this.cacheFilesDirectory);
|
289
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
290
|
+
});
|
223
291
|
}
|
224
292
|
}
|
225
293
|
exports.LMDBLiteCache = LMDBLiteCache;
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@atlaspack/cache",
|
3
3
|
"description": "Interface for defining caches and file-system, IDB and LMDB implementations.",
|
4
|
-
"version": "3.1.1-
|
4
|
+
"version": "3.1.1-dev.55+5a11f33c5",
|
5
5
|
"license": "(MIT OR Apache-2.0)",
|
6
6
|
"type": "commonjs",
|
7
7
|
"publishConfig": {
|
@@ -20,15 +20,16 @@
|
|
20
20
|
"scripts": {
|
21
21
|
"test": "mocha",
|
22
22
|
"build-ts": "mkdir -p lib && flow-to-ts src/types.js > lib/types.d.ts",
|
23
|
-
"check-ts": "tsc --noEmit index.d.ts"
|
23
|
+
"check-ts": "tsc --module node16 --moduleResolution node16 --noEmit index.d.ts"
|
24
24
|
},
|
25
25
|
"dependencies": {
|
26
|
-
"@atlaspack/build-cache": "2.13.3-
|
27
|
-
"@atlaspack/feature-flags": "2.14.1-
|
28
|
-
"@atlaspack/fs": "2.14.5-
|
29
|
-
"@atlaspack/logger": "2.14.5-
|
30
|
-
"@atlaspack/rust": "3.2.1-
|
31
|
-
"@atlaspack/utils": "2.14.5-
|
26
|
+
"@atlaspack/build-cache": "2.13.3-dev.123+5a11f33c5",
|
27
|
+
"@atlaspack/feature-flags": "2.14.1-dev.123+5a11f33c5",
|
28
|
+
"@atlaspack/fs": "2.14.5-dev.55+5a11f33c5",
|
29
|
+
"@atlaspack/logger": "2.14.5-dev.55+5a11f33c5",
|
30
|
+
"@atlaspack/rust": "3.2.1-dev.55+5a11f33c5",
|
31
|
+
"@atlaspack/utils": "2.14.5-dev.55+5a11f33c5",
|
32
|
+
"ncp": "^2.0.0"
|
32
33
|
},
|
33
34
|
"devDependencies": {
|
34
35
|
"idb": "^5.0.8"
|
@@ -36,5 +37,5 @@
|
|
36
37
|
"browser": {
|
37
38
|
"./src/IDBCache.js": "./src/IDBCache.browser.js"
|
38
39
|
},
|
39
|
-
"gitHead": "
|
40
|
+
"gitHead": "5a11f33c51ff74d1cf8d4b72cfa0fda833aa980a"
|
40
41
|
}
|
package/src/FSCache.js
CHANGED
@@ -9,6 +9,8 @@ import stream from 'stream';
|
|
9
9
|
import path from 'path';
|
10
10
|
import {promisify} from 'util';
|
11
11
|
|
12
|
+
import {hashString} from '@atlaspack/rust';
|
13
|
+
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
12
14
|
import logger from '@atlaspack/logger';
|
13
15
|
import {
|
14
16
|
deserialize,
|
@@ -51,6 +53,10 @@ export class FSCache implements Cache {
|
|
51
53
|
}
|
52
54
|
|
53
55
|
_getCachePath(cacheId: string): FilePath {
|
56
|
+
if (getFeatureFlag('cachePerformanceImprovements')) {
|
57
|
+
const cleanId = hashString(cacheId);
|
58
|
+
return path.join(this.dir, cleanId.slice(0, 2), cleanId.slice(2));
|
59
|
+
}
|
54
60
|
return path.join(this.dir, cacheId.slice(0, 2), cacheId.slice(2));
|
55
61
|
}
|
56
62
|
|
@@ -90,6 +96,9 @@ export class FSCache implements Cache {
|
|
90
96
|
}
|
91
97
|
|
92
98
|
#getFilePath(key: string, index: number): string {
|
99
|
+
if (getFeatureFlag('cachePerformanceImprovements')) {
|
100
|
+
return path.join(this.dir, `${hashString(key)}-${index}`);
|
101
|
+
}
|
93
102
|
return path.join(this.dir, `${key}-${index}`);
|
94
103
|
}
|
95
104
|
|
package/src/LMDBLiteCache.js
CHANGED
@@ -10,17 +10,18 @@ import {Lmdb} from '@atlaspack/rust';
|
|
10
10
|
import type {FilePath} from '@atlaspack/types';
|
11
11
|
import type {Cache} from './types';
|
12
12
|
import type {Readable, Writable} from 'stream';
|
13
|
-
|
13
|
+
import fs from 'fs';
|
14
|
+
import ncp from 'ncp';
|
15
|
+
import {promisify} from 'util';
|
14
16
|
import stream from 'stream';
|
15
17
|
import path from 'path';
|
16
|
-
import {promisify} from 'util';
|
17
|
-
|
18
18
|
import {NodeFS} from '@atlaspack/fs';
|
19
|
-
|
20
19
|
// $FlowFixMe
|
21
20
|
import packageJson from '../package.json';
|
22
|
-
|
23
21
|
import {FSCache} from './FSCache';
|
22
|
+
import {instrumentAsync} from '@atlaspack/logger';
|
23
|
+
|
24
|
+
const ncpAsync = promisify(ncp);
|
24
25
|
|
25
26
|
interface DBOpenOptions {
|
26
27
|
name: string;
|
@@ -35,11 +36,6 @@ export class LmdbWrapper {
|
|
35
36
|
|
36
37
|
constructor(lmdb: Lmdb) {
|
37
38
|
this.lmdb = lmdb;
|
38
|
-
|
39
|
-
// $FlowFixMe
|
40
|
-
this[Symbol.dispose] = () => {
|
41
|
-
this.lmdb.close();
|
42
|
-
};
|
43
39
|
}
|
44
40
|
|
45
41
|
has(key: string): boolean {
|
@@ -72,7 +68,9 @@ export class LmdbWrapper {
|
|
72
68
|
}
|
73
69
|
}
|
74
70
|
|
75
|
-
|
71
|
+
compact(targetPath: string) {
|
72
|
+
this.lmdb.compact(targetPath);
|
73
|
+
}
|
76
74
|
}
|
77
75
|
|
78
76
|
export function open(
|
@@ -105,10 +103,15 @@ export class LMDBLiteCache implements Cache {
|
|
105
103
|
dir: FilePath;
|
106
104
|
store: LmdbWrapper;
|
107
105
|
fsCache: FSCache;
|
106
|
+
/**
|
107
|
+
* Directory where we store raw files.
|
108
|
+
*/
|
109
|
+
cacheFilesDirectory: FilePath;
|
108
110
|
|
109
111
|
constructor(cacheDir: FilePath) {
|
110
112
|
this.fs = new NodeFS();
|
111
113
|
this.dir = cacheDir;
|
114
|
+
this.cacheFilesDirectory = path.join(cacheDir, 'files');
|
112
115
|
this.fsCache = new FSCache(this.fs, cacheDir);
|
113
116
|
|
114
117
|
this.store = open(cacheDir, {
|
@@ -129,6 +132,7 @@ export class LMDBLiteCache implements Cache {
|
|
129
132
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
130
133
|
await this.fsCache.ensure();
|
131
134
|
}
|
135
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
132
136
|
return Promise.resolve();
|
133
137
|
}
|
134
138
|
|
@@ -160,14 +164,24 @@ export class LMDBLiteCache implements Cache {
|
|
160
164
|
}
|
161
165
|
|
162
166
|
getStream(key: string): Readable {
|
163
|
-
|
167
|
+
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
168
|
+
return this.fs.createReadStream(path.join(this.dir, key));
|
169
|
+
}
|
170
|
+
|
171
|
+
return fs.createReadStream(this.getFileKey(key));
|
164
172
|
}
|
165
173
|
|
166
|
-
setStream(key: string, stream: Readable): Promise<void> {
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
174
|
+
async setStream(key: string, stream: Readable): Promise<void> {
|
175
|
+
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
176
|
+
return pipeline(
|
177
|
+
stream,
|
178
|
+
this.fs.createWriteStream(path.join(this.dir, key)),
|
179
|
+
);
|
180
|
+
}
|
181
|
+
|
182
|
+
const filePath = this.getFileKey(key);
|
183
|
+
await fs.promises.mkdir(path.dirname(filePath), {recursive: true});
|
184
|
+
return pipeline(stream, fs.createWriteStream(filePath));
|
171
185
|
}
|
172
186
|
|
173
187
|
// eslint-disable-next-line require-await
|
@@ -191,31 +205,25 @@ export class LMDBLiteCache implements Cache {
|
|
191
205
|
return Promise.resolve(this.store.get(key));
|
192
206
|
}
|
193
207
|
|
194
|
-
#getFilePath(key: string, index: number): string {
|
195
|
-
return path.join(this.dir, `${key}-${index}`);
|
196
|
-
}
|
197
|
-
|
198
208
|
hasLargeBlob(key: string): Promise<boolean> {
|
199
209
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
200
210
|
return this.fsCache.hasLargeBlob(key);
|
201
211
|
}
|
202
|
-
|
212
|
+
|
213
|
+
return fs.promises
|
214
|
+
.access(this.getFileKey(key), fs.constants.F_OK)
|
215
|
+
.then(() => true)
|
216
|
+
.catch(() => false);
|
203
217
|
}
|
204
218
|
|
205
|
-
/**
|
206
|
-
* @deprecated Use getBlob instead.
|
207
|
-
*/
|
208
219
|
getLargeBlob(key: string): Promise<Buffer> {
|
209
220
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
210
221
|
return this.fsCache.getLargeBlob(key);
|
211
222
|
}
|
212
|
-
return
|
223
|
+
return fs.promises.readFile(this.getFileKey(key));
|
213
224
|
}
|
214
225
|
|
215
|
-
|
216
|
-
* @deprecated Use setBlob instead.
|
217
|
-
*/
|
218
|
-
setLargeBlob(
|
226
|
+
async setLargeBlob(
|
219
227
|
key: string,
|
220
228
|
contents: Buffer | string,
|
221
229
|
options?: {|signal?: AbortSignal|},
|
@@ -223,7 +231,10 @@ export class LMDBLiteCache implements Cache {
|
|
223
231
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
224
232
|
return this.fsCache.setLargeBlob(key, contents, options);
|
225
233
|
}
|
226
|
-
|
234
|
+
|
235
|
+
const targetPath = this.getFileKey(key);
|
236
|
+
await fs.promises.mkdir(path.dirname(targetPath), {recursive: true});
|
237
|
+
return fs.promises.writeFile(targetPath, contents);
|
227
238
|
}
|
228
239
|
|
229
240
|
/**
|
@@ -241,12 +252,60 @@ export class LMDBLiteCache implements Cache {
|
|
241
252
|
return this.store.keys();
|
242
253
|
}
|
243
254
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
//
|
249
|
-
|
255
|
+
async compact(targetPath: string): Promise<void> {
|
256
|
+
await fs.promises.mkdir(targetPath, {recursive: true});
|
257
|
+
|
258
|
+
const files = await fs.promises.readdir(this.dir);
|
259
|
+
// copy all files except data.mdb and lock.mdb to the target path (recursive)
|
260
|
+
for (const file of files) {
|
261
|
+
const filePath = path.join(this.dir, file);
|
262
|
+
|
263
|
+
if (file === 'data.mdb' || file === 'lock.mdb') {
|
264
|
+
continue;
|
265
|
+
}
|
266
|
+
|
267
|
+
await ncpAsync(filePath, path.join(targetPath, file));
|
268
|
+
}
|
269
|
+
|
270
|
+
this.store.compact(path.join(targetPath, 'data.mdb'));
|
271
|
+
}
|
272
|
+
|
273
|
+
refresh(): void {}
|
274
|
+
|
275
|
+
/**
|
276
|
+
* Streams, packages are stored in files instead of LMDB.
|
277
|
+
*
|
278
|
+
* On this case, if a cache key happens to have a parent traversal, ../..
|
279
|
+
* it is treated specially
|
280
|
+
*
|
281
|
+
* That is, something/../something and something are meant to be different
|
282
|
+
* keys.
|
283
|
+
*
|
284
|
+
* Plus we do not want to store values outside of the cache directory.
|
285
|
+
*/
|
286
|
+
getFileKey(key: string): string {
|
287
|
+
const cleanKey = key
|
288
|
+
.split('/')
|
289
|
+
.map((part) => {
|
290
|
+
if (part === '..') {
|
291
|
+
return '$$__parent_dir$$';
|
292
|
+
}
|
293
|
+
return part;
|
294
|
+
})
|
295
|
+
.join('/');
|
296
|
+
return path.join(this.cacheFilesDirectory, cleanKey);
|
297
|
+
}
|
298
|
+
|
299
|
+
async clear(): Promise<void> {
|
300
|
+
await instrumentAsync('LMDBLiteCache::clear', async () => {
|
301
|
+
const keys = await this.keys();
|
302
|
+
for (const key of keys) {
|
303
|
+
await this.store.delete(key);
|
304
|
+
}
|
305
|
+
|
306
|
+
await this.fs.rimraf(this.cacheFilesDirectory);
|
307
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
308
|
+
});
|
250
309
|
}
|
251
310
|
}
|
252
311
|
|
@@ -6,6 +6,8 @@ import {tmpdir} from 'os';
|
|
6
6
|
import {LMDBLiteCache} from '../src/index';
|
7
7
|
import {deserialize, serialize} from 'v8';
|
8
8
|
import assert from 'assert';
|
9
|
+
import {Worker} from 'worker_threads';
|
10
|
+
import {initializeMonitoring} from '@atlaspack/rust';
|
9
11
|
|
10
12
|
const cacheDir = path.join(tmpdir(), 'lmdb-lite-cache-tests');
|
11
13
|
|
@@ -16,10 +18,6 @@ describe('LMDBLiteCache', () => {
|
|
16
18
|
await fs.promises.rm(cacheDir, {recursive: true, force: true});
|
17
19
|
});
|
18
20
|
|
19
|
-
afterEach(() => {
|
20
|
-
cache.getNativeRef().close();
|
21
|
-
});
|
22
|
-
|
23
21
|
it('can be constructed', async () => {
|
24
22
|
cache = new LMDBLiteCache(cacheDir);
|
25
23
|
await cache.ensure();
|
@@ -51,4 +49,195 @@ describe('LMDBLiteCache', () => {
|
|
51
49
|
const keys = cache.keys();
|
52
50
|
assert.deepEqual(Array.from(keys), ['key1', 'key2']);
|
53
51
|
});
|
52
|
+
|
53
|
+
it('can compact databases', async () => {
|
54
|
+
cache = new LMDBLiteCache(path.join(cacheDir, 'compact_test'));
|
55
|
+
await cache.ensure();
|
56
|
+
await cache.setBlob('key1', Buffer.from(serialize({value: 42})));
|
57
|
+
await cache.setBlob('key2', Buffer.from(serialize({value: 43})));
|
58
|
+
await cache.compact(path.join(cacheDir, 'compact_test_compacted'));
|
59
|
+
|
60
|
+
cache = new LMDBLiteCache(path.join(cacheDir, 'compact_test_compacted'));
|
61
|
+
await cache.ensure();
|
62
|
+
const keys = cache.keys();
|
63
|
+
assert.deepEqual(Array.from(keys), ['key1', 'key2']);
|
64
|
+
});
|
65
|
+
|
66
|
+
describe('getFileKey', () => {
|
67
|
+
it('should return the correct key', () => {
|
68
|
+
const target = path.join(cacheDir, 'test-file-keys');
|
69
|
+
const cache = new LMDBLiteCache(target);
|
70
|
+
const key = cache.getFileKey('key');
|
71
|
+
assert.equal(key, path.join(target, 'files', 'key'));
|
72
|
+
});
|
73
|
+
|
74
|
+
it('should return the correct key for a key with a parent traversal', () => {
|
75
|
+
const target = path.join(cacheDir, 'test-parent-keys');
|
76
|
+
cache = new LMDBLiteCache(target);
|
77
|
+
const key = cache.getFileKey('../../key');
|
78
|
+
assert.equal(
|
79
|
+
key,
|
80
|
+
path.join(target, 'files', '$$__parent_dir$$/$$__parent_dir$$/key'),
|
81
|
+
);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
|
85
|
+
it('can be closed and re-opened', async () => {
|
86
|
+
cache = new LMDBLiteCache(path.join(cacheDir, 'close_and_reopen_test'));
|
87
|
+
await cache.ensure();
|
88
|
+
await cache.setBlob('key', Buffer.from(serialize({value: 42})));
|
89
|
+
cache = new LMDBLiteCache(path.join(cacheDir, 'close_and_reopen_test'));
|
90
|
+
await cache.ensure();
|
91
|
+
const buffer = await cache.getBlob('key');
|
92
|
+
const result = deserialize(buffer);
|
93
|
+
assert.equal(result.value, 42);
|
94
|
+
});
|
95
|
+
|
96
|
+
it('should NOT fail when trying to open the same database twice', async () => {
|
97
|
+
const testDir = path.join(cacheDir, 'double_open_test');
|
98
|
+
const cache1 = new LMDBLiteCache(testDir);
|
99
|
+
await cache1.ensure();
|
100
|
+
|
101
|
+
assert.doesNotThrow(() => {
|
102
|
+
new LMDBLiteCache(testDir);
|
103
|
+
});
|
104
|
+
});
|
105
|
+
|
106
|
+
it('should NOT fail when trying to open after GC', async () => {
|
107
|
+
const testDir = path.join(cacheDir, 'gc_test');
|
108
|
+
|
109
|
+
let cache1 = new LMDBLiteCache(testDir);
|
110
|
+
await cache1.ensure();
|
111
|
+
await cache1.setBlob('key', Buffer.from(serialize({value: 42})));
|
112
|
+
|
113
|
+
cache1 = null;
|
114
|
+
|
115
|
+
if (global.gc) {
|
116
|
+
global.gc();
|
117
|
+
}
|
118
|
+
|
119
|
+
assert.doesNotThrow(() => {
|
120
|
+
new LMDBLiteCache(testDir);
|
121
|
+
});
|
122
|
+
});
|
123
|
+
|
124
|
+
it('should handle rapid open/close cycles', async () => {
|
125
|
+
const testDir = path.join(cacheDir, 'rapid_cycles_test');
|
126
|
+
|
127
|
+
for (let i = 0; i < 10; i++) {
|
128
|
+
const cache = new LMDBLiteCache(testDir);
|
129
|
+
await cache.ensure();
|
130
|
+
await cache.setBlob(`key${i}`, Buffer.from(serialize({value: i})));
|
131
|
+
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
133
|
+
}
|
134
|
+
|
135
|
+
const finalCache = new LMDBLiteCache(testDir);
|
136
|
+
await finalCache.ensure();
|
137
|
+
const buffer = await finalCache.getBlob('key9');
|
138
|
+
const result = deserialize(buffer);
|
139
|
+
assert.equal(result.value, 9);
|
140
|
+
});
|
141
|
+
|
142
|
+
it('should work when there are multiple node.js worker threads accessing the same database', async function () {
|
143
|
+
this.timeout(40000);
|
144
|
+
|
145
|
+
try {
|
146
|
+
initializeMonitoring();
|
147
|
+
} catch (error) {
|
148
|
+
/* empty */
|
149
|
+
}
|
150
|
+
|
151
|
+
const testDir = path.join(cacheDir, 'worker_threads_test');
|
152
|
+
|
153
|
+
let cache = new LMDBLiteCache(testDir);
|
154
|
+
await cache.set('main_thread_key', {
|
155
|
+
mainThreadId: 0,
|
156
|
+
hello: 'world',
|
157
|
+
});
|
158
|
+
setTimeout(() => {
|
159
|
+
cache = null;
|
160
|
+
|
161
|
+
if (global.gc) {
|
162
|
+
global.gc();
|
163
|
+
}
|
164
|
+
}, Math.random() * 300);
|
165
|
+
|
166
|
+
const numWorkers = 10;
|
167
|
+
|
168
|
+
const workers = [];
|
169
|
+
const responsePromises = [];
|
170
|
+
for (let i = 0; i < numWorkers; i++) {
|
171
|
+
const worker = new Worker(path.join(__dirname, 'workerThreadsTest.js'), {
|
172
|
+
workerData: {
|
173
|
+
cacheDir: testDir,
|
174
|
+
},
|
175
|
+
});
|
176
|
+
workers.push(worker);
|
177
|
+
|
178
|
+
const responsePromise = new Promise((resolve, reject) => {
|
179
|
+
worker.addListener('error', (error: Error) => {
|
180
|
+
reject(error);
|
181
|
+
});
|
182
|
+
worker.addListener('message', (message) => {
|
183
|
+
resolve(message);
|
184
|
+
});
|
185
|
+
});
|
186
|
+
|
187
|
+
worker.addListener('message', (message) => {
|
188
|
+
// eslint-disable-next-line no-console
|
189
|
+
console.log('Worker message', message);
|
190
|
+
});
|
191
|
+
worker.addListener('online', () => {
|
192
|
+
worker.postMessage({
|
193
|
+
type: 'go',
|
194
|
+
});
|
195
|
+
});
|
196
|
+
|
197
|
+
responsePromises.push(responsePromise);
|
198
|
+
}
|
199
|
+
|
200
|
+
// eslint-disable-next-line no-console
|
201
|
+
console.log('Waiting for responses');
|
202
|
+
const responses = await Promise.all(responsePromises);
|
203
|
+
|
204
|
+
// eslint-disable-next-line no-console
|
205
|
+
console.log('Responses received');
|
206
|
+
for (const [index, response] of responses.entries()) {
|
207
|
+
const worker = workers[index];
|
208
|
+
|
209
|
+
assert.deepEqual(
|
210
|
+
response,
|
211
|
+
{
|
212
|
+
mainThreadData: {
|
213
|
+
mainThreadId: 0,
|
214
|
+
hello: 'world',
|
215
|
+
},
|
216
|
+
workerId: worker.threadId,
|
217
|
+
},
|
218
|
+
`worker_${index} - Worker ${worker.threadId} should have received the correct data`,
|
219
|
+
);
|
220
|
+
}
|
221
|
+
|
222
|
+
// eslint-disable-next-line no-console
|
223
|
+
console.log('Getting main thread key');
|
224
|
+
cache = new LMDBLiteCache(testDir);
|
225
|
+
const data = await cache?.get('main_thread_key');
|
226
|
+
assert.deepEqual(data, {
|
227
|
+
mainThreadId: 0,
|
228
|
+
hello: 'world',
|
229
|
+
});
|
230
|
+
|
231
|
+
// eslint-disable-next-line no-console
|
232
|
+
console.log('Getting worker keys');
|
233
|
+
for (const worker of workers) {
|
234
|
+
const data = await cache?.get(`worker_key/${worker.threadId}`);
|
235
|
+
assert.deepEqual(data, {
|
236
|
+
workerId: worker.threadId,
|
237
|
+
});
|
238
|
+
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
240
|
+
worker.terminate();
|
241
|
+
}
|
242
|
+
});
|
54
243
|
});
|
@@ -0,0 +1,42 @@
|
|
1
|
+
/* eslint-disable no-inner-declarations */
|
2
|
+
|
3
|
+
require('@atlaspack/babel-register');
|
4
|
+
const {
|
5
|
+
workerData,
|
6
|
+
threadId,
|
7
|
+
parentPort,
|
8
|
+
isMainThread,
|
9
|
+
} = require('worker_threads');
|
10
|
+
const {LMDBLiteCache} = require('../src/index');
|
11
|
+
|
12
|
+
if (!isMainThread) {
|
13
|
+
const cache = new LMDBLiteCache(workerData.cacheDir);
|
14
|
+
|
15
|
+
async function onMessage() {
|
16
|
+
try {
|
17
|
+
cache.set(`worker_key/${threadId}`, {
|
18
|
+
workerId: threadId,
|
19
|
+
});
|
20
|
+
|
21
|
+
const data = await cache.get('main_thread_key');
|
22
|
+
|
23
|
+
parentPort.postMessage({
|
24
|
+
mainThreadData: data,
|
25
|
+
workerId: threadId,
|
26
|
+
});
|
27
|
+
|
28
|
+
setTimeout(() => {
|
29
|
+
parentPort.postMessage({
|
30
|
+
type: 'close',
|
31
|
+
workerId: threadId,
|
32
|
+
});
|
33
|
+
}, Math.random() * 200);
|
34
|
+
} catch (error) {
|
35
|
+
parentPort.postMessage({
|
36
|
+
error: error.message,
|
37
|
+
});
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
parentPort.on('message', onMessage);
|
42
|
+
}
|