• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

C++ Players can't connect in the server 1k+

Status
Not open for further replies.

Sigoles

Discord: @sigoles
Joined
Nov 20, 2015
Messages
1,200
Solutions
2
Reaction score
149
Our server is hitting 1000 players online and after that gets the famous waiting list (When we have 1k online, no one can get in anymore), the question is:

Why does this occur? Since my setup is set to 3000 players.
maxPlayers = "5000"

error on screen:
LUA:
[ServicePort:open] Error: open: Too many open files

Engine: tfs 1.2

Look how it appears:
OvhejKaiQjqYeLgO3Nf3uQ.png

after
3mTy_--1QlWjIeQ56Rz8OA.png


I had tried to put 0, without quotes, to stay without limit and we gave reload, continued the same thing and in addition the server seemed to be offline in otservlist.

PS. I tried limiting to 800 in config.lua and it works, no one logs after 800 online, then the problem is after 1000 online. Maybe it could be host limit?

Our waitinglist.cpp WaitingList::clientLogin
C++:
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2016  Mark Samman <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "otpch.h"

#include "configmanager.h"
#include "game.h"
#include "waitlist.h"

extern ConfigManager g_config;
extern Game g_game;

WaitListIterator WaitingList::findClient(const Player* player, uint32_t& slot)
{
    slot = 1;
    for (WaitListIterator it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it) {
        if (it->playerGUID == player->getGUID()) {
            return it;
        }
        ++slot;
    }

    for (WaitListIterator it = waitList.begin(), end = waitList.end(); it != end; ++it) {
        if (it->playerGUID == player->getGUID()) {
            return it;
        }
        ++slot;
    }
    return waitList.end();
}

uint32_t WaitingList::getTime(uint32_t slot)
{
    if (slot < 5) {
        return 5;
    } else if (slot < 10) {
        return 10;
    } else if (slot < 20) {
        return 20;
    } else if (slot < 50) {
        return 60;
    } else {
        return 120;
    }
}

uint32_t WaitingList::getTimeout(uint32_t slot)
{
    //timeout is set to 15 seconds longer than expected retry attempt
    return getTime(slot) + 15;
}

bool WaitingList::clientLogin(const Player* player)
{
    if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) {
        return true;
    }

    uint32_t maxPlayers = static_cast<uint32_t>(g_config.getNumber(ConfigManager::MAX_PLAYERS));
    if (maxPlayers == 0 || (priorityWaitList.empty() && waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) {
        return true;
    }

    WaitingList::cleanupList(priorityWaitList);
    WaitingList::cleanupList(waitList);

    uint32_t slot;

    WaitListIterator it = findClient(player, slot);
    if (it != waitList.end()) {
        if ((g_game.getPlayersOnline() + slot) <= maxPlayers) {
            //should be able to login now
            waitList.erase(it);
            return true;
        }

        //let them wait a bit longer
        it->timeout = OTSYS_TIME() + (getTimeout(slot) * 1000);
        return false;
    }

    slot = priorityWaitList.size();
    if (player->isPremium()) {
        priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID());
    } else {
        slot += waitList.size();
        waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID());
    }
    return false;
}

uint32_t WaitingList::getClientSlot(const Player* player)
{
    uint32_t slot;
    WaitListIterator it = findClient(player, slot);
    if (it == waitList.end()) {
        return 0;
    }
    return slot;
}

void WaitingList::cleanupList(WaitList& list)
{
    int64_t time = OTSYS_TIME();

    WaitListIterator it = list.begin(), end = list.end();
    while (it != end) {
        if ((it->timeout - time) <= 0) {
            it = list.erase(it);
        } else {
            ++it;
        }
    }
}
 
Last edited by a moderator:
Solution
@Impera-Global
Make sure you apply ulimit to the running process. It may not always apply to the TFS depending how you run it. Safest bet is to increase all limits in /etc/security/limits.conf, then reboot and start the server.
Such a luxuriously issue, not many devs have the opportunity to reproduce it. :p

Could you locate the code that is calling WaitingList::clientLogin?
Should be somewhere in protocolgame.cpp. A temporarily solution would be to just screw waiting list and accept all connections.

void ProtocolGame::login

