Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Commit f3ea0ac

Browse files
authored
Merge pull request #219 from codeoverflow-org/fix/217-persistence-manager-race-condition
Fix race condition in PersitsenceManager
2 parents 60bc358 + b29d36b commit f3ea0ac

File tree

2 files changed

+30
-13
lines changed

2 files changed

+30
-13
lines changed

nodecg-io-core/extension/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = (nodecg: NodeCG): NodeCGIOCore => {
3232
persistenceManager,
3333
).registerMessageHandlers();
3434

35-
registerExitHandlers(nodecg, bundleManager, instanceManager, serviceManager);
35+
registerExitHandlers(nodecg, bundleManager, instanceManager, serviceManager, persistenceManager);
3636

3737
// We use a extra object instead of returning a object containing all the managers and so on, because
3838
// any loaded bundle would be able to call any (public or private) of the managers which is not intended.
@@ -62,7 +62,12 @@ function onExit(
6262
bundleManager: BundleManager,
6363
instanceManager: InstanceManager,
6464
serviceManager: ServiceManager,
65+
persistenceManager: PersistenceManager,
6566
): void {
67+
// Save everything
68+
// This is especially important if some services update some configs (e.g. updated tokens) and they haven't been saved yet.
69+
persistenceManager.save();
70+
6671
// Unset all service instances in all bundles
6772
const bundles = bundleManager.getBundleDependencies();
6873
for (const bundleName in bundles) {
@@ -99,9 +104,10 @@ function registerExitHandlers(
99104
bundleManager: BundleManager,
100105
instanceManager: InstanceManager,
101106
serviceManager: ServiceManager,
107+
persistenceManager: PersistenceManager,
102108
): void {
103109
const handler = () => {
104-
onExit(nodecg, bundleManager, instanceManager, serviceManager);
110+
onExit(nodecg, bundleManager, instanceManager, serviceManager, persistenceManager);
105111
};
106112

107113
// Normal exit

nodecg-io-core/extension/instanceManager.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,30 @@ export class InstanceManager extends EventEmitter {
140140
* Should only be false if it has been validated at a previous point in time, e.g. loading after startup.
141141
* @return void if everything went fine and a string describing the issue if something went wrong.
142142
*/
143-
async updateInstanceConfig(instanceName: string, config: unknown, validation = true): Promise<Result<void>> {
143+
updateInstanceConfig(instanceName: string, config: unknown, validation = true): Promise<Result<void>> {
144144
// Check existence and get service instance.
145145
const inst = this.serviceInstances[instanceName];
146146
if (inst === undefined) {
147-
return error("Service instance doesn't exist.");
147+
return Promise.resolve(error("Service instance doesn't exist."));
148148
}
149149

150150
const service = this.services.getService(inst.serviceType);
151151
if (service.failed) {
152-
return error("The service of this instance couldn't be found.");
152+
return Promise.resolve(error("The service of this instance couldn't be found."));
153153
}
154154

155-
if (validation || !service.result.requiresNoConfig) {
155+
// If we don't need validation, because we are loading the configuration from disk, we can set it directly
156+
// so that after we return the promise from updateInstanceClient the PersistenceManager can be sure that the
157+
// config has been written.
158+
// Can also be used when there is no configuration needed so that we don't spawn another promise.
159+
if (!validation || service.result.requiresNoConfig) {
160+
inst.config = config;
161+
this.emit("change");
162+
return this.updateInstanceClient(inst, instanceName, service.result);
163+
}
164+
165+
// We need to do validation, spawn a Promise
166+
return (async () => {
156167
const schemaValid = this.ajv.validate(service.result.schema, config);
157168
if (!schemaValid) {
158169
return error("Config invalid: " + this.ajv.errorsText());
@@ -170,16 +181,16 @@ export class InstanceManager extends EventEmitter {
170181
);
171182
return error("Config invalid: " + err);
172183
}
173-
}
174184

175-
// All checks passed. Set config.
176-
inst.config = config;
185+
// All checks passed. Set config and save it.
186+
inst.config = config;
187+
this.emit("change");
177188

178-
// Update client of this instance using the new config.
179-
const updateResult = await this.updateInstanceClient(inst, instanceName, service.result);
189+
// Update client of this instance using the new config.
190+
const updateResult = await this.updateInstanceClient(inst, instanceName, service.result);
180191

181-
this.emit("change");
182-
return updateResult;
192+
return updateResult;
193+
})();
183194
}
184195

185196
/**

0 commit comments

Comments
 (0)