Skip to content

runtime: a Windows application launched via Steam sometimes freezes #71242

@hajimehoshi

Description

@hajimehoshi

Go version

go version go1.23.2 windows/amd64

Output of go env in your module/workspace:

set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\hajimehoshi\AppData\Local\go-build
set GOENV=C:\Users\hajimehoshi\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\hajimehoshi\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\hajimehoshi\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.23.2
set GODEBUG=
set GOTELEMETRY=local
set GOTELEMETRYDIR=C:\Users\hajimehoshi\AppData\Roaming\go\telemetry
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=C:\Users\hajimehoshi\ebiten\go.mod
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\HAJIME~1\AppData\Local\Temp\go-build1125585036=/tmp/go-build -gno-record-gcc-switches

What did you do?

Compile this Go program to an execute file (with -ldflags="-H=windowsgui")

EDIT: I could minized the case further. See #71242 (comment)

package main

import (
	"log"
	"os"
	"runtime"
	"runtime/debug"
	"syscall"
	"time"
	"unsafe"
)

const (
	WS_OVERLAPPEDWINDOW = 0x00000000 | 0x00C00000 | 0x00080000 | 0x00040000 | 0x00020000 | 0x00010000
	CW_USEDEFAULT       = ^0x7fffffff
	SW_SHOW             = 5
	WM_DESTROY          = 2
)

type (
	ATOM      uint16
	HANDLE    uintptr
	HINSTANCE HANDLE
	HICON     HANDLE
	HCURSOR   HANDLE
	HBRUSH    HANDLE
	HWND      HANDLE
	HMENU     HANDLE
)

type WNDCLASSEX struct {
	Size       uint32
	Style      uint32
	WndProc    uintptr
	ClsExtra   int32
	WndExtra   int32
	Instance   HINSTANCE
	Icon       HICON
	Cursor     HCURSOR
	Background HBRUSH
	MenuName   *uint16
	ClassName  *uint16
	IconSm     HICON
}

type RECT struct {
	Left, Top, Right, Bottom int32
}

type POINT struct {
	X, Y int32
}

type MSG struct {
	Hwnd    HWND
	Message uint32
	WParam  uintptr
	LParam  uintptr
	Time    uint32
	Pt      POINT
}

func GetModuleHandle(modulename *uint16) HINSTANCE {
	r, _, _ := syscall.SyscallN(procGetModuleHandle.Addr(), uintptr(unsafe.Pointer(modulename)))
	return HINSTANCE(r)
}

func RegisterClassEx(w *WNDCLASSEX) ATOM {
	r, _, _ := syscall.SyscallN(procRegisterClassEx.Addr(), uintptr(unsafe.Pointer(w)))
	return ATOM(r)
}

func CreateWindowEx(exStyle uint, className, windowName *uint16,
	style uint, x, y, width, height int, parent HWND, menu HMENU,
	instance HINSTANCE, param unsafe.Pointer) HWND {
	r, _, _ := syscall.SyscallN(procCreateWindowEx.Addr(), uintptr(exStyle), uintptr(unsafe.Pointer(className)),
		uintptr(unsafe.Pointer(windowName)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height),
		uintptr(parent), uintptr(menu), uintptr(instance), uintptr(param))
	return HWND(r)
}

func AdjustWindowRect(rect *RECT, style uint, menu bool) bool {
	var iMenu uintptr
	if menu {
		iMenu = 1
	}
	r, _, _ := syscall.SyscallN(procAdjustWindowRect.Addr(), uintptr(unsafe.Pointer(rect)), uintptr(style), iMenu)
	return r != 0
}

func ShowWindow(hwnd HWND, cmdshow int) bool {
	r, _, _ := syscall.SyscallN(procShowWindow.Addr(), uintptr(hwnd), uintptr(cmdshow))
	return r != 0
}

func GetMessage(msg *MSG, hwnd HWND, msgFilterMin, msgFilterMax uint32) int {
	r, _, _ := syscall.SyscallN(procGetMessage.Addr(), uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgFilterMin), uintptr(msgFilterMax))
	return int(r)
}

