delete.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package taildrop
  4. import (
  5. "container/list"
  6. "context"
  7. "os"
  8. "strings"
  9. "sync"
  10. "time"
  11. "tailscale.com/ipn"
  12. "tailscale.com/tstime"
  13. "tailscale.com/types/logger"
  14. )
  15. // deleteDelay is the amount of time to wait before we delete a file.
  16. // A shorter value ensures timely deletion of deleted and partial files, while
  17. // a longer value provides more opportunity for partial files to be resumed.
  18. const deleteDelay = time.Hour
  19. // fileDeleter manages asynchronous deletion of files after deleteDelay.
  20. type fileDeleter struct {
  21. logf logger.Logf
  22. clock tstime.DefaultClock
  23. event func(string) // called for certain events; for testing only
  24. mu sync.Mutex
  25. queue list.List
  26. byName map[string]*list.Element
  27. emptySignal chan struct{} // signal that the queue is empty
  28. group sync.WaitGroup
  29. shutdownCtx context.Context
  30. shutdown context.CancelFunc
  31. fs FileOps // must be used for all filesystem operations
  32. }
  33. // deleteFile is a specific file to delete after deleteDelay.
  34. type deleteFile struct {
  35. name string
  36. inserted time.Time
  37. }
  38. func (d *fileDeleter) Init(m *manager, eventHook func(string)) {
  39. d.logf = m.opts.Logf
  40. d.clock = m.opts.Clock
  41. d.event = eventHook
  42. d.fs = m.opts.fileOps
  43. d.byName = make(map[string]*list.Element)
  44. d.emptySignal = make(chan struct{})
  45. d.shutdownCtx, d.shutdown = context.WithCancel(context.Background())
  46. // From a cold-start, load the list of partial and deleted files.
  47. // Only run this if we have ever received at least one file
  48. // to avoid ever touching the taildrop directory on systems (e.g., MacOS)
  49. // that pop up a security dialog window upon first access.
  50. if m.opts.State == nil {
  51. return
  52. }
  53. if b, _ := m.opts.State.ReadState(ipn.TaildropReceivedKey); len(b) == 0 {
  54. return
  55. }
  56. d.group.Go(func() {
  57. d.event("start full-scan")
  58. defer d.event("end full-scan")
  59. if d.fs == nil {
  60. d.logf("deleter: nil FileOps")
  61. }
  62. files, err := d.fs.ListFiles()
  63. if err != nil {
  64. d.logf("deleter: ListDir error: %v", err)
  65. return
  66. }
  67. for _, filename := range files {
  68. switch {
  69. case d.shutdownCtx.Err() != nil:
  70. return // terminate early
  71. case strings.HasSuffix(filename, partialSuffix):
  72. // Only enqueue the file for deletion if there is no active put.
  73. nameID := strings.TrimSuffix(filename, partialSuffix)
  74. if i := strings.LastIndexByte(nameID, '.'); i > 0 {
  75. key := incomingFileKey{clientID(nameID[i+len("."):]), nameID[:i]}
  76. m.incomingFiles.LoadFunc(key, func(_ *incomingFile, loaded bool) {
  77. if !loaded {
  78. d.Insert(filename)
  79. }
  80. })
  81. } else {
  82. d.Insert(filename)
  83. }
  84. case strings.HasSuffix(filename, deletedSuffix):
  85. // Best-effort immediate deletion of deleted files.
  86. name := strings.TrimSuffix(filename, deletedSuffix)
  87. if d.fs.Remove(name) == nil {
  88. if d.fs.Remove(filename) == nil {
  89. continue
  90. }
  91. }
  92. // Otherwise enqueue for later deletion.
  93. d.Insert(filename)
  94. }
  95. }
  96. })
  97. }
  98. // Insert enqueues baseName for eventual deletion.
  99. func (d *fileDeleter) Insert(baseName string) {
  100. d.mu.Lock()
  101. defer d.mu.Unlock()
  102. if d.shutdownCtx.Err() != nil {
  103. return
  104. }
  105. if _, ok := d.byName[baseName]; ok {
  106. return // already queued for deletion
  107. }
  108. d.byName[baseName] = d.queue.PushBack(&deleteFile{baseName, d.clock.Now()})
  109. if d.queue.Len() == 1 && d.shutdownCtx.Err() == nil {
  110. d.group.Go(func() { d.waitAndDelete(deleteDelay) })
  111. }
  112. }
  113. // waitAndDelete is an asynchronous deletion goroutine.
  114. // At most one waitAndDelete routine is ever running at a time.
  115. // It is not started unless there is at least one file in the queue.
  116. func (d *fileDeleter) waitAndDelete(wait time.Duration) {
  117. tc, ch := d.clock.NewTimer(wait)
  118. defer tc.Stop() // cleanup the timer resource if we stop early
  119. d.event("start waitAndDelete")
  120. defer d.event("end waitAndDelete")
  121. select {
  122. case <-d.shutdownCtx.Done():
  123. case <-d.emptySignal:
  124. case now := <-ch:
  125. d.mu.Lock()
  126. defer d.mu.Unlock()
  127. // Iterate over all files to delete, and delete anything old enough.
  128. var next *list.Element
  129. var failed []*list.Element
  130. for elem := d.queue.Front(); elem != nil; elem = next {
  131. next = elem.Next()
  132. file := elem.Value.(*deleteFile)
  133. if now.Sub(file.inserted) < deleteDelay {
  134. break // everything after this is recently inserted
  135. }
  136. // Delete the expired file.
  137. if name, ok := strings.CutSuffix(file.name, deletedSuffix); ok {
  138. if err := d.fs.Remove(name); err != nil && !os.IsNotExist(err) {
  139. d.logf("could not delete: %v", redactError(err))
  140. failed = append(failed, elem)
  141. continue
  142. }
  143. }
  144. if err := d.fs.Remove(file.name); err != nil && !os.IsNotExist(err) {
  145. d.logf("could not delete: %v", redactError(err))
  146. failed = append(failed, elem)
  147. continue
  148. }
  149. d.queue.Remove(elem)
  150. delete(d.byName, file.name)
  151. d.event("deleted " + file.name)
  152. }
  153. for _, elem := range failed {
  154. elem.Value.(*deleteFile).inserted = now // retry after deleteDelay
  155. d.queue.MoveToBack(elem)
  156. }
  157. // If there are still some files to delete, retry again later.
  158. if d.queue.Len() > 0 && d.shutdownCtx.Err() == nil {
  159. file := d.queue.Front().Value.(*deleteFile)
  160. retryAfter := deleteDelay - now.Sub(file.inserted)
  161. d.group.Go(func() { d.waitAndDelete(retryAfter) })
  162. }
  163. }
  164. }
  165. // Remove dequeues baseName from eventual deletion.
  166. func (d *fileDeleter) Remove(baseName string) {
  167. d.mu.Lock()
  168. defer d.mu.Unlock()
  169. if elem := d.byName[baseName]; elem != nil {
  170. d.queue.Remove(elem)
  171. delete(d.byName, baseName)
  172. // Signal to terminate any waitAndDelete goroutines.
  173. if d.queue.Len() == 0 {
  174. select {
  175. case <-d.shutdownCtx.Done():
  176. case d.emptySignal <- struct{}{}:
  177. }
  178. }
  179. }
  180. }
  181. // Shutdown shuts down the deleter.
  182. // It blocks until all goroutines are stopped.
  183. func (d *fileDeleter) Shutdown() {
  184. d.mu.Lock() // acquire lock to ensure no new goroutines start after shutdown
  185. d.shutdown()
  186. d.mu.Unlock()
  187. d.group.Wait()
  188. }