FlexMatch matches stuck on PLACING then goes to TIMEOUT

Hello,

First of all I have to say that we migrated from 4 years on GameSparks to GameLift so sry for newbie questions but we are stuck at the beginning :slight_smile:

I am trying to start FlexMatch game on Realtime Server with lambda code. Realtime server is MegaFrogRace example. Here is the lambda:

import boto3

gl_client = boto3.client('gamelift')



def handler(event, context):
    print "playerId: "
    playerId = event['PlayerName']
    print playerId
    

    playerScore = 20

    response = {'TicketId' : 'AuthError' }

    playerAttr = {'score': {'N': int(playerScore) }}


    # Auth OK, Match Request Go
    try:
        match_response = gl_client.start_matchmaking(
            ConfigurationName='MyMatchMakingConfig',
            Players = [ { 'PlayerId' : playerId, 'PlayerAttributes' : playerAttr } ]
            )
    except TypeError as e:
        print("exce1")
        print(e)
        response = {'TicketId' : 'MatchError' }
        return response
    except:
        print ("except2")
        response = {'TicketId' : 'MatchError' }
        return response

    ticketId = match_response['MatchmakingTicket']['TicketId']

    response = {'TicketId' : ticketId }
    return response

When I click “test” on lambda console with two different arguments for ‘PlayerName’ I see tickets are created. I am tracking tickets with aws cli describe-ticket and I see they are changed from SEARCHING to PLACING. Then it is in PLACING state for like a minute then goes to TIMED_OUT.

TickedId: 8edb88dd-cb70-48d1-99b1-04dab51a7324
FleetId: fleet-ca09d174-95c5-4e28-b77e-7e2763c0f0b2
AliasId: alias-e0890e80-2f26-4e43-a097-07c551a671d8
Queue ARN: arn:aws:gamelift:eu-central-1:943501401976:gamesessionqueue/MojQueue

I dont know if it matters but I can start game with GameLift.createGameSession and createPlayerSession.

I was looking at logs and SQS matchmaking events but couldnt find anything useful.

Thanks,

Mislav

Hi @Mislav_Zlatar,
The game session placement queue is not able to place the game within the timeout period. Can you look at your queue, and check that the alias is listed as a queue destination. If not, add it. Check also that the alias points to the fleet. For development you should use an on-demand fleet; if you are using a spot fleet it is possible that it is inviable and not receiving games. You should definitely have at least one on-demand fleet on your queue.
Are you using latency data?
Thank you,
Al Murray :cowboy_hat_face::+1:

Hi Al, glad to hear from you :slight_smile:

Cant post screenshots cuz I am newbie here :slight_smile:

Lmbda is referencing matchmakingconfiguration. Mathcmaking configuration has correct queue as destination. Queue has correct alias as destination. Alias has correct fleet. Fleet is on-demand.

All that resources are in same eu-central-1 region.

I guess it is some permission/role issue but cant figure where did I failed.

I am not using latency data.

Thanks,

Mislav

PLACING means we’re trying to place a game session on a fleet with capacity, if it goes to timeout it means either we couldn’t find any capacity or your game servers are failing to handle the requests.

Do your fleet metrics show that you have available game sessions at the time? Do you see crashes or abnormal terminations? Are you terminating game sessions and thus freeing up resources?

As Al said, you may want to dial things down to a On demand single instance (with capacity for your game session) in a single fleet. You can then pull the server logs/stream in real time (via instance access) to see what is happening.

I have checked logs via SSH on instance and nothing seems strange. It just continue to send (Heartbeat) GameLiftServerAPI initiating OnHealthCheck…As I said, I can start game with createGameSession and createPlayerSession so GameServer should be OK…

Available game session graph stays on 1, QUeue depth is 0 all the time.

I have tried to point queue to fleet not alias, its the same.

I have tried other Fleet c5.2xlarge, its the same.

Here Is GameServer.js:

// import * as AWS from "aws-sdk";
import * as inspector from "inspector";
import * as util from "util";
import { getUser } from "./utils/GetUser";

// const GameLift = new AWS.GameLift{region: "eu-central-1"});

inspector.open(9229, "127.0.0.1");

// Example override configuration
const configuration = {
};

let playerIds : string[] = [];
let session : any = null;
let sessionTimeoutTimer : any = null;
const SESSION_TIMEOUT = 1 * 60 * 1000;  // milliseconds to wait for players to join (1 minute)