func TranslateMessage(msg *MSG) bool {
	r, _, _ := syscall.SyscallN(procTranslateMessage.Addr(), uintptr(unsafe.Pointer(msg)))
	return r != 0
}

func DispatchMessage(msg *MSG) uintptr {
	r, _, _ := syscall.SyscallN(procDispatchMessage.Addr(), uintptr(unsafe.Pointer(msg)))
	return r
}

func DefWindowProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr {
	r, _, _ := syscall.SyscallN(procDefWindowProc.Addr(), uintptr(hwnd), uintptr(msg), wParam, lParam)
	return r
}

func PostQuitMessage(exitCode int) {
	syscall.SyscallN(procPostQuitMessage.Addr(), uintptr(exitCode))
}

var (
	kernel32            = syscall.NewLazyDLL("kernel32.dll")
	procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")

	user32               = syscall.NewLazyDLL("user32.dll")
	procRegisterClassEx  = user32.NewProc("RegisterClassExW")
	procCreateWindowEx   = user32.NewProc("CreateWindowExW")
	procAdjustWindowRect = user32.NewProc("AdjustWindowRect")
	procShowWindow       = user32.NewProc("ShowWindow")
	procGetMessage       = user32.NewProc("GetMessageW")
	procTranslateMessage = user32.NewProc("TranslateMessage")
	procDispatchMessage  = user32.NewProc("DispatchMessageW")
	procDefWindowProc    = user32.NewProc("DefWindowProcW")
	procPostQuitMessage  = user32.NewProc("PostQuitMessage")
)

func init() {
	runtime.LockOSThread()
}

func main() {
	className, err := syscall.UTF16PtrFromString("Sample Window Class")
	if err != nil {
		panic(err)
	}
	inst := GetModuleHandle(className)

	wc := WNDCLASSEX{
		Size:      uint32(unsafe.Sizeof(WNDCLASSEX{})),
		WndProc:   syscall.NewCallback(wndProc),
		Instance:  inst,
		ClassName: className,
	}

	RegisterClassEx(&wc)

	wr := RECT{
		Left:   0,
		Top:    0,
		Right:  320,
		Bottom: 240,
	}
	title, err := syscall.UTF16PtrFromString("My Title")
	if err != nil {
		panic(err)
	}
	AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, false)
	hwnd := CreateWindowEx(
		0, className,
		title,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, int(wr.Right-wr.Left), int(wr.Bottom-wr.Top),
		0, 0, inst, nil,
	)
	if hwnd == 0 {
		panic(syscall.GetLastError())
	}

	ShowWindow(hwnd, SW_SHOW)

	go func() {
		for {
			_ = make([]byte, 256*1024)
			time.Sleep(time.Millisecond)
		}
	}()

	go func() {
		f, err := os.Create("log.txt")
		if err != nil {
			panic(err)
		}
		defer f.Close()

		log.SetOutput(f)

		for {
			time.Sleep(time.Second)
			var gcStats debug.GCStats
			debug.ReadGCStats(&gcStats)
			log.Printf("LastGC: %s, NumGC: %d, PauseTotal: %s", gcStats.LastGC, gcStats.NumGC, gcStats.PauseTotal)
			if err := f.Sync(); err != nil {
				panic(err)
			}
		}
	}()

	var msg MSG
	for GetMessage(&msg, 0, 0, 0) != 0 {
		TranslateMessage(&msg)
		DispatchMessage(&msg)
	}
}

func wndProc(hwnd HWND, msg uint32, wparam, lparam uintptr) uintptr {
	switch msg {
	case WM_DESTROY:
		PostQuitMessage(0)
	}
	return DefWindowProc(hwnd, msg, wparam, lparam)
}

Replace an exe file in a Steam game with the compiled exe file, and run it via Steam client.

What did you see happen?

The application sometimes freezes for more than 10 seconds. For example, I saw this log

