Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta
#1,583 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
csharp
Ambito
backend

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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

Lingua principale
C#
Stelle
1.9k
Fork
230
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di dotnet/TorchSharp

Tutte le issue di dotnet/TorchSharp

Issue simili

Altre issue su C#

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.