@atlaspack/cache 3.1.1-canary.13 → 3.1.1-canary.130
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 +134 -0
- package/index.d.ts +1 -0
- package/lib/FSCache.js +21 -0
- package/lib/LMDBLiteCache.js +69 -40
- package/package.json +8 -8
- package/src/FSCache.js +9 -0
- package/src/LMDBLiteCache.js +69 -28
- package/test/LMDBLiteCache.test.js +180 -6
- package/test/workerThreadsTest.js +42 -0
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,139 @@
|
|
1
1
|
# @atlaspack/cache
|
2
2
|
|
3
|
+
## 3.2.13
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- [#697](https://github.com/atlassian-labs/atlaspack/pull/697) [`c9631af`](https://github.com/atlassian-labs/atlaspack/commit/c9631aff284b2c1c27e8a52f9da392ce65d666e8) Thanks [@yamadapc](https://github.com/yamadapc)! - Fix bug where cache large blob operations were not atomic
|
8
|
+
|
9
|
+
- Updated dependencies [[`dbb4072`](https://github.com/atlassian-labs/atlaspack/commit/dbb40721ebeb45990a14ba04e6b44e7f836fb32d), [`becf977`](https://github.com/atlassian-labs/atlaspack/commit/becf977f625d5ee46dae3d4c679f173bf5f40cc0), [`becf977`](https://github.com/atlassian-labs/atlaspack/commit/becf977f625d5ee46dae3d4c679f173bf5f40cc0), [`c4415a4`](https://github.com/atlassian-labs/atlaspack/commit/c4415a455543d984ca28452c2cb87a794d22497c), [`f0f7c71`](https://github.com/atlassian-labs/atlaspack/commit/f0f7c7168a1d3d18c6f30d2daed611275692b7c5), [`de23e0c`](https://github.com/atlassian-labs/atlaspack/commit/de23e0ce49d5504fe3947ac26640a3d951087da3), [`18a57cf`](https://github.com/atlassian-labs/atlaspack/commit/18a57cf8a4789b2de5ad8e2676f317a26cc91417), [`a5ed1b4`](https://github.com/atlassian-labs/atlaspack/commit/a5ed1b414498560f393ff491af4da25b6e8dde56)]:
|
10
|
+
- @atlaspack/feature-flags@2.19.0
|
11
|
+
- @atlaspack/utils@2.17.0
|
12
|
+
- @atlaspack/rust@3.4.1
|
13
|
+
- @atlaspack/fs@2.15.13
|
14
|
+
- @atlaspack/logger@2.14.13
|
15
|
+
|
16
|
+
## 3.2.12
|
17
|
+
|
18
|
+
### Patch Changes
|
19
|
+
|
20
|
+
- Updated dependencies [[`c75bf55`](https://github.com/atlassian-labs/atlaspack/commit/c75bf553fff4decc285b5fd499a275853b18f8f2)]:
|
21
|
+
- @atlaspack/rust@3.4.0
|
22
|
+
- @atlaspack/fs@2.15.12
|
23
|
+
- @atlaspack/logger@2.14.12
|
24
|
+
- @atlaspack/utils@2.16.1
|
25
|
+
|
26
|
+
## 3.2.11
|
27
|
+
|
28
|
+
### Patch Changes
|
29
|
+
|
30
|
+
- Updated dependencies [[`e8a60ff`](https://github.com/atlassian-labs/atlaspack/commit/e8a60ffbea41caef265786bbf73349771760081c), [`30ee2cf`](https://github.com/atlassian-labs/atlaspack/commit/30ee2cfcd34cf2646ded0eda13fdb80a2a5de529)]:
|
31
|
+
- @atlaspack/feature-flags@2.18.4
|
32
|
+
- @atlaspack/utils@2.16.0
|
33
|
+
- @atlaspack/fs@2.15.11
|
34
|
+
|
35
|
+
## 3.2.10
|
36
|
+
|
37
|
+
### Patch Changes
|
38
|
+
|
39
|
+
- Updated dependencies [[`5ded263`](https://github.com/atlassian-labs/atlaspack/commit/5ded263c7f11b866e8885b81c73e20dd060b25be)]:
|
40
|
+
- @atlaspack/feature-flags@2.18.3
|
41
|
+
- @atlaspack/fs@2.15.10
|
42
|
+
- @atlaspack/utils@2.15.3
|
43
|
+
|
44
|
+
## 3.2.9
|
45
|
+
|
46
|
+
### Patch Changes
|
47
|
+
|
48
|
+
- Updated dependencies [[`644b157`](https://github.com/atlassian-labs/atlaspack/commit/644b157dee72a871acc2d0facf0b87b8eea51956)]:
|
49
|
+
- @atlaspack/feature-flags@2.18.2
|
50
|
+
- @atlaspack/fs@2.15.9
|
51
|
+
- @atlaspack/utils@2.15.2
|
52
|
+
|
53
|
+
## 3.2.8
|
54
|
+
|
55
|
+
### Patch Changes
|
56
|
+
|
57
|
+
- Updated dependencies [[`ef3d622`](https://github.com/atlassian-labs/atlaspack/commit/ef3d6228f4e006702198a19c61e051d194d325cb), [`26aa9c5`](https://github.com/atlassian-labs/atlaspack/commit/26aa9c599d2be45ce1438a74c5fa22f39b9b554b), [`0501255`](https://github.com/atlassian-labs/atlaspack/commit/05012550da35b05ce7d356a8cc29311e7f9afdca)]:
|
58
|
+
- @atlaspack/logger@2.14.11
|
59
|
+
- @atlaspack/feature-flags@2.18.1
|
60
|
+
- @atlaspack/fs@2.15.8
|
61
|
+
- @atlaspack/utils@2.15.1
|
62
|
+
|
63
|
+
## 3.2.7
|
64
|
+
|
65
|
+
### Patch Changes
|
66
|
+
|
67
|
+
- Updated dependencies [[`10fbcfb`](https://github.com/atlassian-labs/atlaspack/commit/10fbcfbfa49c7a83da5d7c40983e36e87f524a75), [`85c52d3`](https://github.com/atlassian-labs/atlaspack/commit/85c52d3f7717b3c84a118d18ab98cfbfd71dcbd2), [`e39c6cf`](https://github.com/atlassian-labs/atlaspack/commit/e39c6cf05f7e95ce5420dbcea66f401b1cbd397c)]:
|
68
|
+
- @atlaspack/feature-flags@2.18.0
|
69
|
+
- @atlaspack/utils@2.15.0
|
70
|
+
- @atlaspack/fs@2.15.7
|
71
|
+
|
72
|
+
## 3.2.6
|
73
|
+
|
74
|
+
### Patch Changes
|
75
|
+
|
76
|
+
- Updated dependencies [[`73ea3c4`](https://github.com/atlassian-labs/atlaspack/commit/73ea3c4d85d4401fdd15abcbf988237e890e7ad3), [`b1b3693`](https://github.com/atlassian-labs/atlaspack/commit/b1b369317c66f8a431c170df2ebba4fa5b2e38ef)]:
|
77
|
+
- @atlaspack/feature-flags@2.17.0
|
78
|
+
- @atlaspack/fs@2.15.6
|
79
|
+
- @atlaspack/utils@2.14.11
|
80
|
+
|
81
|
+
## 3.2.5
|
82
|
+
|
83
|
+
### Patch Changes
|
84
|
+
|
85
|
+
- Updated dependencies [[`0999fb7`](https://github.com/atlassian-labs/atlaspack/commit/0999fb78da519a6c7582d212883e515fcf6c1252), [`35fdd4b`](https://github.com/atlassian-labs/atlaspack/commit/35fdd4b52da0af20f74667f7b8adfb2f90279b7c), [`6dd4ccb`](https://github.com/atlassian-labs/atlaspack/commit/6dd4ccb753541de32322d881f973d571dd57e4ca)]:
|
86
|
+
- @atlaspack/fs@2.15.5
|
87
|
+
- @atlaspack/rust@3.3.5
|
88
|
+
- @atlaspack/logger@2.14.10
|
89
|
+
- @atlaspack/utils@2.14.10
|
90
|
+
|
91
|
+
## 3.2.4
|
92
|
+
|
93
|
+
### Patch Changes
|
94
|
+
|
95
|
+
- [#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
|
96
|
+
|
97
|
+
- 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)]:
|
98
|
+
- @atlaspack/rust@3.3.4
|
99
|
+
- @atlaspack/fs@2.15.4
|
100
|
+
- @atlaspack/feature-flags@2.16.0
|
101
|
+
- @atlaspack/logger@2.14.9
|
102
|
+
- @atlaspack/utils@2.14.9
|
103
|
+
|
104
|
+
## 3.2.3
|
105
|
+
|
106
|
+
### Patch Changes
|
107
|
+
|
108
|
+
- 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)]:
|
109
|
+
- @atlaspack/feature-flags@2.15.1
|
110
|
+
- @atlaspack/fs@2.15.3
|
111
|
+
- @atlaspack/rust@3.3.3
|
112
|
+
- @atlaspack/utils@2.14.8
|
113
|
+
- @atlaspack/logger@2.14.8
|
114
|
+
|
115
|
+
## 3.2.2
|
116
|
+
|
117
|
+
### Patch Changes
|
118
|
+
|
119
|
+
- Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
|
120
|
+
- @atlaspack/feature-flags@2.15.0
|
121
|
+
- @atlaspack/logger@2.14.7
|
122
|
+
- @atlaspack/rust@3.3.2
|
123
|
+
- @atlaspack/fs@2.15.2
|
124
|
+
- @atlaspack/utils@2.14.7
|
125
|
+
|
126
|
+
## 3.2.1
|
127
|
+
|
128
|
+
### Patch Changes
|
129
|
+
|
130
|
+
- Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
|
131
|
+
- @atlaspack/feature-flags@2.14.4
|
132
|
+
- @atlaspack/rust@3.3.1
|
133
|
+
- @atlaspack/fs@2.15.1
|
134
|
+
- @atlaspack/utils@2.14.6
|
135
|
+
- @atlaspack/logger@2.14.6
|
136
|
+
|
3
137
|
## 3.2.0
|
4
138
|
|
5
139
|
### Minor Changes
|
package/index.d.ts
CHANGED
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,13 +26,6 @@ function _rust() {
|
|
26
26
|
};
|
27
27
|
return data;
|
28
28
|
}
|
29
|
-
function _fs() {
|
30
|
-
const data = _interopRequireDefault(require("fs"));
|
31
|
-
_fs = function () {
|
32
|
-
return data;
|
33
|
-
};
|
34
|
-
return data;
|
35
|
-
}
|
36
29
|
function _ncp() {
|
37
30
|
const data = _interopRequireDefault(require("ncp"));
|
38
31
|
_ncp = function () {
|
@@ -61,26 +54,28 @@ function _path() {
|
|
61
54
|
};
|
62
55
|
return data;
|
63
56
|
}
|
64
|
-
function
|
57
|
+
function _fs() {
|
65
58
|
const data = require("@atlaspack/fs");
|
66
|
-
|
59
|
+
_fs = function () {
|
67
60
|
return data;
|
68
61
|
};
|
69
62
|
return data;
|
70
63
|
}
|
71
64
|
var _package = _interopRequireDefault(require("../package.json"));
|
72
65
|
var _FSCache = require("./FSCache");
|
66
|
+
function _logger() {
|
67
|
+
const data = require("@atlaspack/logger");
|
68
|
+
_logger = function () {
|
69
|
+
return data;
|
70
|
+
};
|
71
|
+
return data;
|
72
|
+
}
|
73
73
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
74
74
|
// $FlowFixMe
|
75
75
|
const ncpAsync = (0, _util().promisify)(_ncp().default);
|
76
76
|
class LmdbWrapper {
|
77
77
|
constructor(lmdb) {
|
78
78
|
this.lmdb = lmdb;
|
79
|
-
|
80
|
-
// $FlowFixMe
|
81
|
-
this[Symbol.dispose] = () => {
|
82
|
-
this.lmdb.close();
|
83
|
-
};
|
84
79
|
}
|
85
80
|
has(key) {
|
86
81
|
return this.lmdb.hasSync(key);
|
@@ -110,20 +105,25 @@ class LmdbWrapper {
|
|
110
105
|
}
|
111
106
|
}
|
112
107
|
exports.LmdbWrapper = LmdbWrapper;
|
113
|
-
function open(directory
|
108
|
+
function open(directory,
|
114
109
|
// eslint-disable-next-line no-unused-vars
|
115
|
-
) {
|
110
|
+
openOptions) {
|
116
111
|
return new LmdbWrapper(new (_rust().Lmdb)({
|
117
112
|
path: directory,
|
118
113
|
asyncWrites: true,
|
119
|
-
mapSize: 1024 * 1024 * 1024 * 15
|
114
|
+
mapSize: process.env.ATLASPACK_BUILD_ENV === 'test' ? 1024 * 1024 * 1024 : 1024 * 1024 * 1024 * 15
|
120
115
|
}));
|
121
116
|
}
|
122
117
|
const pipeline = (0, _util().promisify)(_stream().default.pipeline);
|
123
118
|
class LMDBLiteCache {
|
119
|
+
/**
|
120
|
+
* Directory where we store raw files.
|
121
|
+
*/
|
122
|
+
|
124
123
|
constructor(cacheDir) {
|
125
|
-
this.fs = new (
|
124
|
+
this.fs = new (_fs().NodeFS)();
|
126
125
|
this.dir = cacheDir;
|
126
|
+
this.cacheFilesDirectory = _path().default.join(cacheDir, 'files');
|
127
127
|
this.fsCache = new _FSCache.FSCache(this.fs, cacheDir);
|
128
128
|
this.store = open(cacheDir, {
|
129
129
|
name: 'parcel-cache',
|
@@ -142,6 +142,7 @@ class LMDBLiteCache {
|
|
142
142
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
143
143
|
await this.fsCache.ensure();
|
144
144
|
}
|
145
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
145
146
|
return Promise.resolve();
|
146
147
|
}
|
147
148
|
serialize() {
|
@@ -166,10 +167,18 @@ class LMDBLiteCache {
|
|
166
167
|
await this.setBlob(key, (0, _buildCache().serialize)(value));
|
167
168
|
}
|
168
169
|
getStream(key) {
|
169
|
-
|
170
|
+
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
171
|
+
return this.fs.createReadStream(_path().default.join(this.dir, key));
|
172
|
+
}
|
173
|
+
return this.fs.createReadStream(this.getFileKey(key));
|
170
174
|
}
|
171
|
-
setStream(key, stream) {
|
172
|
-
|
175
|
+
async setStream(key, stream) {
|
176
|
+
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
177
|
+
return pipeline(stream, this.fs.createWriteStream(_path().default.join(this.dir, key)));
|
178
|
+
}
|
179
|
+
const filePath = this.getFileKey(key);
|
180
|
+
await this.fs.mkdirp(_path().default.dirname(filePath));
|
181
|
+
return pipeline(stream, this.fs.createWriteStream(filePath));
|
173
182
|
}
|
174
183
|
|
175
184
|
// eslint-disable-next-line require-await
|
@@ -189,34 +198,25 @@ class LMDBLiteCache {
|
|
189
198
|
getBuffer(key) {
|
190
199
|
return Promise.resolve(this.store.get(key));
|
191
200
|
}
|
192
|
-
#getFilePath(key, index) {
|
193
|
-
return _path().default.join(this.dir, `${key}-${index}`);
|
194
|
-
}
|
195
201
|
hasLargeBlob(key) {
|
196
202
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
197
203
|
return this.fsCache.hasLargeBlob(key);
|
198
204
|
}
|
199
|
-
return this.
|
205
|
+
return this.fs.exists(this.getFileKey(key));
|
200
206
|
}
|
201
|
-
|
202
|
-
/**
|
203
|
-
* @deprecated Use getBlob instead.
|
204
|
-
*/
|
205
207
|
getLargeBlob(key) {
|
206
208
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
207
209
|
return this.fsCache.getLargeBlob(key);
|
208
210
|
}
|
209
|
-
return
|
211
|
+
return this.fs.readFile(this.getFileKey(key));
|
210
212
|
}
|
211
|
-
|
212
|
-
/**
|
213
|
-
* @deprecated Use setBlob instead.
|
214
|
-
*/
|
215
|
-
setLargeBlob(key, contents, options) {
|
213
|
+
async setLargeBlob(key, contents, options) {
|
216
214
|
if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
217
215
|
return this.fsCache.setLargeBlob(key, contents, options);
|
218
216
|
}
|
219
|
-
|
217
|
+
const targetPath = this.getFileKey(key);
|
218
|
+
await this.fs.mkdirp(_path().default.dirname(targetPath));
|
219
|
+
return this.fs.writeFile(targetPath, contents);
|
220
220
|
}
|
221
221
|
|
222
222
|
/**
|
@@ -232,10 +232,8 @@ class LMDBLiteCache {
|
|
232
232
|
return this.store.keys();
|
233
233
|
}
|
234
234
|
async compact(targetPath) {
|
235
|
-
await
|
236
|
-
|
237
|
-
});
|
238
|
-
const files = await _fs().default.promises.readdir(this.dir);
|
235
|
+
await this.fs.mkdirp(targetPath);
|
236
|
+
const files = await this.fs.readdir(this.dir);
|
239
237
|
// copy all files except data.mdb and lock.mdb to the target path (recursive)
|
240
238
|
for (const file of files) {
|
241
239
|
const filePath = _path().default.join(this.dir, file);
|
@@ -247,6 +245,37 @@ class LMDBLiteCache {
|
|
247
245
|
this.store.compact(_path().default.join(targetPath, 'data.mdb'));
|
248
246
|
}
|
249
247
|
refresh() {}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* Streams, packages are stored in files instead of LMDB.
|
251
|
+
*
|
252
|
+
* On this case, if a cache key happens to have a parent traversal, ../..
|
253
|
+
* it is treated specially
|
254
|
+
*
|
255
|
+
* That is, something/../something and something are meant to be different
|
256
|
+
* keys.
|
257
|
+
*
|
258
|
+
* Plus we do not want to store values outside of the cache directory.
|
259
|
+
*/
|
260
|
+
getFileKey(key) {
|
261
|
+
const cleanKey = key.split('/').map(part => {
|
262
|
+
if (part === '..') {
|
263
|
+
return '$$__parent_dir$$';
|
264
|
+
}
|
265
|
+
return part;
|
266
|
+
}).join('/');
|
267
|
+
return _path().default.join(this.cacheFilesDirectory, cleanKey);
|
268
|
+
}
|
269
|
+
async clear() {
|
270
|
+
await (0, _logger().instrumentAsync)('LMDBLiteCache::clear', async () => {
|
271
|
+
const keys = await this.keys();
|
272
|
+
for (const key of keys) {
|
273
|
+
await this.store.delete(key);
|
274
|
+
}
|
275
|
+
await this.fs.rimraf(this.cacheFilesDirectory);
|
276
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
277
|
+
});
|
278
|
+
}
|
250
279
|
}
|
251
280
|
exports.LMDBLiteCache = LMDBLiteCache;
|
252
281
|
(0, _buildCache().registerSerializableClass)(`${_package.default.version}: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-canary.
|
4
|
+
"version": "3.1.1-canary.130+13aef177e",
|
5
5
|
"license": "(MIT OR Apache-2.0)",
|
6
6
|
"type": "commonjs",
|
7
7
|
"publishConfig": {
|
@@ -23,12 +23,12 @@
|
|
23
23
|
"check-ts": "tsc --noEmit index.d.ts"
|
24
24
|
},
|
25
25
|
"dependencies": {
|
26
|
-
"@atlaspack/build-cache": "2.13.3-canary.
|
27
|
-
"@atlaspack/feature-flags": "2.14.1-canary.
|
28
|
-
"@atlaspack/fs": "2.14.5-canary.
|
29
|
-
"@atlaspack/logger": "2.14.5-canary.
|
30
|
-
"@atlaspack/rust": "3.2.1-canary.
|
31
|
-
"@atlaspack/utils": "2.14.5-canary.
|
26
|
+
"@atlaspack/build-cache": "2.13.3-canary.198+13aef177e",
|
27
|
+
"@atlaspack/feature-flags": "2.14.1-canary.198+13aef177e",
|
28
|
+
"@atlaspack/fs": "2.14.5-canary.130+13aef177e",
|
29
|
+
"@atlaspack/logger": "2.14.5-canary.130+13aef177e",
|
30
|
+
"@atlaspack/rust": "3.2.1-canary.130+13aef177e",
|
31
|
+
"@atlaspack/utils": "2.14.5-canary.130+13aef177e",
|
32
32
|
"ncp": "^2.0.0"
|
33
33
|
},
|
34
34
|
"devDependencies": {
|
@@ -37,5 +37,5 @@
|
|
37
37
|
"browser": {
|
38
38
|
"./src/IDBCache.js": "./src/IDBCache.browser.js"
|
39
39
|
},
|
40
|
-
"gitHead": "
|
40
|
+
"gitHead": "13aef177eea289a6e40d2113b5ec1ac9be18a33d"
|
41
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,7 +10,6 @@ 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
|
-
import fs from 'fs';
|
14
13
|
import ncp from 'ncp';
|
15
14
|
import {promisify} from 'util';
|
16
15
|
import stream from 'stream';
|
@@ -19,6 +18,7 @@ import {NodeFS} from '@atlaspack/fs';
|
|
19
18
|
// $FlowFixMe
|
20
19
|
import packageJson from '../package.json';
|
21
20
|
import {FSCache} from './FSCache';
|
21
|
+
import {instrumentAsync} from '@atlaspack/logger';
|
22
22
|
|
23
23
|
const ncpAsync = promisify(ncp);
|
24
24
|
|
@@ -35,11 +35,6 @@ export class LmdbWrapper {
|
|
35
35
|
|
36
36
|
constructor(lmdb: Lmdb) {
|
37
37
|
this.lmdb = lmdb;
|
38
|
-
|
39
|
-
// $FlowFixMe
|
40
|
-
this[Symbol.dispose] = () => {
|
41
|
-
this.lmdb.close();
|
42
|
-
};
|
43
38
|
}
|
44
39
|
|
45
40
|
has(key: string): boolean {
|
@@ -107,10 +102,15 @@ export class LMDBLiteCache implements Cache {
|
|
107
102
|
dir: FilePath;
|
108
103
|
store: LmdbWrapper;
|
109
104
|
fsCache: FSCache;
|
105
|
+
/**
|
106
|
+
* Directory where we store raw files.
|
107
|
+
*/
|
108
|
+
cacheFilesDirectory: FilePath;
|
110
109
|
|
111
110
|
constructor(cacheDir: FilePath) {
|
112
111
|
this.fs = new NodeFS();
|
113
112
|
this.dir = cacheDir;
|
113
|
+
this.cacheFilesDirectory = path.join(cacheDir, 'files');
|
114
114
|
this.fsCache = new FSCache(this.fs, cacheDir);
|
115
115
|
|
116
116
|
this.store = open(cacheDir, {
|
@@ -131,6 +131,7 @@ export class LMDBLiteCache implements Cache {
|
|
131
131
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
132
132
|
await this.fsCache.ensure();
|
133
133
|
}
|
134
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
134
135
|
return Promise.resolve();
|
135
136
|
}
|
136
137
|
|
@@ -162,14 +163,24 @@ export class LMDBLiteCache implements Cache {
|
|
162
163
|
}
|
163
164
|
|
164
165
|
getStream(key: string): Readable {
|
165
|
-
|
166
|
+
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
167
|
+
return this.fs.createReadStream(path.join(this.dir, key));
|
168
|
+
}
|
169
|
+
|
170
|
+
return this.fs.createReadStream(this.getFileKey(key));
|
166
171
|
}
|
167
172
|
|
168
|
-
setStream(key: string, stream: Readable): Promise<void> {
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
+
async setStream(key: string, stream: Readable): Promise<void> {
|
174
|
+
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
175
|
+
return pipeline(
|
176
|
+
stream,
|
177
|
+
this.fs.createWriteStream(path.join(this.dir, key)),
|
178
|
+
);
|
179
|
+
}
|
180
|
+
|
181
|
+
const filePath = this.getFileKey(key);
|
182
|
+
await this.fs.mkdirp(path.dirname(filePath));
|
183
|
+
return pipeline(stream, this.fs.createWriteStream(filePath));
|
173
184
|
}
|
174
185
|
|
175
186
|
// eslint-disable-next-line require-await
|
@@ -193,31 +204,22 @@ export class LMDBLiteCache implements Cache {
|
|
193
204
|
return Promise.resolve(this.store.get(key));
|
194
205
|
}
|
195
206
|
|
196
|
-
#getFilePath(key: string, index: number): string {
|
197
|
-
return path.join(this.dir, `${key}-${index}`);
|
198
|
-
}
|
199
|
-
|
200
207
|
hasLargeBlob(key: string): Promise<boolean> {
|
201
208
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
202
209
|
return this.fsCache.hasLargeBlob(key);
|
203
210
|
}
|
204
|
-
|
211
|
+
|
212
|
+
return this.fs.exists(this.getFileKey(key));
|
205
213
|
}
|
206
214
|
|
207
|
-
/**
|
208
|
-
* @deprecated Use getBlob instead.
|
209
|
-
*/
|
210
215
|
getLargeBlob(key: string): Promise<Buffer> {
|
211
216
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
212
217
|
return this.fsCache.getLargeBlob(key);
|
213
218
|
}
|
214
|
-
return
|
219
|
+
return this.fs.readFile(this.getFileKey(key));
|
215
220
|
}
|
216
221
|
|
217
|
-
|
218
|
-
* @deprecated Use setBlob instead.
|
219
|
-
*/
|
220
|
-
setLargeBlob(
|
222
|
+
async setLargeBlob(
|
221
223
|
key: string,
|
222
224
|
contents: Buffer | string,
|
223
225
|
options?: {|signal?: AbortSignal|},
|
@@ -225,7 +227,10 @@ export class LMDBLiteCache implements Cache {
|
|
225
227
|
if (!getFeatureFlag('cachePerformanceImprovements')) {
|
226
228
|
return this.fsCache.setLargeBlob(key, contents, options);
|
227
229
|
}
|
228
|
-
|
230
|
+
|
231
|
+
const targetPath = this.getFileKey(key);
|
232
|
+
await this.fs.mkdirp(path.dirname(targetPath));
|
233
|
+
return this.fs.writeFile(targetPath, contents);
|
229
234
|
}
|
230
235
|
|
231
236
|
/**
|
@@ -244,9 +249,9 @@ export class LMDBLiteCache implements Cache {
|
|
244
249
|
}
|
245
250
|
|
246
251
|
async compact(targetPath: string): Promise<void> {
|
247
|
-
await fs.
|
252
|
+
await this.fs.mkdirp(targetPath);
|
248
253
|
|
249
|
-
const files = await fs.
|
254
|
+
const files = await this.fs.readdir(this.dir);
|
250
255
|
// copy all files except data.mdb and lock.mdb to the target path (recursive)
|
251
256
|
for (const file of files) {
|
252
257
|
const filePath = path.join(this.dir, file);
|
@@ -262,6 +267,42 @@ export class LMDBLiteCache implements Cache {
|
|
262
267
|
}
|
263
268
|
|
264
269
|
refresh(): void {}
|
270
|
+
|
271
|
+
/**
|
272
|
+
* Streams, packages are stored in files instead of LMDB.
|
273
|
+
*
|
274
|
+
* On this case, if a cache key happens to have a parent traversal, ../..
|
275
|
+
* it is treated specially
|
276
|
+
*
|
277
|
+
* That is, something/../something and something are meant to be different
|
278
|
+
* keys.
|
279
|
+
*
|
280
|
+
* Plus we do not want to store values outside of the cache directory.
|
281
|
+
*/
|
282
|
+
getFileKey(key: string): string {
|
283
|
+
const cleanKey = key
|
284
|
+
.split('/')
|
285
|
+
.map((part) => {
|
286
|
+
if (part === '..') {
|
287
|
+
return '$$__parent_dir$$';
|
288
|
+
}
|
289
|
+
return part;
|
290
|
+
})
|
291
|
+
.join('/');
|
292
|
+
return path.join(this.cacheFilesDirectory, cleanKey);
|
293
|
+
}
|
294
|
+
|
295
|
+
async clear(): Promise<void> {
|
296
|
+
await instrumentAsync('LMDBLiteCache::clear', async () => {
|
297
|
+
const keys = await this.keys();
|
298
|
+
for (const key of keys) {
|
299
|
+
await this.store.delete(key);
|
300
|
+
}
|
301
|
+
|
302
|
+
await this.fs.rimraf(this.cacheFilesDirectory);
|
303
|
+
await this.fs.mkdirp(this.cacheFilesDirectory);
|
304
|
+
});
|
305
|
+
}
|
265
306
|
}
|
266
307
|
|
267
308
|
registerSerializableClass(
|
@@ -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();
|
@@ -59,11 +57,187 @@ describe('LMDBLiteCache', () => {
|
|
59
57
|
await cache.setBlob('key2', Buffer.from(serialize({value: 43})));
|
60
58
|
await cache.compact(path.join(cacheDir, 'compact_test_compacted'));
|
61
59
|
|
62
|
-
cache.getNativeRef().close();
|
63
|
-
|
64
60
|
cache = new LMDBLiteCache(path.join(cacheDir, 'compact_test_compacted'));
|
65
61
|
await cache.ensure();
|
66
62
|
const keys = cache.keys();
|
67
63
|
assert.deepEqual(Array.from(keys), ['key1', 'key2']);
|
68
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
|
+
});
|
69
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
|
+
}
|