Skip to content

Commit d293531

Browse files
committed
Add mutex for windows
1 parent a212cd0 commit d293531

File tree

6 files changed

+92
-23
lines changed

6 files changed

+92
-23
lines changed

.husky/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
git secrets --register-aws || (echo 'Please install git-secrets https://github.com/awslabs/git-secrets to check for accidentally commited secrets!' && exit 1)
22
git secrets --pre_commit_hook -- ""
3-
3+
npm run lint

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@opentelemetry/sdk-trace-node": "2.0.1",
7373
"@tree-sitter-grammars/tree-sitter-yaml": "0.7.1",
7474
"archiver": "7.0.1",
75+
"async-mutex": "0.5.0",
7576
"axios": "1.11.0",
7677
"cfn-guard": "https://gitpkg.now.sh/aws-cloudformation/cloudformation-guard/guard/ts-lib?33d9931",
7778
"deep-object-diff": "1.1.9",

src/datastore/LMDB.ts

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
import { readdirSync, rmSync } from 'fs';
22
import { join } from 'path';
3+
import { Mutex } from 'async-mutex';
34
import { open, Database, RootDatabase } from 'lmdb';
45
import { Logger } from 'pino';
56
import { LoggerFactory } from '../telemetry/LoggerFactory';
67
import { ScopedTelemetry } from '../telemetry/ScopedTelemetry';
78
import { Telemetry } from '../telemetry/TelemetryDecorator';
89
import { TelemetryService } from '../telemetry/TelemetryService';
910
import { pathToArtifact } from '../utils/ArtifactsDir';
11+
import { isWindows } from '../utils/Environment';
1012
import { toString } from '../utils/String';
1113
import { DataStore, DataStoreFactory, StoreName } from './DataStore';
1214
import { encryptionStrategy } from './lmdb/Utils';
1315

