FS: Fix hotpatching and add more throttling (#7878)

- `writeUpdate` state is now stored in a global variable, so hotpatching doesn't crash it
- throttling now writes on the tail (so two throttled `writeUpdate` calls will write one update, not two)
- room settings, punishments, and helptickets are now throttled
This commit is contained in:
Mia 2021-01-09 17:49:30 -06:00 committed by GitHub
parent d5e7f3f778
commit ca94dea20f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 29 additions and 14 deletions

View File

@ -35,7 +35,13 @@ interface PendingUpdate {
throttleTimer: NodeJS.Timer | null;
}
const pendingUpdates = new Map<string, PendingUpdate>();
declare const __fsState: {pendingUpdates: Map<string, PendingUpdate>};
declare const global: {__fsState: typeof __fsState};
if (!global.__fsState) {
global.__fsState = {
pendingUpdates: new Map(),
};
}
export class FSPath {
path: string;
@ -152,9 +158,8 @@ export class FSPath {
*/
writeUpdate(dataFetcher: () => string | Buffer, options: AnyObject = {}) {
if (Config.nofswriting) return;
const pendingUpdate: PendingUpdate | undefined = pendingUpdates.get(this.path);
const pendingUpdate: PendingUpdate | undefined = __fsState.pendingUpdates.get(this.path);
// @ts-ignore
const throttleTime = options.throttle ? Date.now() + options.throttle : 0;
if (pendingUpdate) {
@ -168,11 +173,22 @@ export class FSPath {
return;
}
this.writeUpdateNow(dataFetcher, options);
if (!throttleTime) {
this.writeUpdateNow(dataFetcher, options);
return;
}
const update: PendingUpdate = {
isWriting: false,
pendingDataFetcher: dataFetcher,
pendingOptions: options,
throttleTime,
throttleTimer: setTimeout(() => this.checkNextUpdate(), throttleTime - Date.now()),
};
__fsState.pendingUpdates.set(this.path, update);
}
writeUpdateNow(dataFetcher: () => string | Buffer, options: AnyObject) {
// @ts-ignore
const throttleTime = options.throttle ? Date.now() + options.throttle : 0;
const update = {
isWriting: true,
@ -181,25 +197,25 @@ export class FSPath {
throttleTime,
throttleTimer: null,
};
pendingUpdates.set(this.path, update);
__fsState.pendingUpdates.set(this.path, update);
void this.safeWrite(dataFetcher(), options).then(() => this.finishUpdate());
}
checkNextUpdate() {
const pendingUpdate = pendingUpdates.get(this.path);
const pendingUpdate = __fsState.pendingUpdates.get(this.path);
if (!pendingUpdate) throw new Error(`FS: Pending update not found`);
if (pendingUpdate.isWriting) throw new Error(`FS: Conflicting update`);
const {pendingDataFetcher: dataFetcher, pendingOptions: options} = pendingUpdate;
if (!dataFetcher || !options) {
// no pending update
pendingUpdates.delete(this.path);
__fsState.pendingUpdates.delete(this.path);
return;
}
this.writeUpdateNow(dataFetcher, options);
}
finishUpdate() {
const pendingUpdate = pendingUpdates.get(this.path);
const pendingUpdate = __fsState.pendingUpdates.get(this.path);
if (!pendingUpdate) throw new Error(`FS: Pending update not found`);
if (!pendingUpdate.isWriting) throw new Error(`FS: Conflicting update`);
@ -491,4 +507,3 @@ function getFs(path: string) {
export const FS = Object.assign(getFs, {
FileReadStream,
});

View File

@ -55,7 +55,7 @@ try {
export function writeTickets() {
FS(TICKET_FILE).writeUpdate(
() => JSON.stringify(tickets)
() => JSON.stringify(tickets), {throttle: 5000}
);
}

View File

@ -286,7 +286,7 @@ export const Punishments = new class {
buf += Punishments.renderEntry(entry, id);
}
return buf;
});
}, {throttle: 5000});
}
saveRoomPunishments() {
@ -302,7 +302,7 @@ export const Punishments = new class {
buf += Punishments.renderEntry(entry, id);
}
return buf;
});
}, {throttle: 5000});
}
getEntry(entryId: string) {

View File

@ -1143,7 +1143,7 @@ export class GlobalRoomState {
JSON.stringify(this.settingsList)
.replace(/\{"title":/g, '\n{"title":')
.replace(/\]$/, '\n]')
));
), {throttle: 5000});
}
writeNumRooms() {