diff --git a/src/game-servers/services/game-servers.service.spec.ts b/src/game-servers/services/game-servers.service.spec.ts index 2f346c4e9..7ecda5fe3 100644 --- a/src/game-servers/services/game-servers.service.spec.ts +++ b/src/game-servers/services/game-servers.service.spec.ts @@ -17,6 +17,7 @@ import { MongooseModule, } from '@nestjs/mongoose'; import { GamesService } from '@/games/services/games.service'; +import { GameState } from '@/games/models/game-state'; jest.mock('@/games/services/games.service'); @@ -200,7 +201,14 @@ describe('GameServersService', () => { describe('#findFreeGameServer()', () => { describe('when the server is online but taken', () => { beforeEach(async () => { - testGameServer.game = new ObjectId(); + const game1 = await gameModel.create({ + number: 2, + map: 'cp_badlands', + slots: [], + state: GameState.started + }); + + testGameServer.game = new ObjectId(game1.id); testGameServer.isOnline = true; await testGameServer.save(); }); @@ -212,6 +220,28 @@ describe('GameServersService', () => { }); }); + describe('when the server has a game, but that game ended', () => { + beforeEach(async () => { + + const game2 = await gameModel.create({ + number: 2, + map: 'cp_badlands', + slots: [], + state: GameState.ended + }); + + testGameServer.game = new ObjectId(game2.id); + testGameServer.isOnline = true; + await testGameServer.save(); + }); + + it('should return this game server', async () => { + expect((await service.findFreeGameServer()).id).toEqual( + testGameServer.id, + ); + }); + }); + describe('when the server is free but offline', () => { beforeEach(async () => { testGameServer.isOnline = false; diff --git a/src/game-servers/services/game-servers.service.ts b/src/game-servers/services/game-servers.service.ts index bc2b16cdf..6a297d35e 100644 --- a/src/game-servers/services/game-servers.service.ts +++ b/src/game-servers/services/game-servers.service.ts @@ -14,6 +14,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Model, Error, Types } from 'mongoose'; import { GamesService } from '@/games/services/games.service'; import { filter } from 'rxjs/operators'; +import { GameState } from '@/games/models/game-state'; interface HeartbeatParams { name: string; @@ -155,14 +156,41 @@ export class GameServersService implements OnModuleInit { } async findFreeGameServer(): Promise { - return plainToClass( - GameServer, - await this.gameServerModel - .findOne({ isOnline: true, game: { $exists: false } }) - .orFail() - .lean() - .exec(), - ); + let availableGameServer: GameServer = null; + // Fetch all game servers to determine which ones are free + const availableGameServers = await this.getAllGameServers(); + + // Iterate every server and confirm if the stored game is actually running + // We iterate from last to first to return the first available server of + // all available server instead of the last + for (let i = availableGameServers.length - 1; i > -1; i--) { + const gameServer = availableGameServers[i]; + if (gameServer.game !== undefined) { + const game = await this.gamesService.getById(gameServer.game); + + if ( + game.state === GameState.ended || + game.state === GameState.interrupted + ) { + this.logger.debug( + `game server ${gameServer.id} (${gameServer.name}) had the wrong state of a game`, + ); + + // Release the gameserver since the game it has stored actually finished + await this.releaseServer(gameServer.id); + + availableGameServer = gameServer; + } + } else { + availableGameServer = gameServer; + } + } + + if (availableGameServer !== null) { + return availableGameServer; + } else { + throw new Error.DocumentNotFoundError("No free servers available."); + } } async assignFreeGameServer(gameId: string): Promise {