If you see something like this, just delete it and recompile the server:
C++:
WaitingList& waitingList = WaitingList::getInstance();
if (!waitingList.clientLogin(player)) {
    uint32_t currentSlot = waitingList.getClientSlot(player);
    uint32_t retryTime = WaitingList::getTime(currentSlot);
    std::ostringstream ss;

    ss << "Too many players online.\nYou are at place "
       << currentSlot << " on the waiting list.";

    auto output = OutputMessagePool::getOutputMessage();
    output->addByte(0x16);
    output->addString(ss.str());
    output->addByte(retryTime);
    send(output);
    disconnect();
    return;
}
 
Last edited:
Such a luxuriously issue, not many devs have the opportunity to reproduce it. :p

Could you locate the code that is calling WaitingList::clientLogin?
Should be somewhere in protocolgame.cpp. A temporarily solution would be to just screw waiting list and accept all connections.
void ProtocolGame::login

:DIts so wired, I even opened a ticket for the host company to see if there are connection limits ... My WaitingList::clientLogin is in my waitinglist.cpp (file I quote above).

In my ProtocolGame::login (protocolgame) we have:
C++:
void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem)
{
    //dispatcher thread
    Player* _player = g_game.getPlayerByName(name);
    if (!_player || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
        player = new Player(getThis());
        player->setName(name);

        player->incrementReferenceCounter();
        player->setID();

        if (!IOLoginData::preloadPlayer(player, name)) {
            disconnectClient("Your character could not be loaded.");
            return;
        }

        if (IOBan::isPlayerNamelocked(player->getGUID())) {
            disconnectClient("Your character has been namelocked.");
            return;
        }

        if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
            disconnectClient("The game is just going down.\nPlease try again later.");
            return;
        }

        if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
            disconnectClient("Server is currently closed.\nPlease try again later.");
            return;
        }

        if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) {
            disconnectClient("You may only login with one character\nof your account at the same time.");
            return;
        }

        if (!player->hasFlag(PlayerFlag_CannotBeBanned)) {
            BanInfo banInfo;
            if (IOBan::isAccountBanned(accountId, banInfo)) {
                if (banInfo.reason.empty()) {
                    banInfo.reason = "(none)";
                }

                std::ostringstream ss;
                if (banInfo.expiresAt > 0) {
                    ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
                } else {
                    ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
                }
                disconnectClient(ss.str());
                return;
            }
        }

        if (!WaitingList::getInstance()->clientLogin(player)) {
            uint32_t currentSlot = WaitingList::getInstance()->getClientSlot(player);
            uint32_t retryTime = WaitingList::getTime(currentSlot);
            std::ostringstream ss;

            ss << "Too many players online.\nYou are at place "
               << currentSlot << " on the waiting list.";

            auto output = OutputMessagePool::getOutputMessage();
            output->addByte(0x16);
            output->addString(ss.str());
            output->addByte(retryTime);
            send(output);
            disconnect();
            return;
        }

        if (!IOLoginData::loadPlayerByName(player, name)) {
            disconnectClient("Your character could not be loaded.");
            return;
        }

        player->setOperatingSystem(operatingSystem);

        if (!g_game.placeCreature(player, player->getLoginPosition())) {
            if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) {
                disconnectClient("Temple position is wrong. Contact the administrator.");
                return;
            }
        }

        if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
            player->registerCreatureEvent("ExtendedOpcode");
        }

        player->lastIP = player->getIP();
        player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
        m_acceptPackets = true;
    } else {
        if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) {
            //Already trying to connect
            disconnectClient("You are already logged in.");
            return;
        }

        if (_player->client) {
            _player->disconnect();
            _player->isConnecting = true;

            eventConnect = g_scheduler.addEvent(createSchedulerTask(1000, std::bind(&ProtocolGame::connect, getThis(), _player->getID(), operatingSystem)));
        } else {
            connect(_player->getID(), operatingSystem);
        }
    }
    OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this());
}

Do you think removing the waitinglist part could work? hmm
 
Yep.

