Something went wrong. Try again.
Backend environment for match hosting for lance.blue
Something went wrong. Try again.
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203#!/usr/bin/env bash# Materialise the Suramadu config, the Jetty properties and MegaMek's client# settings for this match.## All three are rendered at launch rather than baked into the image because each# carries something the image cannot know: the player name that the Swing client# logs in as, and the ports. Everything else in them is fixed and lives in# config/*.template under review.## MegaMek rewrites clientsettings.xml on exit, so seeding it once at image build# would not survive; it is re-seeded per launch.set -euo pipefail# shellcheck disable=SC2034 # read by common.sh's log helpersARENA_LOG_TAG=init/config# shellcheck source=../lib/common.sh. "${ARENA_HOME:-/opt/arena}/container/lib/common.sh"
# shellcheck source=/dev/null. "$ARENA_RUN/players.env"
MM_HOME="${MM_HOME:-$ARENA_HOME/megamek}"JAVA="${JAVA:-$ARENA_HOME/jre/bin/java}"CLIENT_HEAP="${ARENA_CLIENT_HEAP:-2G}"
# The paint dispatcher tick. A change waits for the next one, so it is worth# about a tick and a half of latency per repaint - the largest server-side term# in a warm input-to-paint once the board has been sent. 33 was upstream's, 16# is measured better (see PERFORMANCE.md); this makes going lower an experiment# rather than a rebuild.DRAW_DELAY_MS="${ARENA_DRAW_DELAY_MS:-16}"
# Anything else to put on the client JVM, for an experiment that would# otherwise want a rebuild to change one system property. Appended last so it# can override what is set above. Empty in every ordinary match.CLIENT_VMARGS="${ARENA_CLIENT_VMARGS:-}"case "$DRAW_DELAY_MS" in ''|*[!0-9]*) die "ARENA_DRAW_DELAY_MS must be a whole number of milliseconds, got '$DRAW_DELAY_MS'" ;;esac
mkdir -p "$ARENA_RUN/suramadu"
# Served by Suramadu in place of the war's own index.html; see# suramadu/web/ in the repo.## Copied into the run directory rather than served from the image, because one# file in it is written while the match is on: container/watch/results.sh drops# the result there the instant the game is decided, and suramadu/web/report.js# fetches it from the page. The image tree is owned by root and this runs as# uid 10001, so the copy is what makes that directory writable at all.## The alternative was for the page to wait on the result reaching S3 and# headquarters serving it back, which is a good five seconds of network for a# document that is already sitting in this container.WEB_FOLDER="${WEB_FOLDER:-$ARENA_WEB}"if [ "$WEB_FOLDER" = "$ARENA_WEB" ]; then if [ -d "$ARENA_HOME/web" ]; then rm -rf "$ARENA_WEB" cp -r "$ARENA_HOME/web" "$ARENA_WEB" log "web folder staged at $ARENA_WEB ($(find "$ARENA_WEB" -type f | wc -l) files)" else # A checkout that has not assembled an image tree. The war's own page is # served instead, which loses the theme and the report - but a config that # points at a directory with no index.html in it serves nothing at all. warn "no web folder at $ARENA_HOME/web; serving the war's own page" WEB_FOLDER="$ARENA_HOME/web" fifi
# --- the profiling agent, if this match is being profiled ---------------------# The client JVM is the one Suramadu spawns, so the only way to put a flag on it# is through vmArgs here. Empty unless ARENA_PROFILE is set to something other# than 0, and it substitutes to nothing, so an unprofiled match renders the# config it rendered before this existed.## Wall clock, not CPU: the question the client JVM has to answer is where the# EDT's *latency* goes, and a blocked EDT costs a player exactly as much as a# busy one. CPU time on the host JVM is a different question; see# container/entrypoint.sh.## DebugNonSafepoints makes the JIT keep debug info at non-safepoint PCs, without# which inlined frames are attributed to whatever method they were inlined into# - the difference between "paintComponent" and "PNG encode inside# paintComponent", which is the whole question.## The lazy-png-agent javaagent defers Suramadu's ImageConst PNG encode to first# use of toMessage() rather than doing it unconditionally in the constructor.# See instrument/lazy-png-agent/.## On by default now, and it was off for a while: it rewrites a third-party class# at classload, on the path that encodes every frame a player sees, and nothing# had measured it under play. The A/B that earns it back has been run - same# build, same scenario, one human seat, only ARENA_LAZY_PNG differing:## png/deflate samples on the EDT 737 -> 0# EDT samples running 1648 -> 740# first interaction of a match, p50 361ms -> 49ms## The encode does not move off the thread, it stops happening: the constructor# encoded every image unconditionally and DrawConstantPool then threw the result# away whenever the hash already had one. Deferring to toMessage() means a cache# hit never encodes at all, and with ddImageCacheSize at 16384 nearly every# image is a hit. That also removes most of a match's warm-up cliff, which is# the part of the latency a player meets first.## ARENA_LAZY_PNG=0 turns it off, for bisecting a rendering fault against stock# Suramadu behaviour. The Dockerfile runs HarnessMain against the real# ImageConst every build, so "it still applies and still defers" is checked# rather than assumed.LAZY_PNG="${ARENA_LAZY_PNG:-1}"PROFILE_VMARGS=""if [ "$LAZY_PNG" != "0" ]; then LAZY_PNG_AGENT="$ARENA_HOME/instrument/lazy-png-agent.jar" [ -r "$LAZY_PNG_AGENT" ] || die "lazy-png-agent.jar missing at $LAZY_PNG_AGENT (ARENA_LAZY_PNG=0 runs without it)" PROFILE_VMARGS=" -javaagent:$LAZY_PNG_AGENT" log "lazy-png-agent on: ImageConst PNG encode deferred to toMessage()"else log "lazy-png-agent off: ImageConst encodes in its constructor, as upstream does"fiif [ -n "${ARENA_PROFILE:-}" ] && [ "$ARENA_PROFILE" != "0" ]; then AP_SO="$ARENA_HOME/async-profiler/lib/libasyncProfiler.so" AP_EVENT="${ARENA_PROFILE_CLIENT_EVENT:-wall}" AP_INTERVAL="${ARENA_PROFILE_INTERVAL:-10ms}" AP_OUT="$ARENA_STATE/profile-client.collapsed" # A missing agent would make the client JVM refuse to start at all, which # reads as "the match never loaded". Say why here instead. [ -r "$AP_SO" ] || die "ARENA_PROFILE is set but $AP_SO is missing" PROFILE_VMARGS="$PROFILE_VMARGS -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints" PROFILE_VMARGS="$PROFILE_VMARGS -agentpath:$AP_SO=start,event=$AP_EVENT,interval=$AP_INTERVAL,memlimit=128m,collapsed,file=$AP_OUT" log "profiling the client JVM: event=$AP_EVENT interval=$AP_INTERVAL -> $AP_OUT"fi
[ -n "$CLIENT_VMARGS" ] && PROFILE_VMARGS="$PROFILE_VMARGS $CLIENT_VMARGS" \ && log "extra client vmArgs: $CLIENT_VMARGS"
sed -e "s|@PROFILE_VMARGS@|$PROFILE_VMARGS|g" \ -e "s|@WEB_FOLDER@|$WEB_FOLDER|g" \ -e "s|@ARENA_HOME@|$ARENA_HOME|g" \ -e "s|@MM_HOME@|$MM_HOME|g" \ -e "s|@JAVA@|$JAVA|g" \ -e "s|@PORT_MM@|$PORT_MM|g" \ -e "s|@PLAYER_NAME@|$ARENA_CLIENT_SLOT|g" \ -e "s|@CLIENT_HEAP@|$CLIENT_HEAP|g" \ -e "s|@DRAW_DELAY_MS@|$DRAW_DELAY_MS|g" \ "$ARENA_HOME/config/suramadu.config.template" > "$ARENA_RUN/suramadu/suramadu.config"
# One Suramadu app per line of clients.tsv, at /<did> - a single root-level# segment, because that is a hard requirement: Suramadu serves HTTP by# longest prefix, so a nested path renders its page fine, but its WebSocket# routing only resolves single-segment app paths. /play/<did> answered the# page and 404'd the socket, which the browser reports as a lost connection# (verified against the live war: /play/x/async/swing-bin 404s,# /did:plc:x/async/swing-bin upgrades).## Each app is cloned from the rendered template app with only the path, the# display name and the login changed - then the template is deleted, so the# per-identity paths are the only client paths the container serves. Cloned# with jq rather than kept as templates, so no seat's client can drift from# another. The DID as the path is what lets headquarters' proxy match the# caller's identity to the client - a string comparison, no lookup - and# refuse it every other seat's.CFG="$ARENA_RUN/suramadu/suramadu.config"require_cmd jq# A missing list is a broken init, not an empty match; say which file rather# than dying on a redirect.[ -f "$ARENA_RUN/clients.tsv" ] || die "clients.tsv missing; 20-identity.sh did not run"while IFS="$(printf '\t')" read -r key display; do [ -n "$key" ] || continue jq --arg key "$key" --arg name "$display" ' .["/" + $key] = (.["/megamek"] | .path = "/" + $key | .name = "MegaMek " + $name | .swingConfig.launcherConfig.args |= sub(" -player_name .*$"; " -player_name " + $name)) ' "$CFG" > "$CFG.tmp" mv "$CFG.tmp" "$CFG"done < "$ARENA_RUN/clients.tsv"# A match with no client lines would be a container nobody can reach; the# template app dying with a loud line beats one that quietly serves the# wrong login.jq -e 'keys | map(select(. != "/" and . != "/megamek")) | length > 0' "$CFG" >/dev/null \ || die "no client path rendered; clients.tsv was empty"jq 'del(."/megamek")' "$CFG" > "$CFG.tmp"mv "$CFG.tmp" "$CFG"
sed -e "s|@PORT_WS@|$PORT_WS|g" \ "$ARENA_HOME/config/jetty.properties.template" > "$ARENA_RUN/suramadu/jetty.properties"
cp "$ARENA_HOME/config/clientsettings.xml.template" "$MM_HOME/mmconf/clientsettings.xml"# The rules half of the same pair, read by the host rather than the client:# ScenarioV1 loads mmconf/gameoptions.xml for any scenario that does not name a# GameOptionsFile of its own. Re-seeded per launch for the same reason# clientsettings.xml is - a player who opens the game options dialog and# presses OK makes the client write this file back.cp "$ARENA_HOME/config/gameoptions.xml.template" "$MM_HOME/mmconf/gameoptions.xml"
# A leftover placeholder means the client would try to log in as "@PLAYER_NAME@"# and sit in the lobby forever. Easy to catch here, hard to diagnose live.if grep -q '@[A-Z_]*@' "$ARENA_RUN/suramadu/suramadu.config" "$ARENA_RUN/suramadu/jetty.properties"; then die "unsubstituted placeholder left in the rendered config"fi
log "rendered suramadu config (clients: $(paste -sd' ' < <(awk -F'\t' '{ print "/" $1 "->" $2 }' "$ARENA_RUN/clients.tsv")); web :$PORT_WS, game :$PORT_MM)"