Skip to content

Commit

Permalink
fix: wait to read until file is unlocked
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Aug 1, 2024
1 parent 7e2a426 commit 9726388
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 3 deletions.
5 changes: 3 additions & 2 deletions src/config/configFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Global } from '../global';
import { Logger } from '../logger/logger';
import { SfError } from '../sfError';
import { resolveProjectPath, resolveProjectPathSync } from '../util/internal';
import { lockInit, lockInitSync } from '../util/fileLocking';
import { lockInit, lockInitSync, pollUntilUnlock, pollUntilUnlockSync } from '../util/fileLocking';
import { BaseConfigStore } from './configStore';
import { ConfigContents } from './configStackTypes';
import { stateFromContents } from './lwwMap';
Expand Down Expand Up @@ -167,7 +167,7 @@ export class ConfigFile<
!this.hasRead ? 'hasRead is false' : 'force parameter is true'
}`
);

await pollUntilUnlock(this.getPath());
const obj = parseJsonMap<P>(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath());
this.setContentsFromFileContents(obj, (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs);
}
Expand Down Expand Up @@ -203,6 +203,7 @@ export class ConfigFile<
// Only need to read config files once. They are kept up to date
// internally and updated persistently via write().
if (!this.hasRead || force) {
pollUntilUnlockSync(this.getPath());
this.logger.debug(`Reading config file: ${this.getPath()}`);
const obj = parseJsonMap<P>(fs.readFileSync(this.getPath(), 'utf8'));
this.setContentsFromFileContents(obj, fs.statSync(this.getPath(), { bigint: true }).mtimeNs);
Expand Down
57 changes: 56 additions & 1 deletion src/util/fileLocking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
*/
import * as fs from 'node:fs';
import { dirname } from 'node:path';
import { lock, lockSync } from 'proper-lockfile';
import { lock, lockSync, check, checkSync } from 'proper-lockfile';
import { Duration } from '@salesforce/kit';
import { SfError } from '../sfError';
import { Logger } from '../logger/logger';
import { PollingClient } from '../status/pollingClient';
import { StatusResult } from '../status/types';
import { lockOptions, lockRetryOptions } from './lockRetryOptions';

type LockInitResponse = { writeAndUnlock: (data: string) => Promise<void>; unlock: () => Promise<void> };
Expand Down Expand Up @@ -95,3 +98,55 @@ export const lockInitSync = (filePath: string): LockInitSyncResponse => {
unlock,
};
};

/**
* Poll until the file is unlocked.
*
* @param filePath file path to check
*/
export const pollUntilUnlock = async (filePath: string): Promise<void> => {
const options: PollingClient.Options = {
async poll(): Promise<StatusResult> {
try {
const locked = await check(filePath, lockRetryOptions);
return { completed: !locked, payload: 'File unlocked' };
} catch (e) {
if (e instanceof SfError) {
return { completed: true, payload: e.toObject() };
}
if (e instanceof Error) {
return {
completed: true,
payload: {
name: e.name,
message: e.message,
stack: e.stack,
},
};
}

return { completed: true, payload: 'Error occurred' };
}
},
frequency: Duration.milliseconds(10),
timeout: Duration.minutes(1),
};

const client = await PollingClient.create(options);
await client.subscribe();
};

export const pollUntilUnlockSync = (filePath: string): void => {
// Set a counter to ensure that the while loop does not run indefinitely
let counter = 0;
let locked = true;
while (locked && counter < 100) {
try {
locked = checkSync(filePath, lockOptions);
counter++;
} catch {
// Likely a file not found error, which means the file is not locked
locked = false;
}
}
};

3 comments on commit 9726388

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger Benchmarks - ubuntu-latest

Benchmark suite Current: 9726388 Previous: 07f6d83 Ratio
Child logger creation 465107 ops/sec (±0.82%) 478348 ops/sec (±1.39%) 1.03
Logging a string on root logger 754517 ops/sec (±9.83%) 834814 ops/sec (±7.43%) 1.11
Logging an object on root logger 591425 ops/sec (±5.68%) 44327 ops/sec (±182.92%) 0.07494948640994209
Logging an object with a message on root logger 9619 ops/sec (±200.06%) 447373 ops/sec (±8.26%) 46.51
Logging an object with a redacted prop on root logger 412469 ops/sec (±12.98%) 509272 ops/sec (±6.34%) 1.23
Logging a nested 3-level object on root logger 344626 ops/sec (±8.07%) 17679 ops/sec (±187.26%) 0.05129908944769112

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Logger Benchmarks - ubuntu-latest'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 9726388 Previous: 07f6d83 Ratio
Logging an object with a message on root logger 9619 ops/sec (±200.06%) 447373 ops/sec (±8.26%) 46.51

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger Benchmarks - windows-latest

Benchmark suite Current: 9726388 Previous: 07f6d83 Ratio
Child logger creation 328485 ops/sec (±0.90%) 324316 ops/sec (±0.73%) 0.99
Logging a string on root logger 809671 ops/sec (±5.02%) 794039 ops/sec (±6.03%) 0.98
Logging an object on root logger 608030 ops/sec (±9.13%) 601114 ops/sec (±6.81%) 0.99
Logging an object with a message on root logger 2682 ops/sec (±228.87%) 5194 ops/sec (±209.95%) 1.94
Logging an object with a redacted prop on root logger 411356 ops/sec (±27.27%) 509405 ops/sec (±6.12%) 1.24
Logging a nested 3-level object on root logger 331454 ops/sec (±4.14%) 316290 ops/sec (±5.60%) 0.95

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.