Skip to content

Wait for a removal to be observable: WaitGone - #2

Merged
tannevaled merged 1 commit into
mainfrom
wait-gone
Aug 28, 2026
Merged

tannevaled merged 1 commit into
mainfrom
wait-gone

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Open does not return until the display is active. Close had no matching wait, and the gap is not small.

Measured on macOS 26.6.2, M4 Max — closing four displays:

CloseAll returned in 33.167µs; macOS stopped listing 4 displays after 716.051083ms

Six 1920x1080 displays took 1.9 s. So a caller that closes and immediately reads ActiveDisplays sees displays that are already dead and reads them as a leak — which is what happened here, before the timing was measured, while answering a plain question about why virtual screens were not in the macOS display list.

What this adds

func WaitGone(timeout time.Duration, ids ...uint32) error

It polls the active list until none of ids is there, and returns ErrStillPresent naming the ones that stayed — a real state, not merely a timeout: a display whose mode was changed after creation cannot be released at all, as Close already documents. A zero timeout checks once without sleeping.

What it fixes here

This package's own integration tests slept a fixed settle of 1.5 s before asserting a removal — shorter than a batch actually takes. Three sites now wait instead, including the cleanup guard whose something was left behind message could fire on displays that were only still retiring.

The new integration test prints both numbers and carries the negative control: a wait for the main display must fail with ErrStillPresent, or none of the passes above it prove anything.

--- PASS: TestIntegrationRemovalIsAsynchronous (3.44s)
--- PASS: TestIntegrationSeveralAtOnce (4.16s)
--- PASS: TestIntegrationCloseTwice (1.88s)

Portable code stays fully covered; WaitGone and its helper are tested on every platform through the existing seams, with the vacuous-wait control asserted there too (a zero timeout must not sleep, a wait that returns must have polled).

Open does not return until the display is active. Close had no matching
wait, and the gap is not small. Measured on macOS 26.6.2, M4 Max: closing
four displays, CloseAll returned in 33 microseconds and macOS kept listing
them for 716 ms; six 1920x1080 displays took 1.9 s.

A caller that closes and immediately reads ActiveDisplays therefore sees
displays that are already dead and reads them as a leak. That misreading
happened here before the timing was measured, which is what prompted this.

WaitGone(timeout, ids...) polls the active list until none of the ids is
there, returning ErrStillPresent naming the ones that stayed -- which is a
real state, not just a timeout: a display whose mode was changed after
creation cannot be released at all, as Close already documents.

The package's own integration tests slept a fixed 1.5 s before asserting a
removal, which is SHORTER than a batch takes; they now wait, including the
cleanup guard whose "something was left behind" message could fire on
displays that were merely still retiring. The new integration test states
both numbers and carries the negative control: a wait for the MAIN display
must fail, or none of the passes mean anything.
@tannevaled
tannevaled merged commit d84646c into main Aug 28, 2026
14 checks passed
@tannevaled
tannevaled deleted the wait-gone branch August 28, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant