github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/libs/autofile/autofile.go (about) 1 package autofile 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "os/signal" 9 "path/filepath" 10 "sync" 11 "syscall" 12 "time" 13 14 tmrand "github.com/ari-anchor/sei-tendermint/libs/rand" 15 ) 16 17 /* AutoFile usage 18 19 // Create/Append to ./autofile_test 20 af, err := OpenAutoFile("autofile_test") 21 if err != nil { 22 log.Fatal(err) 23 } 24 25 // Stream of writes. 26 // During this time, the file may be moved e.g. by logRotate. 27 for i := 0; i < 60; i++ { 28 af.Write([]byte(Fmt("LOOP(%v)", i))) 29 time.Sleep(time.Second) 30 } 31 32 // Close the AutoFile 33 err = af.Close() 34 if err != nil { 35 log.Fatal(err) 36 } 37 */ 38 39 const ( 40 autoFileClosePeriod = 1000 * time.Millisecond 41 autoFilePerms = os.FileMode(0600) 42 ) 43 44 // ErrAutoFileClosed is reported when operations attempt to use an autofile 45 // after it has been closed. 46 var ErrAutoFileClosed = errors.New("autofile is closed") 47 48 // AutoFile automatically closes and re-opens file for writing. The file is 49 // automatically setup to close itself every 1s and upon receiving SIGHUP. 50 // 51 // This is useful for using a log file with the logrotate tool. 52 type AutoFile struct { 53 ID string 54 Path string 55 56 closeTicker *time.Ticker // signals periodic close 57 cancel func() // cancels the lifecycle context 58 59 mtx sync.Mutex // guards the fields below 60 closed bool // true when the the autofile is no longer usable 61 file *os.File // the underlying file (may be nil) 62 } 63 64 // OpenAutoFile creates an AutoFile in the path (with random ID). If there is 65 // an error, it will be of type *PathError or *ErrPermissionsChanged (if file's 66 // permissions got changed (should be 0600)). 67 func OpenAutoFile(ctx context.Context, path string) (*AutoFile, error) { 68 var err error 69 path, err = filepath.Abs(path) 70 if err != nil { 71 return nil, err 72 } 73 74 ctx, cancel := context.WithCancel(ctx) 75 af := &AutoFile{ 76 ID: tmrand.Str(12) + ":" + path, 77 Path: path, 78 closeTicker: time.NewTicker(autoFileClosePeriod), 79 cancel: cancel, 80 } 81 if err := af.openFile(); err != nil { 82 af.Close() 83 return nil, err 84 } 85 86 // Set up a SIGHUP handler to forcibly flush and close the filehandle. 87 // This forces the next operation to re-open the underlying path. 88 hupc := make(chan os.Signal, 1) 89 signal.Notify(hupc, syscall.SIGHUP) 90 go func() { 91 defer close(hupc) 92 for { 93 select { 94 case <-hupc: 95 _ = af.closeFile() 96 case <-ctx.Done(): 97 return 98 } 99 } 100 }() 101 102 go af.closeFileRoutine(ctx) 103 104 return af, nil 105 } 106 107 // Close shuts down the service goroutine and marks af as invalid. Operations 108 // on af after Close will report an error. 109 func (af *AutoFile) Close() error { 110 return af.withLock(func() error { 111 af.cancel() // signal the close service to stop 112 af.closed = true // mark the file as invalid 113 return af.unsyncCloseFile() 114 }) 115 } 116 117 func (af *AutoFile) closeFileRoutine(ctx context.Context) { 118 for { 119 select { 120 case <-ctx.Done(): 121 _ = af.Close() 122 return 123 case <-af.closeTicker.C: 124 _ = af.closeFile() 125 } 126 } 127 } 128 129 func (af *AutoFile) closeFile() (err error) { 130 return af.withLock(af.unsyncCloseFile) 131 } 132 133 // unsyncCloseFile closes the underlying filehandle if one is open, and reports 134 // any error it returns. The caller must hold af.mtx exclusively. 135 func (af *AutoFile) unsyncCloseFile() error { 136 if fp := af.file; fp != nil { 137 af.file = nil 138 return fp.Close() 139 } 140 return nil 141 } 142 143 // withLock runs f while holding af.mtx, and reports any error it returns. 144 func (af *AutoFile) withLock(f func() error) error { 145 af.mtx.Lock() 146 defer af.mtx.Unlock() 147 return f() 148 } 149 150 // Write writes len(b) bytes to the AutoFile. It returns the number of bytes 151 // written and an error, if any. Write returns a non-nil error when n != 152 // len(b). 153 // Opens AutoFile if needed. 154 func (af *AutoFile) Write(b []byte) (n int, err error) { 155 af.mtx.Lock() 156 defer af.mtx.Unlock() 157 if af.closed { 158 return 0, fmt.Errorf("write: %w", ErrAutoFileClosed) 159 } 160 161 if af.file == nil { 162 if err = af.openFile(); err != nil { 163 return 164 } 165 } 166 167 n, err = af.file.Write(b) 168 return 169 } 170 171 // Sync commits the current contents of the file to stable storage. Typically, 172 // this means flushing the file system's in-memory copy of recently written 173 // data to disk. 174 func (af *AutoFile) Sync() error { 175 return af.withLock(func() error { 176 if af.closed { 177 return fmt.Errorf("sync: %w", ErrAutoFileClosed) 178 } else if af.file == nil { 179 return nil // nothing to sync 180 } 181 return af.file.Sync() 182 }) 183 } 184 185 // openFile unconditionally replaces af.file with a new filehandle on the path. 186 // The caller must hold af.mtx exclusively. 187 func (af *AutoFile) openFile() error { 188 file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, autoFilePerms) 189 if err != nil { 190 return err 191 } 192 193 af.file = file 194 return nil 195 } 196 197 // Size returns the size of the AutoFile. It returns -1 and an error if fails 198 // get stats or open file. 199 // Opens AutoFile if needed. 200 func (af *AutoFile) Size() (int64, error) { 201 af.mtx.Lock() 202 defer af.mtx.Unlock() 203 if af.closed { 204 return 0, fmt.Errorf("size: %w", ErrAutoFileClosed) 205 } 206 207 if af.file == nil { 208 if err := af.openFile(); err != nil { 209 return -1, err 210 } 211 } 212 213 stat, err := af.file.Stat() 214 if err != nil { 215 return -1, err 216 } 217 return stat.Size(), nil 218 }