Remove:
C++:
WaitingList& waitingList = WaitingList::getInstance();
if (!waitingList.clientLogin(player)) {
    uint32_t currentSlot = waitingList.getClientSlot(player);
    uint32_t retryTime = WaitingList::getTime(currentSlot);
    std::ostringstream ss;

    ss << "Too many players online.\nYou are at place "
       << currentSlot << " on the waiting list.";

    auto output = OutputMessagePool::getOutputMessage();
    output->addByte(0x16);
    output->addString(ss.str());
    output->addByte(retryTime);
    send(output);
    disconnect();
    return;
}

And recompile the server.
 
Last edited:
Yep.

Remove:
C++:
WaitingList& waitingList = WaitingList::getInstance();
if (!waitingList.clientLogin(player)) {
    uint32_t currentSlot = waitingList.getClientSlot(player);
    uint32_t retryTime = WaitingList::getTime(currentSlot);
    std::ostringstream ss;

    ss << "Too many players online.\nYou are at place "
       << currentSlot << " on the waiting list.";

    auto output = OutputMessagePool::getOutputMessage();
    output->addByte(0x16);
    output->addString(ss.str());
    output->addByte(retryTime);
    send(output);
    disconnect();
    return;
}

And recompile the server.

Yee, let's try it, tomorrow I come here to tell you if it worked <3

Look how it appears:
OvhejKaiQjqYeLgO3Nf3uQ.png


@Znote I updated the main thread with pictures

I verify and got an error when trying to log in o/
Code:
[ServicePort::open] Error: open: Too many open files
 
Last edited by a moderator:
Tested today and players can't login after 1000 onlines...
and I already did the limit to 3000: ulimit -n 3000

b5tNvb9sTrW9VcAGA1jlvg.png

0iDKSTseQ-ObNRRT5k0NgA.png


another idea about it? I think I'll remove the waiting list from sourcers
 
Last edited by a moderator:
@Impera-Global
Make sure you apply ulimit to the running process. It may not always apply to the TFS depending how you run it. Safest bet is to increase all limits in /etc/security/limits.conf, then reboot and start the server.
 
Solution
@Impera-Global
Make sure you apply ulimit to the running process. It may not always apply to the TFS depending how you run it. Safest bet is to increase all limits in /etc/security/limits.conf, then reboot and start the server.

There are setting nothing in this file, look:

Code:
# /etc/security/limits.conf
#
#Each line describes a limit for a user in the form:
#
#<domain>        <type>  <item>  <value>
#
#Where:
#<domain> can be:
#        - a user name
#        - a group name, with @group syntax
#        - the wildcard *, for default entry
#        - the wildcard %, can be also used with %group syntax,
#                 for maxlogin limit
#        - NOTE: group and wildcard limits are not applied to root.
#          To apply a limit to the root user, <domain> must be
#          the literal username root.
#
#<type> can have the two values:
#        - "soft" for enforcing the soft limits
#        - "hard" for enforcing hard limits
#
#<item> can be one of the following:
#        - core - limits the core file size (KB)
#        - data - max data size (KB)
#        - fsize - maximum filesize (KB)
#        - memlock - max locked-in-memory address space (KB)
#        - nofile - max number of open files
#        - rss - max resident set size (KB)
#        - stack - max stack size (KB)
#        - cpu - max CPU time (MIN)
#        - nproc - max number of processes
#        - as - address space limit (KB)
#        - maxlogins - max number of logins for this user
#        - maxsyslogins - max number of logins on the system
#        - priority - the priority to run user process with
#        - locks - max number of file locks the user can hold
#        - sigpending - max number of pending signals
#        - msgqueue - max memory used by POSIX message queues (bytes)
#        - nice - max nice priority allowed to raise to values: [-20, 19]
#        - rtprio - max realtime priority
#        - chroot - change root to directory (Debian-specific)
#
#<domain>      <type>  <item>         <value>
#

#*               soft    core            0
#root            hard    core            100000
#*               hard    rss             10000
#@student        hard    nproc           20
#@faculty        soft    nproc           20
#@faculty        hard    nproc           50
#ftp             hard    nproc           0
#ftp             -       chroot          /ftp
#@student        -       maxlogins       4

# End of file
 
Add
Code:
*User executing TFS* hard nofile 3000

Sample (if linux username is otsmanager)
Code:
otsmanager hard nofile 3000
 
Last edited by a moderator:
Status
Not open for further replies.

Similar threads

Back
Top