From 15ec6030ffccdbddc06551039d9ebf6b86a5cfd1 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 15 Oct 2024 14:05:27 +0400 Subject: [PATCH] chore: add http/pprof server over unix socket for debug (#295) (#296) * chore: add http/pprof server over unix socket for debug * remove old pprof file without checking if it exists --------- Signed-off-by: Spike Curtis --- main.go | 1 + pprof_unix.go | 42 ++++++++++++++++++++++++++++++++++++++++++ pprof_windows.go | 6 ++++++ 3 files changed, 49 insertions(+) create mode 100644 pprof_unix.go create mode 100644 pprof_windows.go diff --git a/main.go b/main.go index 000436a3..a16c9951 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { + servePprof() plugin.Serve(&plugin.ServeOpts{ ProviderFunc: provider.New, }) diff --git a/pprof_unix.go b/pprof_unix.go new file mode 100644 index 00000000..717bc01b --- /dev/null +++ b/pprof_unix.go @@ -0,0 +1,42 @@ +//go:build !windows + +package main + +import ( + "net" + "net/http" + "net/http/pprof" + "os" +) + +// servePprof starts an HTTP server running the pprof goroutine handler on a local unix domain socket. As described in +// https://github.com/coder/coder/issues/14726 it appears this process is sometimes hanging, unable to exit cleanly, +// and this prevents additional Coder builds that try to reinstall this provider. A goroutine dump should allow us to +// determine what is hanging. +// +// This function is best-effort, and just returns early if we fail to set up the directory/listener. We don't want to +// block the normal functioning of the provider. +func servePprof() { + // Coder runs terraform in a per-build subdirectory of the work directory. The per-build subdirectory uses a + // generated name and is deleted at the end of a build, so we want to place our unix socket up one directory level + // in the provisionerd work directory, so we can connect to it from provisionerd. + err := os.Mkdir("../.coder", 0o700) + if err != nil && !os.IsExist(err) { + return + } + + // remove the old file, if it exists. It's probably from the last run of the provider + if err = os.Remove("../.coder/pprof"); err != nil && !os.IsNotExist(err) { + return + } + l, err := net.Listen("unix", "../.coder/pprof") + if err != nil { + return + } + mux := http.NewServeMux() + mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) + srv := http.Server{Handler: mux} + go srv.Serve(l) + // We just leave the server and domain socket up forever. Go programs exit when the `main()` function returns, so + // this won't block exiting, and it ensures the pprof server stays up for the entire lifetime of the provider. +} diff --git a/pprof_windows.go b/pprof_windows.go new file mode 100644 index 00000000..05cd3143 --- /dev/null +++ b/pprof_windows.go @@ -0,0 +1,6 @@ +//go:build windows + +package main + +// servePprof is not supported on Windows +func servePprof() {}