// server op codes (messages server sends)
const LOGICAL_PLAYER_OP_CODE = 100;
const START_COUNTDOWN_OP_CODE = 101;
const MOVE_PLAYER_OP_CODE = 102;
const WINNER_DETERMINED_OP_CODE = 103;

// client op codes (messages client sends)
const SCENE_READY_OP_CODE = 200;
const HOP_OP_CODE = 201;

///////////////////////////////////////////////////////////////////////////////
// Utility functions
///////////////////////////////////////////////////////////////////////////////

// note that the strings will be Base64 encoded, so they can"t contain colon, comma or double quote
// This function takes a list of peers and then send the opcode and string to the peer
const SendStringToClient = (peerIds : string[], opCode : number, stringToSend : string) => {
    session.getLogger().info("[app] SendStringToClient: peerIds = " + peerIds.toString() + " opCode = " + opCode + " stringToSend = " + stringToSend);

    const gameMessage = session.newTextGameMessage(opCode, session.getServerId(), stringToSend);
    const peerArrayLen = peerIds.length;

    for (let index = 0; index < peerArrayLen; ++index) {
        session.getLogger().info("[app] SendStringToClient: sendMessageT " + gameMessage.toString() + " " + peerIds[index].toString());
        session.sendMessage(gameMessage, peerIds[index]);
    }
};

const StartGame = () => {
    const initHopTime = "13";
    SendStringToClient(playerIds, START_COUNTDOWN_OP_CODE, initHopTime);   // signal clients to start the countdown
};

const StopGame = () => {
    session.getLogger().info("[app] StopGame - killing game session");

    playerIds = [];

    if(session != null)
    {
        // processEnding will stop this instance of the game running
        // and will tell the game session to terminate
        session.processEnding().then(function(outcome : any) {
            session.getLogger().info("Completed process ending with: " + outcome);
            process.exit(0);
        });
    }
};

///////////////////////////////////////////////////////////////////////////////
// App callbacks
///////////////////////////////////////////////////////////////////////////////

// Called when game server is initialized, is passed server object of current session
export const init = (_session : any) => {
    session = _session;
    session.getLogger().info("[app] init(_session): ");
    session.getLogger().info(util.inspect(_session));
};

export const onMessage = (gameMessage : any) => {
    session.getLogger().info("[app] onMessage(gameMessage): ");
    session.getLogger().info(util.inspect(gameMessage));

    // sender 0 is server so we don"t process them
    if (gameMessage.sender != 0) {
        let senderId = gameMessage.sender;

        switch (gameMessage.opCode) {
            case SCENE_READY_OP_CODE:
                // playerReady[logicalSender] = true;
                // have both players signaled they are ready? If so, ready to go
                // if (playerReady[0] === true && playerReady[1] === true) {
                //     StartGame();
                // }
                break;

            case HOP_OP_CODE:
                // ProcessHop(logicalSender);
                session.getLogger().info("Hello world!!!");
                break;

            default:
                session.getLogger().info("[warning] Unrecognized opCode in gameMessage");
        };
    }
};

// On Player Connect is called when a player has passed initial validation
// Return true if player should connect
const onPlayerConnect = (player : any) => {
    session.getLogger().info("[app] onPlayerConnect: " + player.peerId);

    // once a player connects it"s fine to let the game session keep going
    // it will be killed once any client disconnects
    if(sessionTimeoutTimer != null) {
        clearTimeout(sessionTimeoutTimer);
        sessionTimeoutTimer = null;
    }

    if(playerIds.length >= 2) {
        // max of two players so reject any additional connections
        return false;
    }

    return true;
};

// onPlayerAccepted is called when a player has connected and not rejected
// by onPlayerConnect. At this point it"s possible to broadcast to the player
//    session.getLogger().info("[app]");

const onPlayerAccepted = async(player : any) => {
    
    session.getLogger().info("[app] onPlayerAccepted: player.peerId = " + player.peerId);
    // store the ID. Note that the index the player is assigned will be sent
    // to the client and determines if they are "player 0" or "player 1" independent
    // of the peerId
    playerIds.push(player.peerId);
    session.getLogger().info("[app] onPlayerAccepted: new contents of players array = " + playerIds.toString());


    // SendStringToClient([player.peerId], LOGICAL_PLAYER_OP_CODE, logicalID.toString());

};

