@nextclaw/app-runtime 0.13.1 → 0.13.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/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/package.js +1 -1
- package/dist/services/file-lock.service.d.ts +11 -1
- package/dist/services/file-lock.service.d.ts.map +1 -1
- package/dist/services/file-lock.service.js +11 -2
- package/dist/services/file-lock.service.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,6 @@ import { AppHostHandle, AppHostService, AppHostStartOptions } from "./services/a
|
|
|
55
55
|
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
56
56
|
import { AppServiceLaunch, AppServiceLaunchService } from "./services/app-service-launch.service.js";
|
|
57
57
|
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
58
|
-
import { FileLockService } from "./services/file-lock.service.js";
|
|
58
|
+
import { FileLockBusyError, FileLockLease, FileLockService } from "./services/file-lock.service.js";
|
|
59
59
|
import { UiServerService } from "./services/ui-server.service.js";
|
|
60
|
-
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactArchitecture, AppArtifactTarget, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDarwinArtifactTarget, AppDistributionDeclaration, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppLinuxArtifactTarget, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppNativeArtifactTarget, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPlatformTargetService, AppPreparedPublishArtifact, AppPublishArtifact, AppPublishArtifactService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryArtifact, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppServiceLaunch, AppServiceLaunchService, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUniversalArtifactTarget, AppUpdateResult, AppWasiHttpComponentMainManifest, AppWindowsArtifactTarget, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
|
60
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppActivationResult, AppArtifactArchitecture, AppArtifactTarget, AppArtifactValidationExpectation, AppArtifactValidationResult, AppArtifactValidationService, AppBuildResult, AppBuildService, AppBundleChecksums, AppBundleExtractResult, AppBundleMetadata, AppBundlePackResult, AppBundleService, AppComponentKind, AppComponentManifest, AppComponentManifestBundle, AppComponentManifestSummary, AppComponentReference, AppCoreWasmMainManifest, AppDarwinArtifactTarget, AppDistributionDeclaration, AppDistributionMode, AppDocumentAccessMode, AppDocumentAccessScope, AppDocumentGrantMap, AppDocumentGrantMutationResult, AppDocumentGrantState, AppGrantService, AppHomeService, AppHostHandle, AppHostService, AppHostStartOptions, AppInfoResult, AppInstallProgressHandler, AppInstallProgressPhase, AppInstallResult, AppInstallSourceKind, AppInstallationIntegrityService, AppInstallationService, AppInstalledPermissionState, AppInstanceInventory, AppInstanceInventoryDiagnostic, AppInstanceInventoryEntry, AppInstanceInventoryService, AppInstanceMetadata, AppInstanceRecord, AppInstanceService, AppInstanceStorageService, AppLaunchResolution, AppLinuxArtifactTarget, AppMainManifest, AppManifest, AppManifestBundle, AppManifestService, AppManifestSummary, AppMarketplaceClientService, AppMarketplaceMetadata, AppMarketplaceMetadataService, AppMarketplaceVisuals, AppNativeArtifactTarget, AppPermissionSummary, AppPermissions, AppPermissionsService, AppPlatformSecuritySummary, AppPlatformTargetService, AppPreparedPublishArtifact, AppPublishArtifact, AppPublishArtifactService, AppPublishFile, AppPublishPayload, AppPublishResult, AppPublishService, AppPublishValidationResult, AppPublishValidationService, AppPublishValidationWarning, AppPublishValidationWarningCode, AppPublisher, AppRegistry, AppRegistryAppRecord, AppRegistryConfig, AppRegistryConfigService, AppRegistryConfigSnapshot, AppRegistryInstalledVersion, AppRegistryService, AppRemoteRegistryArtifact, AppRemoteRegistryClientService, AppRemoteRegistryDocument, AppRemoteRegistryResolution, AppRemoteRegistryVersion, AppResolvedComponent, AppRollbackResult, AppRunResult, AppRuntimeCliService, AppRuntimeCommandResult, AppRuntimeDeclaration, AppRuntimeDoctorResult, AppRuntimeIsolation, AppRuntimeOptionsService, AppRuntimeProfile, AppRuntimeToolStatus, AppRuntimeToolchainService, AppScaffoldFile, AppScaffoldResult, AppScaffoldService, AppScaffoldTemplate, AppServiceLaunch, AppServiceLaunchService, AppStandaloneManifest, AppStandaloneManifestBundle, AppStandaloneManifestSummary, AppStorageContext, AppStorageDeclaration, AppStorageLayout, AppStorageUsage, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, AppUiManifest, AppUninstallResult, AppUniversalArtifactTarget, AppUpdateResult, AppWasiHttpComponentMainManifest, AppWindowsArtifactTarget, BuildCliOptions, CreateCliOptions, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockBusyError, FileLockLease, FileLockService, GrantCliOptions, GrantCommand, HostBridgeServer, InfoCommand, InstallCliOptions, InstallCommand, InstalledAppListItem, JsonOnlyCliOptions, ListCommand, MainRunRequest, MainRunResult, MainRunnerService, PackCliOptions, PackCommand, PermissionsCommand, PlatformAuthStateService, PlatformPublishAuthState, PublishCliOptions, PublishCommand, RegistryCommand, ResolvedDocumentGrant, ResolvedPermissions, RevokeCliOptions, RevokeCommand, RuntimeCliOptions, UiServerService, UninstallCliOptions, UninstallCommand, UpdateCliOptions, UpdateCommand, ValidatePublishCommand, WasmDocumentSummaryInput, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentHandle, WasmtimeWasiHttpComponentService, WasmtimeWasiHttpComponentStartOptions, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { WasmSidecarClientService } from "./services/wasm-sidecar-client.service
|
|
|
17
17
|
import { WasmMainRunnerService } from "./services/wasm-main-runner.service.js";
|
|
18
18
|
import { AppInstanceService } from "./services/app-instance.service.js";
|
|
19
19
|
import { AppHomeService } from "./services/app-home.service.js";
|
|
20
|
-
import { FileLockService } from "./services/file-lock.service.js";
|
|
20
|
+
import { FileLockBusyError, FileLockService } from "./services/file-lock.service.js";
|
|
21
21
|
import { APP_STORAGE_LAYOUT_VERSION, DEFAULT_APP_INSTANCE_ID } from "./types/app-storage.types.js";
|
|
22
22
|
import { AppInstanceStorageService } from "./services/app-instance-storage.service.js";
|
|
23
23
|
import { AppInstallationIntegrityService } from "./services/app-installation-integrity.service.js";
|
|
@@ -52,4 +52,4 @@ import { ValidatePublishCommand } from "./controllers/validate-publish.controlle
|
|
|
52
52
|
import { AppRuntimeOptionsService } from "./services/app-runtime-options.service.js";
|
|
53
53
|
import { AppRuntimeCliService } from "./services/app-runtime-cli.service.js";
|
|
54
54
|
import { AppInstanceInventoryService } from "./services/app-instance-inventory.service.js";
|
|
55
|
-
export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPlatformTargetService, AppPublishArtifactService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppServiceLaunchService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
|
55
|
+
export { APP_STORAGE_LAYOUT_VERSION, AppArtifactValidationService, AppBuildService, AppBundleService, AppGrantService, AppHomeService, AppHostService, AppInstallationIntegrityService, AppInstallationService, AppInstanceInventoryService, AppInstanceService, AppInstanceStorageService, AppManifestService, AppMarketplaceClientService, AppMarketplaceMetadataService, AppPermissionsService, AppPlatformTargetService, AppPublishArtifactService, AppPublishService, AppPublishValidationService, AppRegistryConfigService, AppRegistryService, AppRemoteRegistryClientService, AppRuntimeCliService, AppRuntimeOptionsService, AppRuntimeToolchainService, AppScaffoldService, AppServiceLaunchService, AppTsHttpLiteScaffoldTemplateService, AppTsHttpScaffoldTemplateService, CreateCommand, DEFAULT_APP_INSTANCE_ID, DEFAULT_APP_MARKETPLACE_API_BASE, DEFAULT_APP_REGISTRY_URL, FileLockBusyError, FileLockService, GrantCommand, HostBridgeServer, InfoCommand, InstallCommand, ListCommand, MainRunnerService, PackCommand, PermissionsCommand, PlatformAuthStateService, PublishCommand, RegistryCommand, RevokeCommand, UiServerService, UninstallCommand, UpdateCommand, ValidatePublishCommand, WasmMainRunnerService, WasmSidecarClientService, WasmtimeWasiHttpComponentService, isAppComponentManifestBundle, isAppStandaloneManifestBundle };
|
package/dist/package.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
//#region src/services/file-lock.service.d.ts
|
|
2
|
+
declare class FileLockBusyError extends Error {
|
|
3
|
+
readonly lockPath: string;
|
|
4
|
+
constructor(lockPath: string);
|
|
5
|
+
}
|
|
6
|
+
type FileLockLease = {
|
|
7
|
+
release(): Promise<void>;
|
|
8
|
+
};
|
|
2
9
|
declare class FileLockService {
|
|
3
10
|
private static readonly localQueues;
|
|
4
11
|
withLock: <T>(lockPath: string, operation: () => Promise<T>, options?: {
|
|
5
12
|
waitTimeoutMs?: number;
|
|
6
13
|
}) => Promise<T>;
|
|
14
|
+
acquireLease: (lockPath: string, options?: {
|
|
15
|
+
waitTimeoutMs?: number;
|
|
16
|
+
}) => Promise<FileLockLease>;
|
|
7
17
|
private acquire;
|
|
8
18
|
private recoverAndAcquire;
|
|
9
19
|
private waitOrThrow;
|
|
@@ -16,5 +26,5 @@ declare class FileLockService {
|
|
|
16
26
|
private isMissingFileError;
|
|
17
27
|
}
|
|
18
28
|
//#endregion
|
|
19
|
-
export { FileLockService };
|
|
29
|
+
export { FileLockBusyError, FileLockLease, FileLockService };
|
|
20
30
|
//# sourceMappingURL=file-lock.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.service.d.ts","names":[],"sources":["../../src/services/file-lock.service.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"file-lock.service.d.ts","names":[],"sources":["../../src/services/file-lock.service.ts"],"mappings":";cAWa,iBAAA,SAA0B,KAAA;EAAA,SAC5B,QAAA;cAEG,QAAA;AAAA;AAAA,KAOF,aAAA;EACV,OAAA,IAAW,OAAA;AAAA;AAAA,cAOA,eAAA;EAAA,wBACa,WAAA;EAExB,QAAA,MACE,QAAA,UACA,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,OAAA;IAAW,aAAA;EAAA,MACV,OAAA,CAAQ,CAAA;EAqBX,YAAA,GACE,QAAA,UACA,OAAA;IAAW,aAAA;EAAA,MACV,OAAA,CAAQ,aAAA;EAAA,QAIH,OAAA;EAAA,QA8CA,iBAAA;EAAA,QA6CA,WAAA;EAAA,QAWA,eAAA;EAAA,QAcA,cAAA;EAAA,QAgBA,aAAA;EAAA,QAmBA,cAAA;EAAA,QAUA,UAAA;EAAA,QASA,oBAAA;EAAA,QAIA,kBAAA;AAAA"}
|
|
@@ -3,6 +3,14 @@ import { access, mkdir, open, readFile, rm, stat } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { setTimeout } from "node:timers/promises";
|
|
5
5
|
//#region src/services/file-lock.service.ts
|
|
6
|
+
var FileLockBusyError = class extends Error {
|
|
7
|
+
lockPath;
|
|
8
|
+
constructor(lockPath) {
|
|
9
|
+
super(`等待文件锁超时:${lockPath}`);
|
|
10
|
+
this.name = "FileLockBusyError";
|
|
11
|
+
this.lockPath = lockPath;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
6
14
|
const DEFAULT_WAIT_TIMEOUT_MS = 15e3;
|
|
7
15
|
const MALFORMED_LOCK_STALE_AFTER_MS = 3e4;
|
|
8
16
|
const RETRY_DELAY_MS = 25;
|
|
@@ -25,6 +33,7 @@ var FileLockService = class FileLockService {
|
|
|
25
33
|
if (FileLockService.localQueues.get(resolvedLockPath) === current) FileLockService.localQueues.delete(resolvedLockPath);
|
|
26
34
|
}
|
|
27
35
|
};
|
|
36
|
+
acquireLease = async (lockPath, options = {}) => ({ release: await this.acquire(path.resolve(lockPath), options.waitTimeoutMs ?? 0) });
|
|
28
37
|
acquire = async (lockPath, waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS) => {
|
|
29
38
|
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
30
39
|
const startedAt = Date.now();
|
|
@@ -101,7 +110,7 @@ var FileLockService = class FileLockService {
|
|
|
101
110
|
}
|
|
102
111
|
};
|
|
103
112
|
waitOrThrow = async (lockPath, startedAt, waitTimeoutMs) => {
|
|
104
|
-
if (Date.now() - startedAt >= waitTimeoutMs) throw new
|
|
113
|
+
if (Date.now() - startedAt >= waitTimeoutMs) throw new FileLockBusyError(lockPath);
|
|
105
114
|
await setTimeout(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));
|
|
106
115
|
};
|
|
107
116
|
removeOwnedLock = async (lockPath, token) => {
|
|
@@ -151,6 +160,6 @@ var FileLockService = class FileLockService {
|
|
|
151
160
|
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
152
161
|
};
|
|
153
162
|
//#endregion
|
|
154
|
-
export { FileLockService };
|
|
163
|
+
export { FileLockBusyError, FileLockService };
|
|
155
164
|
|
|
156
165
|
//# sourceMappingURL=file-lock.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.service.js","names":["delay"],"sources":["../../src/services/file-lock.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, open, readFile, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\ntype FileLockMetadata = {\n token: string;\n pid: number;\n createdAt: string;\n};\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 15_000;\nconst MALFORMED_LOCK_STALE_AFTER_MS = 30_000;\nconst RETRY_DELAY_MS = 25;\n\nexport class FileLockService {\n private static readonly localQueues = new Map<string, Promise<unknown>>();\n\n withLock = async <T>(\n lockPath: string,\n operation: () => Promise<T>,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<T> => {\n const resolvedLockPath = path.resolve(lockPath);\n const previous = FileLockService.localQueues.get(resolvedLockPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(async () => {\n const release = await this.acquire(resolvedLockPath, options.waitTimeoutMs);\n try {\n return await operation();\n } finally {\n await release();\n }\n });\n FileLockService.localQueues.set(resolvedLockPath, current);\n try {\n return await current;\n } finally {\n if (FileLockService.localQueues.get(resolvedLockPath) === current) {\n FileLockService.localQueues.delete(resolvedLockPath);\n }\n }\n };\n\n private acquire = async (\n lockPath: string,\n waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS,\n ): Promise<() => Promise<void>> => {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const startedAt = Date.now();\n const recoveryLockPath = `${lockPath}.recovery`;\n while (true) {\n if (await this.pathExists(recoveryLockPath)) {\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n continue;\n }\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } catch (error) {\n if (!this.isAlreadyExistsError(error)) {\n throw error;\n }\n if (await this.canRecoverLock(lockPath)) {\n const recovered = await this.recoverAndAcquire(lockPath, recoveryLockPath);\n if (recovered) {\n return recovered;\n }\n }\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n }\n }\n };\n\n private recoverAndAcquire = async (\n lockPath: string,\n recoveryLockPath: string,\n ): Promise<(() => Promise<void>) | undefined> => {\n let recoveryHandle;\n try {\n recoveryHandle = await open(recoveryLockPath, \"wx\", 0o600);\n await recoveryHandle.writeFile(`${process.pid}\\n`, \"utf8\");\n await recoveryHandle.sync();\n } catch (error) {\n await recoveryHandle?.close().catch(() => undefined);\n if (this.isAlreadyExistsError(error)) {\n return undefined;\n }\n throw error;\n }\n try {\n if (!await this.canRecoverLock(lockPath)) {\n return undefined;\n }\n await rm(lockPath, { force: true });\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } finally {\n await recoveryHandle.close();\n await rm(recoveryLockPath, { force: true });\n }\n };\n\n private waitOrThrow = async (\n lockPath: string,\n startedAt: number,\n waitTimeoutMs: number,\n ): Promise<void> => {\n if (Date.now() - startedAt >= waitTimeoutMs) {\n throw new Error(`等待文件锁超时:${lockPath}`);\n }\n await delay(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));\n };\n\n private removeOwnedLock = async (lockPath: string, token: string): Promise<void> => {\n try {\n const metadata = this.parseMetadata(await readFile(lockPath, \"utf8\"));\n if (metadata?.token !== token) {\n throw new Error(`文件锁 owner 已变化,拒绝删除:${lockPath}`);\n }\n await rm(lockPath);\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private canRecoverLock = async (lockPath: string): Promise<boolean> => {\n try {\n const [raw, lockStats] = await Promise.all([\n readFile(lockPath, \"utf8\"),\n stat(lockPath),\n ]);\n const metadata = this.parseMetadata(raw);\n if (metadata) {\n return !this.isProcessAlive(metadata.pid);\n }\n return Date.now() - lockStats.mtimeMs >= MALFORMED_LOCK_STALE_AFTER_MS;\n } catch (error) {\n return this.isMissingFileError(error);\n }\n };\n\n private parseMetadata = (raw: string): FileLockMetadata | undefined => {\n try {\n const candidate = JSON.parse(raw) as Partial<FileLockMetadata>;\n if (\n typeof candidate.token !== \"string\" ||\n !candidate.token ||\n typeof candidate.pid !== \"number\" ||\n !Number.isSafeInteger(candidate.pid) ||\n candidate.pid <= 0 ||\n typeof candidate.createdAt !== \"string\"\n ) {\n return undefined;\n }\n return candidate as FileLockMetadata;\n } catch {\n return undefined;\n }\n };\n\n private isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EPERM\";\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isAlreadyExistsError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EEXIST\";\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file-lock.service.js","names":["delay"],"sources":["../../src/services/file-lock.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, open, readFile, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\ntype FileLockMetadata = {\n token: string;\n pid: number;\n createdAt: string;\n};\n\nexport class FileLockBusyError extends Error {\n readonly lockPath: string;\n\n constructor(lockPath: string) {\n super(`等待文件锁超时:${lockPath}`);\n this.name = \"FileLockBusyError\";\n this.lockPath = lockPath;\n }\n}\n\nexport type FileLockLease = {\n release(): Promise<void>;\n};\n\nconst DEFAULT_WAIT_TIMEOUT_MS = 15_000;\nconst MALFORMED_LOCK_STALE_AFTER_MS = 30_000;\nconst RETRY_DELAY_MS = 25;\n\nexport class FileLockService {\n private static readonly localQueues = new Map<string, Promise<unknown>>();\n\n withLock = async <T>(\n lockPath: string,\n operation: () => Promise<T>,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<T> => {\n const resolvedLockPath = path.resolve(lockPath);\n const previous = FileLockService.localQueues.get(resolvedLockPath) ?? Promise.resolve();\n const current = previous.catch(() => undefined).then(async () => {\n const release = await this.acquire(resolvedLockPath, options.waitTimeoutMs);\n try {\n return await operation();\n } finally {\n await release();\n }\n });\n FileLockService.localQueues.set(resolvedLockPath, current);\n try {\n return await current;\n } finally {\n if (FileLockService.localQueues.get(resolvedLockPath) === current) {\n FileLockService.localQueues.delete(resolvedLockPath);\n }\n }\n };\n\n acquireLease = async (\n lockPath: string,\n options: { waitTimeoutMs?: number } = {},\n ): Promise<FileLockLease> => ({\n release: await this.acquire(path.resolve(lockPath), options.waitTimeoutMs ?? 0),\n });\n\n private acquire = async (\n lockPath: string,\n waitTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS,\n ): Promise<() => Promise<void>> => {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const startedAt = Date.now();\n const recoveryLockPath = `${lockPath}.recovery`;\n while (true) {\n if (await this.pathExists(recoveryLockPath)) {\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n continue;\n }\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } catch (error) {\n if (!this.isAlreadyExistsError(error)) {\n throw error;\n }\n if (await this.canRecoverLock(lockPath)) {\n const recovered = await this.recoverAndAcquire(lockPath, recoveryLockPath);\n if (recovered) {\n return recovered;\n }\n }\n await this.waitOrThrow(lockPath, startedAt, waitTimeoutMs);\n }\n }\n };\n\n private recoverAndAcquire = async (\n lockPath: string,\n recoveryLockPath: string,\n ): Promise<(() => Promise<void>) | undefined> => {\n let recoveryHandle;\n try {\n recoveryHandle = await open(recoveryLockPath, \"wx\", 0o600);\n await recoveryHandle.writeFile(`${process.pid}\\n`, \"utf8\");\n await recoveryHandle.sync();\n } catch (error) {\n await recoveryHandle?.close().catch(() => undefined);\n if (this.isAlreadyExistsError(error)) {\n return undefined;\n }\n throw error;\n }\n try {\n if (!await this.canRecoverLock(lockPath)) {\n return undefined;\n }\n await rm(lockPath, { force: true });\n const metadata: FileLockMetadata = {\n token: randomUUID(),\n pid: process.pid,\n createdAt: new Date().toISOString(),\n };\n const handle = await open(lockPath, \"wx\", 0o600);\n try {\n await handle.writeFile(`${JSON.stringify(metadata)}\\n`, \"utf8\");\n await handle.sync();\n } catch (error) {\n await handle.close().catch(() => undefined);\n await rm(lockPath, { force: true }).catch(() => undefined);\n throw error;\n }\n return async () => {\n await handle.close();\n await this.removeOwnedLock(lockPath, metadata.token);\n };\n } finally {\n await recoveryHandle.close();\n await rm(recoveryLockPath, { force: true });\n }\n };\n\n private waitOrThrow = async (\n lockPath: string,\n startedAt: number,\n waitTimeoutMs: number,\n ): Promise<void> => {\n if (Date.now() - startedAt >= waitTimeoutMs) {\n throw new FileLockBusyError(lockPath);\n }\n await delay(RETRY_DELAY_MS + Math.floor(Math.random() * RETRY_DELAY_MS));\n };\n\n private removeOwnedLock = async (lockPath: string, token: string): Promise<void> => {\n try {\n const metadata = this.parseMetadata(await readFile(lockPath, \"utf8\"));\n if (metadata?.token !== token) {\n throw new Error(`文件锁 owner 已变化,拒绝删除:${lockPath}`);\n }\n await rm(lockPath);\n } catch (error) {\n if (!this.isMissingFileError(error)) {\n throw error;\n }\n }\n };\n\n private canRecoverLock = async (lockPath: string): Promise<boolean> => {\n try {\n const [raw, lockStats] = await Promise.all([\n readFile(lockPath, \"utf8\"),\n stat(lockPath),\n ]);\n const metadata = this.parseMetadata(raw);\n if (metadata) {\n return !this.isProcessAlive(metadata.pid);\n }\n return Date.now() - lockStats.mtimeMs >= MALFORMED_LOCK_STALE_AFTER_MS;\n } catch (error) {\n return this.isMissingFileError(error);\n }\n };\n\n private parseMetadata = (raw: string): FileLockMetadata | undefined => {\n try {\n const candidate = JSON.parse(raw) as Partial<FileLockMetadata>;\n if (\n typeof candidate.token !== \"string\" ||\n !candidate.token ||\n typeof candidate.pid !== \"number\" ||\n !Number.isSafeInteger(candidate.pid) ||\n candidate.pid <= 0 ||\n typeof candidate.createdAt !== \"string\"\n ) {\n return undefined;\n }\n return candidate as FileLockMetadata;\n } catch {\n return undefined;\n }\n };\n\n private isProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EPERM\";\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isAlreadyExistsError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"EEXIST\";\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
|