Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Added

- The handle types are public API, with docstrings: the abstract types to annotate with (`AbstractPetscVec`, `AbstractPetscMat`, `AbstractPetscDM`, `AbstractKSP`, `AbstractPC`, `AbstractSNES`, `AbstractTS`, `AbstractPetscOptions`, `AbstractIS`, `AbstractAO`, `AbstractPF`, `AbstractTao`) and the handles `PetscDM`, `PC`, `IS`, `AO`, `PF` and `Tao`, all reachable as `PETSc.X`. `PETSc.realtype(petsclib)` gives the library's real type.
- A `PetscError` prints the C function the failing call went to and PETSc's message for it, for example `PetscError(56) from VecSetSizes: No support for this operation for this object type. Cannot change/reset vector sizes to 4 local 4 global after previously setting them to 3 local 3 global`, where it printed `PetscError(56)`. The fields `func` and `message` hold the two; `PetscError(code)` still works.

### Fixed

Expand Down
68 changes: 66 additions & 2 deletions src/LibPETSc_const.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,74 @@

const PetscErrorCode = Cint

"""
PetscError(code)

A PETSc call returned the nonzero error code `code`. `func` is the C function
the failing call went to (`Symbol("")` when not known), and `message` is PETSc's
text for the error: its generic description followed by the specific message
set where the error started, deepest in the call chain.

# External Links
$(_doc_external("Sys/PetscErrorMessage"))
"""
struct PetscError <: Exception
code::PetscErrorCode
func::Symbol
message::String
end
PetscError(code) = PetscError(code, Symbol(""), "")

function Base.showerror(io::IO, err::PetscError)
print(io, "PetscError(", err.code, ")")
isempty(String(err.func)) || print(io, " from ", err.func)
isempty(err.message) || print(io, ": ", err.message)
return nothing
end

# PETSc's text for error `code`, read right after the failing call: PETSc keeps
# the specific message only until the next error. `library` is what the failing
# `ccall` named. Building an exception must not throw, so a library that can't
# be reached gives an empty message.
@noinline function petsc_error(code, func::Symbol, library)
message = try
petsc_error_message(code, library)
catch
""
end
return PetscError(code, func, message)
end

function petsc_error_message(code, library::AbstractString)
# The library is already loaded, so this only looks up its handle
handle = Libdl.dlopen(library)
fptr = Libdl.dlsym(handle, :PetscErrorMessage)
text, specific = Ref{Ptr{Cchar}}(C_NULL), Ref{Ptr{Cchar}}(C_NULL)
ccall(fptr, PetscErrorCode, (PetscErrorCode, Ptr{Ptr{Cchar}}, Ptr{Ptr{Cchar}}),
code, text, specific) == 0 || return ""
parts = String[unsafe_string(p[]) for p in (text, specific) if p[] != C_NULL]
return join(filter(!isempty, strip.(parts)), ". ")
end
petsc_error_message(_, _) = ""

# The function name and library of `ccall((:Name, library), ...)`, or nothing
# when the call goes through a function pointer
function ccall_target(expr)
Meta.isexpr(expr, :call) && expr.args[1] === :ccall || return nothing
target = expr.args[2]
Meta.isexpr(target, :tuple) && length(target.args) == 2 || return nothing
name = target.args[1]
name isa QuoteNode && name.value isa Symbol || return nothing
return name.value, target.args[2]
end

# Throw a `PetscError` when the PETSc call `expr` returns a nonzero code
macro chk(expr)
:((errcode = $(esc(expr))) == 0 || throw(PetscError(errcode)))
end
target = ccall_target(expr)
if target === nothing
return :((errcode = $(esc(expr))) == 0 || throw(PetscError(errcode)))
end
name, library = target
return :((errcode = $(esc(expr))) == 0 ||
throw(petsc_error(errcode, $(QuoteNode(name)), $(esc(library)))))
end
38 changes: 38 additions & 0 deletions test/core/errors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,41 @@ MPI.Initialized() || MPI.Init()
PETSc.finalize(petsclib)
end
end

@testset "PetscError names the C function and PETSc's message" begin
petsclib = PETSc.petsclibs[1]
PETSc.initialize(petsclib)
PetscInt = petsclib.PetscInt
LibPETSc = PETSc.LibPETSc

v = PETSc.PetscVec(petsclib, 3)
err = try
LibPETSc.VecSetSizes(petsclib, v, PetscInt(4), PetscInt(4))
catch e
e
end
@test err isa LibPETSc.PetscError
@test err.code == 56 # PETSC_ERR_SUP
@test err.func === :VecSetSizes
@test occursin("Cannot change/reset vector sizes", err.message)
@test_throws "PetscError(56) from VecSetSizes: No support for this operation" throw(err)

# An error raised deep inside PETSc is named after the call Julia made
err = try
PETSc.DMDA(petsclib, MPI.COMM_SELF, (PETSc.DM_BOUNDARY_NONE,), (10,), 1, 1; da_refine = "abc")
catch e
e
end
@test err.func === :DMSetFromOptions
@test occursin("Input string abc has no integer value", err.message)

PETSc.destroy!(v)
PETSc.finalize(petsclib)

# The message is captured when the error is raised, so it still prints
# once the library is finalized
@test occursin("Input string abc", sprint(showerror, err))
# A code alone prints as it always has
@test sprint(showerror, LibPETSc.PetscError(63)) == "PetscError(63)"
@test LibPETSc.PetscError(63).code == 63
end
Loading