github.com/wtsi-ssg/wrstat@v1.1.4-0.20221008232152-3030622a8cf8/stat/stat.go (about) 1 /******************************************************************************* 2 * Copyright (c) 2021 Genome Research Ltd. 3 * 4 * Author: Sendu Bala <sb10@sanger.ac.uk> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included 15 * in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 ******************************************************************************/ 25 26 package stat 27 28 import ( 29 "io/fs" 30 "os" 31 "time" 32 33 "github.com/inconshreveable/log15" 34 ) 35 36 type Error string 37 38 func (e Error) Error() string { return string(e) } 39 40 const errLstatSlow = Error("lstat exceeded timeout") 41 42 // Statter is something you use to get stats of files on disk. 43 type Statter interface { 44 // Lstat calls os.Lstat() on the given path, returning the FileInfo. 45 Lstat(path string) (info fs.FileInfo, err error) 46 } 47 48 // StatterWithTimeout is is a Statter implementation. NB: this is NOT thread 49 // safe; you should only call Lstat() one at a time. 50 type StatterWithTimeout struct { 51 timeout time.Duration 52 maxAttempts int 53 currentAttempts int 54 logger log15.Logger 55 } 56 57 // WithTimeout returns a Statter with the given timeout and maxAttempts 58 // configured. Timeouts are logged with the given logger. 59 func WithTimeout(timeout time.Duration, maxAttempts int, logger log15.Logger) *StatterWithTimeout { 60 return &StatterWithTimeout{ 61 timeout: timeout, 62 maxAttempts: maxAttempts, 63 logger: logger, 64 } 65 } 66 67 // Lstat calls os.Lstat() on the given path, but times it out after our 68 // configured timeout, retrying until we've hit our maxAttempts. NB: this is NOT 69 // thread safe, don't call this concurrently. 70 func (s *StatterWithTimeout) Lstat(path string) (info fs.FileInfo, err error) { 71 infoCh := make(chan fs.FileInfo, 1) 72 errCh := make(chan error, 1) 73 s.currentAttempts++ 74 75 timer := time.NewTimer(s.timeout) 76 77 go s.doLstat(path, infoCh, errCh) 78 79 select { 80 case err = <-errCh: 81 info = <-infoCh 82 s.currentAttempts = 0 83 84 timer.Stop() 85 86 return 87 case <-timer.C: 88 if s.currentAttempts <= s.maxAttempts { 89 s.logger.Warn("an lstat call exceeded timeout, will retry", "path", path, "attempts", s.currentAttempts) 90 91 return s.Lstat(path) 92 } 93 94 s.logger.Warn("an lstat call exceeded timeout, giving up", "path", path, "attempts", s.currentAttempts) 95 96 err = errLstatSlow 97 s.currentAttempts = 0 98 99 return 100 } 101 } 102 103 // doLstat does the actual Lstat call and sends results on the given channels. 104 func (s *StatterWithTimeout) doLstat(path string, infoCh chan fs.FileInfo, errCh chan error) { 105 if os.Getenv("WRSTAT_TEST_LSTAT") != "" { 106 <-time.After(1 * time.Millisecond) 107 } 108 109 info, err := os.Lstat(path) 110 infoCh <- info 111 errCh <- err 112 }