monitor.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
  2. //
  3. // This program is free software: you can redistribute it and/or modify it
  4. // under the terms of the GNU General Public License as published by the Free
  5. // Software Foundation, either version 3 of the License, or (at your option)
  6. // any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful, but WITHOUT
  9. // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  11. // more details.
  12. //
  13. // You should have received a copy of the GNU General Public License along
  14. // with this program. If not, see <http://www.gnu.org/licenses/>.
  15. package main
  16. import (
  17. "bufio"
  18. "io"
  19. "os"
  20. "os/exec"
  21. "os/signal"
  22. "path/filepath"
  23. "strings"
  24. "sync"
  25. "syscall"
  26. "time"
  27. )
  28. var (
  29. stdoutFirstLines []string // The first 10 lines of stdout
  30. stdoutLastLines []string // The last 50 lines of stdout
  31. stdoutMut sync.Mutex
  32. )
  33. const (
  34. countRestarts = 5
  35. loopThreshold = 15 * time.Second
  36. )
  37. func monitorMain() {
  38. os.Setenv("STNORESTART", "yes")
  39. os.Setenv("STMONITORED", "yes")
  40. l.SetPrefix("[monitor] ")
  41. args := os.Args
  42. var restarts [countRestarts]time.Time
  43. sign := make(chan os.Signal, 1)
  44. sigTerm := syscall.Signal(0xf)
  45. signal.Notify(sign, os.Interrupt, sigTerm, os.Kill)
  46. for {
  47. if t := time.Since(restarts[0]); t < loopThreshold {
  48. l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
  49. os.Exit(exitError)
  50. }
  51. copy(restarts[0:], restarts[1:])
  52. restarts[len(restarts)-1] = time.Now()
  53. cmd := exec.Command(args[0], args[1:]...)
  54. stderr, err := cmd.StderrPipe()
  55. if err != nil {
  56. l.Fatalln(err)
  57. }
  58. stdout, err := cmd.StdoutPipe()
  59. if err != nil {
  60. l.Fatalln(err)
  61. }
  62. l.Infoln("Starting syncthing")
  63. err = cmd.Start()
  64. if err != nil {
  65. l.Fatalln(err)
  66. }
  67. // Let the next child process know that this is not the first time
  68. // it's starting up.
  69. os.Setenv("STRESTART", "yes")
  70. stdoutMut.Lock()
  71. stdoutFirstLines = make([]string, 0, 10)
  72. stdoutLastLines = make([]string, 0, 50)
  73. stdoutMut.Unlock()
  74. go copyStderr(stderr)
  75. go copyStdout(stdout)
  76. exit := make(chan error)
  77. go func() {
  78. exit <- cmd.Wait()
  79. }()
  80. select {
  81. case s := <-sign:
  82. l.Infof("Signal %d received; exiting", s)
  83. cmd.Process.Kill()
  84. <-exit
  85. return
  86. case err = <-exit:
  87. if err == nil {
  88. // Successfull exit indicates an intentional shutdown
  89. return
  90. } else if exiterr, ok := err.(*exec.ExitError); ok {
  91. if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
  92. switch status.ExitStatus() {
  93. case exitUpgrading:
  94. // Restart the monitor process to release the .old
  95. // binary as part of the upgrade process.
  96. l.Infoln("Restarting monitor...")
  97. os.Setenv("STNORESTART", "")
  98. err := exec.Command(args[0], args[1:]...).Start()
  99. if err != nil {
  100. l.Warnln("restart:", err)
  101. }
  102. return
  103. }
  104. }
  105. }
  106. }
  107. l.Infoln("Syncthing exited:", err)
  108. time.Sleep(1 * time.Second)
  109. }
  110. }
  111. func copyStderr(stderr io.ReadCloser) {
  112. br := bufio.NewReader(stderr)
  113. var panicFd *os.File
  114. for {
  115. line, err := br.ReadString('\n')
  116. if err != nil {
  117. return
  118. }
  119. if panicFd == nil {
  120. os.Stderr.WriteString(line)
  121. if strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:") {
  122. panicFd, err = os.Create(filepath.Join(confDir, time.Now().Format("panic-20060102-150405.log")))
  123. if err != nil {
  124. l.Warnln("Create panic log:", err)
  125. continue
  126. }
  127. l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
  128. l.Warnln("Please create an issue at https://github.com/syncthing/syncthing/issues/ with the panic log attached")
  129. panicFd.WriteString("Panic at " + time.Now().Format(time.RFC1123) + "\n")
  130. stdoutMut.Lock()
  131. for _, line := range stdoutFirstLines {
  132. panicFd.WriteString(line)
  133. }
  134. panicFd.WriteString("...\n")
  135. for _, line := range stdoutLastLines {
  136. panicFd.WriteString(line)
  137. }
  138. }
  139. }
  140. if panicFd != nil {
  141. panicFd.WriteString(line)
  142. }
  143. }
  144. }
  145. func copyStdout(stderr io.ReadCloser) {
  146. br := bufio.NewReader(stderr)
  147. for {
  148. line, err := br.ReadString('\n')
  149. if err != nil {
  150. return
  151. }
  152. stdoutMut.Lock()
  153. if len(stdoutFirstLines) < cap(stdoutFirstLines) {
  154. stdoutFirstLines = append(stdoutFirstLines, line)
  155. }
  156. if l := len(stdoutLastLines); l == cap(stdoutLastLines) {
  157. stdoutLastLines = stdoutLastLines[:l-1]
  158. }
  159. stdoutLastLines = append(stdoutLastLines, line)
  160. stdoutMut.Unlock()
  161. os.Stdout.WriteString(line)
  162. }
  163. }