ContextManagers.jl
ContextManagers
— ModuleContextManagers
ContextManagers.jl provides composable resource management interface for Julia.
using ContextManagers: @with, opentemp, onexit
lck = ReentrantLock()
ch = Channel()
@with(
lck,
(path, io) = opentemp(),
onexit(lock(ch)) do _
unlock(ch)
println("Successfully unlocked!")
end,
) do
println(io, "Hello World")
end
# output
Successfully unlocked!
See also:
ContextManagers.@with
— Macro@with(
resource₁ = source₁,
resource₂ = source₂,
...
resourceₙ = sourceₙ,
) do
use(resource₁, resource₂, ..., resourceₙ)
end
Open resources, run the do block body, and cleanup the resources.
ContextManagers.with
— FunctionContextManagers.with(sources...) do resources...
use(resources...)
end
Open resources, run the do block body, and cleanup the resources.
Tools
ContextManagers.opentemp
— FunctionContextManagers.opentemp([parent]; kwargs...) -> tf
Create and open a temporary file. The path and the IO
object can be accessed through the properties .path
and .io
of the returned object tf
respectively. The positional and named arguments are passed to mktemp
. The file is automatically removed and the IO object is automatically closed when used with @with
or with
.
ContextManagers.opentempdir
— FunctionContextManagers.opentempdir(parent=tempdir(); kwargs...) -> td
Create aa temporary directory. The path can be accessed through the property .path
of the returned object td
. When this is used with @with
or with
, td
is unwrapped to a path
automatically. For example, in the do block of @with(paath = opentempdir()) do; ...; end
, a string path
is available.
ContextManagers.SharedResource
— TypeContextManagers.SharedResource(source)
Create a sharable resource that is closed once the "last" context using the shared resource is closed.
The source
itself should produce "thread-safe" API if this is shared between @spawn
ed task.
SharedResource
supports the following pattern
using Base.Threads: @spawn
using ContextManagers: @with, SharedResource
output = Int[]
@sync begin
ch = Channel()
@with(handle = SharedResource(ch)) do # (1) create a `handle`
for x in 1:3
context = open(handle) # (2) refcount++
@spawn begin
@with(ch = context) do # (3) obtain the `ch` value
put!(ch, x)
end # (4a) refcount--; maybe cleanup
end
end
end # (4b) refcount--; maybe cleanup
append!(output, ch)
end
sort!(output)
# output
3-element Vector{Int64}:
1
2
3
Extended help
(1) The underling source
is entered when SharedResource
is entered. A handle
to this shared resource can be obtained by entering the context of the SharedResource
.
(2) A context
for obtaining the value of the context of the original source
can be obtained by open
the handle
. When sharing the resource across tasks, it typically has to be done before spawning the task.
(3) The value
from the original source
can be obtained by entering the context
.
(4) The last shared context exitting the @with
block ends the original context. In the above pattern, the context of the source
may exit at the end
(4b) of the outer @with
if xs
is empty or all the child tasks exit first.
Notes
This is inspired by Nathaniel J. Smith's comment: https://github.com/python-trio/trio/issues/719#issuecomment-462119589
ContextManagers.IgnoreError
— TypeContextManagers.IgnoreError()
Ignore an error (if any).
Example
julia> using ContextManagers: @with, IgnoreError
julia> @with(
IgnoreError(),
) do
error("error")
end
Extended help
Note that IgnoreError
only ignores the error from the "inner" code. That is to say, in the following code, the error from the doblock
and the context managers c
and d
are ignored but not the error from the context managers a
and b
.
@with(
a,
b,
IgnoreError(),
c,
d,
) do
doblock
end
ContextManagers.onexit
— FunctionContextManagers.onexit(cleanup, x)
ContextManagers.onexit(cleanup)
Create a context manager that runs cleanup(x)
upon exit.
Example
julia> using ContextManagers: @with, onexit
julia> @with(
onexit(111) do x
@show x
end,
) do
end;
x = 111
ContextManagers.onfail
— FunctionContextManagers.onfail(cleanup, x)
ContextManagers.onfail(cleanup)
Create a context manager that runs cleanup(x)
upon unsuccessful exit.
Example
julia> using ContextManagers: @with, IgnoreError, onfail
julia> @with(
onfail(111) do x
@show x
end,
) do
end; # prints nothing
julia> @with(
IgnoreError(),
onfail(111) do x
@show x
end,
) do
error("error")
end;
x = 111
Interface
ContextManagers.maybeenter
— FunctionContextManagers.maybeenter(source) -> context or nothing
Start a context
managing the resource. Or return nothing
when source
does not implement the context manager interface.
Default implementation returns nothing
; i.e., no context manager interface.
ContextManagers.value
— FunctionContextManagers.value(context) -> resource
Default implementation is a pass-through (identity) function.
ContextManagers.exit
— FunctionContextManagers.exit(context)
ContextManagers.exit(context, err) -> nothing or ContextManagers.Handled()
Cleanup the context
. Default implementation is close
.
Roughly speaking,
@with(resource = f(args...)) do
use(resource)
end
is equivalent to
context = something(ContextManagers.maybeenter(source))
try
resource = ContextManagers.value(context)
use(resource)
finally
ContextManagers.exit(context)
end
In the two-argument version ContextManagers.exit(context, err)
(where err
is nothing
or an Exception
), the error can be suppressed by returning ContextManagers.Handled()
.