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  }