close
Stops, uninitialises, disposes, and only then releases the ring, in C and in that order. On iOS, the audio-session lease is released after that complete C teardown, never before RemoteIO stops.
The order is the reason the ring belongs to the sink rather than to the engine: after the dispose no callback can be running, so freeing the ring cannot race a render. Idempotent.
The handles are taken and cleared inside lock, and the destroy runs outside it. That ordering is what makes a concurrent latencyNanos or counter read safe: a reader in flight finishes before the fields are cleared, and one arriving afterwards finds null and reads zeroes. The destroy is outside the lock on purpose, because it blocks until the device's callback is fenced out and nothing else should wait behind the audio device.