@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
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { createGzip } from 'zlib';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import { pipeline } from 'stream';
|
|
7
|
-
const pipe = promisify(pipeline);
|
|
3
|
+
import { existsSync, mkdirSync, statSync, promises as fsProm } from 'fs';
|
|
8
4
|
import * as path from 'path';
|
|
9
|
-
import { createHash } from 'crypto';
|
|
10
5
|
import * as envMgr from '../environment/environmentManager.ts';
|
|
11
6
|
envMgr.initSync();
|
|
12
7
|
import hdbLogger from './harper_logger.ts';
|
|
13
8
|
import { CONFIG_PARAMS } from '../hdbTerms.ts';
|
|
14
9
|
import { convertToMS } from '../common_utils.ts';
|
|
15
10
|
import { onStorageReclamation } from '../../server/storageReclamation.ts';
|
|
11
|
+
import { requestStaleDescriptorRelease } from './logGenerationCoordinator.ts';
|
|
12
|
+
import {
|
|
13
|
+
compressPendingArchives,
|
|
14
|
+
INVALID_MAX_SIZE_MSG,
|
|
15
|
+
isArchivePendingQuiescence,
|
|
16
|
+
parseMaxSize,
|
|
17
|
+
publishArchivedGeneration,
|
|
18
|
+
resolveRotatedLogDir,
|
|
19
|
+
retryPendingGenerations,
|
|
20
|
+
rotateLogFileSync,
|
|
21
|
+
} from './logRotation.ts';
|
|
16
22
|
|
|
17
23
|
// Interval in ms to check log file and decide if it should be rotated.
|
|
18
24
|
const LOG_AUDIT_INTERVAL = 60000;
|
|
25
|
+
const RELEASE_PROOF_REPORT_INTERVAL = 5 * 60 * 1000;
|
|
19
26
|
const INT_SIZE_UNDEFINED_MSG =
|
|
20
27
|
"'interval' and 'maxSize' are both undefined, to enable logging rotation at least one of these values must be defined in harperdb-config.yaml";
|
|
21
28
|
|
|
@@ -27,7 +34,16 @@ export { logRotator };
|
|
|
27
34
|
* If log file is within the values set in config, log file will be renamed/moved and a new empty hdb.log created.
|
|
28
35
|
* @returns LogRotator
|
|
29
36
|
*/
|
|
30
|
-
function logRotator({
|
|
37
|
+
function logRotator({
|
|
38
|
+
logger,
|
|
39
|
+
maxSize,
|
|
40
|
+
interval,
|
|
41
|
+
retention,
|
|
42
|
+
enabled,
|
|
43
|
+
compress,
|
|
44
|
+
path: rotatedLogDir,
|
|
45
|
+
auditInterval,
|
|
46
|
+
}: any) {
|
|
31
47
|
if (enabled === false) return;
|
|
32
48
|
let reclamationPriority = 0;
|
|
33
49
|
onStorageReclamation(
|
|
@@ -42,21 +58,18 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
|
|
|
42
58
|
throw new Error(INT_SIZE_UNDEFINED_MSG);
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
rotatedLogDir = path.join(path.dirname(logger.path), 'rotated');
|
|
47
|
-
}
|
|
61
|
+
rotatedLogDir = resolveRotatedLogDir(logger.path, rotatedLogDir);
|
|
48
62
|
mkdirSync(rotatedLogDir, { recursive: true });
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const unit = maxSize.slice(-1);
|
|
54
|
-
const size = maxSize.slice(0, -1);
|
|
55
|
-
if (unit === 'G') maxBytes = size * 1000000000;
|
|
56
|
-
else if (unit === 'M') maxBytes = size * 1000000;
|
|
57
|
-
else maxBytes = size * 1000;
|
|
64
|
+
const maxBytes = parseMaxSize(maxSize);
|
|
65
|
+
if (maxSize && !maxBytes) {
|
|
66
|
+
hdbLogger.error(`Ignoring logging.rotation.maxSize '${maxSize}': ${INVALID_MAX_SIZE_MSG}`);
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
// The rotation block first, because that is all the write-path guard can read — environmentManager
|
|
70
|
+
// imports harper_logger — and two sources would mean two destruction policies for one log.
|
|
71
|
+
const compressArchives = compress ?? envMgr.get(CONFIG_PARAMS.LOGGING_ROTATION_COMPRESS);
|
|
72
|
+
|
|
60
73
|
// Convert interval param to ms.
|
|
61
74
|
let maxInterval;
|
|
62
75
|
if (interval) {
|
|
@@ -64,127 +77,219 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
|
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
let lastRotatedLogPath;
|
|
67
|
-
// convert date.now to minutes
|
|
68
80
|
let lastRotationTime = Date.now();
|
|
81
|
+
let observedGeneration;
|
|
82
|
+
try {
|
|
83
|
+
const active = statSync(logger.path);
|
|
84
|
+
observedGeneration = generationIdentity(active);
|
|
85
|
+
if (active.birthtimeMs > 0) lastRotationTime = Math.min(lastRotationTime, active.birthtimeMs);
|
|
86
|
+
} catch {}
|
|
69
87
|
hdbLogger.trace('Log rotate enabled, maxSize:', maxSize, 'interval:', interval);
|
|
88
|
+
let tickInFlight = false;
|
|
89
|
+
let ended = false;
|
|
90
|
+
let releaseProofStalled = false;
|
|
91
|
+
let nextReleaseProofReport = 0;
|
|
92
|
+
let retentionCursor = 0;
|
|
93
|
+
const auditIntervalMs = auditInterval ?? LOG_AUDIT_INTERVAL;
|
|
94
|
+
const compressionBudgetMs = Math.max(1, Math.floor(auditIntervalMs / 4));
|
|
95
|
+
/**
|
|
96
|
+
* rename() reports ENOENT for a missing destination directory as well as a missing source, and
|
|
97
|
+
* only the second is the benign lost race. The directory is created once at rotator start, so a
|
|
98
|
+
* rotation target removed under a running instance otherwise stops rotation permanently and
|
|
99
|
+
* silently — and with `interval` and no `maxSize` there is no write-path guard to recreate it.
|
|
100
|
+
*/
|
|
101
|
+
function recoverRotationTarget(err: any) {
|
|
102
|
+
if (err.code !== 'ENOENT' || existsSync(rotatedLogDir)) return;
|
|
103
|
+
try {
|
|
104
|
+
mkdirSync(rotatedLogDir, { recursive: true });
|
|
105
|
+
hdbLogger.warn(`The log rotation directory ${rotatedLogDir} was missing and has been recreated`);
|
|
106
|
+
} catch (mkdirErr) {
|
|
107
|
+
hdbLogger.error('Could not recreate the log rotation directory', rotatedLogDir, mkdirErr);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
70
110
|
const setIntervalId = setInterval(async () => {
|
|
111
|
+
// setInterval does not await the callback, and one pass can now wait on peers; overlapping
|
|
112
|
+
// passes would work the same archive twice and double-compress it.
|
|
113
|
+
if (tickInFlight || ended) return;
|
|
114
|
+
tickInFlight = true;
|
|
115
|
+
const tickDeadline = Date.now() + auditIntervalMs;
|
|
71
116
|
// The tick is async but setInterval doesn't await it, so any error that escapes this callback
|
|
72
117
|
// becomes an unhandled rejection rather than surfacing anywhere useful — and since it isn't
|
|
73
118
|
// caught, it also skips the retention cleanup below. Contain everything here and report via
|
|
74
119
|
// the logger instead, so one bad tick (e.g. an unexpected fs error) never kills rotation for
|
|
75
120
|
// the rest of the process's life.
|
|
76
121
|
try {
|
|
77
|
-
// A missing/relocated active log file only invalidates the rotation checks below — retention cleanup
|
|
78
|
-
// must still run. So skip the individual check on ENOENT rather than returning from the whole tick.
|
|
79
122
|
if (maxBytes) {
|
|
80
|
-
let fileStats;
|
|
81
123
|
try {
|
|
82
|
-
|
|
124
|
+
// statSync, and the rename in the same turn: an await here lets a writing thread rotate
|
|
125
|
+
// the generation this tick measured and start a fresh one, which the tick would then
|
|
126
|
+
// archive near-empty.
|
|
127
|
+
const active = statSync(logger.path);
|
|
128
|
+
if (active.size >= maxBytes) {
|
|
129
|
+
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger, compressArchives, active);
|
|
130
|
+
// The interval clock counts from the last rotation of any kind. Without this an
|
|
131
|
+
// instance whose uptime has passed `interval` archives a freshly-created log every
|
|
132
|
+
// interval on top of the size rotations already doing the work.
|
|
133
|
+
lastRotationTime = Date.now();
|
|
134
|
+
}
|
|
83
135
|
} catch (err) {
|
|
84
|
-
//
|
|
136
|
+
// A missing or already-rotated active log only invalidates this check; retention below
|
|
137
|
+
// must still run, so skip the check rather than leaving the whole tick.
|
|
138
|
+
recoverRotationTarget(err);
|
|
85
139
|
if (err.code !== 'ENOENT') throw err;
|
|
86
140
|
}
|
|
87
|
-
|
|
88
|
-
if (fileStats && fileStats.size >= maxBytes) {
|
|
89
|
-
try {
|
|
90
|
-
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
// If the log file doesn't exist, skip rotation
|
|
93
|
-
if (err.code !== 'ENOENT') throw err;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
141
|
}
|
|
97
142
|
|
|
98
143
|
if (maxInterval) {
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
try {
|
|
145
|
+
const activeGeneration = generationIdentity(statSync(logger.path));
|
|
146
|
+
if (activeGeneration && activeGeneration !== observedGeneration) {
|
|
147
|
+
observedGeneration = activeGeneration;
|
|
148
|
+
lastRotationTime = Date.now();
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
if (err.code !== 'ENOENT') throw err;
|
|
152
|
+
}
|
|
153
|
+
if (Date.now() - lastRotationTime >= maxInterval) {
|
|
101
154
|
try {
|
|
102
|
-
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger);
|
|
155
|
+
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger, compressArchives);
|
|
103
156
|
lastRotationTime = Date.now();
|
|
104
157
|
} catch (err) {
|
|
105
158
|
// If the log file doesn't exist, skip rotation
|
|
159
|
+
recoverRotationTarget(err);
|
|
106
160
|
if (err.code !== 'ENOENT') throw err;
|
|
107
161
|
}
|
|
108
162
|
}
|
|
109
163
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
// The compression sweep and retention both destroy archives this thread did not rotate, so
|
|
165
|
+
// both need the same proof: every peer releases any descriptor that is not on the live
|
|
166
|
+
// generation, and reports the log paths it is writing. One round trip per tick serves both,
|
|
167
|
+
// and it is not gated on retention being configured — retention is unset by default, and a
|
|
168
|
+
// worker's uncompressed archive still needs finishing.
|
|
169
|
+
if (compressArchives || retention || reclamationPriority) {
|
|
170
|
+
// Enumerated BEFORE the proof, and only these are destroyed: an archive created after the
|
|
171
|
+
// proof was never covered by it, and a peer blocked mid-rotation may still be writing to it.
|
|
172
|
+
let candidates;
|
|
116
173
|
try {
|
|
117
|
-
|
|
174
|
+
candidates = await fsProm.readdir(rotatedLogDir);
|
|
118
175
|
} catch (err) {
|
|
119
|
-
// The rotated log dir may not exist yet (nothing rotated so far); nothing to clean up
|
|
120
176
|
if (err.code !== 'ENOENT') hdbLogger.error('Error reading rotated log directory', rotatedLogDir, err);
|
|
121
|
-
|
|
177
|
+
candidates = [];
|
|
122
178
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
179
|
+
const { released, liveLogPaths } = await requestStaleDescriptorRelease(tickDeadline);
|
|
180
|
+
// A peer whose event loop is blocked never answers, and the whole pass then destroys
|
|
181
|
+
// nothing — correct, but it is also the only thing bounding the rotated directory, so an
|
|
182
|
+
// operator who configured retention has to be able to see that it has stopped. Repeat at
|
|
183
|
+
// a low rate so a permanent stall does not disappear after one warning.
|
|
184
|
+
if (!released && Date.now() >= nextReleaseProofReport) {
|
|
185
|
+
nextReleaseProofReport = Date.now() + RELEASE_PROOF_REPORT_INTERVAL;
|
|
186
|
+
releaseProofStalled = true;
|
|
187
|
+
hdbLogger.warn(
|
|
188
|
+
`Log rotation could not prove every thread released its archived log descriptors; compression and retention are paused for ${rotatedLogDir}`
|
|
189
|
+
);
|
|
190
|
+
} else if (released && releaseProofStalled) {
|
|
191
|
+
releaseProofStalled = false;
|
|
192
|
+
nextReleaseProofReport = 0;
|
|
193
|
+
hdbLogger.notify(`Log rotation descriptor release recovered; retention resumed for ${rotatedLogDir}`);
|
|
194
|
+
}
|
|
195
|
+
const liveLogs = new Set([...liveLogPaths, logger.path].map((p) => path.resolve(p)));
|
|
196
|
+
|
|
197
|
+
if (released && (retention || reclamationPriority)) {
|
|
198
|
+
// remove old logs after retention time
|
|
199
|
+
// adjust retention time if there is a reclamation priority in place
|
|
200
|
+
const retentionMs = convertToMS(retention ?? '1M') / (1 + reclamationPriority);
|
|
201
|
+
let retentionCompleted = true;
|
|
202
|
+
let examined = 0;
|
|
203
|
+
for (; examined < candidates.length; examined++) {
|
|
204
|
+
if (ended || Date.now() >= tickDeadline) {
|
|
205
|
+
retentionCompleted = false;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
const file = candidates[(retentionCursor + examined) % candidates.length];
|
|
209
|
+
try {
|
|
210
|
+
const archivePath = path.join(rotatedLogDir, file);
|
|
211
|
+
// An explicitly configured rotation path may contain live component logs too.
|
|
212
|
+
if (liveLogs.has(path.resolve(archivePath))) continue;
|
|
213
|
+
// Unlinking an inode a stalled writer still holds loses whatever it writes next
|
|
214
|
+
// just as surely as compressing over it would.
|
|
215
|
+
if (isArchivePendingQuiescence(archivePath)) continue;
|
|
216
|
+
const fileStats = await fsProm.stat(archivePath);
|
|
217
|
+
if (Date.now() - fileStats.mtimeMs > retentionMs) {
|
|
218
|
+
await fsProm.unlink(archivePath);
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (err.code !== 'ENOENT') hdbLogger.error('Error trying to remove log', file, err);
|
|
128
222
|
}
|
|
129
|
-
} catch (err) {
|
|
130
|
-
hdbLogger.error('Error trying to remove log', file, err);
|
|
131
223
|
}
|
|
224
|
+
if (retentionCompleted) {
|
|
225
|
+
retentionCursor = 0;
|
|
226
|
+
reclamationPriority = 0;
|
|
227
|
+
} else if (candidates.length) {
|
|
228
|
+
retentionCursor = (retentionCursor + examined) % candidates.length;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (released && compressArchives && !ended) {
|
|
233
|
+
const compressionFailure = await compressPendingArchives(rotatedLogDir, candidates, liveLogs, {
|
|
234
|
+
deadline: Math.min(tickDeadline, Date.now() + compressionBudgetMs),
|
|
235
|
+
shouldStop: () => ended,
|
|
236
|
+
});
|
|
237
|
+
if (compressionFailure)
|
|
238
|
+
hdbLogger.error('Error compressing rotated log', compressionFailure.file, compressionFailure.error);
|
|
132
239
|
}
|
|
133
240
|
}
|
|
134
241
|
} catch (err) {
|
|
135
242
|
hdbLogger.error('Error during log rotation audit tick for', logger.path, err);
|
|
243
|
+
} finally {
|
|
244
|
+
try {
|
|
245
|
+
await retryPendingGenerations({ deadline: tickDeadline, shouldStop: () => ended });
|
|
246
|
+
} catch (err) {
|
|
247
|
+
hdbLogger.error('Error retrying pending log generations for', logger.path, err);
|
|
248
|
+
}
|
|
249
|
+
tickInFlight = false;
|
|
136
250
|
}
|
|
137
|
-
},
|
|
251
|
+
}, auditIntervalMs).unref();
|
|
138
252
|
return {
|
|
139
253
|
end() {
|
|
254
|
+
ended = true;
|
|
140
255
|
clearInterval(setIntervalId);
|
|
141
256
|
},
|
|
142
257
|
getLastRotatedLogPath() {
|
|
143
258
|
return lastRotatedLogPath;
|
|
144
259
|
},
|
|
145
260
|
};
|
|
261
|
+
|
|
262
|
+
function generationIdentity(stats: any) {
|
|
263
|
+
return stats.ino ? `${stats.dev}:${stats.ino}` : undefined;
|
|
264
|
+
}
|
|
146
265
|
}
|
|
147
266
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// is
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// sha256, not sha1: this only needs a stable identifier, not cryptographic strength, but a FIPS-mode
|
|
164
|
-
// OpenSSL provider disables sha1 and throws synchronously, which would crash every rotation tick.
|
|
165
|
-
const sourceId = createHash('sha256').update(path.resolve(logPath)).digest('hex').slice(0, 8);
|
|
166
|
-
const uniqueSuffix = `${process.pid}-${rotationSequence++}`;
|
|
167
|
-
let fullRotateLogPath = path.join(
|
|
267
|
+
async function moveLogFile(
|
|
268
|
+
logPath: string,
|
|
269
|
+
rotatedLogPath: string,
|
|
270
|
+
logger?: any,
|
|
271
|
+
compress?: boolean,
|
|
272
|
+
activeStats?: any
|
|
273
|
+
) {
|
|
274
|
+
// The rename and the descriptor close must not be separated by an await: the descriptor would
|
|
275
|
+
// otherwise keep feeding the archived inode while the event loop runs. Closing the rotating
|
|
276
|
+
// logger's own descriptor (not the module-global one) is what makes the next write reopen a
|
|
277
|
+
// fresh log file rather than append to the moved — and, when compressing, unlinked — inode.
|
|
278
|
+
// `activeStats` is the caller's own stat of the live generation, when it has one: the size check
|
|
279
|
+
// must rename the generation it measured, and a second stat here could pick up a newer one.
|
|
280
|
+
const generation = rotateLogFileSync(
|
|
281
|
+
logPath,
|
|
168
282
|
rotatedLogPath,
|
|
169
|
-
|
|
283
|
+
logger?.closeLogFile ?? hdbLogger.closeLogFile,
|
|
284
|
+
activeStats
|
|
285
|
+
);
|
|
286
|
+
const publishedPath = await publishArchivedGeneration(
|
|
287
|
+
generation,
|
|
288
|
+
compress ?? envMgr.get(CONFIG_PARAMS.LOGGING_ROTATION_COMPRESS),
|
|
289
|
+
(error) => hdbLogger.error('Error compressing rotated log', generation.archivePath, error)
|
|
170
290
|
);
|
|
171
|
-
// Move log file to rotated log path first (if we crash
|
|
172
|
-
// during compression, we don't want to restart the compression with a new file)
|
|
173
|
-
await fsProm.rename(logPath, fullRotateLogPath);
|
|
174
|
-
// Close the rotating logger's own file descriptor now that the file has moved. This must be the
|
|
175
|
-
// logger's own closeLogFile (which resets its internal logFD), not the module-global one — otherwise
|
|
176
|
-
// the descriptor stays open on the moved (and, when compressing, subsequently unlinked) inode until the
|
|
177
|
-
// logger's safety timeout fires, pinning disk space and sending any writes in that window into the
|
|
178
|
-
// rotated/deleted file. Closing it here makes the next write reopen a fresh log file immediately.
|
|
179
|
-
(logger?.closeLogFile ?? hdbLogger.closeLogFile)();
|
|
180
|
-
if (compress) {
|
|
181
|
-
logPath = fullRotateLogPath;
|
|
182
|
-
fullRotateLogPath += '.gz';
|
|
183
|
-
await pipe(createReadStream(logPath), createGzip(), createWriteStream(fullRotateLogPath));
|
|
184
|
-
await fsProm.unlink(logPath);
|
|
185
|
-
}
|
|
186
291
|
|
|
187
292
|
// This notify log will create a new log file after the previous one has been rotated. It's important to keep this log as notify
|
|
188
|
-
hdbLogger.notify(`hdb.log rotated, old log moved to ${
|
|
189
|
-
return
|
|
293
|
+
hdbLogger.notify(`hdb.log rotated, old log moved to ${publishedPath}`);
|
|
294
|
+
return publishedPath;
|
|
190
295
|
}
|
package/utility/npmUtilities.ts
CHANGED
|
@@ -31,14 +31,13 @@ export async function installModules(req: any) {
|
|
|
31
31
|
throw handleHDBError(validation, validation.message, HTTP_STATUS_CODES.BAD_REQUEST);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const { projects, dry_run: dryRun } = validatedRequest;
|
|
34
|
+
const { projects, dry_run: dryRun, allowInstallScripts } = validatedRequest;
|
|
35
35
|
|
|
36
36
|
const componentsRootDirPath = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
|
|
37
37
|
|
|
38
38
|
const responseObject: any = {};
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const args = [...packageManagerInstallArguments('npm', true, true), '--json'];
|
|
40
|
+
const args = [...packageManagerInstallArguments('npm', allowInstallScripts, true), '--json'];
|
|
42
41
|
if (dryRun) args.push('--dry-run');
|
|
43
42
|
|
|
44
43
|
for (const project of projects) {
|
|
@@ -101,7 +100,10 @@ function modulesValidator(req: any) {
|
|
|
101
100
|
const funcSchema = Joi.object({
|
|
102
101
|
projects: Joi.array().min(1).items(Joi.string()).required(),
|
|
103
102
|
dry_run: Joi.boolean().default(false),
|
|
104
|
-
|
|
103
|
+
allowInstallScripts: Joi.boolean().default(true),
|
|
104
|
+
})
|
|
105
|
+
.rename('dryRun', 'dry_run', { ignoreUndefined: true })
|
|
106
|
+
.rename('install_allow_scripts', 'allowInstallScripts', { ignoreUndefined: true });
|
|
105
107
|
|
|
106
108
|
return validator.validateAndConvertBySchema(req, funcSchema);
|
|
107
109
|
}
|
|
@@ -11,7 +11,6 @@ import { loggerWithTag } from './logging/harper_logger.ts';
|
|
|
11
11
|
// One-time process-wide warning so a thundering herd of failing watchers doesn't
|
|
12
12
|
// produce hundreds of identical log lines.
|
|
13
13
|
let exhaustionWarned = false;
|
|
14
|
-
const partialReadWarned = new Set<string>();
|
|
15
14
|
|
|
16
15
|
const fallbackLogger = loggerWithTag('watcher');
|
|
17
16
|
|
|
@@ -221,124 +220,3 @@ export function guardedWatch(paths: string | string[], options?: ChokidarOptions
|
|
|
221
220
|
export function _lostNativeWatchCountForTests(): number {
|
|
222
221
|
return lostNativeWatchCount;
|
|
223
222
|
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* A config file replaced in place (truncate, then write) can be read back empty or
|
|
227
|
-
* half-written, and chokidar may emit nothing further for that write — so a watcher that
|
|
228
|
-
* simply drops the unusable read would serve stale config until something else touched the
|
|
229
|
-
* file. Re-read on a later turn instead, bounded so a genuinely empty or corrupt file cannot
|
|
230
|
-
* spin. Callers read synchronously, so the descriptor still never outlives a single turn.
|
|
231
|
-
*/
|
|
232
|
-
const PARTIAL_READ_REREAD_DELAY_MS = 20;
|
|
233
|
-
const PARTIAL_READ_MAX_REREADS = 10;
|
|
234
|
-
|
|
235
|
-
export class PartialReadRetry {
|
|
236
|
-
#filePath: string;
|
|
237
|
-
#timer?: ReturnType<typeof setTimeout>;
|
|
238
|
-
#remaining: number = PARTIAL_READ_MAX_REREADS;
|
|
239
|
-
#closed = false;
|
|
240
|
-
|
|
241
|
-
constructor(filePath: string) {
|
|
242
|
-
this.#filePath = filePath;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** False once the budget is spent, so the caller can fall back to its own error handling. */
|
|
246
|
-
schedule(reread: () => void): boolean {
|
|
247
|
-
if (this.#closed) return false;
|
|
248
|
-
if (this.#timer) return true;
|
|
249
|
-
if (this.#remaining <= 0) return false;
|
|
250
|
-
this.#remaining--;
|
|
251
|
-
this.#timer = setTimeout(() => {
|
|
252
|
-
this.#timer = undefined;
|
|
253
|
-
reread();
|
|
254
|
-
}, PARTIAL_READ_REREAD_DELAY_MS);
|
|
255
|
-
this.#timer.unref?.();
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** A usable read arrived, so any re-read still armed for the previous one would duplicate it. */
|
|
260
|
-
settled() {
|
|
261
|
-
if (this.#timer) clearTimeout(this.#timer);
|
|
262
|
-
this.#timer = undefined;
|
|
263
|
-
this.#remaining = PARTIAL_READ_MAX_REREADS;
|
|
264
|
-
// The file recovered, so the next time it breaks is a new incident and has to be reported
|
|
265
|
-
// again rather than silenced by the warning it emitted weeks ago.
|
|
266
|
-
partialReadWarned.delete(this.#filePath);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* The budget is spent. Distinct from `settled()` in that the report stands — the file has not
|
|
271
|
-
* recovered, and it is shared with every other watcher of it. The budget itself is restored,
|
|
272
|
-
* because the next event may be the repair, and that repair can be observed mid-write too.
|
|
273
|
-
* Returns whether this give-up was the one reported.
|
|
274
|
-
*/
|
|
275
|
-
gaveUp(error?: unknown): boolean {
|
|
276
|
-
if (this.#closed) return false;
|
|
277
|
-
this.#remaining = PARTIAL_READ_MAX_REREADS;
|
|
278
|
-
return warnPartialReadGaveUp(this.#filePath, error);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** Terminal: the watcher is closing, so nothing may re-arm the re-read or report on it. */
|
|
282
|
-
cancel() {
|
|
283
|
-
if (this.#timer) clearTimeout(this.#timer);
|
|
284
|
-
this.#timer = undefined;
|
|
285
|
-
this.#closed = true;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* ENOENT is excluded not because it cannot be transient, but because it already has an answer:
|
|
291
|
-
* `OptionsWatcher` routes it to `remove` (env-only fallback at boot, then removal), and
|
|
292
|
-
* `RootConfigWatcher` keeps its last config rather than tearing down core features on a file
|
|
293
|
-
* that may just be mid-replace. Re-reading would only delay a decision already made.
|
|
294
|
-
*/
|
|
295
|
-
export function isPartialReadError(error: unknown): boolean {
|
|
296
|
-
return !(typeof error === 'object' && error !== null && (error as { code?: string }).code === 'ENOENT');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* A watcher that exhausts its re-read budget serves stale config from then on, so the give-up
|
|
301
|
-
* has to be visible — otherwise the only symptom is a config change that silently did nothing.
|
|
302
|
-
* Warned once per file: every root-config scope watches the same one and would otherwise report
|
|
303
|
-
* a single bad file once each, on every event.
|
|
304
|
-
*/
|
|
305
|
-
export function warnPartialReadGaveUp(filePath: string, error?: unknown): boolean {
|
|
306
|
-
if (partialReadWarned.has(filePath)) return false;
|
|
307
|
-
partialReadWarned.add(filePath);
|
|
308
|
-
// The cause matters to whoever has to fix it: a file that never parses is a typo to correct,
|
|
309
|
-
// while one that reads empty is a writer that never finished. Report the kind and position
|
|
310
|
-
// only — a YAML parse error's message quotes the offending source, and this file holds
|
|
311
|
-
// credentials.
|
|
312
|
-
const cause = error ? `: ${describeReadFailure(error)}` : ' that were empty or incomplete';
|
|
313
|
-
fallbackLogger.warn(`Gave up re-reading ${filePath} after ${PARTIAL_READ_MAX_REREADS} unusable reads${cause}`);
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* The kind and position of a failed config read, never its content: a YAML parse error's message
|
|
319
|
-
* quotes the offending source lines, and these files hold credentials.
|
|
320
|
-
*/
|
|
321
|
-
export function describeReadFailure(error: unknown): string {
|
|
322
|
-
if (typeof error !== 'object' || error === null) return 'unusable';
|
|
323
|
-
const { name, code, linePos } = error as { name?: string; code?: string; linePos?: { line: number; col: number }[] };
|
|
324
|
-
const at = linePos?.[0] ? ` at line ${linePos[0].line}, column ${linePos[0].col}` : '';
|
|
325
|
-
return `${code ?? name ?? 'unusable'}${at}`;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/** Test-only: whether a give-up warning for this file is currently suppressed as a duplicate. */
|
|
329
|
-
export function isPartialReadWarned(filePath: string): boolean {
|
|
330
|
-
return partialReadWarned.has(filePath);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** Test-only: forget that this file was reported, so a suite can start from a known state. */
|
|
334
|
-
export function clearPartialReadWarning(filePath: string) {
|
|
335
|
-
partialReadWarned.delete(filePath);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* A listener that throws while applying new config is a bug in that listener, not evidence the
|
|
340
|
-
* file was half-written — the watcher's own state is already updated, so it keeps going.
|
|
341
|
-
*/
|
|
342
|
-
export function warnWatcherListenerError(filePath: string, error: unknown) {
|
|
343
|
-
fallbackLogger.warn(`Error applying a configuration change from ${filePath}`, error);
|
|
344
|
-
}
|
|
@@ -11,6 +11,7 @@ import * as hdbUtils from '../utility/common_utils.ts';
|
|
|
11
11
|
import * as hdbTerms from '../utility/hdbTerms.ts';
|
|
12
12
|
import { getDomainSocketPathMaxBytes } from '../utility/domainSocket.ts';
|
|
13
13
|
import { bareHostViolation } from '../utility/nodeIdentity.ts';
|
|
14
|
+
import { parseMaxSize } from '../utility/logging/logRotation.ts';
|
|
14
15
|
import * as validator from './validationWrapper.ts';
|
|
15
16
|
|
|
16
17
|
const DEFAULT_LOG_FOLDER = 'log';
|
|
@@ -19,7 +20,7 @@ const INVALID_SIZE_UNIT_MSG = 'Invalid logging.rotation.maxSize unit. Available
|
|
|
19
20
|
const INVALID_INTERVAL_UNIT_MSG =
|
|
20
21
|
'Invalid logging.rotation.interval unit. Available units are D (days), H (hours), M (months) or m (minutes)';
|
|
21
22
|
const INVALID_MAX_SIZE_VALUE_MSG =
|
|
22
|
-
"Invalid logging.rotation.maxSize value. Value should be a number followed by unit e.g. '10M'";
|
|
23
|
+
"Invalid logging.rotation.maxSize value. Value should be a positive number followed by unit e.g. '10M'";
|
|
23
24
|
const INVALID_INTERVAL_VALUE_MSG =
|
|
24
25
|
"Invalid logging.rotation.interval value. Value should be a number followed by unit e.g. '10D'";
|
|
25
26
|
const INVALID_RETENTION_UNIT_MSG =
|
|
@@ -434,6 +435,7 @@ export function configValidator(configJson, skipFsValidation = false) {
|
|
|
434
435
|
})
|
|
435
436
|
),
|
|
436
437
|
maxHeapMemory: number.min(0).optional(),
|
|
438
|
+
maxIsolated: number.integer().min(0).optional(),
|
|
437
439
|
preload: Joi.alternatives([string, array.items(string)])
|
|
438
440
|
.allow(null)
|
|
439
441
|
.optional(),
|
|
@@ -599,8 +601,9 @@ function validateRotationMaxSize(value, helpers) {
|
|
|
599
601
|
return helpers.message(INVALID_SIZE_UNIT_MSG);
|
|
600
602
|
}
|
|
601
603
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
+
// The same parser the write-path size check uses, so a value accepted here can never become a
|
|
605
|
+
// zero, negative or NaN byte limit downstream. `parseInt` alone accepted '0K', '-1K' and '1xK'.
|
|
606
|
+
if (!parseMaxSize(value)) {
|
|
604
607
|
return helpers.message(INVALID_MAX_SIZE_VALUE_MSG);
|
|
605
608
|
}
|
|
606
609
|
|