Skip to content

Commit 7a24ca1

Browse files
committed
Channel constructor requires an explict size.
Move channel tests into its own file. Implement 0-sized channels.
1 parent 9f80eab commit 7a24ca1

File tree

8 files changed

+176
-79
lines changed

8 files changed

+176
-79
lines changed

base/channels.jl

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,33 @@
22

33
abstract AbstractChannel
44

5-
const DEF_CHANNEL_SZ=32
6-
75
type Channel{T} <: AbstractChannel
86
cond_take::Condition # waiting for data to become available
97
cond_put::Condition # waiting for a writeable slot
108
state::Symbol
119

1210
data::Array{T,1}
13-
sz_max::Int # maximum size of channel
11+
sz_max::UInt # maximum size of channel
12+
13+
# Used when sz_max == 0
14+
takers::Array{Condition}
1415

15-
function Channel(sz)
16-
sz_max = sz == typemax(Int) ? typemax(Int) - 1 : sz
17-
new(Condition(), Condition(), :open, Array{T}(0), sz_max)
16+
function Channel(sz::Float64)
17+
if sz == Inf
18+
Channel{T}(typemax(UInt))
19+
else
20+
Channel{T}(convert(UInt, sz))
21+
end
22+
end
23+
function Channel(sz::Integer)
24+
if sz < 0
25+
throw(ArgumentError("Channel size must be either 0, a positive integer or Inf"))
26+
end
27+
new(Condition(), Condition(), :open, Array{T}(0), sz, Array{Condition}(0))
1828
end
1929
end
2030

21-
Channel(sz::Int = DEF_CHANNEL_SZ) = Channel{Any}(sz)
31+
Channel(sz) = Channel{Any}(sz)
2232

2333
closed_exception() = InvalidStateException("Channel is closed.", :closed)
2434

@@ -49,6 +59,10 @@ Appends an item `v` to the channel `c`. Blocks if the channel is full.
4959
"""
5060
function put!(c::Channel, v)
5161
!isopen(c) && throw(closed_exception())
62+
put!(c,v,Val{c.sz_max==0})
63+
end
64+
65+
function put!(c::Channel, v, ::Type{Val{false}})
5266
while length(c.data) == c.sz_max
5367
wait(c.cond_put)
5468
end
@@ -57,19 +71,34 @@ function put!(c::Channel, v)
5771
v
5872
end
5973

74+
# 0-sized channel
75+
function put!(c::Channel, v, ::Type{Val{true}})
76+
while length(c.takers) == 0
77+
notify(c.cond_take, nothing, true, false) # Required to handle wait() on 0-sized channels
78+
wait(c.cond_put)
79+
end
80+
cond_taker = shift!(c.takers)
81+
notify(cond_taker, v, false, false)
82+
v
83+
end
84+
6085
push!(c::Channel, v) = put!(c, v)
6186

62-
function fetch(c::Channel)
87+
fetch(c::Channel) = fetch(c, Val{c.sz_max==0})
88+
function fetch(c::Channel, ::Type{Val{false}})
6389
wait(c)
6490
c.data[1]
6591
end
92+
fetch(c::Channel, ::Type{Val{true}}) = throw(ErrorException("`fetch` on a 0-sized Channel is not supported."))
93+
6694

6795
"""
6896
take!(c::Channel)
6997
7098
Removes and returns a value from a `Channel`. Blocks till data is available.
7199
"""
72-
function take!(c::Channel)
100+
take!(c::Channel) = take!(c, Val{c.sz_max==0})
101+
function take!(c::Channel, ::Type{Val{false}})
73102
wait(c)
74103
v = shift!(c.data)
75104
notify(c.cond_put, nothing, false, false) # notify only one, since only one slot has become available for a put!.
@@ -78,13 +107,37 @@ end
78107

79108
shift!(c::Channel) = take!(c)
80109

110+
# 0-size channel
111+
function take!(c::Channel, ::Type{Val{true}})
112+
!isopen(c) && throw(closed_exception())
113+
cond_taker = Condition()
114+
push!(c.takers, cond_taker)
115+
notify(c.cond_put, nothing, false, false)
116+
try
117+
return wait(cond_taker)
118+
catch e
119+
if isa(e, InterruptException)
120+
# remove self from the list of takers
121+
filter!(x -> x != cond_taker, c.takers)
122+
else
123+
rethrow(e)
124+
end
125+
end
126+
end
127+
81128
"""
82129
isready(c::Channel)
83130
84131
Determine whether a `Channel` has a value stored to it.
132+
133+
For 0-sized channels returns true if there are tasks waiting
134+
on a `put!`
135+
85136
`isready` on `Channel`s is non-blocking.
86137
"""
87-
isready(c::Channel) = n_avail(c) > 0
138+
isready(c::Channel) = n_avail(c, Val{c.sz_max==0}) > 0
139+
n_avail(c::Channel, ::Type{Val{false}}) = length(c.data)
140+
n_avail(c::Channel, ::Type{Val{true}}) = n_waiters(c.cond_put)
88141

89142
function wait(c::Channel)
90143
while !isready(c)
@@ -97,13 +150,12 @@ end
97150
function notify_error(c::Channel, err)
98151
notify_error(c.cond_take, err)
99152
notify_error(c.cond_put, err)
153+
foreach(x->notify_error(x, err), c.takers)
100154
end
101155

102156
eltype{T}(::Type{Channel{T}}) = T
103157

104-
n_avail(c::Channel) = length(c.data)
105-
106-
show(io::IO, c::Channel) = print(io, "$(typeof(c))(sz_max:$(c.sz_max),sz_curr:$(n_avail(c)))")
158+
show(io::IO, c::Channel) = print(io, "$(typeof(c))(sz_max:$(c.sz_max),sz_curr:$(n_avail(c, Val{c.sz_max==0})))")
107159

108160
start{T}(c::Channel{T}) = Ref{Nullable{T}}()
109161
function done(c::Channel, state::Ref)

base/docs/helpdb/Base.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,10 +1437,16 @@ endof
14371437
Constructs a `Channel` that can hold a maximum of `sz` objects of type `T`. `put!` calls on
14381438
a full channel block till an object is removed with `take!`.
14391439
1440+
`Channel(0)` constructs a Channel without a backing store. Consequently a `put!` on a
1441+
0-sized channel will block till another task calls a `take!` on it. And vice-versa.
1442+
1443+
`isready` on a 0-sized channel returns true if there are any tasks blocked on a `put!`
1444+
`fetch` is unsupported on a 0-sized channel.
1445+
14401446
Other constructors:
14411447
1442-
- `Channel()` - equivalent to `Channel{Any}(32)`
1443-
- `Channel(sz::Int)` equivalent to `Channel{Any}(sz)`
1448+
- `Channel(Inf)` - equivalent to `Channel{Any}(typemax(UInt))`
1449+
- `Channel(sz)` equivalent to `Channel{Any}(sz)`
14441450
"""
14451451
Channel
14461452

