@@ -20,12 +20,15 @@ import (
2020 "net"
2121 "net/http"
2222 "os"
23+ "os/signal"
24+ "path/filepath"
2325 "slices"
2426 "sort"
2527 "strconv"
2628 "strings"
2729 "sync"
2830 "sync/atomic"
31+ "syscall"
2932 "time"
3033
3134 "github.com/KimMachineGun/automemlimit/memlimit"
@@ -59,6 +62,8 @@ var ErrSuspendingDisallowed = errors.New("database does not allow suspending")
5962
6063var allServers = []serverType {publicServer , adminServer , metricsServer , diagnosticServer }
6164
65+ const stackFilePrefix = "sg_stack_trace_"
66+
6267// serverInfo represents an instance of an HTTP server from sync gateway
6368type serverInfo struct {
6469 server * http.Server // server is the HTTP server instance
@@ -204,9 +209,33 @@ func NewServerContext(ctx context.Context, config *StartupConfig, persistentConf
204209
205210 sc .startStatsLogger (ctx )
206211
212+ sc .registerSignalHandlerForStackTrace (ctx )
213+
207214 return sc
208215}
209216
217+ // registerSignalHandlerForStackTrace will register a signal handler to capture stack traces
218+ // - SIGUSR1 causes Sync Gateway to record a stack trace of all running goroutines.
219+ func (sc * ServerContext ) registerSignalHandlerForStackTrace (ctx context.Context ) {
220+ signalChannel := make (chan os.Signal , 1 )
221+ signal .Notify (signalChannel , syscall .SIGUSR1 )
222+
223+ go func () {
224+ for sig := range signalChannel {
225+ base .InfofCtx (ctx , base .KeyAll , "Handling signal: %v" , sig )
226+ switch sig {
227+ case syscall .SIGUSR1 :
228+ // stack trace signal received
229+ currentTime := time .Now ()
230+ timestamp := currentTime .Format (time .RFC3339 )
231+ sc .logStackTraces (ctx , timestamp )
232+ default :
233+ // unhandled signal here
234+ }
235+ }
236+ }()
237+ }
238+
210239func (sc * ServerContext ) WaitForRESTAPIs (ctx context.Context ) error {
211240 timeout := 30 * time .Second
212241 interval := time .Millisecond * 100
@@ -1844,6 +1873,38 @@ func (sc *ServerContext) logStats(ctx context.Context) error {
18441873
18451874}
18461875
1876+ func (sc * ServerContext ) logStackTraces (ctx context.Context , timestamp string ) {
1877+
1878+ base .InfofCtx (ctx , base .KeyAll , "Collecting stack trace for all goroutines" )
1879+ stackTrace := base .GetStackTrace ()
1880+
1881+ // log to console
1882+ _ , _ = fmt .Fprintf (os .Stderr , "Stack trace:\n %s\n " , stackTrace )
1883+
1884+ filename := filepath .Join (sc .Config .Logging .LogFilePath , stackFilePrefix + timestamp + ".log" )
1885+ file , err := base .CreateFileInLoggingDirectory (filename )
1886+ defer func () {
1887+ err = file .Close ()
1888+ if err != nil {
1889+ base .WarnfCtx (ctx , "Error closing stack trace file %s: %v" , filename , err )
1890+ }
1891+ }()
1892+ if err != nil {
1893+ base .DebugfCtx (ctx , base .KeyAll , "Error opening stack trace file %s: %v" , filename , err )
1894+ }
1895+
1896+ _ , err = file .WriteString (fmt .Sprintf ("Stack trace:\n %s\n " , stackTrace ))
1897+ if err != nil {
1898+ base .DebugfCtx (ctx , base .KeyAll , "Error writing stack trace to file %s: %v" , filename , err )
1899+ }
1900+
1901+ rotatePath := filepath .Join (sc .Config .Logging .LogFilePath , stackFilePrefix + "*.log" )
1902+ err = base .RotateProfilesIfNeeded (rotatePath )
1903+ if err != nil {
1904+ base .DebugfCtx (ctx , base .KeyAll , "Error rotating stack trace files in path %s: %v" , rotatePath , err )
1905+ }
1906+ }
1907+
18471908func (sc * ServerContext ) logNetworkInterfaceStats (ctx context.Context ) {
18481909
18491910 if err := sc .statsContext .addPublicNetworkInterfaceStatsForHostnamePort (sc .Config .API .PublicInterface ); err != nil {
0 commit comments