1416
export class LMDBStore implements DataStore {
15-
private readonly log: Logger;
16-
private readonly telemetry: ScopedTelemetry;
17-
1817
constructor(
1918
public readonly name: StoreName,
20-
private readonly store: Database<unknown, string>,
19+
protected readonly store: Database<unknown, string>,
20+
private readonly telemetry: ScopedTelemetry = TelemetryService.instance.get(`LMDB.${name}`),
21+
private readonly log: Logger = LoggerFactory.getLogger(`LMDB.${name}`),
2122
) {
22-
this.telemetry = TelemetryService.instance.get(`LMDB.${name}`);
23-
this.log = LoggerFactory.getLogger(`LMDB.${name}`);
2423
this.log.info('Initialized');
2524
}
2625

@@ -29,21 +28,21 @@ export class LMDBStore implements DataStore {
2928
return this.store.get(key) as T | undefined;
3029
}
3130

32-
put<T>(key: string, value: T): Promise<boolean> {
31+
async put<T>(key: string, value: T): Promise<boolean> {
3332
this.log.info(`Put ${key}`);
34-
return this.telemetry.measureAsync('put', () => {
33+
return await this.telemetry.measureAsync('put', () => {
3534
return this.store.put(key, value);
3635
});
3736
}
3837

39-
remove(key: string): Promise<boolean> {
38+
async remove(key: string): Promise<boolean> {
4039
this.log.info(`Remove ${key}`);
41-
return this.store.remove(key);
40+
return await this.store.remove(key);
4241
}
4342

44-
clear(): Promise<void> {
43+
async clear(): Promise<void> {
4544
this.log.info(`Clear ${this.name}`);
46-
return this.store.clearAsync();
45+
return await this.store.clearAsync();
4746
}
4847

4948
keys(limit: number): ReadonlyArray<string> {
@@ -56,6 +55,52 @@ export class LMDBStore implements DataStore {
5655
}
5756
}
5857

58+
export class LockedLMDBStore extends LMDBStore {
59+
private readonly lock = new Mutex();
60+
61+
constructor(name: StoreName, store: Database<unknown, string>) {
62+
super(
63+
name,
64+
store,
65+
TelemetryService.instance.get(`LockedLMDBStore.${name}`),
66+
LoggerFactory.getLogger(`LockedLMDBStore.${name}`),
67+
);
68+
}
69+
70+
override get<T>(key: string): T | undefined {
71+
if (this.lock.isLocked()) {
72+
return undefined;
73+
}
74+
75+
return super.get(key);
76+
}
77+
78+
override async put<T>(key: string, value: T): Promise<boolean> {
79+
return await this.lock.runExclusive(async () => {
80+
return await super.put(key, value);
81+
});
82+
}
83+
84+
override async remove(key: string): Promise<boolean> {
85+
return await this.lock.runExclusive(async () => {
86+
return await super.remove(key);
87+
});
88+
}
89+
90+
override async clear(): Promise<void> {
91+
return await this.lock.runExclusive(async () => {
92+
return await super.clear();
93+
});
94+
}
95+
96+
override keys(limit: number): ReadonlyArray<string> {
97+
if (this.lock.isLocked()) {
98+
return [];
99+
}
100+
return super.keys(limit);
101+
}
102+
}
103+
59104
export class LMDBStoreFactory implements DataStoreFactory {
60105
private readonly log: Logger;
61106
@Telemetry({ scope: 'LMDB.Global' }) private readonly telemetry!: ScopedTelemetry;
@@ -90,7 +135,14 @@ export class LMDBStoreFactory implements DataStoreFactory {
90135
name: store,
91136
encoding: Encoding,
92137
});
93-
this.stores.set(store, new LMDBStore(store, database));
138+
139+
let lmdbStore: LMDBStore;
140+
if (isWindows) {
141+
lmdbStore = new LockedLMDBStore(store, database);
142+
} else {
143+
lmdbStore = new LMDBStore(store, database);
144+
}
145+
this.stores.set(store, lmdbStore);
94146
}
95147
this.registerLMDBGauges();
96148

@@ -177,7 +229,7 @@ export class LMDBStoreFactory implements DataStoreFactory {
177229

178230
const Version = 4;
179231
const VersionFileName = `.v${Version}.lmdb`;
180-
const Encoding: 'msgpack' | 'json' | 'string' | 'binary' | 'ordered-binary' = 'msgpack';
232+
const Encoding = 'msgpack';
181233
const TotalMaxDbSize = 5 * 250 * 1024 * 1024; // 250MB max size
182234

183235
function stats(store: RootDatabase | Database): StoreStatsType {

src/datastore/MemoryStore.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,21 @@ export class MemoryStore implements DataStore {
3636
keys(limit: number): ReadonlyArray<string> {
3737
return [...this.store.keys()].slice(0, limit);
3838
}
39+
40+
size() {
41+
return this.store.size;
42+
}
3943
}
4044

4145
export class MemoryStoreFactory implements DataStoreFactory {
4246
@Telemetry({ scope: 'MemoryStore.Global' }) private readonly telemetry!: ScopedTelemetry;
4347

4448
private readonly stores = new Map<StoreName, MemoryStore>();
4549

50+
constructor() {
51+
this.registerMemoryStoreGauges();
52+
}
53+
4654
get(store: StoreName): DataStore {
4755
let val = this.stores.get(store);
4856
if (val === undefined) {
@@ -62,13 +70,10 @@ export class MemoryStoreFactory implements DataStoreFactory {
6270
}
6371

6472
private registerMemoryStoreGauges(): void {
65-
this.telemetry.registerGaugeProvider('stores.count', () => this.stores.size);
66-
this.telemetry.registerGaugeProvider('global.entries', () => {
67-
let total = 0;
68-
for (const store of this.stores.values()) {
69-
total += store.keys(1000).length;
70-
}
71-
return total;
72-
});
73+
this.telemetry.registerGaugeProvider('env.entries', () => this.stores.size);
74+
75+
for (const [name, store] of this.stores.entries()) {
76+
this.telemetry.registerGaugeProvider(`store.${name}.entries`, () => store.size());
77+
}
7378
}
7479
}

src/schema/SchemaRetriever.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class SchemaRetriever implements SettingsConfigurable, Closeable {
7878
initialize() {
7979
this.getRegionalSchemasIfStale();
8080
this.getSamSchemasIfMissingOrStale();
81+
this.schemaTaskManager.runPrivateTask();
8182
}
8283

8384
getDefault(): CombinedSchemas {

0 commit comments

Comments
 (0)