namspill
ℹ️ If you don't switch Linux-kernel namespaces and don't use a Go module that
does, you most probably don't need to fret about namspill
.
In the spirit of leak tests for
goroutines
and file descriptors, namspill
tests for
Linux kernel namespaces unintendedly "leaking" between goroutines due to
incorrect OS thread locking (or
rather, the lack thereof) when switching namespaces in a Go program.
namspill
is primarily designed to integrate smoothly with the
Ginkgo BDD testing framework and the
Gomega matcher/assertion library. (It may be
used also outside Ginkgo/Gomega, but such usage is out of scope.)
Install
go get github.com/thediveo/namspill
Usage
In its simplest form, just after each test gather information about the
namespaces of the tasks (threads) of the program and check that no namespaces
have leaked, with some tasks being attached to other namespaces than the rest of
the tasks.
As namspill
exports only very few symbols and is intended to extend the Gomega
DSL, you might want to dot-import it.
import . "github.com/thediveo/namspill"
AfterEach(func() {
// You might want to use Eventually(Tasks)... in case you don't
// have a preceeding Eventually(Goroutines)... that waits for
// the Goroutines (and thus Linux threads/tasks) to settle first.
Expect(Tasks()).To(BeUniformlyNamespaced())
})
Normally, you shouldn't then see anything unless there is a problem with threads
attached to other Linux-kernel namespaces when they shouldn't. In this case, the
BeUniformlyNamespaced
matcher will fail and show you the list of tasks with
the namespaces they're attached to.
Unfortunately, there's no way to show you which goroutine might have caused this
nor the call site (and more importantly, the call stack) where the namespace
switch happened.
Background
namspill
mostly serves as a canary to (quickly) detect forgetting to switch
back namespaces before unlocking OS threads so that they can be freely scheduled
to any arbitrary goroutine.
The Initial Thread M0 Is Special
Additionally, namespill
checks safeguard against alternatively not terminating
thread-locked goroutines. And that is where there's a catch when it comes to the
Go scheduler: if a Go program's initial thread ("M0" in Go scheduler parlance)
ends up being locked to a (non-main) goroutine and this goroutine exits ... then
the initial thread doesn't get terminated, because that usually has unwanted
side-effects on several operating systems. Instead, the Go scheduler "wedges"
the initial thread and never schedules any goroutine to it again.
This situation will (correctly) trigger failed namspill
assertions. To avoid
the initial thread getting wedged, simply lock it in an init
function to the
initial/main goroutine, so it never ends up getting scheduled on any other
goroutine (that might be subjected to locking it to a thread and then
terminating it while being locked):
func init() {
runtime.LockOSThread()
}
References
For further background information, please see the following references:
Make Targets
make
: lists all targets.
make coverage
: runs all tests with coverage and then updates the coverage
badge in README.md
.
make pkgsite
: installs x/pkgsite
, as
well as the browser-sync
and
nodemon
npm packages first, if not
already done so. Then runs the pkgsite
and hot reloads it whenever the
documentation changes.
make report
: installs
@gojp/goreportcard
if not yet done
so and then runs it on the code base.
make test
: runs all tests.
Copyright and License
namspill
is Copyright 2022, 2022 Harald Albrecht, and licensed under the
Apache License, Version 2.0.