util.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "fmt"
  9. "path/filepath"
  10. "strings"
  11. "sync"
  12. "time"
  13. "github.com/pkg/errors"
  14. "github.com/syncthing/syncthing/lib/events"
  15. "github.com/syncthing/syncthing/lib/fs"
  16. "github.com/syncthing/syncthing/lib/ur"
  17. )
  18. type Holdable interface {
  19. Holders() string
  20. }
  21. func newDeadlockDetector(timeout time.Duration, evLogger events.Logger, fatal func(error)) *deadlockDetector {
  22. return &deadlockDetector{
  23. warnTimeout: timeout,
  24. fatalTimeout: 10 * timeout,
  25. lockers: make(map[string]sync.Locker),
  26. evLogger: evLogger,
  27. fatal: fatal,
  28. }
  29. }
  30. type deadlockDetector struct {
  31. warnTimeout, fatalTimeout time.Duration
  32. lockers map[string]sync.Locker
  33. evLogger events.Logger
  34. fatal func(error)
  35. }
  36. func (d *deadlockDetector) Watch(name string, mut sync.Locker) {
  37. d.lockers[name] = mut
  38. go func() {
  39. for {
  40. time.Sleep(d.warnTimeout / 4)
  41. done := make(chan struct{}, 1)
  42. go func() {
  43. mut.Lock()
  44. _ = 1 // empty critical section
  45. mut.Unlock()
  46. done <- struct{}{}
  47. }()
  48. d.watchInner(name, done)
  49. }
  50. }()
  51. }
  52. func (d *deadlockDetector) watchInner(name string, done chan struct{}) {
  53. warn := time.NewTimer(d.warnTimeout)
  54. fatal := time.NewTimer(d.fatalTimeout)
  55. defer func() {
  56. warn.Stop()
  57. fatal.Stop()
  58. }()
  59. select {
  60. case <-warn.C:
  61. failure := ur.FailureDataWithGoroutines(fmt.Sprintf("potential deadlock detected at %s (short timeout)", name))
  62. failure.Extra["timeout"] = d.warnTimeout.String()
  63. d.evLogger.Log(events.Failure, failure)
  64. case <-done:
  65. return
  66. }
  67. select {
  68. case <-fatal.C:
  69. err := fmt.Errorf("potential deadlock detected at %s (long timeout)", name)
  70. failure := ur.FailureDataWithGoroutines(err.Error())
  71. failure.Extra["timeout"] = d.fatalTimeout.String()
  72. others := d.otherHolders()
  73. failure.Extra["other-holders"] = others
  74. d.evLogger.Log(events.Failure, failure)
  75. d.fatal(err)
  76. // Give it a minute to shut down gracefully, maybe shutting down
  77. // can get out of the deadlock (or it's not really a deadlock).
  78. time.Sleep(time.Minute)
  79. panic(fmt.Sprintf("%v:\n%v", err, others))
  80. case <-done:
  81. }
  82. }
  83. func (d *deadlockDetector) otherHolders() string {
  84. var b strings.Builder
  85. for otherName, otherMut := range d.lockers {
  86. if otherHolder, ok := otherMut.(Holdable); ok {
  87. b.WriteString("===" + otherName + "===\n" + otherHolder.Holders() + "\n")
  88. }
  89. }
  90. return b.String()
  91. }
  92. // inWritableDir calls fn(path), while making sure that the directory
  93. // containing `path` is writable for the duration of the call.
  94. func inWritableDir(fn func(string) error, targetFs fs.Filesystem, path string, ignorePerms bool) error {
  95. dir := filepath.Dir(path)
  96. info, err := targetFs.Stat(dir)
  97. if err != nil {
  98. return err
  99. }
  100. if !info.IsDir() {
  101. return errors.New("Not a directory: " + path)
  102. }
  103. const permBits = fs.ModePerm | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky
  104. var parentErr error
  105. if mode := info.Mode() & permBits; mode&0200 == 0 {
  106. // A non-writeable directory (for this user; we assume that's the
  107. // relevant part). Temporarily change the mode so we can delete the
  108. // file or directory inside it.
  109. parentErr = targetFs.Chmod(dir, mode|0700)
  110. if parentErr != nil {
  111. l.Debugf("Failed to make parent directory writable: %v", parentErr)
  112. } else {
  113. // Chmod succeeded, we should change the permissions back on the way
  114. // out. If we fail we log the error as we have irrevocably messed up
  115. // at this point. :( (The operation we were called to wrap has
  116. // succeeded or failed on its own so returning an error to the
  117. // caller is inappropriate.)
  118. defer func() {
  119. if err := targetFs.Chmod(dir, mode); err != nil && !fs.IsNotExist(err) {
  120. logFn := l.Warnln
  121. if ignorePerms {
  122. logFn = l.Debugln
  123. }
  124. logFn("Failed to restore directory permissions after gaining write access:", err)
  125. }
  126. }()
  127. }
  128. }
  129. err = fn(path)
  130. if fs.IsPermission(err) && parentErr != nil {
  131. err = fmt.Errorf("error after failing to make parent directory writable: %w", err)
  132. }
  133. return err
  134. }