// On Player Disconnect is called when a player has left or been forcibly terminated
// Is only called players that actually connect to the server and not those rejected by validation
// This is called before the player is removed from the player list
const onPlayerDisconnect = (peerId : string) => {
    session.getLogger().info("[app] onPlayerDisconnect: " + peerId);
    StopGame();
};

// On Process Started is called when the process has begun and we need to perform any
// bootstrapping.  This is where the developer should insert any necessary code to prepare
// the process to be able to host a game session.
// Return true if the process has been appropriately prepared and it is okay to invoke the
// GameLift ProcessReady() call.
const onProcessStarted = () => {
    session.getLogger().info("Starting process...");
    session.getLogger().info("Ready to host games...");
    return true;
};

// On Start Game Session is called when GameLift creates a game session that runs this server script
// A Game Session is one instance of your game actually running. Each instance will have its
// own instance of this script.
const onStartGameSession = (gameSession : any) => {

    session.getLogger().info("[app] onStartGameSession");
    // The game session is started by the client service Lambda function
    // If no player joins, we want to kill the game session after
    // a certain period of time so it doesn"t hang around forever taking up
    // a game instance.
    sessionTimeoutTimer = setTimeout(StopGame, SESSION_TIMEOUT);
};

// module.exports = {
//     configuration: configuration,
//     init: init,
//     onMessage: onMessage,
//     onPlayerConnect: onPlayerConnect,
//     onPlayerDisconnect: onPlayerDisconnect,
//     onProcessStarted: onProcessStarted,
//     onPlayerAccepted: onPlayerAccepted,
//     onStartGameSession: onStartGameSession
// };



exports.ssExports = {
    configuration: configuration,
    init: init,
    onMessage: onMessage,
    onPlayerConnect: onPlayerConnect,
    onPlayerDisconnect: onPlayerDisconnect,
    onProcessStarted: onProcessStarted,
    onPlayerAccepted: onPlayerAccepted,
    onStartGameSession: onStartGameSession
};

Mislav

Sorry that this is still not working for you. The team is looking at your GameLift queue and fleet and hopefully will have some more information for you soon.

Thanks Pip,

I am wondering where did I mess up :slight_smile:
Mislav

Hi guys,

Any news on this?

Mislav

Hi @Pip,

I’m working with mislav. I tried making the same setup on different regions but I always had the same result with matches being stuck in PLACING.
This seems to be similar to this issue.
Could this be related?

I believe we’ve come across the same problem. After a lot of trial and error, we’ve determined that the likely source is some interaction between the Queue and a Realtime Fleet. Matchmaking / Queue works with the sample (non-Realtime) Build, but doesn’t work with Realtime Scripts. We’ve tried our own script, the Realtime example script, and the minimal script. Tried tests with both OnDemand and Spot, and both work for Build but not Realtime. It seems that the Matchmaking succeeds in matching players, then places them in queue, after which point it times out. Attempting to directly make a Game Session Placement with the Queue for a Realtime fleet also results in a timeout. However, creating a Game Session directly on the Realtime fleet works.

We have a tight deadline, so what we’re going to try next is receiving matchmaking events from SNS and attempt to bypass the queue and directly create game sessions on the Realtime fleet. Not sure if that will work for you (let us know if you try), but hopefully this gets addressed.

Hi @ibnzterrell,

that’s what I was afraid off, I was trying to get custom build working, but I wasn’t successful yet.

We too are on a tight schedule, so if this doesn’t get addressed soon, my plan is to use this implementation to start a Custom/Build and make it work like Realtime.
Downside of this approach is setting the server up, setting up ports, writing install scripts to install node on the server and other stuff and then make it all run. This has a bit more boilerplate because Realtime takes care of most of this stuff for you.

I hope this will make matchmaking work and to me, it seems like a more scalable solution than what you propose, using SNS you might lose some scalability but I’m not sure tbh.

1 Like

Thanks for bringing this to our attention. It looks like this is actually an issue with amazon linux 2, not RTS – it just happens that RTS always uses AL2… In any event, we have someone working on a fix and we will update this thread once that is available everywhere.

2 Likes

The issue has been fixed for all regions

2 Likes

I’ve confirmed that it is now working, thanks for the help!

Yes, it’s working now, thank you.