@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cliOperations.ts +12 -0
- package/bin/restart.ts +66 -6
- package/components/Application.ts +1134 -112
- package/components/DESIGN.md +421 -0
- package/components/OptionsWatcher.ts +368 -102
- package/components/Scope.ts +20 -1
- package/components/componentLoader.ts +43 -4
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/DESIGN.md +109 -0
- package/components/mcp/audit.ts +21 -17
- package/components/operations.js +284 -52
- package/components/operationsValidation.js +49 -2
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/config/DESIGN.md +306 -0
- package/config/RootConfigWatcher.ts +191 -37
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +78 -26
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/DESIGN.md +179 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
- package/dataLayer/restoreMarker.ts +92 -25
- package/dist/bin/cliOperations.js +13 -0
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/restart.js +42 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/components/Application.d.ts +104 -9
- package/dist/components/Application.js +954 -102
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -1
- package/dist/components/OptionsWatcher.js +378 -104
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.js +15 -1
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +35 -3
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -1
- package/dist/components/mcp/audit.js +21 -17
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +263 -54
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +48 -2
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +2 -0
- package/dist/config/RootConfigWatcher.js +189 -35
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +10 -9
- package/dist/config/configUtils.js +63 -27
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +21 -8
- package/dist/dataLayer/restoreMarker.js +94 -27
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +3 -0
- package/dist/resources/DatabaseTransaction.d.ts +25 -0
- package/dist/resources/DatabaseTransaction.js +224 -18
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +22 -3
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +22 -6
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +1 -1
- package/dist/resources/RecordEncoder.js +46 -10
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +97 -13
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -0
- package/dist/resources/RocksIndexStore.js +2 -1
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
- package/dist/resources/RocksTransactionLogStore.js +104 -33
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +58 -7
- package/dist/resources/Table.js +1143 -348
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +3 -0
- package/dist/resources/analytics/write.js +49 -16
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +170 -0
- package/dist/resources/auditStore.js +457 -11
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/crdt.d.ts +10 -0
- package/dist/resources/crdt.js +22 -0
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +16 -13
- package/dist/resources/databases.js +625 -177
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.js +3 -2
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
- package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
- package/dist/resources/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +47 -4
- package/dist/resources/recordLock.js +138 -7
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2597 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.js +5 -0
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +160 -40
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +59 -23
- package/dist/security/auth.js.map +1 -1
- package/dist/security/deferredAuthentication.d.ts +11 -0
- package/dist/security/deferredAuthentication.js +25 -3
- package/dist/security/deferredAuthentication.js.map +1 -1
- package/dist/security/jsLoader.js +9 -4
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/REST.js +39 -5
- package/dist/server/REST.js.map +1 -1
- package/dist/server/http.d.ts +5 -1
- package/dist/server/http.js +34 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/mqtt.js +5 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +2 -0
- package/dist/server/serverHelpers/Headers.js +6 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
- package/dist/server/serverHelpers/contentTypes.js +189 -15
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
- package/dist/server/serverHelpers/serverUtilities.js +110 -20
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +4 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
- package/dist/server/storageReclamation.js +1 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- package/dist/server/threads/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +64 -6
- package/dist/server/threads/manageThreads.js +261 -12
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +196 -13
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +30 -7
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +24 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.js +218 -38
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +172 -92
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/npmUtilities.js +6 -4
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/watcherFallback.d.ts +0 -45
- package/dist/utility/watcherFallback.js +1 -125
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.js +6 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +9 -0
- package/json/systemSchema.json +3 -0
- package/npm-shrinkwrap.json +177 -87
- package/package.json +13 -5
- package/resources/DESIGN.md +689 -19
- package/resources/DatabaseTransaction.ts +230 -17
- package/resources/LMDBTransaction.ts +21 -3
- package/resources/PrimaryRocksDatabase.ts +20 -7
- package/resources/RecordEncoder.ts +52 -10
- package/resources/Resource.ts +97 -13
- package/resources/ResourceInterface.ts +8 -0
- package/resources/RocksIndexStore.ts +2 -1
- package/resources/RocksTransactionLogStore.ts +111 -31
- package/resources/Table.ts +1273 -396
- package/resources/analytics/DESIGN.md +38 -0
- package/resources/analytics/write.ts +50 -17
- package/resources/auditStore.ts +460 -11
- package/resources/crdt.ts +22 -0
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +611 -147
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +3 -2
- package/resources/indexes/DESIGN.md +833 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
- package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
- package/resources/indexes/fullTextNativeBinding.ts +146 -0
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/record-locks.md +1407 -0
- package/resources/recordLock.ts +173 -7
- package/resources/recordLockCoordinator.ts +3082 -0
- package/resources/replayLogs.ts +5 -0
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/DESIGN.md +40 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +169 -49
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +4 -4
- package/security/DESIGN.md +175 -0
- package/security/auth.ts +53 -24
- package/security/deferredAuthentication.ts +24 -2
- package/security/jsLoader.ts +9 -4
- package/server/DESIGN.md +275 -0
- package/server/REST.ts +42 -6
- package/server/http.ts +34 -2
- package/server/mqtt.ts +6 -4
- package/server/serverHelpers/Headers.ts +5 -1
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +188 -15
- package/server/serverHelpers/serverUtilities.ts +157 -27
- package/server/serverHelpers/uwsServer.ts +4 -1
- package/server/serverHelpers/webSocketCloseReason.ts +25 -0
- package/server/storageReclamation.ts +2 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +254 -12
- package/server/threads/socketRouter.ts +217 -11
- package/server/threads/threadServer.js +30 -7
- package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
- package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
- package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
- package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
- package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
- package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
- package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
- package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
- package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
- package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
- package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
- package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
- package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
- package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
- package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
- package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
- package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
- package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
- package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
- package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
- package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
- package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
- package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
- package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
- package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
- package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/DESIGN.md +55 -0
- package/utility/errors/hdbError.ts +54 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/logging/harper_logger.ts +210 -30
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +196 -91
- package/utility/npmUtilities.ts +6 -4
- package/utility/watcherFallback.ts +0 -122
- package/validation/configValidator.ts +6 -3
|
@@ -2,12 +2,24 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RootConfigWatcher = void 0;
|
|
4
4
|
exports.getSharedRootConfigWatcher = getSharedRootConfigWatcher;
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
5
|
const configUtils_ts_1 = require("./configUtils.js");
|
|
6
|
+
const readConfigFileSync_ts_1 = require("./readConfigFileSync.js");
|
|
7
7
|
const node_events_1 = require("node:events");
|
|
8
|
-
const
|
|
8
|
+
const parseConfigFile_ts_1 = require("./parseConfigFile.js");
|
|
9
9
|
const watcherFallback_ts_1 = require("../utility/watcherFallback.js");
|
|
10
10
|
const watchPath_ts_1 = require("../utility/watchPath.js");
|
|
11
|
+
const harper_logger_ts_1 = require("../utility/logging/harper_logger.js");
|
|
12
|
+
const configReadRetry_ts_1 = require("./configReadRetry.js");
|
|
13
|
+
const watcherArming_ts_1 = require("./watcherArming.js");
|
|
14
|
+
function isMissingFile(error) {
|
|
15
|
+
return !!error && typeof error === 'object' && error.code === 'ENOENT';
|
|
16
|
+
}
|
|
17
|
+
// `harper_logger` imports this module at its own bottom to break their cycle, so a tagged logger
|
|
18
|
+
// built at module scope would run `loggerWithTag()` before `mainLogger` is initialized.
|
|
19
|
+
let taggedLogger;
|
|
20
|
+
function logger() {
|
|
21
|
+
return (taggedLogger ??= (0, harper_logger_ts_1.loggerWithTag)('config-watcher'));
|
|
22
|
+
}
|
|
11
23
|
let sharedWatcher;
|
|
12
24
|
/**
|
|
13
25
|
* The isolate-wide watcher for the root config file. Consumers (logging, models) subscribe to this
|
|
@@ -25,28 +37,79 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
|
|
|
25
37
|
#usingPolling;
|
|
26
38
|
#closed;
|
|
27
39
|
#openCount = 0;
|
|
28
|
-
#
|
|
40
|
+
#readCount = 0;
|
|
41
|
+
#readRetry = new configReadRetry_ts_1.ConfigReadRetry();
|
|
42
|
+
#armGate = new watcherArming_ts_1.ArmGate();
|
|
43
|
+
// The gate above is chokidar's scan finishing; this is the barrier's gate. A terminal outcome
|
|
44
|
+
// opens it without claiming the watch is armed, so the arming re-read still runs afterwards.
|
|
45
|
+
#barrierOpen = false;
|
|
46
|
+
#configLoaded = false;
|
|
47
|
+
#readyStaged = false;
|
|
48
|
+
#readyEmitted = false;
|
|
29
49
|
ready;
|
|
30
50
|
constructor(configFilePath = (0, configUtils_ts_1.getConfigFilePath)()) {
|
|
31
51
|
super();
|
|
32
52
|
this.#configFilePath = configFilePath;
|
|
33
53
|
const watchTarget = (0, watchPath_ts_1.resolveWatchTarget)(this.#configFilePath);
|
|
34
54
|
this.#watchPath = watchTarget.path;
|
|
35
|
-
this.#partialRead = new watcherFallback_ts_1.PartialReadRetry(this.#configFilePath);
|
|
36
55
|
this.#usingPolling = watchTarget.mustPoll;
|
|
37
56
|
this.#closed = false;
|
|
38
57
|
this.ready = (0, node_events_1.once)(this, 'ready');
|
|
39
58
|
this.#openWatcher();
|
|
40
59
|
}
|
|
41
60
|
#openWatcher() {
|
|
42
|
-
this.#openCount
|
|
61
|
+
const generation = ++this.#openCount;
|
|
43
62
|
this.#watcher = (0, watcherFallback_ts_1.guardedWatch)(this.#watchPath, {
|
|
44
63
|
persistent: false,
|
|
45
64
|
...(this.#usingPolling ? watcherFallback_ts_1.POLLING_FALLBACK_OPTIONS : {}),
|
|
46
65
|
})
|
|
47
66
|
.on('add', this.handleChange.bind(this))
|
|
48
67
|
.on('change', this.handleChange.bind(this))
|
|
49
|
-
.on('error', this.handleError.bind(this))
|
|
68
|
+
.on('error', this.handleError.bind(this))
|
|
69
|
+
// Generation-bound: `#armGate.reset()` runs before the failed watcher is closed, so a
|
|
70
|
+
// `ready` still queued on it would arm the gate on the replacement's behalf and the
|
|
71
|
+
// replacement's own `ready` would then be a no-op — leaving its scan window unre-read.
|
|
72
|
+
.on('ready', () => this.#handleArmed(generation));
|
|
73
|
+
}
|
|
74
|
+
#handleArmed(generation) {
|
|
75
|
+
if (this.#closed || generation !== this.#openCount)
|
|
76
|
+
return;
|
|
77
|
+
this.#armGate.arm(() => this.#markArmed());
|
|
78
|
+
}
|
|
79
|
+
#markArmed() {
|
|
80
|
+
this.#barrierOpen = true;
|
|
81
|
+
// A write that landed while the watch was unarmed was never reported, and a scan that found
|
|
82
|
+
// no file at all reported nothing either, so arming always re-reads rather than publishing
|
|
83
|
+
// what an earlier read staged — or, with no file, staying pending forever.
|
|
84
|
+
this.#read(true);
|
|
85
|
+
// A read that armed the ladder settles `ready` itself, with the newer config.
|
|
86
|
+
if (!this.#readRetry.pending)
|
|
87
|
+
this.#emitReady();
|
|
88
|
+
}
|
|
89
|
+
#emitReady() {
|
|
90
|
+
if (this.#readyEmitted || !this.#barrierOpen || !this.#readyStaged || this.#closed)
|
|
91
|
+
return;
|
|
92
|
+
this.#readyEmitted = true;
|
|
93
|
+
try {
|
|
94
|
+
this.emit('ready', this.#config);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger().warn('A Harper configuration listener failed', (0, harper_logger_ts_1.errorForLog)(error));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// `harper_logger.start()` awaits `ready` with no timeout, so every terminal outcome has to
|
|
101
|
+
// settle the barrier. A failed read discards an earlier staged value, but a watcher error keeps
|
|
102
|
+
// it because no later read superseded it. No config is represented by `undefined`, not `{}`:
|
|
103
|
+
// an empty object is a configuration that turns logging off.
|
|
104
|
+
#stageBootFallback(discardStaged = true) {
|
|
105
|
+
if (this.#readyEmitted)
|
|
106
|
+
return;
|
|
107
|
+
if (discardStaged) {
|
|
108
|
+
this.#config = undefined;
|
|
109
|
+
this.#configLoaded = false;
|
|
110
|
+
}
|
|
111
|
+
this.#readyStaged = true;
|
|
112
|
+
this.#emitReady();
|
|
50
113
|
}
|
|
51
114
|
// Test-only: simulate the underlying chokidar watcher emitting an error.
|
|
52
115
|
// Exposed so the polling-fallback path can be exercised without triggering a
|
|
@@ -54,15 +117,23 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
|
|
|
54
117
|
_simulateWatcherErrorForTests(error) {
|
|
55
118
|
this.handleError(error);
|
|
56
119
|
}
|
|
57
|
-
// Test-only: whether the watcher has fallen back to polling.
|
|
58
120
|
get _usingPollingForTests() {
|
|
59
121
|
return this.#usingPolling;
|
|
60
122
|
}
|
|
61
|
-
// Test-only: number of times the underlying watcher has been (re)opened.
|
|
62
123
|
get _openCountForTests() {
|
|
63
124
|
return this.#openCount;
|
|
64
125
|
}
|
|
126
|
+
// Distinguishes a ladder rung from a watcher event.
|
|
127
|
+
get _readCountForTests() {
|
|
128
|
+
return this.#readCount;
|
|
129
|
+
}
|
|
130
|
+
get _armedForTests() {
|
|
131
|
+
return this.#armGate.armed;
|
|
132
|
+
}
|
|
65
133
|
handleError(error) {
|
|
134
|
+
// A queued chokidar error can land after close(), which has dropped every listener.
|
|
135
|
+
if (this.#closed)
|
|
136
|
+
return;
|
|
66
137
|
// See EntryHandler.#handleWatcherError: a lost native watch handle is benign
|
|
67
138
|
// and must not be surfaced to consumers as a config-watch failure.
|
|
68
139
|
if ((0, watcherFallback_ts_1.claimLostNativeWatchError)(error))
|
|
@@ -74,6 +145,9 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
|
|
|
74
145
|
if (!this.#usingPolling) {
|
|
75
146
|
(0, watcherFallback_ts_1.warnWatcherFallback)(this.#configFilePath);
|
|
76
147
|
this.#usingPolling = true;
|
|
148
|
+
// The generation that just failed no longer speaks for the watch; the replacement
|
|
149
|
+
// arms on its own scan, and re-reads then as the first one did.
|
|
150
|
+
this.#armGate.reset();
|
|
77
151
|
// Start close() from a microtask, not directly here, so a synchronous throw
|
|
78
152
|
// can't escape this 'error' listener as an uncaught exception.
|
|
79
153
|
Promise.resolve()
|
|
@@ -85,55 +159,135 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
|
|
|
85
159
|
if (!this.#closed)
|
|
86
160
|
this.#openWatcher();
|
|
87
161
|
})
|
|
88
|
-
.catch((error) =>
|
|
162
|
+
.catch((error) => logger().warn(`Could not reopen the ${this.#configFilePath} watch on polling`, (0, harper_logger_ts_1.errorForLog)(error)));
|
|
89
163
|
}
|
|
164
|
+
else {
|
|
165
|
+
// Already polling — the replacement failed too, or the watch was polling from
|
|
166
|
+
// construction (`mustPoll`) and never had a fallback to take. Either way the branch
|
|
167
|
+
// above reopens only once, so this is the watch's terminal outcome and the barrier
|
|
168
|
+
// has to settle or `harper_logger.start()` awaits it forever.
|
|
169
|
+
this.#barrierOpen = true;
|
|
170
|
+
this.#stageBootFallback(false);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// chokidar may never reach its own `ready` after a scan error, and nothing else would
|
|
175
|
+
// settle the barrier: the error is this read's terminal outcome. The scan is not over
|
|
176
|
+
// though, so the arm gate stays closed and a later `ready` still takes the arming re-read.
|
|
177
|
+
this.#barrierOpen = true;
|
|
178
|
+
this.#stageBootFallback(false);
|
|
179
|
+
// Settling the barrier removed the `error` listener `once(this, 'ready')` attached, and an
|
|
180
|
+
// emit with none left throws the error back into chokidar's dispatch — as does a consumer
|
|
181
|
+
// that throws from its own handler.
|
|
182
|
+
if (this.listenerCount('error') === 0) {
|
|
183
|
+
logger().warn(`The Harper configuration watcher at ${this.#configFilePath} failed`, (0, harper_logger_ts_1.errorForLog)(error));
|
|
90
184
|
return;
|
|
91
185
|
}
|
|
92
|
-
|
|
186
|
+
try {
|
|
187
|
+
this.emit('error', error);
|
|
188
|
+
}
|
|
189
|
+
catch (listenerError) {
|
|
190
|
+
logger().warn('A Harper configuration error listener failed', (0, harper_logger_ts_1.errorForLog)(listenerError));
|
|
191
|
+
}
|
|
93
192
|
}
|
|
94
|
-
// See the descriptor-lifetime invariant on atomicWriteFile (DESIGN.md).
|
|
95
193
|
handleChange() {
|
|
194
|
+
this.#read(true);
|
|
195
|
+
}
|
|
196
|
+
// `harper_logger.start()` awaits `ready` with no timeout and the ladder may be the only thing
|
|
197
|
+
// left to settle it, so until then its timer keeps the thread alive rather than letting it
|
|
198
|
+
// drain and exit mid-boot.
|
|
199
|
+
#schedule() {
|
|
200
|
+
return this.#readRetry.schedule(() => this.#read(false), !this.#readyEmitted);
|
|
201
|
+
}
|
|
202
|
+
#read(waitForLock) {
|
|
203
|
+
// A queued chokidar callback can still land after close(), which has already discarded the
|
|
204
|
+
// config and dropped every listener.
|
|
205
|
+
if (this.#closed)
|
|
206
|
+
return;
|
|
207
|
+
this.#readCount++;
|
|
208
|
+
let data;
|
|
209
|
+
try {
|
|
210
|
+
data = (0, readConfigFileSync_ts_1.readConfigFileSync)(this.#configFilePath, waitForLock);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
// A missing file is not a lock — `readConfigFileSync` does not retry it either, and
|
|
214
|
+
// `OptionsWatcher` settles it immediately as the install window. Taking the ladder here
|
|
215
|
+
// would disagree with that and cost `harper_logger.start()` the whole budget on every
|
|
216
|
+
// boot that has no config file (an env-var-only deployment, an empty mounted rootPath).
|
|
217
|
+
if (!isMissingFile(error) && this.#schedule())
|
|
218
|
+
return;
|
|
219
|
+
// A ladder armed by an earlier empty read is spent by the time a rung lands on ENOENT,
|
|
220
|
+
// and every other terminal path clears its deadline.
|
|
221
|
+
this.#readRetry.reset();
|
|
222
|
+
logger().warn(`Unable to read the Harper configuration file at ${this.#configFilePath}` +
|
|
223
|
+
(this.#configLoaded ? ', continuing with the previously loaded configuration' : '; none has been loaded yet'), (0, harper_logger_ts_1.errorForLog)(error));
|
|
224
|
+
this.#stageBootFallback();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// See DESIGN.md, "An empty read is a writer mid-write, not an empty config".
|
|
228
|
+
if (!data) {
|
|
229
|
+
if (this.#schedule())
|
|
230
|
+
return;
|
|
231
|
+
logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
|
|
232
|
+
this.#stageBootFallback();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
96
235
|
let config;
|
|
97
|
-
// Only the read and parse are guarded: a listener that throws must not be mistaken for a
|
|
98
|
-
// half-written file and replayed.
|
|
99
236
|
try {
|
|
100
|
-
config = (0,
|
|
237
|
+
config = (0, parseConfigFile_ts_1.parseConfigFile)(data, this.#configFilePath);
|
|
101
238
|
}
|
|
102
239
|
catch (error) {
|
|
103
|
-
// A
|
|
104
|
-
|
|
105
|
-
|
|
240
|
+
// A read taken mid-write is untrustworthy, not only an empty one: the writer's first
|
|
241
|
+
// `write(2)` can land a prefix of the document, and the event carrying the rest is the one
|
|
242
|
+
// chokidar throttles away. So an unparseable read rides out the same ladder as an empty
|
|
243
|
+
// one, and only a read that parses releases it.
|
|
244
|
+
if (this.#schedule())
|
|
245
|
+
return;
|
|
246
|
+
logger().warn(error.message);
|
|
247
|
+
this.#stageBootFallback();
|
|
106
248
|
return;
|
|
107
249
|
}
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
250
|
+
// The third shape a mid-write read takes, and the only one that parses: a truncated
|
|
251
|
+
// document, a lone `\n`, a file that is nothing but comments all yield `null` rather than
|
|
252
|
+
// throwing, and adopting one hands every consumer a config with nothing in it. Same ladder
|
|
253
|
+
// as the two above, and past it the file is empty rather than mid-write.
|
|
111
254
|
if (!config || typeof config !== 'object') {
|
|
112
|
-
this.#
|
|
255
|
+
if (this.#schedule())
|
|
256
|
+
return;
|
|
257
|
+
logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
|
|
258
|
+
this.#stageBootFallback();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.#readRetry.reset();
|
|
262
|
+
// Before `ready` goes out there is no prior state to have changed *since*.
|
|
263
|
+
this.#configLoaded = true;
|
|
264
|
+
this.#readyStaged = true;
|
|
265
|
+
if (!this.#readyEmitted) {
|
|
266
|
+
this.#config = config;
|
|
267
|
+
this.#emitReady();
|
|
113
268
|
return;
|
|
114
269
|
}
|
|
115
|
-
this.#partialRead.settled();
|
|
116
270
|
try {
|
|
117
|
-
if (!this.#config) {
|
|
118
|
-
this.#config = config;
|
|
119
|
-
this.emit('ready', this.#config);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
271
|
this.emit('change', (this.#config = config));
|
|
123
272
|
}
|
|
124
273
|
catch (error) {
|
|
125
|
-
(0,
|
|
274
|
+
logger().warn('A Harper configuration change listener failed', (0, harper_logger_ts_1.errorForLog)(error));
|
|
126
275
|
}
|
|
127
276
|
}
|
|
128
|
-
#scheduleReread(error) {
|
|
129
|
-
if (this.#partialRead.schedule(() => this.handleChange()))
|
|
130
|
-
return;
|
|
131
|
-
this.#partialRead.gaveUp(error);
|
|
132
|
-
}
|
|
133
277
|
close() {
|
|
278
|
+
// Closing is a terminal outcome too: leaving `ready` pending would hang anything still
|
|
279
|
+
// awaiting the barrier. Through `#emitReady`, so a listener that throws cannot skip the
|
|
280
|
+
// teardown below it and leave the watcher and its arm timer running.
|
|
281
|
+
this.#barrierOpen = true;
|
|
282
|
+
this.#readyStaged = true;
|
|
283
|
+
this.#emitReady();
|
|
134
284
|
this.#closed = true;
|
|
135
|
-
this.#
|
|
136
|
-
this.#
|
|
285
|
+
this.#readRetry.cancel();
|
|
286
|
+
this.#armGate.cancel();
|
|
287
|
+
// chokidar's close() is a promise; an unhandled teardown rejection would reach Node as one,
|
|
288
|
+
// on the path whose whole job is to stop caring about this watcher. Same shape as the
|
|
289
|
+
// exhaustion-recovery close above, and as `OptionsWatcher.close`.
|
|
290
|
+
Promise.resolve(this.#watcher.close()).catch(() => { });
|
|
137
291
|
this.#config = undefined;
|
|
138
292
|
this.emit('close');
|
|
139
293
|
this.removeAllListeners();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RootConfigWatcher.js","sourceRoot":"","sources":["../../config/RootConfigWatcher.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"RootConfigWatcher.js","sourceRoot":"","sources":["../../config/RootConfigWatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigReadRetry = void 0;
|
|
4
|
+
exports._setRetryBudgetForTests = _setRetryBudgetForTests;
|
|
5
|
+
// Why a lock that outlives the reader's budget is retried from a timer, and why both the bound and
|
|
6
|
+
// the backoff are wall clock rather than an attempt count: see "Root config watchers must read
|
|
7
|
+
// synchronously" in DESIGN.md (harper#2191).
|
|
8
|
+
const RETRY_BUDGET_MS = 3_100;
|
|
9
|
+
const INITIAL_DELAY_MS = 100;
|
|
10
|
+
const MAX_DELAY_MS = 1_600;
|
|
11
|
+
let retryBudgetMs = RETRY_BUDGET_MS;
|
|
12
|
+
// Test-only, the read-side twin of `atomicWriteFile`'s `retryBudgetMs` option. A case that only
|
|
13
|
+
// needs the ladder *spent* has no way to get there but wall clock, so without this each one sits
|
|
14
|
+
// out the shipped 3.1 s; a dozen of them cost the unit job 40 s. Called with no argument it
|
|
15
|
+
// restores the shipped value, and `configReadRetry.test.js` still spends that value in full, so
|
|
16
|
+
// shortening it elsewhere cannot hide a change to what ships.
|
|
17
|
+
function _setRetryBudgetForTests(ms = RETRY_BUDGET_MS) {
|
|
18
|
+
retryBudgetMs = ms;
|
|
19
|
+
}
|
|
20
|
+
class ConfigReadRetry {
|
|
21
|
+
#timer;
|
|
22
|
+
#deadline;
|
|
23
|
+
// Captured with the deadline: the backoff is a fraction of the budget the deadline came from,
|
|
24
|
+
// so reading the module value again mid-ladder would mis-scale it if a test had changed it.
|
|
25
|
+
#budgetMs = retryBudgetMs;
|
|
26
|
+
// `holdEventLoop` is for a caller whose boot barrier this ladder is the only thing left to
|
|
27
|
+
// settle: an unref'd timer would let the thread drain and exit mid-boot instead.
|
|
28
|
+
schedule(retry, holdEventLoop = false) {
|
|
29
|
+
this.cancel();
|
|
30
|
+
const now = performance.now();
|
|
31
|
+
if (this.#deadline === undefined) {
|
|
32
|
+
this.#budgetMs = retryBudgetMs;
|
|
33
|
+
this.#deadline = now + this.#budgetMs;
|
|
34
|
+
}
|
|
35
|
+
const remainingMs = this.#deadline - now;
|
|
36
|
+
if (remainingMs <= 0) {
|
|
37
|
+
this.reset();
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const elapsedMs = this.#budgetMs - remainingMs;
|
|
41
|
+
const delayMs = Math.min(Math.max(elapsedMs, INITIAL_DELAY_MS), MAX_DELAY_MS, remainingMs);
|
|
42
|
+
this.#timer = setTimeout(retry, delayMs);
|
|
43
|
+
if (!holdEventLoop)
|
|
44
|
+
this.#timer.unref();
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
get pending() {
|
|
48
|
+
return this.#timer !== undefined;
|
|
49
|
+
}
|
|
50
|
+
reset() {
|
|
51
|
+
this.cancel();
|
|
52
|
+
this.#deadline = undefined;
|
|
53
|
+
}
|
|
54
|
+
cancel() {
|
|
55
|
+
if (this.#timer) {
|
|
56
|
+
clearTimeout(this.#timer);
|
|
57
|
+
this.#timer = undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.ConfigReadRetry = ConfigReadRetry;
|
|
62
|
+
//# sourceMappingURL=configReadRetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configReadRetry.js","sourceRoot":"","sources":["../../config/configReadRetry.ts"],"names":[],"mappings":";;;AAcA,0DAEC;AAhBD,mGAAmG;AACnG,+FAA+F;AAC/F,6CAA6C;AAC7C,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,YAAY,GAAG,KAAK,CAAC;AAE3B,IAAI,aAAa,GAAG,eAAe,CAAC;AAEpC,gGAAgG;AAChG,iGAAiG;AACjG,4FAA4F;AAC5F,gGAAgG;AAChG,8DAA8D;AAC9D,SAAgB,uBAAuB,CAAC,KAAa,eAAe;IACnE,aAAa,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,MAAa,eAAe;IAC3B,MAAM,CAAkB;IACxB,SAAS,CAAU;IACnB,8FAA8F;IAC9F,4FAA4F;IAC5F,SAAS,GAAW,aAAa,CAAC;IAElC,2FAA2F;IAC3F,iFAAiF;IACjF,QAAQ,CAAC,KAAiB,EAAE,gBAAyB,KAAK;QACzD,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC;YAC/B,IAAI,CAAC,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QACvC,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;QACzC,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;QAC3F,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,aAAa;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;IAClC,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,CAAC;IAED,MAAM;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACzB,CAAC;IACF,CAAC;CACD;AA3CD,0CA2CC"}
|
|
@@ -5,19 +5,19 @@ export declare function resolvePath(relativePath: string): string;
|
|
|
5
5
|
* @param param
|
|
6
6
|
*/
|
|
7
7
|
export declare function getConfigPath(param: string): any;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export declare function atomicWriteFile(filePath: any, content: any, { maxRetries, initialDelayMs, maxDelayMs, skipIfUnchanged, }?: {
|
|
8
|
+
type RenameRetryOptions = {
|
|
9
|
+
retryBudgetMs?: number;
|
|
11
10
|
maxRetries?: number;
|
|
12
11
|
initialDelayMs?: number;
|
|
13
12
|
maxDelayMs?: number;
|
|
13
|
+
};
|
|
14
|
+
type AtomicWriteOptions = RenameRetryOptions & {
|
|
14
15
|
skipIfUnchanged?: boolean;
|
|
15
|
-
}
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}): void;
|
|
16
|
+
};
|
|
17
|
+
export declare function isStorageExhausted(error: any): boolean;
|
|
18
|
+
export declare function persistConfigDuringBoot(artifactPath: string, write: () => void): boolean;
|
|
19
|
+
export declare function atomicWriteFile(filePath: any, content: any, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs, skipIfUnchanged, }?: AtomicWriteOptions): boolean;
|
|
20
|
+
export declare function renameWithRetry(fromPath: any, toPath: any, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs, }?: RenameRetryOptions): void;
|
|
21
21
|
/**
|
|
22
22
|
* Builds the Harper config file using user inputs and default values from defaultConfig.yaml
|
|
23
23
|
* @param args - any args that the user provided.
|
|
@@ -137,3 +137,4 @@ export declare function addConfig(topLevelElement: any, values: any): Promise<vo
|
|
|
137
137
|
export declare function deleteConfigFromFile(param: string): void;
|
|
138
138
|
export declare function getConfigObj(): any;
|
|
139
139
|
export declare function getFlatConfigObj(): any;
|
|
140
|
+
export {};
|
|
@@ -146,21 +146,41 @@ function getConfigPath(param) {
|
|
|
146
146
|
return value;
|
|
147
147
|
return path_1.default.resolve(rootPath, value);
|
|
148
148
|
}
|
|
149
|
-
// Write atomically via temp file + rename so readers
|
|
150
|
-
//
|
|
151
|
-
// in the same millisecond can't collide on the temp name and then race the rename.
|
|
152
|
-
//
|
|
149
|
+
// Write atomically via a randomized temp file + rename so readers do not observe partial content
|
|
150
|
+
// and concurrent workers, which share process.pid, do not collide on a temp path.
|
|
153
151
|
// Windows has no POSIX-style "replace an open file" semantics: rename() fails with
|
|
154
|
-
// EPERM/EACCES while another
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
152
|
+
// EPERM/EACCES/EBUSY while another worker or AV holds the destination open. Root config watchers use
|
|
153
|
+
// readConfigFileSync so this blocking retry cannot wait on a read owned by its own worker.
|
|
154
|
+
// The budget is the wall-clock window of the 12-attempt schedule it replaced
|
|
155
|
+
// (10+20+40+80+160+320+500*6), so it stays a deadline rather than an attempt count without
|
|
156
|
+
// widening the stall: this loop blocks the calling worker's event loop, and `set_configuration`
|
|
157
|
+
// reaches it from a live request thread.
|
|
158
|
+
const RENAME_RETRY_BUDGET_MS = 3_630;
|
|
159
|
+
// Secondary guard only: stops a degenerate zero-delay option set from spinning the whole budget.
|
|
160
|
+
const RENAME_RETRY_MAX_ATTEMPTS = 25;
|
|
160
161
|
const RENAME_RETRY_INITIAL_DELAY_MS = 10;
|
|
161
162
|
const RENAME_RETRY_MAX_DELAY_MS = 500;
|
|
162
|
-
// Never notified;
|
|
163
|
+
// Never notified; Atomics.wait uses this only as a CPU-idle synchronous sleep.
|
|
163
164
|
const renameRetrySleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
165
|
+
// Classified by code alone rather than gated to win32 like the read side: `process.platform` does
|
|
166
|
+
// not answer whether this filesystem can replace an open file — WSL drvfs, CIFS/SMB and Docker
|
|
167
|
+
// Desktop bind mounts all report `linux` and return these codes transiently.
|
|
168
|
+
function isRetryableRenameError(code) {
|
|
169
|
+
return code === 'EPERM' || code === 'EACCES' || code === 'EBUSY';
|
|
170
|
+
}
|
|
171
|
+
function validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs }) {
|
|
172
|
+
const invalidOption = !Number.isFinite(retryBudgetMs) ||
|
|
173
|
+
retryBudgetMs < 0 ||
|
|
174
|
+
(!Number.isFinite(maxRetries) && maxRetries !== Infinity) ||
|
|
175
|
+
maxRetries < 0 ||
|
|
176
|
+
!Number.isFinite(initialDelayMs) ||
|
|
177
|
+
initialDelayMs < 0 ||
|
|
178
|
+
!Number.isFinite(maxDelayMs) ||
|
|
179
|
+
maxDelayMs < 0;
|
|
180
|
+
if (invalidOption) {
|
|
181
|
+
throw new RangeError('rename retry options must be non-negative numbers');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
164
184
|
// Linux has no libuv mapping for EDQUOT, so a quota-exhausted write surfaces as
|
|
165
185
|
// `Unknown system error -122` with an unusable `code`; the numeric errno is the portable signal
|
|
166
186
|
// (EDQUOT is 122 on Linux, 69 on macOS).
|
|
@@ -186,7 +206,9 @@ function persistConfigDuringBoot(artifactPath, write) {
|
|
|
186
206
|
}
|
|
187
207
|
}
|
|
188
208
|
// Returns true when the file was written, false when an unchanged write was skipped.
|
|
189
|
-
function atomicWriteFile(filePath, content, { maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, skipIfUnchanged = false, } = {}) {
|
|
209
|
+
function atomicWriteFile(filePath, content, { retryBudgetMs = RENAME_RETRY_BUDGET_MS, maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, skipIfUnchanged = false, } = {}) {
|
|
210
|
+
// Before the temp write, so an option set that can never rename leaves no file behind.
|
|
211
|
+
validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
|
|
190
212
|
// Opt-in: skipping means no mtime bump, so no watcher event. Only callers that re-derive the
|
|
191
213
|
// same file every boot want that.
|
|
192
214
|
if (skipIfUnchanged && matchesFileContent(filePath, content))
|
|
@@ -201,19 +223,24 @@ function atomicWriteFile(filePath, content, { maxRetries = RENAME_RETRY_MAX_ATTE
|
|
|
201
223
|
throw err;
|
|
202
224
|
}
|
|
203
225
|
try {
|
|
204
|
-
renameWithRetry(tempPath, filePath, { maxRetries, initialDelayMs, maxDelayMs });
|
|
226
|
+
renameWithRetry(tempPath, filePath, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
|
|
205
227
|
}
|
|
206
228
|
catch (err) {
|
|
229
|
+
// The temp name carries fresh randomness on every call, so a spent budget would otherwise
|
|
230
|
+
// leave a file nothing else will ever collect.
|
|
207
231
|
removeTempFile(tempPath);
|
|
208
232
|
throw err;
|
|
209
233
|
}
|
|
210
234
|
return true;
|
|
211
235
|
}
|
|
212
|
-
function renameWithRetry(fromPath, toPath, { maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, } = {}) {
|
|
236
|
+
function renameWithRetry(fromPath, toPath, { retryBudgetMs = RENAME_RETRY_BUDGET_MS, maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, } = {}) {
|
|
237
|
+
validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
|
|
213
238
|
let retries = maxRetries;
|
|
214
239
|
let delayMs = initialDelayMs;
|
|
240
|
+
let retryDeadline;
|
|
241
|
+
let finalAttempt = false;
|
|
215
242
|
let attempts = 0;
|
|
216
|
-
const startedAt =
|
|
243
|
+
const startedAt = performance.now();
|
|
217
244
|
while (true) {
|
|
218
245
|
try {
|
|
219
246
|
attempts++;
|
|
@@ -221,20 +248,29 @@ function renameWithRetry(fromPath, toPath, { maxRetries = RENAME_RETRY_MAX_ATTEM
|
|
|
221
248
|
return;
|
|
222
249
|
}
|
|
223
250
|
catch (err) {
|
|
224
|
-
if (retries > 0 && (err.code
|
|
251
|
+
if (!finalAttempt && retries > 0 && isRetryableRenameError(err.code)) {
|
|
225
252
|
retries--;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
253
|
+
if (retryDeadline === undefined) {
|
|
254
|
+
retryDeadline = performance.now() + retryBudgetMs;
|
|
255
|
+
}
|
|
256
|
+
const remainingBudgetMs = retryDeadline - performance.now();
|
|
257
|
+
if (remainingBudgetMs > 0) {
|
|
258
|
+
// Sleep synchronously (all call sites are sync) to allow the holder to close the
|
|
259
|
+
// file. Atomics.wait yields the thread to the OS instead of spinning the CPU,
|
|
260
|
+
// which is what makes a multi-second worst-case budget affordable.
|
|
261
|
+
const sleepMs = Math.min(delayMs, remainingBudgetMs);
|
|
262
|
+
finalAttempt = sleepMs === remainingBudgetMs;
|
|
263
|
+
if (sleepMs > 0)
|
|
264
|
+
Atomics.wait(renameRetrySleepBuffer, 0, 0, sleepMs);
|
|
265
|
+
delayMs = Math.min(Math.max(delayMs * 2, RENAME_RETRY_INITIAL_DELAY_MS), maxDelayMs);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
233
268
|
}
|
|
234
|
-
//
|
|
235
|
-
// a
|
|
236
|
-
|
|
237
|
-
|
|
269
|
+
// Whether the budget was spent or the code was never retryable is the difference
|
|
270
|
+
// between a holder that never released and a one-off failure, and neither survives on
|
|
271
|
+
// the rethrown error.
|
|
272
|
+
if (isRetryableRenameError(err.code)) {
|
|
273
|
+
harper_logger_ts_1.default.warn(`Could not replace ${toPath}: ${err.code} after ${attempts} attempts over ${Math.round(performance.now() - startedAt)}ms`);
|
|
238
274
|
}
|
|
239
275
|
throw err;
|
|
240
276
|
}
|