-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
deprecate produce, consume and task iteration #19841
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ type Channel{T} <: AbstractChannel | |
cond_take::Condition # waiting for data to become available | ||
cond_put::Condition # waiting for a writeable slot | ||
state::Symbol | ||
excp::Nullable{Exception} # Exception to be thrown when state != :open | ||
|
||
data::Array{T,1} | ||
sz_max::Int # maximum size of channel | ||
|
@@ -39,7 +40,7 @@ type Channel{T} <: AbstractChannel | |
if sz < 0 | ||
throw(ArgumentError("Channel size must be either 0, a positive integer or Inf")) | ||
end | ||
new(Condition(), Condition(), :open, Array{T}(0), sz, Array{Condition}(0)) | ||
new(Condition(), Condition(), :open, Nullable{Exception}(), Array{T}(0), sz, Array{Condition}(0)) | ||
end | ||
|
||
# deprecated empty constructor | ||
|
@@ -53,13 +54,82 @@ end | |
|
||
Channel(sz) = Channel{Any}(sz) | ||
|
||
# special constructors | ||
""" | ||
Channel(func::Function; ctype=Any, csize=0, taskref=nothing) | ||
Creates a new task from `func`, binds it to a new channel of type | ||
`ctype` and size `csize`, schedules the task, all in a single call. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and schedules the task |
||
`func` must accept the bound channel as its only argument. | ||
If you need a reference to the created task, pass a `Ref{Task}` object via | ||
keyword argument `taskref`. | ||
Returns a Channel. | ||
```jldoctest | ||
julia> chnl = Channel(c->foreach(i->put!(c,i), 1:4)); | ||
julia> @show typeof(chnl); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
typeof(chnl) = Channel{Any} | ||
julia> for i in chnl | ||
@show i | ||
end; | ||
i = 1 | ||
i = 2 | ||
i = 3 | ||
i = 4 | ||
``` | ||
An example of referencing the created task: | ||
```jldoctest | ||
julia> taskref = Ref{Task}(); | ||
julia> chnl = Channel(c->(@show take!(c)); taskref=taskref); | ||
julia> task = taskref[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a really awkward API There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Think of it is pass-by-reference in C. Most times since the channel is bound to the task, the user will not need the task object. But if required, can be retrieved. The alternative would be to export a new function which would return a tuple of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Julia isn't C, I can't think of any Julia APIs that do this. A separate function for this would make more sense, might not even need to be exported if it isn't used very often. Is there any other way of identifying whether or not a Channel has a task bound to it? Something like being able to ask for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, no Julia APIs do this yet, but it may catch on. If a need is felt for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I seriously hope not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. calling bind separately is sufficient though? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but you typically need to ensure that the Task is bound before it is scheduled to handle early errors in the task. For example,
can lead to the for-loop missing any exception thrown by A safer way is
With the special Channel constructor, the above code block is equivalent to
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not saying the latter isn't useful, but if needing to use the task afterwards is uncommon I think the former pattern is clearer and more idiomatic than passing a reference argument. |
||
julia> @show istaskdone(task); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
istaskdone(task) = false | ||
julia> put!(chnl, "Hello"); | ||
take!(c) = "Hello" | ||
julia> @show istaskdone(task); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
istaskdone(task) = true | ||
``` | ||
""" | ||
function Channel(func::Function; ctype=Any, csize=0, taskref=nothing) | ||
chnl = Channel{ctype}(csize) | ||
task = Task(()->func(chnl)) | ||
bind(chnl,task) | ||
schedule(task) | ||
yield() | ||
|
||
isa(taskref, Ref{Task}) && (taskref.x = task) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't |
||
return chnl | ||
end | ||
|
||
|
||
|
||
# deprecated empty constructor | ||
Channel() = Channel{Any}() | ||
|
||
closed_exception() = InvalidStateException("Channel is closed.", :closed) | ||
|
||
isbuffered(c::Channel) = c.sz_max==0 ? false : true | ||
|
||
function check_channel_state(c::Channel) | ||
if !isopen(c) | ||
!isnull(c.excp) && throw(get(c.excp)) | ||
throw(closed_exception()) | ||
end | ||
end | ||
""" | ||
close(c::Channel) | ||
|
@@ -70,11 +140,110 @@ Closes a channel. An exception is thrown by: | |
""" | ||
function close(c::Channel) | ||
c.state = :closed | ||
notify_error(c::Channel, closed_exception()) | ||
c.excp = Nullable{}(closed_exception()) | ||
notify_error(c) | ||
nothing | ||
end | ||
isopen(c::Channel) = (c.state == :open) | ||
|
||
""" | ||
bind(chnl::Channel, task::Task) | ||
Associates the lifetime of `chnl` with a task. | ||
Channel `chnl` is automatically closed when the task terminates. | ||
Any uncaught exception in the task is propagated to all waiters on `chnl`. | ||
The `chnl` object can be explicitly closed independent of task termination. | ||
Terminating tasks have no effect on already closed Channel objects. | ||
When a channel is bound to multiple tasks, the first task to terminate will | ||
close the channel. When multiple channels are bound to the same task, | ||
termination of the task will close all channels. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all of the bound channels, not absolutely all channels that might exist There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is obvious it refers to the "multiple channels" mentioned before. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it is not obvious, it sounds confusing since it's missing an article or qualifier. |
||
```jldoctest | ||
julia> c = Channel(0); | ||
julia> task = @schedule foreach(i->put!(c, i), 1:4); | ||
julia> bind(c,task); | ||
julia> for i in c | ||
@show i | ||
end; | ||
i = 1 | ||
i = 2 | ||
i = 3 | ||
i = 4 | ||
julia> @show isopen(c); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
isopen(c) = false | ||
``` | ||
```jldoctest | ||
julia> c = Channel(0); | ||
julia> task = @schedule (put!(c,1);error("foo")); | ||
julia> bind(c,task); | ||
julia> take!(c); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not suppressing the output would be worthwhile here |
||
julia> put!(c,1); | ||
ERROR: foo | ||
Stacktrace: | ||
[1] check_channel_state(::Channel{Any}) at ./channels.jl:129 | ||
[2] put!(::Channel{Any}, ::Int64) at ./channels.jl:247 | ||
``` | ||
""" | ||
function bind(c::Channel, task::Task) | ||
ref = WeakRef(c) | ||
register_taskdone_hook(task, tsk->close_chnl_on_taskdone(tsk, ref)) | ||
c | ||
end | ||
|
||
""" | ||
channeled_tasks(n::Int, funcs...; ctypes=fill(Any,n), csizes=fill(0,n)) | ||
A convenience method to create `n` channels and bind them to tasks started | ||
from the provided functions in a single call. Each `func` must accept `n` arguments | ||
which are the created channels. Channel types and sizes may be specified via | ||
keyword arguments `ctypes` and `csizes` respectively. If unspecified, all channels are | ||
of type `Channel{Any}(0)`. | ||
Returns a tuple, `(Array{Channel}, Array{Task})`, of the created channels and tasks. | ||
""" | ||
function channeled_tasks(n::Int, funcs...; ctypes=fill(Any,n), csizes=fill(0,n)) | ||
@assert length(csizes) == n | ||
@assert length(ctypes) == n | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if users are ever going to call this, then these are user errors and should get better error messages instead of being asserts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not exported yet. I found it useful and is used in one of the tests. Have left it in for now. The interface will probably be modified a bit before this is exposed. |
||
|
||
chnls = map(i->Channel{ctypes[i]}(csizes[i]), 1:n) | ||
tasks=Task[Task(()->f(chnls...)) for f in funcs] | ||
|
||
# bind all tasks to all channels and schedule them | ||
foreach(t -> foreach(c -> bind(c,t), chnls), tasks) | ||
foreach(t->schedule(t), tasks) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
yield() # Allow scheduled tasks to run | ||
|
||
return (chnls, tasks) | ||
end | ||
|
||
function close_chnl_on_taskdone(t::Task, ref::WeakRef) | ||
if ref.value !== nothing | ||
c = ref.value | ||
!isopen(c) && return | ||
if istaskfailed(t) | ||
c.state = :closed | ||
c.excp = Nullable{Exception}(task_result(t)) | ||
notify_error(c) | ||
else | ||
close(c) | ||
end | ||
end | ||
end | ||
|
||
type InvalidStateException <: Exception | ||
msg::AbstractString | ||
state::Symbol | ||
|
@@ -89,7 +258,7 @@ For unbuffered channels, blocks until a [`take!`](@ref) is performed by a differ | |
task. | ||
""" | ||
function put!(c::Channel, v) | ||
!isopen(c) && throw(closed_exception()) | ||
check_channel_state(c) | ||
isbuffered(c) ? put_buffered(c,v) : put_unbuffered(c,v) | ||
end | ||
|
||
|
@@ -98,7 +267,9 @@ function put_buffered(c::Channel, v) | |
wait(c.cond_put) | ||
end | ||
push!(c.data, v) | ||
notify(c.cond_take, nothing, true, false) # notify all, since some of the waiters may be on a "fetch" call. | ||
|
||
# notify all, since some of the waiters may be on a "fetch" call. | ||
notify(c.cond_take, nothing, true, false) | ||
v | ||
end | ||
|
||
|
@@ -108,7 +279,7 @@ function put_unbuffered(c::Channel, v) | |
wait(c.cond_put) | ||
end | ||
cond_taker = shift!(c.takers) | ||
notify(cond_taker, v, false, false) | ||
notify(cond_taker, v, false, false) > 0 && yield() | ||
v | ||
end | ||
|
||
|
@@ -148,7 +319,7 @@ shift!(c::Channel) = take!(c) | |
|
||
# 0-size channel | ||
function take_unbuffered(c::Channel) | ||
!isopen(c) && throw(closed_exception()) | ||
check_channel_state(c) | ||
cond_taker = Condition() | ||
push!(c.takers, cond_taker) | ||
notify(c.cond_put, nothing, false, false) | ||
|
@@ -178,7 +349,7 @@ n_avail(c::Channel) = isbuffered(c) ? length(c.data) : n_waiters(c.cond_put) | |
|
||
function wait(c::Channel) | ||
while !isready(c) | ||
!isopen(c) && throw(closed_exception()) | ||
check_channel_state(c) | ||
wait(c.cond_take) | ||
end | ||
nothing | ||
|
@@ -189,19 +360,20 @@ function notify_error(c::Channel, err) | |
notify_error(c.cond_put, err) | ||
foreach(x->notify_error(x, err), c.takers) | ||
end | ||
notify_error(c::Channel) = notify_error(c, get(c.excp)) | ||
|
||
eltype{T}(::Type{Channel{T}}) = T | ||
|
||
show(io::IO, c::Channel) = print(io, "$(typeof(c))(sz_max:$(c.sz_max),sz_curr:$(n_avail(c)))") | ||
|
||
type ChannelState{T} | ||
type ChannelIterState{T} | ||
hasval::Bool | ||
val::T | ||
ChannelState(x) = new(x) | ||
ChannelIterState(x) = new(x) | ||
end | ||
|
||
start{T}(c::Channel{T}) = ChannelState{T}(false) | ||
function done(c::Channel, state::ChannelState) | ||
start{T}(c::Channel{T}) = ChannelIterState{T}(false) | ||
function done(c::Channel, state::ChannelIterState) | ||
try | ||
# we are waiting either for more data or channel to be closed | ||
state.hasval && return false | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have been deprecated