A caution about cgo's error returns for errno
Go's cgo system for calling C functions offers a very convenient feature. As the documentation puts it:
Any C function (even void functions) may be called in a multiple assignment context to retrieve both the return value (if any) and the C errno variable as an error [...]
Reading this, you may be tempted to write more or less standard Go error-handling code like the following:
kcid, err := C.kstat_chain_update(t.kc) if err != nil { return err }
This code is a potential mistake. Unless the documentation for the
C function you're calling says so explicitly, there is no guarantee
that errno
is zero on success. If the function returns success
but errno
is non-zero, cgo will dutifully generate a non-nil
error return from it and then your Go code will bail out with an
error that isn't.
This is not cgo's fault. Cgo has no magic knowledge of what C
function return values are and aren't errors, so all it can do is
exactly what it said it was going to do; if errno
is non zero,
you get an error version of it. This is just a C API issue (that
ultimately comes about because errno
is both an implicit return
and global state). You'd never write code like this in Go, where
'only return non-nil error on actual errors' is well established,
but we're stuck with the C API that we actually have instead of the
Go-like one we'd like. So we have to deal with it, which means
checking return values explicitly.
(In this case the real 'there has been an error' marker is a kcid
return value of -1. I actually hit an irregular test failure when
my code was just checking err
, which is how I re-stubbed my toe
on this particular C API issue.)
PS: the ultimate cause of this is that C code often doesn't explicitly
set errno
to zero on success but instead leaves it alone, which
means errno
can wind up set from whatever internal system call or
library routine last failed and set it. There are many possibilities
for how this can happen; a classical one is seeing ENOTTY
from
something checking to see if the file descriptor it is writing to
is a TTY and so should be in line-buffered mode.
(In my case I saw EAGAIN
, which I believe was the OmniOS kernel telling kstat_chain_update()
that the buffer it had been given wasn't large enough, please try
again with a bigger one.)
|
|