base/event.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ notify1(c::Condition, arg=nothing) = notify(c, arg, all=false)
5757
notify_error(c::Condition, err) = notify(c, err, error=true)
5858
notify1_error(c::Condition, err) = notify(c, err, error=true, all=false)
5959

60+
n_waiters(c::Condition) = length(c.waitq)
6061

6162
# schedule an expression to run asynchronously, with minimal ceremony
6263
"""

doc/stdlib/base.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,3 +1640,4 @@ Internals
16401640
.. Docstring generated from Julia source
16411641
16421642
Compile the given function ``f`` for the argument tuple (of types) ``args``\ , but do not execute it.
1643+

doc/stdlib/parallel.rst

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,14 @@ Tasks
117117
118118
Constructs a ``Channel`` that can hold a maximum of ``sz`` objects of type ``T``\ . ``put!`` calls on a full channel block till an object is removed with ``take!``\ .
119119

120+
``Channel(0)`` constructs a Channel without a backing store. Consequently a ``put!`` on a 0-sized channel will block till another task calls a ``take!`` on it. And vice-versa.
121+
122+
``isready`` on a 0-sized channel returns true if there are any tasks blocked on a ``put!`` ``fetch`` is unsupported on a 0-sized channel.
123+
120124
Other constructors:
121125

122-
* ``Channel()`` - equivalent to ``Channel{Any}(32)``
123-
* ``Channel(sz::Int)`` equivalent to ``Channel{Any}(sz)``
126+
* ``Channel(Inf)`` - equivalent to ``Channel{Any}(typemax(UInt))``
127+
* ``Channel(sz)`` equivalent to ``Channel{Any}(sz)``
124128

125129
General Parallel Computing Support
126130
----------------------------------
@@ -384,7 +388,11 @@ General Parallel Computing Support
384388

385389
.. Docstring generated from Julia source
386390
387-
Determine whether a ``Channel`` has a value stored to it. ``isready`` on ``Channel``\ s is non-blocking.
391+
Determine whether a ``Channel`` has a value stored to it.
392+
393+
For 0-sized channels returns true if there are tasks waiting on a ``put!``
394+
395+
``isready`` on ``Channel``\ s is non-blocking.
388396

389397
.. function:: isready(rr::RemoteChannel, args...)
390398

test/channels.jl

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# This file is a part of Julia. License is MIT: http://julialang.org/license
2+
3+
# Test various constructors
4+
c=Channel(1)
5+
@test eltype(c) == Any
6+
@test put!(c, 1) == 1
7+
@test isready(c) == true
8+
@test take!(c) == 1
9+
@test isready(c) == false
10+
11+
@test eltype(Channel(1.0)) == Any
12+
13+
c=Channel{Int}(1)
14+
@test eltype(c) == Int
15+
@test_throws MethodError put!(c, "Hello")
16+
17+
c=Channel{Int}(Inf)
18+
@test eltype(c) == Int
19+
pvals = map(i->put!(c,i), 1:10^6)
20+
tvals = Int[take!(c) for i in 1:10^6]
21+
@test pvals == tvals
22+
23+
@test_throws MethodError Channel()
24+
@test_throws ArgumentError Channel(-1)
25+
@test_throws InexactError Channel(1.5)
26+
27+
# Test multiple concurrent put!/take! on a channel for different sizes
28+
function testcpt(sz)
29+
c = Channel{Int}(sz)
30+
size = 0
31+
inc() = size += 1
32+
dec() = size -= 1
33+
@sync for i = 1:10^4
34+
@async (sleep(rand()); put!(c, i); inc())
35+
@async (sleep(rand()); take!(c); dec())
36+
end
37+
@test size == 0
38+
end
39+
testcpt(0)
40+
testcpt(1)
41+
testcpt(32)
42+
testcpt(Inf)
43+
44+
# Test multiple "for" loops waiting on the same channel which
45+
# is closed after adding a few elements.
46+
c=Channel(32)
47+
results=[]
48+
@sync begin
49+
for i in 1:20
50+
@async for i in c
51+
push!(results, i)
52+
end
53+
end
54+
sleep(1.0)
55+
for i in 1:5
56+
put!(c,i)
57+
end
58+
close(c)
59+
end
60+
@test sum(results) == 15
61+
62+
# Testing timedwait on multiple channels
63+
@sync begin
64+
rr1 = Channel(1)
65+
rr2 = Channel(1)
66+
rr3 = Channel(1)
67+
68+
callback() = all(map(isready, [rr1, rr2, rr3]))
69+
# precompile functions which will be tested for execution time
70+
@test !callback()
71+
@test timedwait(callback, 0.0) === :timed_out
72+
73+
@async begin sleep(0.5); put!(rr1, :ok) end
74+
@async begin sleep(1.0); put!(rr2, :ok) end
75+
@async begin sleep(2.0); put!(rr3, :ok) end
76+
77+
tic()
78+
timedwait(callback, 1.0)
79+
et=toq()
80+
# assuming that 0.5 seconds is a good enough buffer on a typical modern CPU
81+
try
82+
@test (et >= 1.0) && (et <= 1.5)
83+
@test !isready(rr3)
84+
catch
85+
warn("timedwait tests delayed. et=$et, isready(rr3)=$(isready(rr3))")
86+
end
87+
@test isready(rr1)
88+
end

test/choosetests.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ function choosetests(choices = [])
3333
"markdown", "base64", "serialize", "misc", "threads",
3434
"enums", "cmdlineargs", "i18n", "workspace", "libdl", "int",
3535
"checked", "intset", "floatfuncs", "compile", "parallel", "inline",
36-
"boundscheck", "error", "ambiguous", "cartesian", "asmvariant"
36+
"boundscheck", "error", "ambiguous", "cartesian", "asmvariant",
37+
"channels"
3738
]
3839

3940
if Base.USE_GPL_LIBS

test/parallel_exec.jl

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -512,66 +512,6 @@ workloads = Int[sum(ids .== i) for i in 2:nprocs()]
512512
# @parallel reduction should work even with very short ranges
513513
@test @parallel(+, for i=1:2; i; end) == 3
514514

515-
# Testing timedwait on multiple channels
516-
@sync begin
517-
rr1 = Channel()
518-
rr2 = Channel()
519-
rr3 = Channel()
520-
521-
callback() = all(map(isready, [rr1, rr2, rr3]))
522-
# precompile functions which will be tested for execution time
523-
@test !callback()
524-
@test timedwait(callback, 0.0) === :timed_out
525-
526-
@async begin sleep(0.5); put!(rr1, :ok) end
527-
@async begin sleep(1.0); put!(rr2, :ok) end
528-
@async begin sleep(2.0); put!(rr3, :ok) end
529-
530-
tic()
531-
timedwait(callback, 1.0)
532-
et=toq()
533-
# assuming that 0.5 seconds is a good enough buffer on a typical modern CPU
534-
try
535-
@test (et >= 1.0) && (et <= 1.5)
536-
@test !isready(rr3)
537-
catch
538-
warn("timedwait tests delayed. et=$et, isready(rr3)=$(isready(rr3))")
539-
end
540-
@test isready(rr1)
541-
end
542-
543-
# Test multiple concurrent put!/take! on a channel
544-
function testcpt()
545-
c = Channel()
546-
size = 0
547-
inc() = size += 1
548-
dec() = size -= 1
549-
@sync for i = 1:10^4
550-
@async (sleep(rand()); put!(c, i); inc())
551-
@async (sleep(rand()); take!(c); dec())
552-
end
553-
@test size == 0
554-
end
555-
testcpt()
556-
557-
# Test multiple "for" loops waiting on the same channel which
558-
# is closed after adding a few elements.
559-
c=Channel()
560-
results=[]
561-
@sync begin
562-
for i in 1:20
563-
@async for i in c
564-
push!(results, i)
565-
end
566-
end
567-
sleep(1.0)
568-
for i in 1:5
569-
put!(c,i)
570-
end
571-
close(c)
572-
end
573-
@test sum(results) == 15
574-
575515
@test_throws ArgumentError sleep(-1)
576516
@test_throws ArgumentError timedwait(()->false, 0.1, pollint=-0.5)
577517

0 commit comments

Comments
 (0)