Skip to content

Commit

Permalink
Implement and export isfull (JuliaLang#53159)
Browse files Browse the repository at this point in the history
This PR implements `isfull(c::Channel)`. 

It calls `n_avail(c) ≥ c.sz_max` in all cases. The original
implementation was inspired by [this
comment](https://discourse.julialang.org/t/function-to-check-if-channel-is-full/44795/3?u=thelatekronos),
and therefore had a special case for unbuffered channels, which fell
back to `isready`. I opted against this behaviour, because it fails to
respect that an unbuffered channel is always full, in two important
senses:
1) The number of elements available is greater than or equal the
capacity
    2) A call to `put!` will block

With the current implementation, the behaviour is simply understood and
summarized in all cases by the start of the docstring:
> Determines whether a `Channel` is full, in the 
sense that calling `put!(c, some_value)` will block.

Shoutout to @SamuraiAku for their work in
JuliaLang#40720, which helped me a lot on
thinking this through, and remembering to change all relevant files. In
particular, the detail around how `c.cond_take.waitq` may result in
immediate unblocking, which is a really important caveat on a function
that may be used to check if `put!`ing will block. However, for buffered
channels, `isfull` is extremely close to `putwillblock` from JuliaLang#40720
(just a little better, with >= instead of ==), and for unbuffered
channels it does not make much sense to see if `put!`ing will block.

This PR is created based on
[this](JuliaLang#22863 (comment))
"call to action".

Checklist:
- [x] Entry in news
- [x] Docstring with example
- [x] Export function
- [x] Mention in manual
- [x] Entry in
[docs-reference](https://docs.julialang.org/en/v1/base/parallel/)

---------

Co-authored-by: Jameson Nash <[email protected]>
  • Loading branch information
KronosTheLate and vtjnash committed Feb 29, 2024
1 parent 962bbf7 commit c06662a
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 3 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ New library functions
---------------------

* `logrange(start, stop; length)` makes a range of constant ratio, instead of constant step ([#39071])
* The new `isfull(c::Channel)` function can be used to check if `put!(c, some_value)` will block. ([#53159])

New library features
--------------------
Expand Down
43 changes: 42 additions & 1 deletion base/channels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ end
Determines whether a [`Channel`](@ref) has a value stored in it.
Returns immediately, does not block.
For unbuffered channels returns `true` if there are tasks waiting on a [`put!`](@ref).
For unbuffered channels, return `true` if there are tasks waiting on a [`put!`](@ref).
# Examples
Expand Down Expand Up @@ -559,6 +559,47 @@ function n_avail(c::Channel)
@atomic :monotonic c.n_avail_items
end

"""
isfull(c::Channel)
Determines if a [`Channel`](@ref) is full, in the sense
that calling `put!(c, some_value)` would have blocked.
Returns immediately, does not block.
Note that it may frequently be the case that `put!` will
not block after this returns `true`. Users must take
precautions not to accidentally create live-lock bugs
in their code by calling this method, as these are
generally harder to debug than deadlocks. It is also
possible that `put!` will block after this call
returns `false`, if there are multiple producer
tasks calling `put!` in parallel.
# Examples
Buffered channel:
```jldoctest
julia> c = Channel(1); # capacity = 1
julia> isfull(c)
false
julia> put!(c, 1);
julia> isfull(c)
true
```
Unbuffered channel:
```jldoctest
julia> c = Channel(); # capacity = 0
julia> isfull(c) # unbuffered channel is always full
true
```
"""
isfull(c::Channel) = n_avail(c) c.sz_max

lock(c::Channel) = lock(c.cond_take)
lock(f, c::Channel) = lock(f, c.cond_take)
unlock(c::Channel) = unlock(c.cond_take)
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ export
# channels
take!,
put!,
isfull,
isready,
fetch,
bind,
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/parallel.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Base.Channel
Base.Channel(::Function)
Base.put!(::Channel, ::Any)
Base.take!(::Channel)
Base.isfull(::Channel)
Base.isready(::Channel)
Base.fetch(::Channel)
Base.close(::Channel)
Expand Down
5 changes: 3 additions & 2 deletions doc/src/manual/asynchronous-programming.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,11 @@ A channel can be visualized as a pipe, i.e., it has a write end and a read end :
to the maximum number of elements that can be held in the channel at any time. For example, `Channel(32)`
creates a channel that can hold a maximum of 32 objects of any type. A `Channel{MyType}(64)` can
hold up to 64 objects of `MyType` at any time.
* If a [`Channel`](@ref) is empty, readers (on a [`take!`](@ref) call) will block until data is available.
* If a [`Channel`](@ref) is full, writers (on a [`put!`](@ref) call) will block until space becomes available.
* If a [`Channel`](@ref) is empty, readers (on a [`take!`](@ref) call) will block until data is available (see [`isempty`](@ref)).
* If a [`Channel`](@ref) is full, writers (on a [`put!`](@ref) call) will block until space becomes available (see [`isfull`](@ref)).
* [`isready`](@ref) tests for the presence of any object in the channel, while [`wait`](@ref)
waits for an object to become available.
* Note that if another task is currently waiting to `put!` an object into a channel, a channel can have more items available than its capacity.
* A [`Channel`](@ref) is in an open state initially. This means that it can be read from and written to
freely via [`take!`](@ref) and [`put!`](@ref) calls. [`close`](@ref) closes a [`Channel`](@ref).
On a closed [`Channel`](@ref), [`put!`](@ref) will fail. For example:
Expand Down
7 changes: 7 additions & 0 deletions test/channels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ end
c = Channel()
@test eltype(c) == Any
@test c.sz_max == 0
@test isempty(c) == true # Nothing in it
@test isfull(c) == true # But no more room

c = Channel(1)
@test eltype(c) == Any
Expand All @@ -49,6 +51,11 @@ end
@test isready(c) == false
@test eltype(Channel(1.0)) == Any

c = Channel(1)
@test isfull(c) == false
put!(c, 1)
@test isfull(c) == true

c = Channel{Int}(1)
@test eltype(c) == Int
@test_throws MethodError put!(c, "Hello")
Expand Down

0 comments on commit c06662a

Please sign in to comment.