Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/containers/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ Base.isempty(c::AbstractCache) = @lock c.lock isempty(c.index)

# Iteration walks a snapshot copied under the lock, so it is safe while other tasks use the
# cache (holding the lock across `iterate` calls would deadlock on an early `break`).
# The length may change between `length` and `iterate`, so `collect` must not rely on it.
Base.IteratorSize(::Type{<:AbstractCache}) = Base.SizeUnknown()
function Base.iterate(c::AbstractCache)
snapshot = @lock c.lock _pairs(c)
return iterate(c, (snapshot, 1))
Expand Down
18 changes: 18 additions & 0 deletions test/test_containers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,24 @@ end
@test all(((k, v),) -> v == 3k, c)
end

@testset "$C: collect while other tasks write" for C in CACHETYPES
c = C{Int, Int}(; maxsize = 50)
done = Threads.Atomic{Bool}(false)
writer = Threads.@spawn while !done[]
foreach(k -> get!(() -> k, c, k), 1:200)
empty!(c)
yield()
end
ok = true
for _ in 1:2_000
ok &= all(((k, v),) -> k == v, collect(c)) # used to throw when the length changed
yield()
end
done[] = true
wait(writer)
@test ok
end

@testset "$C: freed slots release their values" for C in CACHETYPES
c = C{Int, Base.RefValue{Int}}(; maxsize = 3)
# build the value inside a function so no local keeps it alive
Expand Down
Loading