Przeglądaj źródła

fix: pin COM/WASAPI goroutines to OS thread

runtime.LockOSThread/UnlockOSThread added to withEndpointVolume (volume)
and Capturer.run (viz). COM apartments are thread-affine on Windows;
without the lock the Go scheduler can migrate a goroutine mid-call to a
thread that never called CoInitializeEx, causing sporadic failures.

Also check CoInitializeEx HRESULT: S_OK (0) and S_FALSE (1) are success,
anything else is a real error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
Jan Svabenik 1 miesiąc temu
rodzic
commit
69a810998f
2 zmienionych plików z 26 dodań i 3 usunięć
  1. +10
    -1
      internal/viz/capture.go
  2. +16
    -2
      internal/volume/volume.go

+ 10
- 1
internal/viz/capture.go Wyświetl plik

@@ -10,6 +10,7 @@ import (
"log"
"math"
"math/cmplx"
"runtime"
"syscall"
"time"
"unsafe"
@@ -137,7 +138,15 @@ func (c *Capturer) Start(ctx context.Context) {
}

func (c *Capturer) run(ctx context.Context) error {
coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED
// Pin this goroutine to its OS thread — WASAPI COM objects are
// thread-affine; the scheduler must not migrate us mid-call.
runtime.LockOSThread()
defer runtime.UnlockOSThread()

hr, _, _ := coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED
if hr > 1 { // S_OK=0, S_FALSE=1 are both success
return fmt.Errorf("CoInitializeEx: HRESULT 0x%08X", hr)
}
defer coUninitialize.Call()

// ── IMMDeviceEnumerator ──────────────────────────────────────────────────


+ 16
- 2
internal/volume/volume.go Wyświetl plik

@@ -7,6 +7,7 @@ package volume

import (
"fmt"
"runtime"
"syscall"
"unsafe"

@@ -91,13 +92,26 @@ var (

// withEndpointVolume acquires an IAudioEndpointVolume for the default render
// device, calls fn with its pointer, and releases all COM objects.
//
// COM apartments are thread-affine on Windows. LockOSThread pins this
// goroutine to its current OS thread for the duration of the call so that
// every COM call runs on the thread that initialized COM.
func withEndpointVolume(fn func(vol uintptr) error) error {
coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED
runtime.LockOSThread()
defer runtime.UnlockOSThread()

// CoInitializeEx returns S_OK (0) on first init, S_FALSE (1) if already
// initialized on this thread. Both are success. Anything ≥ 2 is an error
// (HRESULT error codes are 0x8xxxxxxx, always > 1 as unsigned).
hr, _, _ := coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED
if hr > 1 {
return fmt.Errorf("CoInitializeEx: HRESULT 0x%08X", hr)
}
defer coUninitialize.Call()

// CoCreateInstance(CLSID_MMDeviceEnumerator) → IMMDeviceEnumerator
var enumerator uintptr
hr, _, _ := coCreateInstance.Call(
hr, _, _ = coCreateInstance.Call(
uintptr(unsafe.Pointer(&clsidMMDeviceEnumerator)),
0,
clsctxAll,


Ładowanie…
Anuluj
Zapisz