Linux 100% CPU Load on "Server Input" thread on Dedicated Server

perryflynn

Terrarian
Steam or GOG
Steam
Single Player/Multiplayer
Multi
Operating System
Linux Other
Terraria Version
1.4.4.9
Controls Used
Controller (please specify type)
Hello,

I just started the terraria dedicated server version "terraria-server-1449.zip" on a fully patched Debian 12 "Bookworm".

Startup command:

Bash:
/app/current/TerrariaServer.bin.x86_64 -players 50 \
    -motd "Brickburg" -port 7777 -autocreate 3 \
    -worldname brickburg01 \
    -world /home/swuser/.local/share/Terraria/Worlds/brickburg01.wld

It appears, that the "Server Input" Thread is always on 100% CPU load, even if there is no player connected.

1737250976274.png


Is this normal Behaviour?

Regards
 
Do you happen to have the standard input stream redirected in some way? The game uses this funny method to retrieve the user input from the console:
C#:
private static string ReadLineInput()
{
    string text = null;
    do
    {
        text = Console.ReadLine();
    }
    while (text == null);
    return text;
}
As you can tell, it will turn into a busy wait loop when Console.ReadLine permanently returns null, which it does in various situations when the input stream is redirected and has no more data available.
 
That was the cause. I spawn a Docker Container via Ansible and (usually it is not necessary) didn't set tty: true and interactive: true.

YAML:
- name: Manual Terraria Container
  docker_container:
    name: terraria
    hostname: terraria
    pull: true
    restart: false
    recreate: false
    detach: true
    tty: true
    interactive: true
    networks_cli_compatible: true
    restart_policy: unless-stopped
    state: started
    comparisons:
      networks: strict
    image: reg.git.brickburg.de/bbcontainers/hub/debian:bb-bookworm-slim
    command: >-
      /app/current/TerrariaServer.bin.x86_64 -players 50 -motd "Brickburg" -port 7777 -autocreate 3 -worldname brickburg01 -world /home/swuser/.local/share/Terraria/Worlds/brickburg01.wld
    networks:
      - name: "prj_{{project}}"
    ports:
      - "{{ip}}:7777:7777"
    volumes:
      - "{{databasedir}}/{{project}}-terraria/bin:/app"
      - "{{databasedir}}/{{project}}-terraria/data:/home/swuser/.local/share/Terraria"
    memory: 2048M
    cpus: 2

(This is just a default Debian container, Terraria Server is just unzipped to the volume, so that it is updatable without rebuilding docker images, lazyness solution, shall we say)

The equivalent with the docker CLI would be like so:

Bash:
docker run -it -d \
    --name terraria \
    -p 7777:7777 \
    -v /opt/docker-terraria/bin:/root/bin/terraria \
    -v /opt/docker-terraria/world:/root/.local/share/Terraria \
    --workdir /root/bin/terraria \
    --entrypoint ./TerrariaServer.bin.x86_64 \
    debian:buster-slim \
        -players 50 \
        -motd "LadL 2022" \
        -port 7777 \
        -autocreate 3 \
        -worldname myworld \
        -world /root/.local/share/Terraria/Worlds/myworld.wld

See the -it.

Now the input Thread is at 0% CPU.

TBH, I would expect that the game starts wo delay the loop after a few ReadLine() calls failed. Even a Thread.Sleep(2) (2 milliseconds, or so) would help in that case.

Thank you, @punchready 👍
 
TBH, I would expect that the game starts wo delay the loop after a few ReadLine() calls failed. Even a Thread.Sleep(2) (2 milliseconds, or so) would help in that case.
Yeah, the intended use of ReadLine is to stop asking for user input once it returns null for the first time. Maybe ReLogic will bless us with a fix.
 
Just worked a little bit more on that. I wanted to be able to send commands to the Terraria Server console from another processes. The following script is using a FIFO as STDIN for the Terraria Server process. It also implements traps to force a proper exit when Docker sends a SIGTERM or SIGINT to end the container main process. A SIGHUP allows it now (similar to nginx does it) to save the world manually.

Bash:
#!/bin/bash

# create console fifo
tercon=/tmp/terraria-console
mkfifo -m 600 $tercon

# setup signal traps
sigend() {
    >&2 echo "Server termination requested"
    echo "say Server is shutting down, bye" > $tercon
    sleep 2
    echo "exit" > $tercon
}

sigsave() {
    >&2 echo "World saving requested"
    echo "save" > $tercon
    echo "say World was saved triggered by SIGHUP signal" > $tercon
}

trap sigend SIGINT SIGTERM SIGQUIT
trap sigsave SIGHUP

# start terraria
tail -f /tmp/terraria-console | /app/current/TerrariaServer.bin.x86_64 \
    -noupnp -port 7777 \
    -players 5 -motd "Brickburg" \
    -autocreate 3 -worldname brickburg01 \
    -world /home/swuser/.local/share/Terraria/Worlds/brickburg01.wld &

tpid=$!

# event loop
while true
do

    if ps -p $tpid > /dev/null
    then
        sleep 0.1
    else
        >&2 echo "Server is not running anymore, abort."
        break
    fi

done

The script is used as the container command instead of the TerrariaServer command before.

  • docker stop terraria is sending a exit to the Terraria Console
  • docker kill -s SIGHUP terraria is sending a save to the Terraria Console
  • docker exec terraria /bin/bash -c "echo say hello > /tmp/terraria-console" is sending a command to the Terraria Console from the outside

This also makes the interactive/tty/-it options unnecessary.

Regards
 
Back
Top Bottom