Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Temporary Scalars can be finalized while native code still uses them (crash in Tensor.add under concurrency)

Abierto
#1,583 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
45/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
csharp
Área
backend

Línea de trabajo

Start with the implicit Scalar conversions and native call in src/TorchSharp/Tensor/Tensor.Math.cs, then review the referenced issues #1434 and #1496 for existing approaches. Check other examples listed in the report, including softplus and scalar operators, to understand the scope. Done means every Scalar passed to native code stays alive until the call returns; the issue does not specify a deterministic reproduction test.

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

Descripción

bug

Describe the bug

A temporary Scalar that TorchSharp creates for a native call can be freed by its finalizer while libtorch is still reading it. That is a use-after-free: it can crash the process or give wrong results.

Scalar frees its native value in a finalizer, isn't a SafeHandle, and isn't tracked by any DisposeScope. Many methods create one implicitly and pass only its handle to native code, e.g. src/TorchSharp/Tensor/Tensor.Math.cs:

public Tensor add(Tensor target)
{
    return add(target, 1);                 // temporary `Scalar` for alpha
}

public Tensor add(Tensor target, `Scalar` alpha)
{
    var res = THSTensor_add(Handle, target.Handle, alpha.Handle);   // nothing keeps alpha alive from here on
    ...
}

Once .Handle has been read, the JIT may consider the Scalar unreachable. A garbage collection that starts during the native call (for example one triggered by another thread's allocations) can then run ~Scalar() → THSTorch_dispose_scalar mid-call.

The same pattern is everywhere a .NET number is implicitly converted to a Scalar argument:

  • add/sub with their default alpha, and tensor +;
  • scalar arithmetic operators;
  • masked_fill(mask, 0), pow(2), eq(0);
  • functions like softplus, which convert their double parameters internally.

#1434 and #1496 already dispose these temporaries, framed as a memory leak. They would also fix this crash, because a Scalar that is disposed after the call stays alive during it. This issue is to report that the problem is a memory-safety bug as well as a leak, which may help prioritize those PRs.

To Reproduce

The failure is intermittent and depends on GC timing, so there's no deterministic repro. We hit it in StanzaSharp, which runs Stanza's NLP models on TorchSharp. In a CI test, 8 threads run inference concurrently on shared modules, each thread inside its own DisposeScope and no_grad(). The failing line was:

var predicted = 1 + torch.nn.functional.softplus(dist);

Crash (process terminated):

System.Runtime.InteropServices.ExternalException (0x80004005): Expected false to be true, but got false.
Exception raised from toLong at C:\a\pytorch\pytorch\pytorch\c10/core/Scalar.h:140 (most recent call first):
c10.dll!c10::detail::torchCheckFail
torch_cpu.DLL!at::native::add
torch_cpu.DLL!at::Tensor::add
at TorchSharp.torch.CheckForErrors()
at TorchSharp.torch.Tensor.op_Addition(Int32 left, Tensor right)
...

It crashed once on Windows Arm64 and passed on a rerun. It never reproduced on x64, even with a thread looping GC.Collect(); GC.WaitForPendingFinalizers(); during inference. Presumably the x64 JIT happens to keep the temporary alive across the call, but nothing guarantees it.

Expected behavior

Every Scalar passed to native code stays alive until the call returns, whatever the GC does.

Please complete the following information:

  • OS: Windows 11 on Arm64 (GitHub Actions windows-11-arm runner), .NET 10
  • Package Type: TorchSharp 0.107.0 with libtorch-cpu-win-arm64 2.10.0
  • Version: 0.107.0

Additional context

Possible fixes, any of:

  • merge #1434 / #1496 (dispose temporaries right after the call);
  • add GC.KeepAlive(scalar) after each P/Invoke that takes a Scalar handle;
  • make Scalar wrap a SafeHandle, which keeps it alive for the duration of every P/Invoke and fixes all call sites at once.

Tensor temporaries created outside a DisposeScope probably have the same exposure.

Our workaround: never let TorchSharp create a Scalar. We keep our own in static readonly fields or using var locals, and call the overloads that take them, e.g. a.add(b, one) instead of a + b

Lenguaje dominante
C#
Estrellas
1.9k
Forks
230
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

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/TorchSharp

Todos los issues de dotnet/TorchSharp

Issues similares

Más issues de C#

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.