Skip to content

fix(zapcore): ignore invalid stdio sync handles on Windows - #1595

Open
sb123sb123 wants to merge 1 commit into
uber-go:masterfrom
sb123sb123:codex/zap-991-windows-sync
Open

sb123sb123 wants to merge 1 commit into
uber-go:masterfrom
sb123sb123:codex/zap-991-windows-sync

Conversation

@sb123sb123

Copy link
Copy Markdown

Fixes #991

Rationale

On Windows, syncing a closed standard-output or standard-error handle returns an *fs.PathError wrapping ERROR_INVALID_HANDLE (syscall.Errno(6)). zapcore.Lock(os.Stdout) currently propagates that expected shutdown condition, even when other cores still need their real Sync calls.

This change wraps only the current os.Stdout and os.Stderr *os.File values when passed through zapcore.Lock, ignores only ERROR_INVALID_HANDLE from those wrappers, and preserves all other sync errors and regular-file behavior. Non-Windows builds keep the existing behavior.

Tests

Windows amd64, Go 1.26.8:

  • Raw reproduction: go.exe run zap-991-repro.go returned raw os.File.Sync after closing stdout handle: *fs.PathError: sync |1: The handle is invalid.
  • go.exe test -modfile=zap-991-test.mod ./zapcore -count=1 -run 'TestLock(StdioSyncIgnoresInvalidHandle|RegularFileSyncPropagatesInvalidHandle)$' -v — PASS; stdout, stderr, and regular-file subtests passed.
  • go.exe vet -modfile=zap-991-test.mod ./zapcore — PASS.
  • GOOS=linux GOARCH=amd64 go.exe test -modfile=zap-991-test.mod ./zapcore -run '^$' -c -o zap-991-linux.test — PASS.

The focused commands used a task-local temporary modfile and the task-provided module cache because direct access to proxy.golang.org timed out on this host.

Limitations

  • go.exe test -modfile=zap-991-test.mod ./zapcore -count=1 remains red on this Windows checkout only in TestIOCore, where testing.TempDir cannot remove a log file still held open by another process.
  • go.exe test -modfile=zap-991-test.mod ./... also retains the checkout's unrelated Windows failures in TestConfig, TestConfigWithSamplingHook, TestOpen, TestOpenOtherErrors, TestStacktraceFiltersVendorZap, Example_basicConfiguration, and zapcore.TestIOCore.

AI assistance: OpenAI Codex (GPT-5.6 Luna).

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@otrumb otrumb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows-specific error filtering is appropriately narrow for direct Lock(os.Stdout) and Lock(os.Stderr) calls: wrapped ERROR_INVALID_HANDLE is ignored, while ordinary files and unrelated errors still propagate.

However, the fix does not cover zap's documented high-level stdout/stderr path or composed write syncers. lockWriteSyncer only recognizes a direct *os.File, but Open("stdout") returns a nopCloserSink and CombineWriteSyncers passes the resulting wrapper or multiWriteSyncer into Lock.

On Windows with CGO_ENABLED=0, after reassigning os.Stdout to a pipe and closing its handle:

  • Open("stdout").Sync() still returns sync |1: The handle is invalid.
  • Lock(NewMultiWriteSyncer(os.Stdout, os.Stdout)).Sync() returns two invalid-handle errors.

Please apply the stdio adaptation to each current stdout/stderr member before wrapper or multi-writer composition. Regression tests should cover Open("stdout"), duplicate/mixed write syncers, and verify another core still receives Sync and propagates its unrelated error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

sync /dev/stdout: The handle is invalid.

3 participants