diff --git a/benches/db_1KiB.ts b/benches/db_1KiB.ts index 80318009..c02a53a1 100644 --- a/benches/db_1KiB.ts +++ b/benches/db_1KiB.ts @@ -38,7 +38,6 @@ async function main() { }, }, dbPath: dbPath1, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); // Db2 uses workers @@ -52,7 +51,6 @@ async function main() { }, }, dbPath: dbPath2, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); db2.setWorkerManager(workerManager); diff --git a/benches/db_1MiB.ts b/benches/db_1MiB.ts index 994a6d0c..06759ca8 100644 --- a/benches/db_1MiB.ts +++ b/benches/db_1MiB.ts @@ -38,7 +38,6 @@ async function main() { }, }, dbPath: dbPath1, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); // Db2 uses workers @@ -52,7 +51,6 @@ async function main() { }, }, dbPath: dbPath2, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); db2.setWorkerManager(workerManager); diff --git a/benches/db_24KiB.ts b/benches/db_24KiB.ts index 38800ac2..62ae0c8a 100644 --- a/benches/db_24KiB.ts +++ b/benches/db_24KiB.ts @@ -38,7 +38,6 @@ async function main() { }, }, dbPath: dbPath1, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); // Db2 uses workers @@ -52,7 +51,6 @@ async function main() { }, }, dbPath: dbPath2, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); db2.setWorkerManager(workerManager); diff --git a/package-lock.json b/package-lock.json index 3251b9fe..837a3586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@matrixai/async-init": "^1.8.2", "@matrixai/async-locks": "^3.1.2", - "@matrixai/db": "^4.0.2", + "@matrixai/db": "^5.0.2", "@matrixai/errors": "^1.1.3", "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.4", @@ -38,6 +38,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.0.0", + "fast-check": "^3.0.1", "jest": "^28.1.1", "jest-extended": "^3.0.1", "jest-junit": "^14.0.0", @@ -1207,25 +1208,25 @@ } }, "node_modules/@matrixai/db": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-4.0.5.tgz", - "integrity": "sha512-X3gBcyPxC+bTEfi1J1Y49n1bglvg7HjM8MKNH5s+OUEswqKSZgeg1uWfXqvUqq72yjBtgRi4Ghmy4MdrIB1oMw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.2.tgz", + "integrity": "sha512-m8cSvCExzF0t7x/J8C+THLyGauNNTTn+PzvvXj70L0ifQzTWo6VqGhfhLgH6H400eUjWuxGtV1szuCEPHVUfYA==", + "hasInstallScript": true, "dependencies": { - "@matrixai/async-init": "^1.7.3", - "@matrixai/errors": "^1.1.1", - "@matrixai/logger": "^2.1.1", + "@matrixai/async-init": "^1.8.1", + "@matrixai/async-locks": "^3.1.1", + "@matrixai/errors": "^1.1.2", + "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.3", - "@matrixai/workers": "^1.3.3", - "@types/abstract-leveldown": "^7.2.0", - "level": "7.0.1", + "@matrixai/workers": "^1.3.5", + "node-gyp-build": "4.4.0", "threads": "^1.6.5" + }, + "engines": { + "msvs": "2019", + "node": "^16.15.0" } }, - "node_modules/@matrixai/db/node_modules/@matrixai/logger": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.3.0.tgz", - "integrity": "sha512-DbsUv9eBubB2WxA8aGygnY/A2Ggm9a+ZnnnL2hIWWnE+sid92FK96gubW1a+u8OrXWx559HqUTBkcPDs83zV/A==" - }, "node_modules/@matrixai/errors": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.3.tgz", @@ -1578,11 +1579,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "node_modules/@types/abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==" - }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -1915,22 +1911,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", - "dependencies": { - "buffer": "^6.0.3", - "catering": "^2.0.0", - "is-buffer": "^2.0.5", - "level-concat-iterator": "^3.0.0", - "level-supports": "^2.0.1", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/acorn": { "version": "8.7.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", @@ -2222,25 +2202,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/benchmark": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", @@ -2350,29 +2311,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2424,14 +2362,6 @@ } ] }, - "node_modules/catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "engines": { - "node": ">=6" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2646,18 +2576,6 @@ "node": ">=0.10.0" } }, - "node_modules/deferred-leveldown": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-7.0.0.tgz", - "integrity": "sha512-QKN8NtuS3BC6m0B8vAnBls44tX1WXAFATUsJlruyAYbZpysWV3siH6o/i3g9DCHauzodksO60bdj5NazNbjCmg==", - "dependencies": { - "abstract-leveldown": "^7.2.0", - "inherits": "^2.0.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -2748,20 +2666,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/encoding-down": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.1.0.tgz", - "integrity": "sha512-ky47X5jP84ryk5EQmvedQzELwVJPjCgXDQZGeb9F6r4PdChByCGHTBrVcF3h8ynKVJ1wVbkxTsDC8zBROPypgQ==", - "dependencies": { - "abstract-leveldown": "^7.2.0", - "inherits": "^2.0.3", - "level-codec": "^10.0.0", - "level-errors": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -3259,6 +3163,22 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/fast-check": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.1.1.tgz", + "integrity": "sha512-3vtXinVyuUKCKFKYcwXhGE6NtGWkqF8Yh3rvMZNzmwz8EPrgoc/v4pDdLHyLnCyCI5MZpZZkDEwFyXyEONOxpA==", + "dev": true, + "dependencies": { + "pure-rand": "^5.0.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3673,25 +3593,6 @@ "node": ">=10.17.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -3805,28 +3706,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, "node_modules/is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -4851,127 +4730,6 @@ "node": ">=6" } }, - "node_modules/level": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/level/-/level-7.0.1.tgz", - "integrity": "sha512-w3E64+ALx2eZf8RV5JL4kIcE0BFAvQscRYd1yU4YVqZN9RGTQxXSvH202xvK15yZwFFxRXe60f13LJjcJ//I4Q==", - "dependencies": { - "level-js": "^6.1.0", - "level-packager": "^6.0.1", - "leveldown": "^6.1.0" - }, - "engines": { - "node": ">=10.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/level" - } - }, - "node_modules/level-codec": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-10.0.0.tgz", - "integrity": "sha512-QW3VteVNAp6c/LuV6nDjg7XDXx9XHK4abmQarxZmlRSDyXYk20UdaJTSX6yzVvQ4i0JyWSB7jert0DsyD/kk6g==", - "dependencies": { - "buffer": "^6.0.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-concat-iterator": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", - "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", - "dependencies": { - "catering": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-errors": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-3.0.1.tgz", - "integrity": "sha512-tqTL2DxzPDzpwl0iV5+rBCv65HWbHp6eutluHNcVIftKZlQN//b6GEnZDM2CvGZvzGYMwyPtYppYnydBQd2SMQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/level-iterator-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-5.0.0.tgz", - "integrity": "sha512-wnb1+o+CVFUDdiSMR/ZymE2prPs3cjVLlXuDeSq9Zb8o032XrabGEXcTCsBxprAtseO3qvFeGzh6406z9sOTRA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-js": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-6.1.0.tgz", - "integrity": "sha512-i7mPtkZm68aewfv0FnIUWvFUFfoyzIvVKnUmuQGrelEkP72vSPTaA1SGneWWoCV5KZJG4wlzbJLp1WxVNGuc6A==", - "dependencies": { - "abstract-leveldown": "^7.2.0", - "buffer": "^6.0.3", - "inherits": "^2.0.3", - "ltgt": "^2.1.2", - "run-parallel-limit": "^1.1.0" - } - }, - "node_modules/level-packager": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-6.0.1.tgz", - "integrity": "sha512-8Ezr0XM6hmAwqX9uu8IGzGNkWz/9doyPA8Oo9/D7qcMI6meJC+XhIbNYHukJhIn8OGdlzQs/JPcL9B8lA2F6EQ==", - "dependencies": { - "encoding-down": "^7.1.0", - "levelup": "^5.1.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/level-supports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", - "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/leveldown": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", - "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", - "hasInstallScript": true, - "dependencies": { - "abstract-leveldown": "^7.2.0", - "napi-macros": "~2.0.0", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/levelup": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-5.1.1.tgz", - "integrity": "sha512-0mFCcHcEebOwsQuk00WJwjLI6oCjbBuEYdh/RaRqhjnyVlzqf41T1NnDtCedumZ56qyIh8euLFDqV1KfzTAVhg==", - "dependencies": { - "catering": "^2.0.0", - "deferred-leveldown": "^7.0.0", - "level-errors": "^3.0.1", - "level-iterator-stream": "^5.0.0", - "level-supports": "^2.0.1", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5072,11 +4830,6 @@ "node": ">=10" } }, - "node_modules/ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" - }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -5206,11 +4959,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5226,9 +4974,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", - "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -5699,10 +5447,21 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.1.tgz", + "integrity": "sha512-ksWccjmXOHU2gJBnH0cK1lSYdvSZ0zLoCMSz/nTGh6hDvCSgcRxDyIcOBD6KNxFz3xhMPm/T267Tbe2JRymKEQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -5908,28 +5667,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/run-parallel-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", - "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7773,25 +7510,18 @@ } }, "@matrixai/db": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-4.0.5.tgz", - "integrity": "sha512-X3gBcyPxC+bTEfi1J1Y49n1bglvg7HjM8MKNH5s+OUEswqKSZgeg1uWfXqvUqq72yjBtgRi4Ghmy4MdrIB1oMw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.2.tgz", + "integrity": "sha512-m8cSvCExzF0t7x/J8C+THLyGauNNTTn+PzvvXj70L0ifQzTWo6VqGhfhLgH6H400eUjWuxGtV1szuCEPHVUfYA==", "requires": { - "@matrixai/async-init": "^1.7.3", - "@matrixai/errors": "^1.1.1", - "@matrixai/logger": "^2.1.1", + "@matrixai/async-init": "^1.8.1", + "@matrixai/async-locks": "^3.1.1", + "@matrixai/errors": "^1.1.2", + "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.3", - "@matrixai/workers": "^1.3.3", - "@types/abstract-leveldown": "^7.2.0", - "level": "7.0.1", + "@matrixai/workers": "^1.3.5", + "node-gyp-build": "4.4.0", "threads": "^1.6.5" - }, - "dependencies": { - "@matrixai/logger": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-2.3.0.tgz", - "integrity": "sha512-DbsUv9eBubB2WxA8aGygnY/A2Ggm9a+ZnnnL2hIWWnE+sid92FK96gubW1a+u8OrXWx559HqUTBkcPDs83zV/A==" - } } }, "@matrixai/errors": { @@ -8009,11 +7739,6 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "@types/abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-q5veSX6zjUy/DlDhR4Y4cU0k2Ar+DT2LUraP00T19WLmTO6Se1djepCCaqU6nQrwcJ5Hyo/CWqxTzrrFg8eqbQ==" - }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -8257,19 +7982,6 @@ "eslint-visitor-keys": "^3.0.0" } }, - "abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", - "requires": { - "buffer": "^6.0.3", - "catering": "^2.0.0", - "is-buffer": "^2.0.5", - "level-concat-iterator": "^3.0.0", - "level-supports": "^2.0.1", - "queue-microtask": "^1.2.3" - } - }, "acorn": { "version": "8.7.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", @@ -8485,11 +8197,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, "benchmark": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", @@ -8571,15 +8278,6 @@ "node-int64": "^0.4.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -8612,11 +8310,6 @@ "integrity": "sha512-3PDmaP56wz/qz7G508xzjx8C+MC2qEm4SYhSEzC9IBROo+dGXFWRuaXkWti0A9tuI00g+toiriVqxtWMgl350g==", "dev": true }, - "catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==" - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8788,15 +8481,6 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, - "deferred-leveldown": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-7.0.0.tgz", - "integrity": "sha512-QKN8NtuS3BC6m0B8vAnBls44tX1WXAFATUsJlruyAYbZpysWV3siH6o/i3g9DCHauzodksO60bdj5NazNbjCmg==", - "requires": { - "abstract-leveldown": "^7.2.0", - "inherits": "^2.0.3" - } - }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -8860,17 +8544,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "encoding-down": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.1.0.tgz", - "integrity": "sha512-ky47X5jP84ryk5EQmvedQzELwVJPjCgXDQZGeb9F6r4PdChByCGHTBrVcF3h8ynKVJ1wVbkxTsDC8zBROPypgQ==", - "requires": { - "abstract-leveldown": "^7.2.0", - "inherits": "^2.0.3", - "level-codec": "^10.0.0", - "level-errors": "^3.0.0" - } - }, "errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -9253,6 +8926,15 @@ "jest-util": "^28.1.3" } }, + "fast-check": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.1.1.tgz", + "integrity": "sha512-3vtXinVyuUKCKFKYcwXhGE6NtGWkqF8Yh3rvMZNzmwz8EPrgoc/v4pDdLHyLnCyCI5MZpZZkDEwFyXyEONOxpA==", + "dev": true, + "requires": { + "pure-rand": "^5.0.1" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9560,11 +9242,6 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -9645,11 +9322,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - }, "is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -10400,95 +10072,6 @@ "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", "dev": true }, - "level": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/level/-/level-7.0.1.tgz", - "integrity": "sha512-w3E64+ALx2eZf8RV5JL4kIcE0BFAvQscRYd1yU4YVqZN9RGTQxXSvH202xvK15yZwFFxRXe60f13LJjcJ//I4Q==", - "requires": { - "level-js": "^6.1.0", - "level-packager": "^6.0.1", - "leveldown": "^6.1.0" - } - }, - "level-codec": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-10.0.0.tgz", - "integrity": "sha512-QW3VteVNAp6c/LuV6nDjg7XDXx9XHK4abmQarxZmlRSDyXYk20UdaJTSX6yzVvQ4i0JyWSB7jert0DsyD/kk6g==", - "requires": { - "buffer": "^6.0.3" - } - }, - "level-concat-iterator": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", - "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", - "requires": { - "catering": "^2.1.0" - } - }, - "level-errors": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-3.0.1.tgz", - "integrity": "sha512-tqTL2DxzPDzpwl0iV5+rBCv65HWbHp6eutluHNcVIftKZlQN//b6GEnZDM2CvGZvzGYMwyPtYppYnydBQd2SMQ==" - }, - "level-iterator-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-5.0.0.tgz", - "integrity": "sha512-wnb1+o+CVFUDdiSMR/ZymE2prPs3cjVLlXuDeSq9Zb8o032XrabGEXcTCsBxprAtseO3qvFeGzh6406z9sOTRA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "level-js": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-6.1.0.tgz", - "integrity": "sha512-i7mPtkZm68aewfv0FnIUWvFUFfoyzIvVKnUmuQGrelEkP72vSPTaA1SGneWWoCV5KZJG4wlzbJLp1WxVNGuc6A==", - "requires": { - "abstract-leveldown": "^7.2.0", - "buffer": "^6.0.3", - "inherits": "^2.0.3", - "ltgt": "^2.1.2", - "run-parallel-limit": "^1.1.0" - } - }, - "level-packager": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-6.0.1.tgz", - "integrity": "sha512-8Ezr0XM6hmAwqX9uu8IGzGNkWz/9doyPA8Oo9/D7qcMI6meJC+XhIbNYHukJhIn8OGdlzQs/JPcL9B8lA2F6EQ==", - "requires": { - "encoding-down": "^7.1.0", - "levelup": "^5.1.1" - } - }, - "level-supports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", - "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==" - }, - "leveldown": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", - "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", - "requires": { - "abstract-leveldown": "^7.2.0", - "napi-macros": "~2.0.0", - "node-gyp-build": "^4.3.0" - } - }, - "levelup": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-5.1.1.tgz", - "integrity": "sha512-0mFCcHcEebOwsQuk00WJwjLI6oCjbBuEYdh/RaRqhjnyVlzqf41T1NnDtCedumZ56qyIh8euLFDqV1KfzTAVhg==", - "requires": { - "catering": "^2.0.0", - "deferred-leveldown": "^7.0.0", - "level-errors": "^3.0.1", - "level-iterator-stream": "^5.0.0", - "level-supports": "^2.0.1", - "queue-microtask": "^1.2.3" - } - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10571,11 +10154,6 @@ "yallist": "^4.0.0" } }, - "ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" - }, "lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -10674,11 +10252,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10691,9 +10264,9 @@ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "node-gyp-build": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", - "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" }, "node-int64": { "version": "0.4.0", @@ -11030,10 +10603,17 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "pure-rand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.1.tgz", + "integrity": "sha512-ksWccjmXOHU2gJBnH0cK1lSYdvSZ0zLoCMSz/nTGh6hDvCSgcRxDyIcOBD6KNxFz3xhMPm/T267Tbe2JRymKEQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true }, "react-is": { "version": "18.2.0", @@ -11161,14 +10741,6 @@ "queue-microtask": "^1.2.2" } }, - "run-parallel-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", - "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index 8d5fbc59..5e59d200 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "dependencies": { "@matrixai/async-init": "^1.8.2", "@matrixai/async-locks": "^3.1.2", - "@matrixai/db": "^4.0.2", + "@matrixai/db": "^5.0.2", "@matrixai/errors": "^1.1.3", "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.4", @@ -83,6 +83,7 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^3.9.0", "typedoc": "^0.22.15", - "typescript": "^4.5.2" + "typescript": "^4.5.2", + "fast-check": "^3.0.1" } } diff --git a/src/EncryptedFS.ts b/src/EncryptedFS.ts index ea338207..a6f12e54 100644 --- a/src/EncryptedFS.ts +++ b/src/EncryptedFS.ts @@ -104,7 +104,6 @@ class EncryptedFS { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger: logger.getChild(DB.name), fresh, }); @@ -723,6 +722,7 @@ class EncryptedFS { dstPath, dstFlags, srcINodeStat.mode, + tran, ); const dstINode = dstFd.ino; const dstINodeType = (await this.iNodeMgr.get(dstINode, tran))?.type; @@ -1893,6 +1893,7 @@ class EncryptedFS { path: Path, flags: string | number, mode: number = permissions.DEFAULT_FILE_PERM, + tran?: DBTransaction, ): Promise<[FileDescriptor, FdIndex]> { path = this.getPath(path); if (typeof flags === 'string') { @@ -1992,22 +1993,22 @@ class EncryptedFS { // It must continue the loop, by restarting the loop with an inherited transaction context // This ensures that handling the existing inode is consistent let raced = false; - let tran: DBTransaction | null = null; - let tranRelease: ResourceRelease | null = null; + let _tran: DBTransaction | undefined = tran; + let _tranRelease: ResourceRelease | undefined = undefined; // Loop necessary due to following symlinks and optional `O_CREAT` file creation while (true) { if (navigated.target != null) { // Handle existing target - if (tran == null || tranRelease == null) { + if (_tran == null && _tranRelease == null) { const tranAcquire = this.iNodeMgr.transaction(navigated.target); - [tranRelease, tran] = (await tranAcquire()) as [ + [_tranRelease, _tran] = (await tranAcquire()) as [ ResourceRelease, DBTransaction, ]; } let e: Error | undefined; try { - const target = await this.iNodeMgr.get(navigated.target, tran); + const target = await this.iNodeMgr.get(navigated.target, _tran); if (target == null) { // Try to find the target again navigated = await this.navigate(path, false); @@ -2033,7 +2034,7 @@ class EncryptedFS { path, // Only preserve the transaction context if it was inherited // from a coalesced call, as it would already have be for `navigated.dir` - raced ? tran : undefined, + raced ? _tran : undefined, ); // Restart the opening procedure with the new target continue; @@ -2073,26 +2074,28 @@ class EncryptedFS { flags & constants.O_TRUNC && flags & (constants.O_WRONLY | constants.O_RDWR) ) { - await this.iNodeMgr.fileClearData(navigated.target, tran); + await this.iNodeMgr.fileClearData(navigated.target, _tran); await this.iNodeMgr.fileSetBlocks( navigated.target, Buffer.alloc(0), this.blockSize, undefined, - tran, + _tran, ); } // Terminates loop, creates file descriptor - return await createFd(flags, navigated.target, tran!); + return await createFd(flags, navigated.target, _tran!); } } catch (e_) { e = e_; throw e_; } finally { - await tranRelease(e); - // Clear the transaction variables - tran = null; - tranRelease = null; + if (_tranRelease != null) { + await _tranRelease(e); + // Clear the transaction variables + _tran = undefined; + _tranRelease = undefined; + } } } else { // Handle non-existing target @@ -2109,15 +2112,17 @@ class EncryptedFS { ResourceRelease, INodeIndex, ]; - const tranAcquire = this.iNodeMgr.transaction(ino, navigated.dir); - [tranRelease, tran] = (await tranAcquire()) as [ - ResourceRelease, - DBTransaction, - ]; + if (_tran == null && _tranRelease == null) { + const tranAcquire = this.iNodeMgr.transaction(ino, navigated.dir); + [_tranRelease, _tran] = (await tranAcquire()) as [ + ResourceRelease, + DBTransaction, + ]; + } // INode may be created while waiting for lock // Transaction is maintained and not released // This is to ensure that the already created locks are held - if ((await this.iNodeMgr.get(ino, tran)) != null) { + if ((await this.iNodeMgr.get(ino, _tran)) != null) { navigated.target = ino; await inoRelease(); raced = true; @@ -2127,7 +2132,7 @@ class EncryptedFS { try { const navigatedDirStat = await this.iNodeMgr.statGet( navigated.dir, - tran, + _tran, ); // Cannot create if the current directory has been unlinked from its parent directory if (navigatedDirStat.nlink < 2) { @@ -2153,25 +2158,27 @@ class EncryptedFS { }, this.blockSize, undefined, - tran, + _tran, ); await this.iNodeMgr.dirSetEntry( navigated.dir, navigated.name, ino!, - tran, + _tran, ); // Terminates loop, creates file descriptor - return await createFd(flags, ino!, tran!); + return await createFd(flags, ino!, _tran!); } catch (e_) { e = e_; throw e_; } finally { - await tranRelease(e); await inoRelease(e); - // Clear the transaction variables - tran = null; - tranRelease = null; + if (_tranRelease != null) { + await _tranRelease(e); + // Clear the transaction variables + _tran = undefined; + _tranRelease = undefined; + } } } } diff --git a/src/inodes/INodeManager.ts b/src/inodes/INodeManager.ts index 1e168fc6..f613007f 100644 --- a/src/inodes/INodeManager.ts +++ b/src/inodes/INodeManager.ts @@ -14,7 +14,6 @@ import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import { Lock, LockBox } from '@matrixai/async-locks'; import { withF, withG } from '@matrixai/resources'; import Counter from 'resource-counter'; import * as inodesUtils from './utils'; @@ -68,7 +67,6 @@ class INodeManager { protected iNodeCounter: Counter = new Counter(1); protected iNodeAllocations: Map> = new Map(); protected refs: Map = new Map(); - protected locks: LockBox = new LockBox(); constructor({ db, logger }: { db: DB; logger: Logger }) { this.logger = logger; @@ -81,10 +79,9 @@ class INodeManager { await this.db.clear(this.mgrDbPath); } // Populate the inode counter with pre-existing inodes - for await (const [kP] of this.db.iterator( - { values: false }, - this.iNodesDbPath, - )) { + for await (const [kP] of this.db.iterator(this.iNodesDbPath, { + values: false, + })) { this.iNodeCounter.allocate(inodesUtils.uniNodeId(kP[0] as INodeId)); } // Clean up all dangling inodes that could not be removed due to references @@ -122,10 +119,9 @@ class INodeManager { */ protected async gcAll(): Promise { await withF([this.db.transaction()], async ([tran]) => { - for await (const [kP] of tran.iterator( - { values: false }, - this.gcDbPath, - )) { + for await (const [kP] of tran.iterator(this.gcDbPath, { + values: false, + })) { const ino = inodesUtils.uniNodeId(kP[0] as INodeId); // Snapshot doesn't need to be used because `this.gcAll` is only executed at `this.stop` const type = (await tran.get([ @@ -210,25 +206,9 @@ class INodeManager { ...inos: Array ): ResourceAcquire { return async () => { - const locksAcquire = this.locks.lock( - ...inos.map<[INodeIndex, typeof Lock]>((ino) => [ino, Lock]), - ); - const transactionAcquire = this.db.transaction(); - const [locksRelease] = await locksAcquire(); - let transactionRelease, tran; - try { - [transactionRelease, tran] = await transactionAcquire(); - } catch (e) { - await locksRelease(); - throw e; - } - return [ - async () => { - await transactionRelease(); - await locksRelease(); - }, - tran, - ]; + const [transactionRelease, tran] = await this.db.transaction()(); + await tran!.lock(...inos); + return [transactionRelease, tran]; }; } @@ -240,13 +220,10 @@ class INodeManager { ] ): Promise { const f = params.pop() as (tran: DBTransaction) => Promise; - const lockRequests = (params as Array).map< - [INodeIndex, typeof Lock] - >((ino) => [ino, Lock]); - return withF( - [this.db.transaction(), this.locks.lock(...lockRequests)], - ([tran]) => f(tran), - ); + return await this.db.withTransactionF(async (tran) => { + await tran.lock(...(params as Array)); + return f(tran); + }); } @ready(new inodesErrors.ErrorINodeManagerNotRunning()) @@ -259,13 +236,10 @@ class INodeManager { const g = params.pop() as ( tran: DBTransaction, ) => AsyncGenerator; - const lockRequests = (params as Array).map< - [INodeIndex, typeof Lock] - >((ino) => [ino, Lock]); - return withG( - [this.db.transaction(), this.locks.lock(...lockRequests)], - ([tran]) => g(tran), - ); + return this.db.withTransactionG(async function* (tran) { + await tran.lock(...(params as Array)); + return yield* g(tran); + }); } @ready(new inodesErrors.ErrorINodeManagerNotRunning()) @@ -289,17 +263,12 @@ class INodeManager { if (typeof params[0] !== 'number') { navigated = params.shift() as Readonly<{ dir: INodeIndex; name: string }>; } - const lockRequests = (params as Array).map< - [INodeIndex, typeof Lock] - >((ino) => [ino, Lock]); return withF( - [ - this.inoAllocation(navigated), - ([ino]: [INodeIndex]) => - this.locks.lock([ino, Lock], ...lockRequests)(), - this.db.transaction(), - ], - ([ino, _, tran]) => f(ino, tran), + [this.inoAllocation(navigated), this.db.transaction()], + async ([ino, tran]) => { + await tran.lock(ino, ...(params as Array)); + return f(ino, tran); + }, ); } @@ -330,17 +299,12 @@ class INodeManager { if (typeof params[0] !== 'number') { navigated = params.shift() as Readonly<{ dir: INodeIndex; name: string }>; } - const lockRequests = (params as Array).map< - [INodeIndex, typeof Lock] - >((ino) => [ino, Lock]); return withG( - [ - this.inoAllocation(navigated), - ([ino]: [INodeIndex]) => - this.locks.lock([ino, Lock], ...lockRequests)(), - this.db.transaction(), - ], - ([ino, _, tran]) => g(ino, tran), + [this.inoAllocation(navigated), this.db.transaction()], + async function* ([ino, tran]) { + await tran.lock(ino, ...(params as Array)); + return yield* g(ino, tran); + }, ); } @@ -518,7 +482,7 @@ class INodeManager { tran: DBTransaction, ): Promise { const dataPath = [...this.dataDbPath, ino.toString()]; - for await (const [kP] of tran.iterator({ values: false }, dataPath)) { + for await (const [kP] of tran.iterator(dataPath, { values: false })) { await tran.del(dataPath.concat(kP)); } await this.iNodeDestroy(ino, tran); @@ -548,7 +512,7 @@ class INodeManager { } else { await this.dirUnsetRoot(tran); } - for await (const [kP] of tran.iterator({ values: false }, dirPath)) { + for await (const [kP] of tran.iterator(dirPath, { values: false })) { await tran.del(dirPath.concat(kP)); } await this.iNodeDestroy(ino, tran); @@ -640,14 +604,13 @@ class INodeManager { return yield* this.withTransactionG((tran) => this.getAll(tran)); } // Consistent iteration on iNodesDbPath and gcDbPath - const gcIterator = tran.iterator( - { valueAsBuffer: false }, - this.gcDbPath, - ); + const gcIterator = tran.iterator(this.gcDbPath, { + valueAsBuffer: false, + }); try { for await (const [inoData, type] of tran.iterator( - { valueAsBuffer: false }, this.iNodesDbPath, + { valueAsBuffer: false }, )) { const ino = inodesUtils.uniNodeId(inoData[0] as INodeId); gcIterator.seek(inoData); @@ -659,7 +622,7 @@ class INodeManager { }; } } finally { - await gcIterator.end(); + await gcIterator.destroy(); } } @@ -931,8 +894,8 @@ class INodeManager { ); } for await (const [kP, v] of tran.iterator( - { keyAsBuffer: false, valueAsBuffer: false }, [...this.dirsDbPath, ino.toString()], + { keyAsBuffer: false, valueAsBuffer: false }, )) { yield [kP.toString(), v]; } @@ -1100,10 +1063,10 @@ class INodeManager { } : { gte: inodesUtils.bufferId(startIdx) }; let blockCount = startIdx; - for await (const [kP, v] of tran.iterator(options, [ - ...this.dataDbPath, - ino.toString(), - ])) { + for await (const [kP, v] of tran.iterator( + [...this.dataDbPath, ino.toString()], + options, + )) { // This is to account for the case where a some blocks are missing in a database // i.e. blocks 0 -> 3 have data and a write operation was performed on blocks 7 -> 8 while (blockCount < inodesUtils.unbufferId(kP[0] as BufferId)) { @@ -1131,10 +1094,10 @@ class INodeManager { } const options = { limit: 1, reverse: true }; let key, value; - for await (const [kP, v] of tran.iterator(options, [ - ...this.dataDbPath, - ino.toString(), - ])) { + for await (const [kP, v] of tran.iterator( + [...this.dataDbPath, ino.toString()], + options, + )) { key = inodesUtils.unbufferId(kP[0] as BufferId); value = v; } diff --git a/tests/EncryptedFS.concurrent.test.ts b/tests/EncryptedFS.concurrent.test.ts index fdad64b5..30f96e1f 100644 --- a/tests/EncryptedFS.concurrent.test.ts +++ b/tests/EncryptedFS.concurrent.test.ts @@ -6,13 +6,14 @@ import pathNode from 'path'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { code as errno } from 'errno'; import { DB } from '@matrixai/db'; +import * as fc from 'fast-check'; import EncryptedFS from '@/EncryptedFS'; import { ErrorEncryptedFSError } from '@/errors'; import * as utils from '@/utils'; import * as constants from '@/constants'; import INodeManager from '@/inodes/INodeManager'; import { promise } from '@/utils'; -import { expectReason, sleep } from './utils'; +import { expectError, expectReason, sleep, scheduleCall } from './utils'; describe(`${EncryptedFS.name} Concurrency`, () => { const logger = new Logger(`${EncryptedFS.name} Concurrency`, LogLevel.WARN, [ @@ -23,6 +24,15 @@ describe(`${EncryptedFS.name} Concurrency`, () => { let db: DB; let iNodeMgr: INodeManager; let efs: EncryptedFS; + + const totalINodes = async (iNodeMgr: INodeManager) => { + let counter = 0; + for await (const _ of iNodeMgr.getAll()) { + counter += 1; + } + return counter; + }; + beforeEach(async () => { dataDir = await fs.promises.mkdtemp( pathNode.join(os.tmpdir(), 'encryptedfs-test-'), @@ -36,7 +46,6 @@ describe(`${EncryptedFS.name} Concurrency`, () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger: logger.getChild(DB.name), }); iNodeMgr = await INodeManager.createINodeManager({ @@ -57,6 +66,57 @@ describe(`${EncryptedFS.name} Concurrency`, () => { }); }); describe('concurrent inode creation', () => { + test('EncryptedFS.open, EncryptedFS.mknod and EncryptedFS.mkdir', async () => { + const path1 = pathNode.join('dir', 'file1'); + + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const prom = Promise.allSettled([ + scheduleCall( + s, + () => efs.mknod(path1, constants.S_IFREG, 0, 0), + 'mknod 1', + ), + scheduleCall( + s, + () => efs.mknod(path1, constants.S_IFREG, 0, 0), + 'mknod 2', + ), + scheduleCall( + s, + () => efs.open(path1, constants.O_RDWR | constants.O_CREAT), + 'open 1', + ), + scheduleCall( + s, + () => efs.open(path1, constants.O_RDWR | constants.O_CREAT), + 'open 2', + ), + scheduleCall(s, () => efs.mkdir(path1), 'mkdir 1'), + scheduleCall(s, () => efs.mkdir(path1), 'mkdir 2'), + ]); + await s.waitAll(); + const results = await prom; + results.map((item) => { + if (item.status !== 'fulfilled') { + // Should fail as a normal FS error + expectReason(item, ErrorEncryptedFSError); + } + }); + // Should have at least 1 success + expect(results.some((item) => item.status === 'fulfilled')).toBe( + true, + ); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.open', async () => { // Only one call wins the race to create the file await Promise.all([ @@ -240,120 +300,163 @@ describe(`${EncryptedFS.name} Concurrency`, () => { }); describe('concurrent file writes', () => { test('EncryptedFS.write on multiple file descriptors', async () => { - // Concurrent writes of the same length results in "last write wins" - let fds: Array = [ - await efs.open('test', constants.O_RDWR | constants.O_CREAT), - await efs.open('test', constants.O_RDWR | constants.O_CREAT), - ]; - let contents = ['one', 'two']; - let promises: Array>; - promises = []; - for (let i = 0; i < 2; i++) { - promises.push(efs.write(fds[i], contents[i])); - } - await Promise.all(promises); - expect(['one', 'two']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), - ); - for (const fd of fds) { - await efs.close(fd); - } // Concurrent writes of different length results in "last write wins" or a merge - fds = [ - await efs.open('test', constants.O_RDWR | constants.O_CREAT), - await efs.open('test', constants.O_RDWR | constants.O_CREAT), - ]; - contents = ['one1', 'two']; - promises = []; - for (let i = 0; i < 2; i++) { - promises.push(efs.write(fds[i], contents[i])); - } - expect(['one1', 'two', 'two1']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), + const contents = ['one', 'two', 'one1', 'two2']; + await fc.assert( + fc.asyncProperty(fc.scheduler(), async (s) => { + const fds: Array = [ + await efs.open('test', constants.O_RDWR | constants.O_CREAT), + await efs.open('test', constants.O_RDWR | constants.O_CREAT), + await efs.open('test', constants.O_RDWR | constants.O_CREAT), + await efs.open('test', constants.O_RDWR | constants.O_CREAT), + ]; + + // Concurrent writes of the same length results in "last write wins" + const prom = Promise.all([ + scheduleCall(s, () => efs.write(fds[0], contents[0]), 'write 1'), + scheduleCall(s, () => efs.write(fds[1], contents[1]), 'write 2'), + scheduleCall(s, () => efs.write(fds[2], contents[2]), 'write 3'), + scheduleCall(s, () => efs.write(fds[3], contents[3]), 'write 4'), + ]); + await s.waitAll(); + await prom; + + expect(['one', 'two', 'one1', 'one2', 'two2', 'two1']).toContainEqual( + await efs.readFile('test', { encoding: 'utf-8' }), + ); + for (const fd of fds) { + await efs.close(fd); + } + }), + { + numRuns: 50, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); - for (const fd of fds) { - await efs.close(fd); - } }); test('EncryptedFS.write on the same file descriptor', async () => { - await efs.writeFile('test', ''); - const fd = await efs.open('test', 'w'); - await Promise.all([ - efs.write(fd, Buffer.from('aaa')), - efs.write(fd, Buffer.from('bbb')), - ]); - expect(['aaabbb', 'bbbaaa']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), + await fc.assert( + fc.asyncProperty(fc.scheduler(), async (s) => { + await efs.writeFile('test', ''); + const fd = await efs.open('test', 'w'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.write(fd, 'aaa'), 'write 1'), + scheduleCall(s, () => efs.write(fd, 'bbb'), 'write 2'), + ]); + await s.waitAll(); + await prom; + + expect(['aaabbb', 'bbbaaa']).toContainEqual( + await efs.readFile('test', { encoding: 'utf-8' }), + ); + await efs.close(fd); + }), + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); - await efs.close(fd); }); test('EncryptedFS.writeFile', async () => { - let promises: Array>; - // Concurrent writes of the same length results in "last write wins" - promises = []; - for (const data of ['one', 'two']) { - promises.push(efs.writeFile('test', data)); - } - await Promise.all(promises); - expect(['one', 'two']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), - ); - // Concurrent writes of different length results in "last write wins" or a merge - for (let i = 0; i < 10; i++) { - promises = []; - for (const data of ['one1', 'two']) { - promises.push(efs.writeFile('test', data)); - } - await Promise.all(promises); - expect(['one1', 'two', 'two1']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), - ); - } - // Explicit last write wins - promises = [ - (async () => { - // One is written last - await sleep(0); - return efs.writeFile('test', 'one'); - })(), - efs.writeFile('test', 'two'), - ]; - await Promise.all(promises); - expect(['one']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), - ); - promises = [ - efs.writeFile('test', 'one'), - (async () => { - // Two1 is written last - await sleep(0); - return efs.writeFile('test', 'two1'); - })(), - ]; - await Promise.all(promises); - expect(['two1']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), + await fc.assert( + fc.asyncProperty(fc.scheduler(), async (s) => { + // Concurrent writes of different length results in "last write wins" or a merge + await efs.writeFile('test', ''); + + const prom = Promise.all([ + scheduleCall(s, () => efs.writeFile('test', 'one'), 'writeFile 1'), + scheduleCall(s, () => efs.writeFile('test', 'one1'), 'writeFile 2'), + scheduleCall(s, () => efs.writeFile('test', 'two'), 'writeFile 2'), + scheduleCall(s, () => efs.writeFile('test', 'two2'), 'writeFile 2'), + ]); + await s.waitAll(); + await prom; + + expect(['one', 'two', 'one1', 'one2', 'two2', 'two1']).toContainEqual( + await efs.readFile('test', { encoding: 'utf-8' }), + ); + expect(await totalINodes(iNodeMgr)).toEqual(2); + }), + { + numRuns: 50, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); - const inodeDatas: Array = []; - for await (const inodeData of iNodeMgr.getAll()) { - inodeDatas.push(inodeData); - } - expect(inodeDatas).toStrictEqual([ - { ino: 1, type: 'Directory', gc: false }, - { ino: 2, type: 'File', gc: false }, - ]); }); test('EncryptedFS.appendFile', async () => { - await efs.writeFile('test', 'original'); - // Concurrent appends results in mutually exclusive writes - const promises = [ - efs.appendFile('test', 'one'), - efs.appendFile('test', 'two'), - ]; - await Promise.all(promises); - // Either order of appending is acceptable - expect(['originalonetwo', 'originaltwoone']).toContainEqual( - await efs.readFile('test', { encoding: 'utf-8' }), + await fc.assert( + fc.asyncProperty(fc.scheduler(), async (s) => { + // Concurrent appends results in mutually exclusive writes + await efs.writeFile('test', 'original'); + + const prom = Promise.all([ + scheduleCall( + s, + () => efs.appendFile('test', 'one'), + 'appendFile 1', + ), + scheduleCall( + s, + () => efs.appendFile('test', 'two'), + 'appendFile 2', + ), + ]); + await s.waitAll(); + await prom; + + // Either order of appending is acceptable + expect(['originalonetwo', 'originaltwoone']).toContainEqual( + await efs.readFile('test', { encoding: 'utf-8' }), + ); + }), + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, + ); + }); + test('EncryptedFS.fallocate, EncryptedFS.writeFile, EncryptedFS.write and EncryptedFS.createWriteStream ', async () => { + const path1 = pathNode.join('dir', 'file1'); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.fallocate(fd, 0, 100), 'fallocate'), + scheduleCall(s, () => efs.writeFile(path1, 'test'), 'writeFile'), + scheduleCall(s, () => efs.write(fd, 'test'), 'write'), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (let i = 0; i < 10; i++) { + writeStream.write(i.toString()); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream', + ), + ]); + await s.waitAll(); + await prom; + expect((await efs.stat(path1)).size).toBe(100); + const contents = await efs.readFile(path1); + expect(contents.length).toBeGreaterThanOrEqual(4); + expect(contents.length).toBeLessThanOrEqual(100); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.fallocate and EncryptedFS.writeFile', async () => { @@ -656,6 +759,50 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(contents.length).toEqual(10); } }); + test('EncryptedFS.truncate and EncryptedFS.writeFile, EncryptedFS.write and EncryptedFS.createWriteStream', async () => { + const path1 = pathNode.join('dir', 'file1'); + const phrase = 'The quick brown fox jumped over the lazy dog'; + const phraseSplit = phrase.split(' '); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.truncate(path1, 27), 'truncate'), + scheduleCall(s, () => efs.writeFile(path1, phrase), 'writeFile'), + scheduleCall(s, () => efs.write(fd, phrase), 'write'), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (const i of phraseSplit) { + writeStream.write(i + ' '); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream', + ), + ]); + await s.waitAll(); + await prom; + const contents = await efs.readFile(path1); + expect(contents.length).toBeGreaterThanOrEqual(27); + expect(contents.length).toBeLessThanOrEqual(45); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.truncate and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -965,6 +1112,50 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(contents.length).toEqual(27); } }); + test('EncryptedFS.ftruncate and EncryptedFS.writeFile, EncryptedFS.write and EncryptedFS.createWriteStream', async () => { + const path1 = pathNode.join('dir', 'file1'); + const phrase = 'The quick brown fox jumped over the lazy dog'; + const phraseSplit = phrase.split(' '); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.ftruncate(fd, 27), 'ftruncate'), + scheduleCall(s, () => efs.writeFile(path1, phrase), 'writeFile'), + scheduleCall(s, () => efs.write(fd, phrase), 'write'), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (const i of phraseSplit) { + writeStream.write(i + ' '); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream', + ), + ]); + await s.waitAll(); + await prom; + const contents = await efs.readFile(path1); + expect(contents.length).toBeGreaterThanOrEqual(27); + expect(contents.length).toBeLessThanOrEqual(45); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.ftruncate and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -1274,6 +1465,33 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(contents.length).toEqual(27); } }); + test('EncryptedFS.utimes, EncryptedFS.futimes and EncryptedFS.writeFile', async () => { + const path1 = pathNode.join('dir', 'file1'); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + await efs.writeFile(fd, 'test'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.utimes(path1, 0, 0), 'utimes file'), + scheduleCall(s, () => efs.utimes('dir', 0, 0), 'utimes dir'), + scheduleCall(s, () => efs.futimes(fd, 0, 0), 'futimes'), + scheduleCall(s, () => efs.writeFile(path1, 'test'), 'writeFile'), + ]); + await s.waitAll(); + await prom; + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.utimes and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); const nowTime = Date.now(); @@ -1428,6 +1646,62 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(stat.mtime.getTime()).toBeGreaterThan(nowTime); } }); + test('EncryptedFS.lseek, EncryptedFS.writeFile, EncryptedFS.writeFile with fd, EncryptedFS.write, EncryptedFS.readFile, EncryptedFS.read, and seeking position', async () => { + const path1 = pathNode.join('dir', 'file1'); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + await efs.writeFile( + path1, + 'The quick brown fox jumped over the lazy dog', + ); + + const buffer = Buffer.alloc(45); + const prom = Promise.all([ + scheduleCall( + s, + () => efs.lseek(fd, 20, constants.SEEK_CUR), + 'seek move', + ), + scheduleCall( + s, + () => efs.writeFile(path1, 'test'), + 'writeFile path', + ), + scheduleCall(s, () => efs.writeFile(fd, 'test'), 'writeFile fd'), + scheduleCall(s, () => efs.write(fd, 'test'), 'write'), + scheduleCall(s, () => efs.readFile(path1), 'readFile'), + scheduleCall( + s, + () => efs.read(fd, buffer, undefined, 44), + 'read', + ), + scheduleCall( + s, + () => efs.lseek(fd, 15, constants.SEEK_SET), + 'seek set', + ), + ]); + await s.waitAll(); + await prom; + const stat = await efs.stat(path1); + expect(stat.size).toBeGreaterThanOrEqual(4); + expect(stat.size).toBeLessThanOrEqual(80); + const contents = await efs.readFile(path1); + expect(contents.length).toBeGreaterThanOrEqual(4); + expect(contents.length).toBeLessThanOrEqual(80); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.lseek and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -1756,6 +2030,110 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(pos).toEqual(15); } }); + test('EncryptedFS.createReadStream, EncryptedFS.createWriteStream, EncryptedFS.write, EncryptedFS.read', async () => { + const path1 = pathNode.join('dir', 'file1'); + const dataA = 'AAAAA'; + const dataB = 'BBBBB'.repeat(5); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + await efs.writeFile(path1, dataB); + + const buffer = Buffer.alloc(110); + const prom = Promise.all([ + scheduleCall( + s, + async () => { + const readProm = new Promise((resolve, reject) => { + const readStream = efs.createReadStream(path1); + let readData = ''; + readStream.on('data', (data) => { + readData += data.toString(); + }); + readStream.on('end', () => { + resolve(readData); + }); + readStream.on('error', (e) => { + reject(e); + }); + }); + return await readProm; + }, + 'readStream 1', + ), + scheduleCall( + s, + async () => { + const readProm = new Promise((resolve, reject) => { + const readStream = efs.createReadStream(path1); + let readData = ''; + readStream.on('data', (data) => { + readData += data.toString(); + }); + readStream.on('end', () => { + resolve(readData); + }); + readStream.on('error', (e) => { + reject(e); + }); + }); + return await readProm; + }, + 'readStream 2', + ), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (let i = 0; i < 10; i++) { + writeStream.write(dataA); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream 1', + ), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (let i = 0; i < 10; i++) { + writeStream.write(dataA); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream 2', + ), + scheduleCall(s, () => efs.write(fd, dataB), 'write'), + scheduleCall( + s, + () => efs.read(fd, buffer, undefined, 100), + 'read', + ), + ]); + await s.waitAll(); + await prom; + const stat = await efs.stat(path1); + expect(stat.size).toEqual(50); + const contents = await efs.readFile(path1); + expect(contents.length).toEqual(50); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.createReadStream and EncryptedFS.createWriteStream', async () => { const path1 = utils.pathJoin('dir', 'file1'); const dataA = 'AAAAA'; @@ -2205,6 +2583,63 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(stat.size).toEqual(50); } }); + test('EncryptedFS.unlink, EncryptedFS.writeFile, EncryptedFS.open, EncryptedFS.write and EncryptedFS.createWriteStream', async () => { + const path1 = pathNode.join('dir', 'file1'); + const dataA = 'AAAAA'; + const dataB = 'BBBBB'.repeat(5); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + await efs.writeFile(path1, dataB); + + const prom = Promise.all([ + scheduleCall(s, () => efs.unlink(path1), 'unlink'), + scheduleCall(s, () => efs.writeFile(path1, 'test'), 'writeFile'), + scheduleCall( + s, + async () => { + let fd: FdIndex; + try { + fd = await efs.open(path1, 'r+'); + await efs.close(fd!); + } catch (e) { + // Ignore FS errors + if (!(e instanceof ErrorEncryptedFSError)) throw e; + } + }, + 'open', + ), + scheduleCall(s, () => efs.write(fd, 'test'), 'write'), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (let i = 0; i < 10; i++) { + writeStream.write(dataA); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream', + ), + ]); + await s.waitAll(); + // Expecting no transaction errors + await prom; + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.unlink and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -2430,6 +2865,63 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(await efs.exists(path1)).toEqual(false); } }); + test('EncryptedFS.appendFIle, EncryptedFS.writeFile, EncryptedFS.writeFile with fd, EncryptedFS.write, EncryptedFS.createReadStream', async () => { + const path1 = pathNode.join('dir', 'file1'); + const dataA = 'A'.repeat(10); + const dataB = 'B'.repeat(10); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + await efs.writeFile(path1, ''); + + const prom = Promise.all([ + scheduleCall( + s, + () => efs.appendFile(path1, dataA), + 'appendFile path', + ), + scheduleCall(s, () => efs.appendFile(fd, dataA), 'appendFile fd'), + scheduleCall( + s, + () => efs.writeFile(path1, dataB), + 'writeFile path', + ), + scheduleCall(s, () => efs.writeFile(fd, dataB), 'writeFile fd'), + scheduleCall(s, () => efs.write(fd, dataB), 'write'), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (let i = 0; i < 10; i++) { + writeStream.write(dataA); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'readStream', + ), + ]); + await s.waitAll(); + // Expecting no transaction errors + await prom; + const stat = await efs.stat(path1); + expect(stat.size).toEqual(100); + const contents = await efs.readFile(path1); + expect(contents.length).toEqual(100); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.appendFIle and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); const dataA = 'A'.repeat(10); @@ -2726,6 +3218,60 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(stat.size).toEqual(50); } }); + test('EncryptedFS.copyFile, EncryptedFS.writeFile, EncryptedFS.writeFile with fd, EncryptedFS.write and EncryptedFS.createWriteStream', async () => { + const path1 = pathNode.join('dir', 'file1'); + const path2 = utils.pathJoin('dir', 'file2'); + const dataA = 'A'.repeat(10); + const dataB = 'B'.repeat(10); + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + const fd = await efs.open(path1, 'wx+'); + await efs.writeFile(path1, ''); + await efs.writeFile(path1, dataA); + + const prom = Promise.all([ + scheduleCall(s, () => efs.copyFile(path1, path2), 'copyFile'), + scheduleCall( + s, + () => efs.writeFile(path1, dataB), + 'writeFile path', + ), + scheduleCall(s, () => efs.writeFile(fd, dataB), 'writeFile fd'), + scheduleCall(s, () => efs.write(fd, dataB), 'write'), + scheduleCall( + s, + async () => { + const writeStream = efs.createWriteStream(path1); + for (let i = 0; i < 10; i++) { + writeStream.write(dataA); + } + writeStream.end(); + const endProm = promise(); + writeStream.on('finish', () => endProm.resolveP()); + await endProm.p; + }, + 'writeStream', + ), + ]); + await s.waitAll(); + // Expecting no transaction errors + await prom; + const stat = await efs.stat(path1); + expect(stat.size).toEqual(100); + const contents = await efs.readFile(path1); + expect(contents.length).toEqual(100); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.copyFile and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); const path2 = utils.pathJoin('dir', 'file2'); @@ -2941,7 +3487,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { { status: 'fulfilled', value: undefined }, ]); // All A, in multiples of 5 - expect(contents).toMatch(RegExp('^[^A]*((AAAAA)+[^A]*)$')); + expect(contents).toMatch(RegExp('^(AAAAA)*$')); // Contents length between 0 and 10*5 expect(contents.length).toBeGreaterThanOrEqual(0); expect(contents.length).toBeLessThanOrEqual(50); @@ -2977,7 +3523,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { { status: 'fulfilled', value: undefined }, ]); // All A, in multiples of 5 - expect(contents).toMatch(RegExp('^[^A]*((AAAAA)+[^A]*)$')); + expect(contents).toMatch(RegExp('^(AAAAA)*$')); // Contents length between 0 and 10*5 expect(contents.length).toBeGreaterThanOrEqual(0); expect(contents.length).toBeLessThanOrEqual(50); @@ -3022,59 +3568,96 @@ describe(`${EncryptedFS.name} Concurrency`, () => { expect(stat.size).toBeLessThanOrEqual(50); }); test('EncryptedFS.readFile and EncryptedFS.writeFile', async () => { - const path1 = utils.pathJoin('dir', 'file1'); - await efs.mkdir('dir'); + const path1 = pathNode.join('dir', 'file1'); const dataA = 'AAAAA'; const dataB = 'BBBBB'; - await efs.writeFile(path1, dataA); - - let results = await Promise.allSettled([ - (async () => { - return await efs.writeFile(path1, dataB); - })(), - (async () => { - return (await efs.readFile(path1)).toString(); - })(), - ]); - - if (results[1].status === 'fulfilled' && results[1].value[0] === 'A') { - expect(results).toStrictEqual([ - { status: 'fulfilled', value: undefined }, - { status: 'fulfilled', value: dataA }, - ]); - } else { - expect(results).toStrictEqual([ - { status: 'fulfilled', value: undefined }, - { status: 'fulfilled', value: dataB }, - ]); - } - - // Cleaning up - await efs.rmdir('dir', { recursive: true }); - await efs.mkdir('dir'); - await efs.writeFile(path1, dataA); - - results = await Promise.allSettled([ - (async () => { - await sleep(100); - return await efs.writeFile(path1, dataB); - })(), - (async () => { - return (await efs.readFile(path1)).toString(); - })(), - ]); - - if (results[1].status === 'fulfilled' && results[1].value[0] === 'A') { - expect(results).toStrictEqual([ - { status: 'fulfilled', value: undefined }, - { status: 'fulfilled', value: dataA }, - ]); - } else { - expect(results).toStrictEqual([ - { status: 'fulfilled', value: undefined }, - { status: 'fulfilled', value: dataB }, - ]); - } + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + await efs.writeFile(path1, dataA); + + const prom = Promise.allSettled([ + scheduleCall(s, () => efs.writeFile(path1, dataB), 'writeFile'), + scheduleCall( + s, + async () => { + return (await efs.readFile(path1)).toString(); + }, + 'readFile', + ), + ]); + await s.waitAll(); + // Expecting no transaction errors + const results = await prom; + if ( + results[1].status === 'fulfilled' && + results[1].value[0] === 'A' + ) { + expect(results).toStrictEqual([ + { status: 'fulfilled', value: undefined }, + { status: 'fulfilled', value: dataA }, + ]); + } else { + expect(results).toStrictEqual([ + { status: 'fulfilled', value: undefined }, + { status: 'fulfilled', value: dataB }, + ]); + } + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, + ); + }); + test('EncryptedFS.read and EncryptedFS.write', async () => { + const path1 = pathNode.join('dir', 'file1'); + const dataA = 'AAAAA'; + const dataB = 'BBBBB'; + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + await efs.writeFile(path1, dataA); + const fd1 = await efs.open(path1, 'r+'); + const fd2 = await efs.open(path1, 'r+'); + const buffer1 = Buffer.alloc(100); + const buffer2 = Buffer.alloc(100); + + const prom = Promise.all([ + scheduleCall(s, () => efs.write(fd1, dataB), 'write fd1'), + scheduleCall( + s, + () => efs.read(fd1, buffer1, undefined, 100), + 'read fd1', + ), + scheduleCall( + s, + () => efs.read(fd2, buffer2, undefined, 100), + 'read fd2', + ), + ]); + await s.waitAll(); + // Expecting no transaction errors + await prom; + + await efs.close(fd1); + await efs.close(fd2); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, + ); }); test('EncryptedFS.read and EncryptedFS.write with different fd', async () => { const path1 = utils.pathJoin('dir', 'file1'); @@ -3215,6 +3798,41 @@ describe(`${EncryptedFS.name} Concurrency`, () => { }); }); describe('concurrent directory manipulation', () => { + test('EncryptedFS.mkdir, recursive and rename', async () => { + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + const prom = Promise.all([ + scheduleCall(s, () => efs.mkdir('dir'), 'mkdir 1'), + scheduleCall(s, () => efs.mkdir('dir'), 'mkdir 2'), + scheduleCall( + s, + () => efs.mkdir('dir/dira/dirb', { recursive: true }), + 'mkdir recursive 1', + ), + scheduleCall( + s, + () => efs.mkdir('dir/dira/dirb', { recursive: true }), + 'mkdir recursive 2', + ), + scheduleCall(s, () => efs.rename('dir', 'one'), 'rename 1'), + scheduleCall(s, () => efs.rename('dir', 'two'), 'rename 2'), + ]); + await s.waitAll(); + // Expecting no transaction errors + try { + await prom; + } catch (e) { + if (!(e instanceof ErrorEncryptedFSError)) throw e; + } + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.mkdir', async () => { const results = await Promise.allSettled([ efs.mkdir('dir'), @@ -3288,6 +3906,35 @@ describe(`${EncryptedFS.name} Concurrency`, () => { ).toBe(true); expect(await efs.readdir('.')).toContain('one'); }); + test('EncryptedFS.readdir, EncryptedFS.rmdir, EncryptedFS.mkdir, EncryptedFS.writeFile and EncryptedFS.rename', async () => { + const path1 = utils.pathJoin('dir', 'file1'); + const path2 = utils.pathJoin('dir', 'file2'); + + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + const prom = Promise.all([ + scheduleCall(s, () => efs.readdir('dir'), 'readdir'), + scheduleCall( + s, + () => efs.rmdir('dir', { recursive: true }), + 'rmdir', + ), + scheduleCall(s, () => efs.mkdir(path1), 'mkdir'), + scheduleCall(s, () => efs.writeFile(path1, 'test'), 'writeFile'), + scheduleCall(s, () => efs.rename(path1, path2), 'rename'), + ]); + await s.waitAll(); + // Expecting no transaction errors + await expectError(prom, ErrorEncryptedFSError); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.readdir and EncryptedFS.rmdir', async () => { await efs.mkdir('dir'); // It is possible for only one to succeed or both can succeed @@ -3621,68 +4268,86 @@ describe(`${EncryptedFS.name} Concurrency`, () => { } }); test('EncryptedFS.rmdir and EncryptedFS.rename', async () => { - const PATH1 = utils.pathJoin('dir', 'p1'); - const PATH2 = utils.pathJoin('dir', 'p2'); - await efs.mkdir('dir'); - await efs.mkdir(PATH1); - - // Directories - let results = await Promise.allSettled([ - (async () => { - return await efs.rmdir(PATH1); - })(), - (async () => { - return await efs.rename(PATH1, PATH2); - })(), - ]); - if ( - results[0].status === 'fulfilled' && - results[1].status === 'rejected' - ) { - expect(results[0]).toStrictEqual({ - status: 'fulfilled', - value: undefined, - }); - expectReason(results[1], ErrorEncryptedFSError, errno.ENOENT); - } else { - expectReason(results[0], ErrorEncryptedFSError, errno.ENOENT); - expect(results[1]).toStrictEqual({ - status: 'fulfilled', - value: undefined, - }); - } - await efs.rmdir('dir', { recursive: true }); - await efs.mkdir('dir'); - await efs.mkdir(PATH1); + const path1 = utils.pathJoin('dir', 'file1'); + const path2 = utils.pathJoin('dir', 'file2'); - results = await Promise.allSettled([ - (async () => { - await sleep(100); - return await efs.rmdir(PATH1); - })(), - (async () => { - return await efs.rename(PATH1, PATH2); - })(), - ]); - if ( - results[0].status === 'fulfilled' && - results[1].status === 'rejected' - ) { - expect(results[0]).toStrictEqual({ - status: 'fulfilled', - value: undefined, - }); - expectReason(results[1], ErrorEncryptedFSError, errno.ENOENT); - } else { - expectReason(results[0], ErrorEncryptedFSError, errno.ENOENT); - expect(results[1]).toStrictEqual({ - status: 'fulfilled', - value: undefined, - }); - } + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + await efs.mkdir(path1); + + const prom = Promise.allSettled([ + scheduleCall(s, () => efs.rmdir(path1), 'rmdir'), + scheduleCall(s, () => efs.rename(path1, path2), 'rename'), + ]); + await s.waitAll(); + // Expecting no transaction errors + const results = await prom; + if ( + results[0].status === 'fulfilled' && + results[1].status === 'rejected' + ) { + expect(results[0]).toStrictEqual({ + status: 'fulfilled', + value: undefined, + }); + expectReason(results[1], ErrorEncryptedFSError, errno.ENOENT); + } else { + expectReason(results[0], ErrorEncryptedFSError, errno.ENOENT); + expect(results[1]).toStrictEqual({ + status: 'fulfilled', + value: undefined, + }); + } + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, + ); }); }); describe('concurrent symlinking', () => { + test('EncryptedFS.symlink, EncryptedFS.symlink and EncryptedFS.mknod', async () => { + const path1 = utils.pathJoin('dir', 'file1'); + const path2 = utils.pathJoin('dir', 'file2'); + + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + await efs.writeFile(path1, 'test'); + const fd = await efs.open(path1, 'r+'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.symlink(path1, path2), 'symlink 1'), + scheduleCall(s, () => efs.symlink(path1, path2), 'symlink 2'), + scheduleCall( + s, + () => efs.mknod(path2, constants.S_IFREG, 0, 0), + 'mknod', + ), + scheduleCall(s, () => efs.mkdir(path2), 'mkdir'), + scheduleCall(s, () => efs.write(fd, 'test'), 'write'), + ]); + await s.waitAll(); + // Expecting no transaction errors + await expectError(prom, ErrorEncryptedFSError); + + await efs.close(fd); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, + ); + }); test('EncryptedFS.symlink and EncryptedFS.symlink', async () => { const path1 = utils.pathJoin('dir', 'file1'); const path2 = utils.pathJoin('dir', 'file2'); @@ -3894,6 +4559,35 @@ describe(`${EncryptedFS.name} Concurrency`, () => { }); }); describe('concurrent inode linking and unlinking', () => { + test('EncryptedFS.link, EncryptedFS.link and EncryptedFS.symlink', async () => { + const path1 = utils.pathJoin('dir', 'file1'); + const path2 = utils.pathJoin('dir', 'file2'); + + await fc.assert( + fc + .asyncProperty(fc.scheduler(), async (s) => { + await efs.mkdir('dir'); + await efs.writeFile(path1, 'test'); + + const prom = Promise.all([ + scheduleCall(s, () => efs.link(path1, path2), 'link 1'), + scheduleCall(s, () => efs.link(path1, path2), 'link 2'), + scheduleCall(s, () => efs.symlink(path1, path2), 'symlink'), + ]); + await s.waitAll(); + // Expecting no transaction errors + await expectError(prom, ErrorEncryptedFSError); + }) + .afterEach(async () => { + // Cleaning up + await efs.rmdir('dir', { recursive: true }); + }), + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, + ); + }); test('EncryptedFS.link and EncryptedFS.link', async () => { const path1 = utils.pathJoin('dir', 'file1'); const path2 = utils.pathJoin('dir', 'file2'); diff --git a/tests/fd/FileDescriptor.test.ts b/tests/fd/FileDescriptor.test.ts index c431d4f9..7e3f3455 100644 --- a/tests/fd/FileDescriptor.test.ts +++ b/tests/fd/FileDescriptor.test.ts @@ -33,7 +33,6 @@ describe('File Descriptor', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); await db.start(); diff --git a/tests/fd/FileDescriptorManager.test.ts b/tests/fd/FileDescriptorManager.test.ts index 7b175bc3..6070c330 100644 --- a/tests/fd/FileDescriptorManager.test.ts +++ b/tests/fd/FileDescriptorManager.test.ts @@ -32,7 +32,6 @@ describe('File Descriptor Manager', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); await db.start(); diff --git a/tests/inodes/INodeManager.dir.test.ts b/tests/inodes/INodeManager.dir.test.ts index b719faff..f767eacc 100644 --- a/tests/inodes/INodeManager.dir.test.ts +++ b/tests/inodes/INodeManager.dir.test.ts @@ -28,7 +28,6 @@ describe('INodeManager Directory', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); await db.start(); diff --git a/tests/inodes/INodeManager.file.test.ts b/tests/inodes/INodeManager.file.test.ts index 4b2a7b7d..e595915c 100644 --- a/tests/inodes/INodeManager.file.test.ts +++ b/tests/inodes/INodeManager.file.test.ts @@ -30,7 +30,6 @@ describe('INodeManager File', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); await db.start(); diff --git a/tests/inodes/INodeManager.symlink.test.ts b/tests/inodes/INodeManager.symlink.test.ts index 75cc2684..a1acdce3 100644 --- a/tests/inodes/INodeManager.symlink.test.ts +++ b/tests/inodes/INodeManager.symlink.test.ts @@ -26,7 +26,6 @@ describe('INodeManager Symlink', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); await db.start(); diff --git a/tests/inodes/INodeManager.test.ts b/tests/inodes/INodeManager.test.ts index ca187dc7..9c3d3969 100644 --- a/tests/inodes/INodeManager.test.ts +++ b/tests/inodes/INodeManager.test.ts @@ -2,7 +2,7 @@ import os from 'os'; import pathNode from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; +import { DB, errors as dbErrors } from '@matrixai/db'; import INodeManager from '@/inodes/INodeManager'; import * as utils from '@/utils'; import * as permissions from '@/permissions'; @@ -27,7 +27,6 @@ describe('INodeManager', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); }); @@ -64,7 +63,6 @@ describe('INodeManager', () => { decrypt: utils.decrypt, }, }, - // @ts-ignore - version of js-logger is incompatible (remove when js-db updates to 5.* here) logger, }); iNodeMgr = await INodeManager.createINodeManager({ @@ -89,16 +87,24 @@ describe('INodeManager', () => { await iNodeMgr.withTransactionF(async (tran) => { await tran.put([...iNodeMgr.mgrDbPath, 'test'], 0); }); - await Promise.all([ - iNodeMgr.withTransactionF(async (tran) => { - const num = (await tran.get([...iNodeMgr.mgrDbPath, 'test']))!; - await tran.put([...iNodeMgr.mgrDbPath, 'test'], num + 1); - }), - iNodeMgr.withTransactionF(async (tran) => { - const num = (await tran.get([...iNodeMgr.mgrDbPath, 'test']))!; - await tran.put([...iNodeMgr.mgrDbPath, 'test'], num + 1); - }), - ]); + await expect( + Promise.all([ + iNodeMgr.withTransactionF(async (tran) => { + const num = (await tran.get([ + ...iNodeMgr.mgrDbPath, + 'test', + ]))!; + await tran.put([...iNodeMgr.mgrDbPath, 'test'], num + 1); + }), + iNodeMgr.withTransactionF(async (tran) => { + const num = (await tran.get([ + ...iNodeMgr.mgrDbPath, + 'test', + ]))!; + await tran.put([...iNodeMgr.mgrDbPath, 'test'], num + 1); + }), + ]), + ).rejects.toThrow(dbErrors.ErrorDBTransactionConflict); await iNodeMgr.withTransactionF(async (tran) => { const num = (await tran.get([...iNodeMgr.mgrDbPath, 'test']))!; // Race condition clobbers the counter diff --git a/tests/utils.ts b/tests/utils.ts index 0602105f..952c7d6a 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,4 +1,5 @@ import type EncryptedFS from '@/EncryptedFS'; +import type * as fc from 'fast-check'; import * as constants from '@/constants'; /** @@ -101,6 +102,20 @@ function setId(efs: EncryptedFS, uid: number, gid?: number) { efs.gid = gid ?? uid; } -export { expectError, expectReason, createFile, supportedTypes, sleep, setId }; +const scheduleCall = ( + s: fc.Scheduler, + f: () => Promise, + label: string = 'scheduled call', +) => s.schedule(Promise.resolve(label)).then(() => f()); + +export { + expectError, + expectReason, + createFile, + supportedTypes, + sleep, + setId, + scheduleCall, +}; export type { FileTypes };