Skip to content

Commit

Permalink
fix(games): launch orphaned games in a critical section (#1523)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrappachc authored Mar 29, 2022
1 parent 95bfe15 commit ac68079
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 17 deletions.
19 changes: 12 additions & 7 deletions src/games/services/game-launcher.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import { Events } from '@/events/events';
import { GameServer } from '@/game-servers/models/game-server';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { mongooseTestingModule } from '@/utils/testing-mongoose-module';
import { getModelToken, MongooseModule } from '@nestjs/mongoose';
import {
getConnectionToken,
getModelToken,
MongooseModule,
} from '@nestjs/mongoose';
import { Game, GameDocument, gameSchema } from '../models/game';
import { Model, Types, Error } from 'mongoose';
import { Model, Types, Error, Connection } from 'mongoose';
import { staticGameServerProviderName } from '@/game-servers/providers/static-game-server/static-game-server-provider-name';
import { NotImplementedError } from '@/game-servers/errors/not-implemented.error';

jest.mock('@/game-servers/services/game-servers.service');
jest.mock('./games.service');
Expand All @@ -26,6 +29,7 @@ describe('GameLauncherService', () => {
let game: GameDocument;
let gameModel: Model<GameDocument>;
let mockGameServer: jest.Mocked<GameServer>;
let connection: Connection;

beforeAll(async () => (mongod = await MongoMemoryServer.create()));
afterAll(async () => await mongod.stop());
Expand Down Expand Up @@ -55,6 +59,7 @@ describe('GameLauncherService', () => {
gameServersService = module.get(GameServersService);
serverConfiguratorService = module.get(ServerConfiguratorService);
gameModel = module.get(getModelToken(Game.name));
connection = module.get(getConnectionToken());

mockGameServer = {
id: 'FAKE_GAME_SERVER_ID',
Expand Down Expand Up @@ -82,12 +87,11 @@ describe('GameLauncherService', () => {

// @ts-expect-error
game = await gamesService._createOne();
game.gameServer = new Types.ObjectId();
await game.save();
});

afterEach(async () => {
await gameModel.deleteMany({});
await connection.close();
});

it('should be defined', () => {
Expand Down Expand Up @@ -125,9 +129,10 @@ describe('GameLauncherService', () => {
});

it('should launch orphaned games', async () => {
const spy = jest.spyOn(service, 'launch');
await service.launchOrphanedGames();
expect(spy).toHaveBeenCalledWith(game.id);
expect(serverConfiguratorService.configureServer).toHaveBeenCalledWith(
game.id,
);
});
});
});
30 changes: 20 additions & 10 deletions src/games/services/game-launcher.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GameServersService } from '@/game-servers/services/game-servers.service
import { ServerConfiguratorService } from './server-configurator.service';
import { Cron, CronExpression } from '@nestjs/schedule';
import { Game } from '../models/game';
import { Mutex } from 'async-mutex';

/**
* This service is responsible for launching a single game.
Expand All @@ -14,6 +15,7 @@ import { Game } from '../models/game';
@Injectable()
export class GameLauncherService {
private logger = new Logger(GameLauncherService.name);
private mutex = new Mutex();

constructor(
@Inject(forwardRef(() => GamesService)) private gamesService: GamesService,
Expand All @@ -28,6 +30,23 @@ export class GameLauncherService {
* @memberof GameLauncherService
*/
async launch(gameId: string): Promise<Game> {
return await this.mutex.runExclusive(
async () => await this.doLaunch(gameId),
);
}

@Cron(CronExpression.EVERY_MINUTE)
async launchOrphanedGames() {
return await this.mutex.runExclusive(async () => {
const orphanedGames = await this.gamesService.getOrphanedGames();
for (const game of orphanedGames) {
this.logger.verbose(`launching game #${game.number}...`);
await this.doLaunch(game.id);
}
});
}

private async doLaunch(gameId: string): Promise<Game> {
let game = await this.gamesService.getById(gameId);

if (!game.isInProgress()) {
Expand All @@ -47,8 +66,8 @@ export class GameLauncherService {

// step 2: obtain logsecret
const logSecret = await gameServer.getLogsecret();
this.logger.debug(`[${gameServer.name}] logsecret is ${game.logSecret}`);
game = await this.gamesService.update(game.id, { logSecret });
this.logger.debug(`[${gameServer.name}] logsecret is ${game.logSecret}`);

// step 3: configure server
const { connectString, stvConnectString } =
Expand All @@ -70,13 +89,4 @@ export class GameLauncherService {
this.logger.error(`Error launching game #${game.number}: ${error}`);
}
}

@Cron(CronExpression.EVERY_MINUTE) // every minute
async launchOrphanedGames() {
const orphanedGames = await this.gamesService.getOrphanedGames();
for (const game of orphanedGames) {
this.logger.verbose(`launching game #${game.number}...`);
await this.launch(game.id);
}
}
}

0 comments on commit ac68079

Please sign in to comment.