2025/01/13 23:23:41 LastGC: 2025-01-13 23:23:41.6060533 +0900 JST, NumGC: 51, PauseTotal: 1.3186ms
2025/01/13 23:23:42 LastGC: 2025-01-13 23:23:42.6470915 +0900 JST, NumGC: 102, PauseTotal: 4.5945ms
2025/01/13 23:23:43 LastGC: 2025-01-13 23:23:43.6434896 +0900 JST, NumGC: 152, PauseTotal: 7.8752ms
2025/01/13 23:23:44 LastGC: 2025-01-13 23:23:44.6481645 +0900 JST, NumGC: 204, PauseTotal: 10.224ms
2025/01/13 23:23:45 LastGC: 2025-01-13 23:23:45.6448025 +0900 JST, NumGC: 255, PauseTotal: 12.5067ms
2025/01/13 23:23:46 LastGC: 2025-01-13 23:23:46.6584686 +0900 JST, NumGC: 309, PauseTotal: 14.2881ms
2025/01/13 23:23:47 LastGC: 2025-01-13 23:23:47.6550747 +0900 JST, NumGC: 362, PauseTotal: 17.056ms
2025/01/13 23:23:48 LastGC: 2025-01-13 23:23:48.6681264 +0900 JST, NumGC: 413, PauseTotal: 18.5012ms
2025/01/13 23:23:49 LastGC: 2025-01-13 23:23:49.6577372 +0900 JST, NumGC: 464, PauseTotal: 21.2877ms
2025/01/13 23:23:50 LastGC: 2025-01-13 23:23:50.6675317 +0900 JST, NumGC: 514, PauseTotal: 24.9508ms
2025/01/13 23:23:51 LastGC: 2025-01-13 23:23:51.6642908 +0900 JST, NumGC: 563, PauseTotal: 27.2671ms
2025/01/13 23:23:52 LastGC: 2025-01-13 23:23:52.6696781 +0900 JST, NumGC: 612, PauseTotal: 29.6692ms
2025/01/13 23:23:53 LastGC: 2025-01-13 23:23:53.6818947 +0900 JST, NumGC: 661, PauseTotal: 31.9968ms
2025/01/13 23:23:54 LastGC: 2025-01-13 23:23:54.6830572 +0900 JST, NumGC: 711, PauseTotal: 34.9958ms
2025/01/13 23:23:55 LastGC: 2025-01-13 23:23:55.6889185 +0900 JST, NumGC: 761, PauseTotal: 38.2468ms
2025/01/13 23:23:56 LastGC: 2025-01-13 23:23:56.6869067 +0900 JST, NumGC: 813, PauseTotal: 41.5747ms
2025/01/13 23:23:57 LastGC: 2025-01-13 23:23:57.6920325 +0900 JST, NumGC: 863, PauseTotal: 45.5415ms
2025/01/13 23:24:18 LastGC: 2025-01-13 23:23:58.2810119 +0900 JST, NumGC: 894, PauseTotal: 47.2387ms
2025/01/13 23:24:19 LastGC: 2025-01-13 23:24:19.3442472 +0900 JST, NumGC: 945, PauseTotal: 51.3869ms
2025/01/13 23:24:20 LastGC: 2025-01-13 23:24:20.3460036 +0900 JST, NumGC: 995, PauseTotal: 54.0004ms
2025/01/13 23:24:21 LastGC: 2025-01-13 23:24:21.3371656 +0900 JST, NumGC: 1047, PauseTotal: 55.3437ms
2025/01/13 23:24:22 LastGC: 2025-01-13 23:24:22.344327 +0900 JST, NumGC: 1098, PauseTotal: 56.9757ms
2025/01/13 23:24:23 LastGC: 2025-01-13 23:24:23.3523815 +0900 JST, NumGC: 1147, PauseTotal: 61.8229ms
2025/01/13 23:24:24 LastGC: 2025-01-13 23:24:24.3560493 +0900 JST, NumGC: 1200, PauseTotal: 64.7476ms
2025/01/13 23:24:25 LastGC: 2025-01-13 23:24:25.3552534 +0900 JST, NumGC: 1250, PauseTotal: 67.5551ms

You can see a freeze happens between 22:23:57 and 22:24:18.

What did you expect to see?

The application doesn't freeze.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.OS-Windowscompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions