SoundPlayer.Play() does not preserve in-memory WAV buffer lifetime

Abierto
#136,991 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
48/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
csharp
Área
backend, desktop

Línea de trabajo

Start with src/libraries/System.Windows.Extensions/src/System/Media/SoundPlayer.cs, especially Play(), PlayLooping(), and the PlaySoundW import, to trace how _streamData is passed to asynchronous playback and how stopping or replacing playback is handled. Then find the related SoundPlayer tests and run them. Done means in-memory WAV data stays at a stable address until asynchronous playback completes or looping playback stops, while synchronous and file-path playback remain unaffected.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

area-System.IO bug untriaged
Description

For Stream and non-file URL sources, SoundPlayer copies the WAV into its private _streamData array. Play() and PlayLooping() then call PlaySoundW with SND_MEMORY | SND_ASYNC (plus SND_LOOP for looping).

The generated [LibraryImport] stub pins the managed array only for the duration of the P/Invoke call. PlaySoundW returns immediately under SND_ASYNC, ending that pin while playback may still be using the buffer. The Win32 contract requires the buffer to remain valid until playback completes.

Required:  pin [ PlaySoundW call ---------------- playback complete/stop ]
Actual:    pin [ PlaySoundW call ] return ------- playback may continue
Why this is a product bug

This occurs through a supported public API path, such as new SoundPlayer(Stream).Play() or PlayLooping(). SoundPlayer allocates and owns the private _streamData copy, selects SND_MEMORY | SND_ASYNC, and initiates the native operation. The caller cannot access or pin _streamData, and Play() exposes no completion task or callback through which the caller could maintain its address.

The lifetime obligation therefore belongs to SoundPlayer: it must satisfy the downstream WinMM contract for the operation it chose. This is not caller misuse or a general thread-safety issue.

Impact

The generated P/Invoke pins _streamData only for the duration of the PlaySoundW call. Because SND_ASYNC returns when playback starts, WinMM may continue accessing the buffer after the pin is released.

The PlaySound contract requires a buffer used with SND_MEMORY | SND_ASYNC to remain valid until playback completes. A compacting GC can move _streamData, and replacing Stream can clear the final managed reference, allowing the array to be collected and its address reused while playback is active.

A diagnostic reproduction can observe the private array moving:

using var player = new SoundPlayer(new MemoryStream(wav, writable: false));
player.Load();
player.PlayLooping();

GC.Collect(
    GC.MaxGeneration,
    GCCollectionMode.Forced,
    blocking: true,
    compacting: true);

Private reflection is needed only to compare the array's address before and after the collection, not to create the underlying lifetime condition.

Expected behavior

Keep the in-memory WAV at a stable address until one-shot playback completes or looping playback is stopped, for example through a persistent pin or native-owned stable copy.

PlaySync() is not affected by this specific issue because PlaySoundW returns only after synchronous playback completes. File-path playback is also unaffected because Windows receives a filename rather than a managed WAV pointer.

Lenguaje dominante
C#
Estrellas
18.4k
Forks
5.8k
Merge medio
2 d 14 h
PR fusionados (30 d)
585

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de dotnet/runtime

Todos los issues de dotnet/runtime

Issues similares

Más issues de C#

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.