#3878·candle

potential deadlock risk from multiple read locks over storage in tensor operations

Author: scaspinCreated Aug 12, 2026Updated Aug 28, 2026

Multiple tensor operations (for example binary_op) take two (or more) read locks over underlying storage that can deadlock if the self and rhs/kernel "other" storages are the same and there is a concurrent write lock request on the same storage. std-sync RwLock can deadlock two reads on the same thread if there is a concurrent write.

Since storage() takes a read lock and returns the guard https://github.com/huggingface/candle/blob/6f74e7c390c717f8fd34f23ce02aceb058173370/candle-core/src/tensor.rs#L2741-L2743 for the following example from binary_op https://github.com/huggingface/candle/blob/6f74e7c390c717f8fd34f23ce02aceb058173370/candle-core/src/tensor.rs#L101-L102 when self and rhs are the same, the underlying storage and its lock would be the same.

There is a similar reported issue https://github.com/huggingface/candle/issues/3227 that shows deadlock without concurrent writes, these are included here too but are a different kind of bug. The deadlocks with no concurrency in custom_op:

Function Guards
Tensor::scatter_set self[W] → indexes[R], source[R]
Tensor::scatter_add_set self[W] → indexes[R], source[R]
Tensor::inplace_op2 self[W] → rhs[R]
Tensor::inplace_op3 self[W] → t2[R], t3[R]

scatter_set and scatter_add_set already guard against source and self being the same, but not against indexes also being the same (seems like the "same storage" check is for correctness rather than deadlock prevention here). Tensor::slice_set and Var::set already check if they have the same storage and bail if true.

Deadlocks can occur if there are concurrent writes between the .storage() calls over the following variables:

In tensor.rs:

Function Overlapping
add, sub, mul, div selfrhs
cmp selfrhs
matmul selfrhs
where_cond self, on_true, on_false
scatter indexessource
scatter_add indexessource
index_add self, indexes, source
gather selfindexes
index_select selfindexes

In conv.rs, all selfkernel:

Function
conv1d_single_group
conv_transpose1d_single_group
conv2d_single_group
conv_transpose2d

In custom_op.rs:

Function Overlapping
apply_op2_no_bwd selfrhs
apply_op3_no_bwd self, t2, t3
apply_op2_arc selfrhs
apply_op3_arc self, t2, t3

These possible deadlocks were found by a static analysis tool. There could be more deadlocks hiding behind storage_and_layout().

The fix should check whether the two storages are the same before attempting to lock the second one or switch over to a reentrant RwLock like the one in parking_lot.