Skip to content

Commit da165d7

Browse files
committed
Better docstring for at-spawn
1 parent 3c14a49 commit da165d7

File tree

1 file changed

+82
-54
lines changed

1 file changed

+82
-54
lines changed

src/thunk.jl

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -280,54 +280,6 @@ function Base.showerror(io::IO, ex::ThunkFailedException)
280280
print(io, " This Thunk: $t_str")
281281
end
282282

283-
"""
284-
spawn(f, args...; kwargs...) -> DTask
285-
286-
Spawns a task with `f` as the function, `args` as the arguments, and `kwargs`
287-
as the keyword arguments, returning an `DTask`. Uses a scheduler running
288-
in the background to execute code.
289-
"""
290-
function spawn(f, args...; kwargs...)
291-
@nospecialize f args kwargs
292-
293-
# Get all options and determine which propagate beyond this task
294-
options = get_options()
295-
propagates = get(options, :propagates, ())
296-
propagates = Tuple(unique(Symbol[propagates..., keys(options)...]))
297-
if length(args) >= 1 && first(args) isa Options
298-
spawn_options = first(args).options
299-
options = merge(options, spawn_options)
300-
args = args[2:end]
301-
end
302-
303-
# Wrap f in a Chunk if necessary
304-
processor = haskey(options, :processor) ? options.processor : nothing
305-
scope = haskey(options, :scope) ? options.scope : nothing
306-
if !isnothing(processor) || !isnothing(scope)
307-
f = tochunk(f,
308-
something(processor, get_options(:processor, OSProc())),
309-
something(scope, get_options(:scope, DefaultScope())))
310-
end
311-
312-
# Process the args and kwargs into Pair form
313-
args_kwargs = args_kwargs_to_pairs(args, kwargs)
314-
315-
# Get task queue, and don't let it propagate
316-
task_queue = get_options(:task_queue, EagerTaskQueue())
317-
options = NamedTuple(filter(opt->opt[1] != :task_queue, Base.pairs(options)))
318-
propagates = filter(prop->prop != :task_queue, propagates)
319-
options = merge(options, (;propagates))
320-
321-
# Construct task spec and handle
322-
spec = EagerTaskSpec(f, args_kwargs, options)
323-
task = eager_spawn(spec)
324-
325-
# Enqueue the task into the task queue
326-
enqueue!(task_queue, spec=>task)
327-
328-
return task
329-
end
330-
331283
"""
332284
@par [opts] f(args...; kwargs...) -> Thunk
333285
@@ -358,12 +310,40 @@ macro par(exs...)
358310
end
359311

360312
"""
361-
@spawn [opts] f(args...) -> Thunk
362-
363-
Convenience macro like `Dagger.@par`, but eagerly executed from the moment it's
364-
called (equivalent to `spawn`).
365-
366-
See the docs for `@par` for more information and usage examples.
313+
Dagger.@spawn [option=value]... f(args...; kwargs...) -> DTask
314+
315+
Spawns a Dagger `DTask` that will call `f(args...; kwargs...)`. This `DTask` is like a Julia `Task`, and has many similarities:
316+
- The `DTask` can be `wait`'d on and `fetch`'d from to see its final result
317+
- By default, the `DTask` will be automatically run on the first available compute resource
318+
- If all dependencies are satisfied, the `DTask` will be run as soon as possible
319+
- The `DTask` may be run in parallel with other `DTask`s, and the scheduler will automatically manage dependencies
320+
- If a `DTask` throws an exception, it will be propagated to any calls to `fetch`, but not to calls to `wait`
321+
322+
However, the `DTask` also has many key differences from a `Task`:
323+
- The `DTask` may run on any thread of any Julia process, and even on a remote machine, in your cluster (see `Distributed.addprocs`)
324+
- The `DTask` might automatically utilize GPUs or other accelerators, if available
325+
- If arguments to a `DTask` are also `DTask`s, then the scheduler will execute those arguments' `DTask`s first, before running the "downstream" task
326+
- If an argument to a `DTask` `t2` is a `DTask` `t1`, then the *result* of `t1` (gotten via `fetch(t1)`) will be passed to `t2` (no need for `t2` to call `fetch`!)
327+
- `DTask`s are generally expected to be defined "functionally", meaning that they should not mutate global state, mutate their arguments, or have side effects
328+
- `DTask`s are function call-focused, meaning that `Dagger.@spawn` expects a single function call, and not a block of code
329+
- All `DTask` arguments are expected to be safe to serialize and send to other Julia processes; if not, use the `scope` option or `Dagger.@mutable` to control execution location
330+
331+
Options to the `DTask` can be set before the call to `f` with key-value syntax, e.g.
332+
`Dagger.@spawn myopt=2 do_something(1, 3.0)`, which would set the option
333+
`myopt` to `2` for this task. Multiple options may be provided, which are
334+
specified like `Dagger.@spawn myopt=2 otheropt=4 do_something(1, 3.0)`.
335+
336+
These options control a variety of properties of the resulting `DTask`:
337+
- `scope`: The execution "scope" of the task, which determines where the task will run. By default, the task will run on the first available compute resource. If you have multiple compute resources, you can specify a scope to run the task on a specific resource. For example, `Dagger.@spawn scope=Dagger.scope(worker=2) do_something(1, 3.0)` would run `do_something(1, 3.0)` on worker 2.
338+
- `meta`: If `true`, instead of the scheduler automatically fetching values from other tasks, the raw `Chunk` objects will be passed to `f`. Useful for doing manual fetching or manipulation of `Chunk` references. Non-`Chunk` arguments are still passed as-is.
339+
340+
Other options exist; see `Dagger.Sch.ThunkOptions` for the full list.
341+
342+
This macro is a semi-thin wrapper around `Dagger.spawn` - it creates a call to
343+
`Dagger.spawn` on `f` with arguments `args` and keyword arguments `kwargs`, and
344+
also passes along any options in an `Options` struct. For example,
345+
`Dagger.@spawn myopt=2 do_something(1, 3.0)` would essentially become
346+
`Dagger.spawn(do_something, Dagger.Options(;myopt=2), 1, 3.0)`.
367347
"""
368348
macro spawn(exs...)
369349
opts = exs[1:end-1]
@@ -417,6 +397,54 @@ end
417397
_par(ex::Symbol; kwargs...) = esc(ex)
418398
_par(ex; kwargs...) = ex
419399

400+
"""
401+
Dagger.spawn(f, args...; kwargs...) -> DTask
402+
403+
Spawns a `DTask` that will call `f(args...; kwargs...)`. Also supports passing a
404+
`Dagger.Options` struct as the first argument to set task options. See
405+
`Dagger.@spawn` for more details on `DTask`s.
406+
"""
407+
function spawn(f, args...; kwargs...)
408+
@nospecialize f args kwargs
409+
410+
# Get all options and determine which propagate beyond this task
411+
options = get_options()
412+
propagates = get(options, :propagates, ())
413+
propagates = Tuple(unique(Symbol[propagates..., keys(options)...]))
414+
if length(args) >= 1 && first(args) isa Options
415+
spawn_options = first(args).options
416+
options = merge(options, spawn_options)
417+
args = args[2:end]
418+
end
419+
420+
# Wrap f in a Chunk if necessary
421+
processor = haskey(options, :processor) ? options.processor : nothing
422+
scope = haskey(options, :scope) ? options.scope : nothing
423+
if !isnothing(processor) || !isnothing(scope)
424+
f = tochunk(f,
425+
something(processor, get_options(:processor, OSProc())),
426+
something(scope, get_options(:scope, DefaultScope())))
427+
end
428+
429+
# Process the args and kwargs into Pair form
430+
args_kwargs = args_kwargs_to_pairs(args, kwargs)
431+
432+
# Get task queue, and don't let it propagate
433+
task_queue = get_options(:task_queue, EagerTaskQueue())
434+
options = NamedTuple(filter(opt->opt[1] != :task_queue, Base.pairs(options)))
435+
propagates = filter(prop->prop != :task_queue, propagates)
436+
options = merge(options, (;propagates))
437+
438+
# Construct task spec and handle
439+
spec = EagerTaskSpec(f, args_kwargs, options)
440+
task = eager_spawn(spec)
441+
442+
# Enqueue the task into the task queue
443+
enqueue!(task_queue, spec=>task)
444+
445+
return task
446+
end
447+
420448
persist!(t::Thunk) = (t.persist=true; t)
421449
cache_result!(t::Thunk) = (t.cache=true; t)
422450

0 commit comments

Comments
 (0)