From 8994bdedc3bf8a3d7dcec40c9c4f7b0998eb87f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:13:41 +0000 Subject: [PATCH] fix: resolve AsyncLocalStorage + dir issues in custom server, update benchmark script Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com> --- apps/nextjs/package.json | 2 +- apps/nextjs/server.ts | 8 +- scripts/benchmark-memory.sh | 190 +++++++++++++++++++++--------------- 3 files changed, 120 insertions(+), 80 deletions(-) diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 35fa96033..a5d5a5994 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build": "pnpm with-env next build && pnpm build:custom-server", - "build:custom-server": "esbuild server.ts --bundle --platform=node --outfile=customServer.cjs --external:bcrypt --external:@opentelemetry/api --external:deasync --external:cpu-features --loader:.html=text --loader:.scss=text --loader:.node=text", + "build:custom-server": "esbuild server.ts --bundle --platform=node --outfile=customServer.cjs --external:next --external:bcrypt --external:@opentelemetry/api --external:deasync --external:cpu-features --loader:.html=text --loader:.scss=text --loader:.node=text", "clean": "git clean -xdf .next .turbo node_modules", "dev": "pnpm with-env tsx ./server.ts", "format": "prettier --check . --ignore-path ../../.gitignore", diff --git a/apps/nextjs/server.ts b/apps/nextjs/server.ts index 40ec83fac..4269e5771 100644 --- a/apps/nextjs/server.ts +++ b/apps/nextjs/server.ts @@ -1,4 +1,8 @@ +// Must be imported first to set up globalThis.AsyncLocalStorage for Next.js +import "next/dist/server/node-environment-baseline"; + import { createServer } from "http"; +import path from "path"; import next from "next"; import { parse } from "url"; import { applyWSSHandler } from "@trpc/server/adapters/ws"; @@ -15,8 +19,10 @@ const logger = createLogger({ module: "customServer" }); const dev = process.env.NODE_ENV !== "production"; const hostname = process.env.HOSTNAME || "localhost"; const port = parseInt(process.env.PORT || "3000", 10); +// Resolve the directory of this file so Next.js can find the app/pages dirs +const dir = path.resolve(__dirname); -const app = next({ dev, hostname, port }); +const app = next({ dev, hostname, port, dir }); const handle = app.getRequestHandler(); app.prepare().then(() => { diff --git a/scripts/benchmark-memory.sh b/scripts/benchmark-memory.sh index 6fea6c71b..8d421ca87 100755 --- a/scripts/benchmark-memory.sh +++ b/scripts/benchmark-memory.sh @@ -1,114 +1,148 @@ #!/usr/bin/env bash -# Benchmark memory usage of the WebSocket server approach -# Usage: ./scripts/benchmark-memory.sh [approach] -# approach: "separate" (current: separate ws server) or "integrated" (custom Next.js server) +# Benchmark memory usage: separate ws server vs integrated custom Next.js server +# Usage: ./scripts/benchmark-memory.sh [approach] [wait_seconds] +# approach: "separate" | "integrated" | "both" (default: both) set -e -APPROACH="${1:-separate}" -WAIT_SECONDS="${2:-10}" +APPROACH="${1:-both}" +WAIT_SECONDS="${2:-15}" -echo "============================================" -echo "Memory Benchmark: $APPROACH approach" -echo "============================================" -echo "" - -measure_memory() { - local pid=$1 +measure_process_tree_memory() { + local root_pid=$1 local label=$2 - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - local rss - rss=$(ps -o rss= -p "$pid" 2>/dev/null | tr -d ' ') - if [ -n "$rss" ]; then - local rss_mb - rss_mb=$(echo "scale=2; $rss / 1024" | bc) - echo "$label (PID $pid): ${rss_mb} MB (RSS: ${rss} KB)" - echo "$rss" - else - echo "$label (PID $pid): unable to read memory" - echo "0" - fi - else + local total_rss=0 + + if [ -z "$root_pid" ] || ! kill -0 "$root_pid" 2>/dev/null; then echo "$label: not running" - echo "0" + return fi + + # Measure root process + local root_rss + root_rss=$(grep VmRSS /proc/$root_pid/status 2>/dev/null | awk '{print $2}') + root_rss=${root_rss:-0} + total_rss=$root_rss + local root_mb + root_mb=$(echo "scale=2; $root_rss / 1024" | bc) + echo " $label (PID $root_pid): ${root_mb} MB" + + # Measure child processes recursively + for cpid in $(pgrep -P "$root_pid" 2>/dev/null); do + if kill -0 "$cpid" 2>/dev/null; then + local child_rss + child_rss=$(grep VmRSS /proc/$cpid/status 2>/dev/null | awk '{print $2}') + child_rss=${child_rss:-0} + total_rss=$((total_rss + child_rss)) + local child_mb + child_mb=$(echo "scale=2; $child_rss / 1024" | bc) + local child_cmd + child_cmd=$(cat /proc/$cpid/cmdline 2>/dev/null | tr '\0' ' ' | head -c 60) + echo " Child (PID $cpid): ${child_mb} MB - $child_cmd" + + # Grandchildren + for gpid in $(pgrep -P "$cpid" 2>/dev/null); do + if kill -0 "$gpid" 2>/dev/null; then + local gchild_rss + gchild_rss=$(grep VmRSS /proc/$gpid/status 2>/dev/null | awk '{print $2}') + gchild_rss=${gchild_rss:-0} + total_rss=$((total_rss + gchild_rss)) + local gchild_mb + gchild_mb=$(echo "scale=2; $gchild_rss / 1024" | bc) + local gchild_cmd + gchild_cmd=$(cat /proc/$gpid/cmdline 2>/dev/null | tr '\0' ' ' | head -c 60) + echo " Grandchild (PID $gpid): ${gchild_mb} MB - $gchild_cmd" + fi + done + fi + done + + local total_mb + total_mb=$(echo "scale=2; $total_rss / 1024" | bc) + echo " Subtotal: ${total_mb} MB" + # Export for caller + eval "${label}_TOTAL_RSS=$total_rss" } -cleanup() { - echo "" - echo "Cleaning up processes..." - for pid in "${PIDS[@]}"; do +cleanup_pids() { + for pid in "$@"; do if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true fi done wait 2>/dev/null || true - echo "Done." } -trap cleanup EXIT +run_separate() { + echo "============================================" + echo "SEPARATE APPROACH (Current: WS + Next.js)" + echo "============================================" -PIDS=() -TOTAL_RSS=0 - -if [ "$APPROACH" = "separate" ]; then - echo "Starting separate WebSocket server (current approach)..." - echo "---" - - # Start the WebSocket server + # Start WS server node apps/websocket/wssServer.cjs & - WSS_PID=$! - PIDS+=("$WSS_PID") + WS_PID=$! + sleep 5 - # Start the Next.js server - node apps/nextjs/server.js & - NEXTJS_PID=$! - PIDS+=("$NEXTJS_PID") + WS_LISTEN_PID=$(lsof -i :3001 -t 2>/dev/null | head -1) + echo "WS server listening PID: $WS_LISTEN_PID" - echo "Waiting ${WAIT_SECONDS}s for processes to stabilize..." - sleep "$WAIT_SECONDS" + WS_RSS=$(grep VmRSS /proc/${WS_LISTEN_PID}/status 2>/dev/null | awk '{print $2}') + WS_RSS=${WS_RSS:-0} + WS_MB=$(echo "scale=2; $WS_RSS / 1024" | bc) + echo " WebSocket Server: ${WS_MB} MB" echo "" - echo "--- Memory Usage (Separate Approach) ---" - wss_mem=$(measure_memory "$WSS_PID" "WebSocket Server" | tail -1) - nextjs_mem=$(measure_memory "$NEXTJS_PID" "Next.js Server" | tail -1) + echo "TOTAL (Separate WS only): ${WS_MB} MB" + echo "Note: Next.js dev server not included (would add ~1.5-2.5 GB)" + echo "============================================" - TOTAL_RSS=$((wss_mem + nextjs_mem)) - TOTAL_MB=$(echo "scale=2; $TOTAL_RSS / 1024" | bc) + cleanup_pids "$WS_PID" + SEPARATE_RSS=$WS_RSS +} - echo "" - echo "TOTAL: ${TOTAL_MB} MB (RSS: ${TOTAL_RSS} KB)" +run_integrated() { + echo "============================================" + echo "INTEGRATED APPROACH (Custom Next.js Server)" + echo "============================================" -elif [ "$APPROACH" = "integrated" ]; then - echo "Starting integrated custom Next.js server (new approach)..." - echo "---" - - # Start the custom server (Next.js + WebSocket integrated) - node apps/nextjs/customServer.cjs & + cd apps/nextjs + node customServer.cjs & CUSTOM_PID=$! - PIDS+=("$CUSTOM_PID") + cd ../.. - echo "Waiting ${WAIT_SECONDS}s for process to stabilize..." + echo "Waiting ${WAIT_SECONDS}s for server to stabilize..." sleep "$WAIT_SECONDS" + measure_process_tree_memory "$CUSTOM_PID" "CustomServer" echo "" - echo "--- Memory Usage (Integrated Approach) ---" - custom_mem=$(measure_memory "$CUSTOM_PID" "Custom Server (Next.js + WS)" | tail -1) + INTEGRATED_RSS=${CustomServer_TOTAL_RSS:-0} + INTEGRATED_MB=$(echo "scale=2; $INTEGRATED_RSS / 1024" | bc) + echo "TOTAL (Integrated): ${INTEGRATED_MB} MB" + echo "============================================" - TOTAL_RSS=$custom_mem - TOTAL_MB=$(echo "scale=2; $TOTAL_RSS / 1024" | bc) + cleanup_pids "$CUSTOM_PID" +} +if [ "$APPROACH" = "separate" ] || [ "$APPROACH" = "both" ]; then + run_separate echo "" - echo "TOTAL: ${TOTAL_MB} MB (RSS: ${TOTAL_RSS} KB)" - -else - echo "Unknown approach: $APPROACH" - echo "Usage: $0 [separate|integrated] [wait_seconds]" - exit 1 fi -echo "" -echo "============================================" -echo "Benchmark complete for: $APPROACH" -echo "Total memory: ${TOTAL_MB} MB" -echo "============================================" +if [ "$APPROACH" = "integrated" ] || [ "$APPROACH" = "both" ]; then + # Clean up lock from previous run + rm -rf apps/nextjs/.next/dev/lock + run_integrated + echo "" +fi + +if [ "$APPROACH" = "both" ]; then + echo "============================================" + echo "COMPARISON" + echo "============================================" + SEP_MB=$(echo "scale=2; ${SEPARATE_RSS:-0} / 1024" | bc) + INT_MB=$(echo "scale=2; ${INTEGRATED_RSS:-0} / 1024" | bc) + echo "Separate WS server only: ${SEP_MB} MB" + echo "Integrated custom server: ${INT_MB} MB" + echo "(The integrated server includes both Next.js AND WebSocket)" + echo "============================================" +fi