From c7a3e3cf32b9da2187a959293e27a686e6b9ddae Mon Sep 17 00:00:00 2001 From: Emma Casolin Date: Mon, 25 Jul 2022 13:13:48 +1000 Subject: [PATCH 1/7] build: updating `js-logger` to 3.0.0 and `js-db` to 5.0.1 --- package-lock.json | 524 +++---------------------------------- package.json | 2 +- src/inodes/INodeManager.ts | 47 ++-- 3 files changed, 56 insertions(+), 517 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3251b9fe..26061a12 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.1", "@matrixai/errors": "^1.1.3", "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.4", @@ -1207,18 +1207,23 @@ } }, "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.1", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.1.tgz", + "integrity": "sha512-5M+2+QPRzQd1LUgdCq0j6I3z9mvXQFxJ+FoW40q7NwQMm5gTJjlmJ6pEsWIYcS32xaVWpKJdfLcGoSMndiZ9DA==", + "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": { @@ -1578,11 +1583,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 +1915,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 +2206,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 +2315,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 +2366,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 +2580,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 +2670,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", @@ -3673,25 +3581,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 +3694,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 +4718,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 +4818,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 +4947,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 +4962,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", @@ -5703,6 +5439,7 @@ "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 +5645,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,17 +7488,17 @@ } }, "@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.1", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.1.tgz", + "integrity": "sha512-5M+2+QPRzQd1LUgdCq0j6I3z9mvXQFxJ+FoW40q7NwQMm5gTJjlmJ6pEsWIYcS32xaVWpKJdfLcGoSMndiZ9DA==", "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": { @@ -8009,11 +7724,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 +7967,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 +8182,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 +8263,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 +8295,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 +8466,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 +8529,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", @@ -9560,11 +9218,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 +9298,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 +10048,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 +10130,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 +10228,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 +10240,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", @@ -11033,7 +10582,8 @@ "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 +10711,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..4d1684d3 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.1", "@matrixai/errors": "^1.1.3", "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.4", diff --git a/src/inodes/INodeManager.ts b/src/inodes/INodeManager.ts index 1e168fc6..f054e07a 100644 --- a/src/inodes/INodeManager.ts +++ b/src/inodes/INodeManager.ts @@ -81,10 +81,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 +121,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([ @@ -518,7 +516,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 +546,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 +638,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 +656,7 @@ class INodeManager { }; } } finally { - await gcIterator.end(); + await gcIterator.destroy(); } } @@ -931,8 +928,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 +1097,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 +1128,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; } From 957530bfc46d91d7562df20bb2c8233213b97975 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Wed, 17 Aug 2022 15:22:34 +1000 Subject: [PATCH 2/7] feat: `INodeManager` uses transaction locks for locking - `@matrixai/db` 5.0.2 supports `ToString` lock requests --- package-lock.json | 26 ++++--------- package.json | 2 +- src/inodes/INodeManager.ts | 76 +++++++++++--------------------------- 3 files changed, 29 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26061a12..0b7d52a3 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": "^5.0.1", + "@matrixai/db": "^5.0.2", "@matrixai/errors": "^1.1.3", "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.4", @@ -1207,9 +1207,9 @@ } }, "node_modules/@matrixai/db": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.1.tgz", - "integrity": "sha512-5M+2+QPRzQd1LUgdCq0j6I3z9mvXQFxJ+FoW40q7NwQMm5gTJjlmJ6pEsWIYcS32xaVWpKJdfLcGoSMndiZ9DA==", + "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.8.1", @@ -1226,11 +1226,6 @@ "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", @@ -7488,9 +7483,9 @@ } }, "@matrixai/db": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.1.tgz", - "integrity": "sha512-5M+2+QPRzQd1LUgdCq0j6I3z9mvXQFxJ+FoW40q7NwQMm5gTJjlmJ6pEsWIYcS32xaVWpKJdfLcGoSMndiZ9DA==", + "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.8.1", "@matrixai/async-locks": "^3.1.1", @@ -7500,13 +7495,6 @@ "@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": { diff --git a/package.json b/package.json index 4d1684d3..3f5da66b 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": "^5.0.1", + "@matrixai/db": "^5.0.2", "@matrixai/errors": "^1.1.3", "@matrixai/logger": "^3.0.0", "@matrixai/resources": "^1.1.4", diff --git a/src/inodes/INodeManager.ts b/src/inodes/INodeManager.ts index f054e07a..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; @@ -208,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]; }; } @@ -238,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()) @@ -257,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()) @@ -287,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); + }, ); } @@ -328,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); + }, ); } From 823c1d0821a56c5957a835309469b3a8f0e38c8e Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Wed, 17 Aug 2022 15:25:28 +1000 Subject: [PATCH 3/7] fix: `_open` can compose with transactions This was needed for the very specific case where `_open` was used to create a file during an existing transaction. --- src/EncryptedFS.ts | 62 ++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/EncryptedFS.ts b/src/EncryptedFS.ts index ea338207..f51a0cf1 100644 --- a/src/EncryptedFS.ts +++ b/src/EncryptedFS.ts @@ -723,6 +723,7 @@ class EncryptedFS { dstPath, dstFlags, srcINodeStat.mode, + tran, ); const dstINode = dstFd.ino; const dstINodeType = (await this.iNodeMgr.get(dstINode, tran))?.type; @@ -1893,6 +1894,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 +1994,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 +2035,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 +2075,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 +2113,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 +2133,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 +2159,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; + } } } } From 633e03c49b64777f2f2e48fe2e50dbf2e2e2f4b0 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Wed, 17 Aug 2022 15:26:00 +1000 Subject: [PATCH 4/7] tests: fixing test failures fix: `tests/inodes/INodeManager.test.ts` change to using `await expect().rejects.toThrow` --- tests/EncryptedFS.concurrent.test.ts | 4 ++-- tests/inodes/INodeManager.test.ts | 30 ++++++++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/EncryptedFS.concurrent.test.ts b/tests/EncryptedFS.concurrent.test.ts index fdad64b5..1da8e900 100644 --- a/tests/EncryptedFS.concurrent.test.ts +++ b/tests/EncryptedFS.concurrent.test.ts @@ -2941,7 +2941,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 +2977,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); diff --git a/tests/inodes/INodeManager.test.ts b/tests/inodes/INodeManager.test.ts index ca187dc7..9c81540b 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'; @@ -89,16 +89,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 From 83315cb0d609061b695d750eff3ddfee061772e3 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Wed, 17 Aug 2022 19:14:45 +1000 Subject: [PATCH 5/7] tests: expanding concurrency tests with fast-check This just adds some tests that hit the API with a bunch of concurrent operations with random order. This is to check for any db transaction errors. --- package-lock.json | 42 + package.json | 3 +- tests/EncryptedFS.concurrent.test.ts | 1106 +++++++++++++++++++++----- 3 files changed, 936 insertions(+), 215 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b7d52a3..837a3586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -3162,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", @@ -5430,6 +5447,16 @@ "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", @@ -8899,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", @@ -10567,6 +10603,12 @@ "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", diff --git a/package.json b/package.json index 3f5da66b..5e59d200 100644 --- a/package.json +++ b/package.json @@ -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/tests/EncryptedFS.concurrent.test.ts b/tests/EncryptedFS.concurrent.test.ts index 1da8e900..0b2de4bf 100644 --- a/tests/EncryptedFS.concurrent.test.ts +++ b/tests/EncryptedFS.concurrent.test.ts @@ -6,23 +6,40 @@ 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 } from './utils'; describe(`${EncryptedFS.name} Concurrency`, () => { const logger = new Logger(`${EncryptedFS.name} Concurrency`, LogLevel.WARN, [ new StreamHandler(), ]); const dbKey: Buffer = utils.generateKeySync(256); + const interruptAfterTimeLimit = globalThis.defaultTimeout - 2000; let dataDir: string; let db: DB; let iNodeMgr: INodeManager; let efs: EncryptedFS; + + const scheduleCall = ( + s: fc.Scheduler, + f: () => Promise, + label: string = 'scheduled call', + ) => s.schedule(Promise.resolve(label)).then(() => f()); + + 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-'), @@ -57,6 +74,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 }, + ); + }); test('EncryptedFS.open', async () => { // Only one call wins the race to create the file await Promise.all([ @@ -240,120 +308,151 @@ 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 }, ); - 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 }, ); - 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 }, ); - 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 }, + ); + }); + 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 }, ); }); test('EncryptedFS.fallocate and EncryptedFS.writeFile', async () => { @@ -656,6 +755,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 }, + ); + }); test('EncryptedFS.truncate and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -965,6 +1108,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 }, + ); + }); test('EncryptedFS.ftruncate and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -1274,6 +1461,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 }, + ); + }); test('EncryptedFS.utimes and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); const nowTime = Date.now(); @@ -1428,6 +1642,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 }, + ); + }); test('EncryptedFS.lseek and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -1756,6 +2026,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 }, + ); + }); test('EncryptedFS.createReadStream and EncryptedFS.createWriteStream', async () => { const path1 = utils.pathJoin('dir', 'file1'); const dataA = 'AAAAA'; @@ -2205,6 +2579,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 }, + ); + }); test('EncryptedFS.unlink and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); await efs.mkdir('dir'); @@ -2430,6 +2861,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 }, + ); + }); test('EncryptedFS.appendFIle and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); const dataA = 'A'.repeat(10); @@ -2726,6 +3214,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 }, + ); + }); test('EncryptedFS.copyFile and EncryptedFS.writeFile', async () => { const path1 = utils.pathJoin('dir', 'file1'); const path2 = utils.pathJoin('dir', 'file2'); @@ -3022,59 +3564,90 @@ 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 }, + ); + }); + 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 }, + ); }); test('EncryptedFS.read and EncryptedFS.write with different fd', async () => { const path1 = utils.pathJoin('dir', 'file1'); @@ -3215,6 +3788,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 }, + ); + }); test('EncryptedFS.mkdir', async () => { const results = await Promise.allSettled([ efs.mkdir('dir'), @@ -3288,6 +3896,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 }, + ); + }); 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 +4258,83 @@ 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 }, + ); }); }); 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 }, + ); + }); test('EncryptedFS.symlink and EncryptedFS.symlink', async () => { const path1 = utils.pathJoin('dir', 'file1'); const path2 = utils.pathJoin('dir', 'file2'); @@ -3894,6 +4546,32 @@ 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 }, + ); + }); test('EncryptedFS.link and EncryptedFS.link', async () => { const path1 = utils.pathJoin('dir', 'file1'); const path2 = utils.pathJoin('dir', 'file2'); From 55b27184b87c186c6927cd696ce18f0d8e7cbb95 Mon Sep 17 00:00:00 2001 From: Roger Qiu Date: Fri, 19 Aug 2022 13:22:18 +1000 Subject: [PATCH 6/7] fix: `@matrixai/db` 5.* supports `@matrixai/logger` at 3.* --- benches/db_1KiB.ts | 2 -- benches/db_1MiB.ts | 2 -- benches/db_24KiB.ts | 2 -- src/EncryptedFS.ts | 1 - tests/EncryptedFS.concurrent.test.ts | 1 - tests/fd/FileDescriptor.test.ts | 1 - tests/fd/FileDescriptorManager.test.ts | 1 - tests/inodes/INodeManager.dir.test.ts | 1 - tests/inodes/INodeManager.file.test.ts | 1 - tests/inodes/INodeManager.symlink.test.ts | 1 - tests/inodes/INodeManager.test.ts | 2 -- 11 files changed, 15 deletions(-) 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/src/EncryptedFS.ts b/src/EncryptedFS.ts index f51a0cf1..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, }); diff --git a/tests/EncryptedFS.concurrent.test.ts b/tests/EncryptedFS.concurrent.test.ts index 0b2de4bf..6fca3225 100644 --- a/tests/EncryptedFS.concurrent.test.ts +++ b/tests/EncryptedFS.concurrent.test.ts @@ -53,7 +53,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({ 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 9c81540b..9c3d3969 100644 --- a/tests/inodes/INodeManager.test.ts +++ b/tests/inodes/INodeManager.test.ts @@ -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({ From 2e9969261213ad55be6010455c4ca415c0367ffd Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Fri, 19 Aug 2022 16:26:06 +1000 Subject: [PATCH 7/7] tests: inlined `interruptAfterTimeLimit` and moved `scheduleCall` to `tests/utils.ts` --- tests/EncryptedFS.concurrent.test.ts | 75 +++++++++++++++++----------- tests/utils.ts | 17 ++++++- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/tests/EncryptedFS.concurrent.test.ts b/tests/EncryptedFS.concurrent.test.ts index 6fca3225..30f96e1f 100644 --- a/tests/EncryptedFS.concurrent.test.ts +++ b/tests/EncryptedFS.concurrent.test.ts @@ -13,25 +13,18 @@ import * as utils from '@/utils'; import * as constants from '@/constants'; import INodeManager from '@/inodes/INodeManager'; import { promise } from '@/utils'; -import { expectError, expectReason, sleep } from './utils'; +import { expectError, expectReason, sleep, scheduleCall } from './utils'; describe(`${EncryptedFS.name} Concurrency`, () => { const logger = new Logger(`${EncryptedFS.name} Concurrency`, LogLevel.WARN, [ new StreamHandler(), ]); const dbKey: Buffer = utils.generateKeySync(256); - const interruptAfterTimeLimit = globalThis.defaultTimeout - 2000; let dataDir: string; let db: DB; let iNodeMgr: INodeManager; let efs: EncryptedFS; - const scheduleCall = ( - s: fc.Scheduler, - f: () => Promise, - label: string = 'scheduled call', - ) => s.schedule(Promise.resolve(label)).then(() => f()); - const totalINodes = async (iNodeMgr: INodeManager) => { let counter = 0; for await (const _ of iNodeMgr.getAll()) { @@ -121,7 +114,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.open', async () => { @@ -335,7 +328,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { await efs.close(fd); } }), - { numRuns: 50, interruptAfterTimeLimit }, + { + numRuns: 50, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.write on the same file descriptor', async () => { @@ -356,7 +352,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { ); await efs.close(fd); }), - { numRuns: 20, interruptAfterTimeLimit }, + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.writeFile', async () => { @@ -379,7 +378,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { ); expect(await totalINodes(iNodeMgr)).toEqual(2); }), - { numRuns: 50, interruptAfterTimeLimit }, + { + numRuns: 50, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.appendFile', async () => { @@ -408,7 +410,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { await efs.readFile('test', { encoding: 'utf-8' }), ); }), - { numRuns: 20, interruptAfterTimeLimit }, + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.fallocate, EncryptedFS.writeFile, EncryptedFS.write and EncryptedFS.createWriteStream ', async () => { @@ -451,7 +456,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.fallocate and EncryptedFS.writeFile', async () => { @@ -795,7 +800,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.truncate and EncryptedFS.writeFile', async () => { @@ -1148,7 +1153,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.ftruncate and EncryptedFS.writeFile', async () => { @@ -1484,7 +1489,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.utimes and EncryptedFS.writeFile', async () => { @@ -1694,7 +1699,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.lseek and EncryptedFS.writeFile', async () => { @@ -2126,7 +2131,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.createReadStream and EncryptedFS.createWriteStream', async () => { @@ -2632,7 +2637,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.unlink and EncryptedFS.writeFile', async () => { @@ -2914,7 +2919,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.appendFIle and EncryptedFS.writeFile', async () => { @@ -3264,7 +3269,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.copyFile and EncryptedFS.writeFile', async () => { @@ -3604,7 +3609,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { numRuns: 20, interruptAfterTimeLimit }, + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.read and EncryptedFS.write', async () => { @@ -3645,7 +3653,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { numRuns: 20, interruptAfterTimeLimit }, + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.read and EncryptedFS.write with different fd', async () => { @@ -3819,7 +3830,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.mkdir', async () => { @@ -3921,7 +3932,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.readdir and EncryptedFS.rmdir', async () => { @@ -4294,7 +4305,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { numRuns: 20, interruptAfterTimeLimit }, + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); }); @@ -4331,7 +4345,7 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { interruptAfterTimeLimit }, + { interruptAfterTimeLimit: globalThis.defaultTimeout - 2000 }, ); }); test('EncryptedFS.symlink and EncryptedFS.symlink', async () => { @@ -4568,7 +4582,10 @@ describe(`${EncryptedFS.name} Concurrency`, () => { // Cleaning up await efs.rmdir('dir', { recursive: true }); }), - { numRuns: 20, interruptAfterTimeLimit }, + { + numRuns: 20, + interruptAfterTimeLimit: globalThis.defaultTimeout - 2000, + }, ); }); test('EncryptedFS.link and EncryptedFS.link', async () => { 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 };