Skip to content

Commit c06662a

Browse files
Implement and export isfull (#53159)
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 #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 #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](#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]>
1 parent 962bbf7 commit c06662a

File tree

6 files changed

+55
-3
lines changed

6 files changed

+55
-3
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ New library functions
3636
---------------------
3737

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

4041
New library features
4142
--------------------

base/channels.jl

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ end
519519
Determines whether a [`Channel`](@ref) has a value stored in it.
520520
Returns immediately, does not block.
521521
522-
For unbuffered channels returns `true` if there are tasks waiting on a [`put!`](@ref).
522+
For unbuffered channels, return `true` if there are tasks waiting on a [`put!`](@ref).
523523
524524
# Examples
525525
@@ -559,6 +559,47 @@ function n_avail(c::Channel)
559559
@atomic :monotonic c.n_avail_items
560560
end
561561

562+
"""
563+
isfull(c::Channel)
564+
565+
Determines if a [`Channel`](@ref) is full, in the sense
566+
that calling `put!(c, some_value)` would have blocked.
567+
Returns immediately, does not block.
568+
569+
Note that it may frequently be the case that `put!` will
570+
not block after this returns `true`. Users must take
571+
precautions not to accidentally create live-lock bugs
572+
in their code by calling this method, as these are
573+
generally harder to debug than deadlocks. It is also
574+
possible that `put!` will block after this call
575+
returns `false`, if there are multiple producer
576+
tasks calling `put!` in parallel.
577+
578+
# Examples
579+
580+
Buffered channel:
581+
```jldoctest
582+
julia> c = Channel(1); # capacity = 1
583+
584+
julia> isfull(c)
585+
false
586+
587+
julia> put!(c, 1);
588+
589+
julia> isfull(c)
590+
true
591+
```
592+
593+
Unbuffered channel:
594+
```jldoctest
595+
julia> c = Channel(); # capacity = 0
596+
597+
julia> isfull(c) # unbuffered channel is always full
598+
true
599+
```
600+
"""
601+
isfull(c::Channel) = n_avail(c) c.sz_max
602+
562603
lock(c::Channel) = lock(c.cond_take)
563604
lock(f, c::Channel) = lock(f, c.cond_take)
564605
unlock(c::Channel) = unlock(c.cond_take)

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ export
719719
# channels
720720
take!,
721721
put!,
722+
isfull,
722723
isready,
723724
fetch,
724725
bind,

doc/src/base/parallel.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Base.Channel
6262
Base.Channel(::Function)
6363
Base.put!(::Channel, ::Any)
6464
Base.take!(::Channel)
65+
Base.isfull(::Channel)
6566
Base.isready(::Channel)
6667
Base.fetch(::Channel)
6768
Base.close(::Channel)

doc/src/manual/asynchronous-programming.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,11 @@ A channel can be visualized as a pipe, i.e., it has a write end and a read end :
194194
to the maximum number of elements that can be held in the channel at any time. For example, `Channel(32)`
195195
creates a channel that can hold a maximum of 32 objects of any type. A `Channel{MyType}(64)` can
196196
hold up to 64 objects of `MyType` at any time.
197-
* If a [`Channel`](@ref) is empty, readers (on a [`take!`](@ref) call) will block until data is available.
198-
* If a [`Channel`](@ref) is full, writers (on a [`put!`](@ref) call) will block until space becomes available.
197+
* If a [`Channel`](@ref) is empty, readers (on a [`take!`](@ref) call) will block until data is available (see [`isempty`](@ref)).
198+
* If a [`Channel`](@ref) is full, writers (on a [`put!`](@ref) call) will block until space becomes available (see [`isfull`](@ref)).
199199
* [`isready`](@ref) tests for the presence of any object in the channel, while [`wait`](@ref)
200200
waits for an object to become available.
201+
* 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.
201202
* A [`Channel`](@ref) is in an open state initially. This means that it can be read from and written to
202203
freely via [`take!`](@ref) and [`put!`](@ref) calls. [`close`](@ref) closes a [`Channel`](@ref).
203204
On a closed [`Channel`](@ref), [`put!`](@ref) will fail. For example:

test/channels.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ end
4040
c = Channel()
4141
@test eltype(c) == Any
4242
@test c.sz_max == 0
43+
@test isempty(c) == true # Nothing in it
44+
@test isfull(c) == true # But no more room
4345

4446
c = Channel(1)
4547
@test eltype(c) == Any
@@ -49,6 +51,11 @@ end
4951
@test isready(c) == false
5052
@test eltype(Channel(1.0)) == Any
5153

54+
c = Channel(1)
55+
@test isfull(c) == false
56+
put!(c, 1)
57+
@test isfull(c) == true
58+
5259
c = Channel{Int}(1)
5360
@test eltype(c) == Int
5461
@test_throws MethodError put!(c, "Hello")

0 commit comments

Comments
 (0)