Skip to content

partr: fix lost tasks when threads race to grow a pool's multiqueue - #62372

Merged
vtjnash merged 1 commit into
JuliaLang:masterfrom
IanButterworth:ib/fix-multiq-grow-race
Jul 14, 2026
Merged

partr: fix lost tasks when threads race to grow a pool's multiqueue#62372
vtjnash merged 1 commit into
JuliaLang:masterfrom
IanButterworth:ib/fix-multiq-grow-race

Conversation

@IanButterworth

Copy link
Copy Markdown
Member

Fixes #62144

Claude:


multiq_size reads tpheaps = heaps[tp] before taking heaps_lock[tp], and then re-checks length(tpheaps) on that stale local after acquiring the lock. When several threads concurrently perform the first inserts into a pool (e.g. many default-pool tasks spawning :interactive tasks at once), each of them sees the pre-growth length, so each one in turn allocates a fresh heaps vector and publishes it with heaps[tp] = newheaps, replacing the vector the previous thread just published. Any task inserted into one of the replaced vectors in that window is orphaned: it is unreachable from heaps[tp], so no thread can ever pop it, and anything waiting on it hangs. With -t 8,4 and a burst of cross-pool spawn/fetch pairs this reproduces within seconds.

Re-read heaps[tp] under the lock so the re-check and the copyto! operate on the current vector, making the grow-if-needed check correct and the publication monotonic.

`multiq_size` reads `tpheaps = heaps[tp]` before taking `heaps_lock[tp]`,
and then re-checks `length(tpheaps)` on that stale local after acquiring
the lock. When several threads concurrently perform the first inserts
into a pool (e.g. many default-pool tasks spawning `:interactive` tasks
at once), each of them sees the pre-growth length, so each one in turn
allocates a fresh heaps vector and publishes it with `heaps[tp] =
newheaps`, replacing the vector the previous thread just published.
Any task inserted into one of the replaced vectors in that window is
orphaned: it is unreachable from `heaps[tp]`, so no thread can ever pop
it, and anything waiting on it hangs. With `-t 8,4` and a burst of
cross-pool spawn/fetch pairs this reproduces within seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes JuliaLang#62144

Co-Authored-By: Claude <claude@users.noreply.github.com>
@IanButterworth IanButterworth added multithreading Base.Threads and related functionality backport 1.12 Change should be backported to release-1.12 backport 1.13 Change should be backported to release-1.13 labels Jul 13, 2026
@vtjnash
vtjnash merged commit 733410f into JuliaLang:master Jul 14, 2026
6 of 10 checks passed
@IanButterworth
IanButterworth deleted the ib/fix-multiq-grow-race branch July 14, 2026 07:50
KristofferC pushed a commit that referenced this pull request Jul 18, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
KristofferC pushed a commit that referenced this pull request Jul 18, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
@KristofferC KristofferC removed the backport 1.13 Change should be backported to release-1.13 label Jul 20, 2026
KristofferC pushed a commit that referenced this pull request Jul 21, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
KristofferC pushed a commit that referenced this pull request Jul 21, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
KristofferC pushed a commit that referenced this pull request Jul 24, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
DilumAluthge pushed a commit that referenced this pull request Jul 25, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
KristofferC pushed a commit that referenced this pull request Aug 7, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
KristofferC pushed a commit that referenced this pull request Aug 8, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
KristofferC pushed a commit that referenced this pull request Aug 8, 2026
…62372)

`multiq_size` reads `tpheaps = heaps[tp]` before taking
`heaps_lock[tp]`, and then re-checks `length(tpheaps)` on that stale
local after acquiring the lock. When several threads concurrently
perform the first inserts into a pool (e.g. many default-pool tasks
spawning `:interactive` tasks at once), each of them sees the pre-growth
length, so each one in turn allocates a fresh heaps vector and publishes
it with `heaps[tp] = newheaps`, replacing the vector the previous thread
just published. Any task inserted into one of the replaced vectors in
that window is orphaned: it is unreachable from `heaps[tp]`, so no
thread can ever pop it, and anything waiting on it hangs. With `-t 8,4`
and a burst of cross-pool spawn/fetch pairs this reproduces within
seconds.

Re-read `heaps[tp]` under the lock so the re-check and the `copyto!`
operate on the current vector, making the grow-if-needed check correct
and the publication monotonic.

Fixes #62144
Co-authored-by: Claude <claude@users.noreply.github.com>

(cherry picked from commit 733410f)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport 1.12 Change should be backported to release-1.12 multithreading Base.Threads and related functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Julia 1.12+ (stochastically) hangs when sleeping with many threads

3 participants