github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/relay/binlog_writer.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package relay 15 16 import ( 17 "bytes" 18 "encoding/json" 19 "fmt" 20 "os" 21 "path/filepath" 22 "sync" 23 24 "github.com/pingcap/tiflow/dm/pkg/log" 25 "github.com/pingcap/tiflow/dm/pkg/terror" 26 "go.uber.org/atomic" 27 "go.uber.org/zap" 28 ) 29 30 const ( 31 bufferSize = 1 * 1024 * 1024 // 1MB 32 chanSize = 1024 33 ) 34 35 var nilErr error 36 37 // BinlogWriter is a binlog event writer which writes binlog events to a file. 38 // Open/Write/Close cannot be called concurrently. 39 type BinlogWriter struct { 40 offset atomic.Int64 41 file *os.File 42 relayDir string 43 uuid atomic.String 44 filename atomic.String 45 err atomic.Error 46 47 logger log.Logger 48 49 input chan []byte 50 flushWg sync.WaitGroup 51 wg sync.WaitGroup 52 } 53 54 // BinlogWriterStatus represents the status of a BinlogWriter. 55 type BinlogWriterStatus struct { 56 Filename string `json:"filename"` 57 Offset int64 `json:"offset"` 58 } 59 60 // String implements Stringer.String. 61 func (s *BinlogWriterStatus) String() string { 62 data, err := json.Marshal(s) 63 if err != nil { 64 // do not use %v/%+v for `s`, it will call this `String` recursively 65 return fmt.Sprintf("marshal status %#v to json error %v", s, err) 66 } 67 return string(data) 68 } 69 70 // NewBinlogWriter creates a BinlogWriter instance. 71 func NewBinlogWriter(logger log.Logger, relayDir string) *BinlogWriter { 72 return &BinlogWriter{ 73 logger: logger, 74 relayDir: relayDir, 75 } 76 } 77 78 // run starts the binlog writer. 79 func (w *BinlogWriter) run() { 80 var ( 81 buf = &bytes.Buffer{} 82 errOccurs bool 83 ) 84 85 // writeToFile writes buffer to file 86 writeToFile := func() { 87 if buf.Len() == 0 { 88 return 89 } 90 91 if w.file == nil { 92 w.err.CompareAndSwap(nilErr, terror.ErrRelayWriterNotOpened.Generate()) 93 errOccurs = true 94 return 95 } 96 n, err := w.file.Write(buf.Bytes()) 97 if err != nil { 98 w.err.CompareAndSwap(nilErr, terror.ErrBinlogWriterWriteDataLen.Delegate(err, n)) 99 errOccurs = true 100 return 101 } 102 buf.Reset() 103 } 104 105 for bs := range w.input { 106 if errOccurs { 107 continue 108 } 109 if bs != nil { 110 buf.Write(bs) 111 } 112 // we use bs = nil to mean flush 113 if bs == nil || buf.Len() > bufferSize || len(w.input) == 0 { 114 writeToFile() 115 } 116 if bs == nil { 117 w.flushWg.Done() 118 } 119 } 120 if !errOccurs { 121 writeToFile() 122 } 123 } 124 125 func (w *BinlogWriter) Open(uuid, filename string) error { 126 fullName := filepath.Join(w.relayDir, uuid, filename) 127 f, err := os.OpenFile(fullName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600) 128 if err != nil { 129 return terror.ErrBinlogWriterOpenFile.Delegate(err) 130 } 131 fs, err := f.Stat() 132 if err != nil { 133 err2 := f.Close() // close the file opened before 134 if err2 != nil { 135 w.logger.Error("fail to close file", zap.String("component", "file writer"), zap.Error(err2)) 136 } 137 return terror.ErrBinlogWriterGetFileStat.Delegate(err, f.Name()) 138 } 139 140 w.offset.Store(fs.Size()) 141 w.file = f 142 w.uuid.Store(uuid) 143 w.filename.Store(filename) 144 w.err.Store(nilErr) 145 146 w.input = make(chan []byte, chanSize) 147 w.wg.Add(1) 148 go func() { 149 defer w.wg.Done() 150 w.run() 151 }() 152 153 return nil 154 } 155 156 func (w *BinlogWriter) Close() error { 157 if w.input != nil { 158 close(w.input) 159 } 160 w.wg.Wait() 161 162 if w.file != nil { 163 if err := w.file.Sync(); err != nil { 164 w.logger.Error("fail to flush buffered data", zap.String("component", "file writer"), zap.Error(err)) 165 } 166 if err := w.file.Close(); err != nil { 167 w.err.CompareAndSwap(nilErr, err) 168 } 169 } 170 171 w.file = nil 172 w.offset.Store(0) 173 w.uuid.Store("") 174 w.filename.Store("") 175 w.input = nil 176 return w.err.Swap(nilErr) 177 } 178 179 func (w *BinlogWriter) Write(rawData []byte) error { 180 if w.file == nil { 181 return terror.ErrRelayWriterNotOpened.Generate() 182 } 183 w.input <- rawData 184 w.offset.Add(int64(len(rawData))) 185 return w.err.Load() 186 } 187 188 func (w *BinlogWriter) Flush() error { 189 w.flushWg.Add(1) 190 if err := w.Write(nil); err != nil { 191 return err 192 } 193 w.flushWg.Wait() 194 return w.err.Load() 195 } 196 197 func (w *BinlogWriter) Status() *BinlogWriterStatus { 198 return &BinlogWriterStatus{ 199 Filename: w.filename.Load(), 200 Offset: w.offset.Load(), 201 } 202 } 203 204 func (w *BinlogWriter) Offset() int64 { 205 return w.offset.Load() 206 } 207 208 func (w *BinlogWriter) isActive(uuid, filename string) (bool, int64) { 209 return uuid == w.uuid.Load() && filename == w.filename.Load(), w.offset.Load() 210 }