github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/wal/hydro.go (about)

     1  package wal
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"time"
     7  
     8  	"github.com/alphadose/haxmap"
     9  	"github.com/cockroachdb/errors"
    10  	"github.com/projecteru2/core/log"
    11  	coretypes "github.com/projecteru2/core/types"
    12  	"github.com/projecteru2/core/wal/kv"
    13  )
    14  
    15  const (
    16  	fileMode = 0600
    17  )
    18  
    19  // Hydro is the simplest wal implementation.
    20  type Hydro struct {
    21  	*haxmap.Map[string, EventHandler]
    22  	store kv.KV
    23  }
    24  
    25  // NewHydro initailizes a new Hydro instance.
    26  func NewHydro(path string, timeout time.Duration) (*Hydro, error) {
    27  	store := kv.NewLithium()
    28  	if err := store.Open(path, fileMode, timeout); err != nil {
    29  		return nil, err
    30  	}
    31  	return &Hydro{
    32  		Map:   haxmap.New[string, EventHandler](),
    33  		store: store,
    34  	}, nil
    35  }
    36  
    37  // Close disconnects the kvdb.
    38  func (h *Hydro) Close() error {
    39  	return h.store.Close()
    40  }
    41  
    42  // Register registers a new event handler.
    43  func (h *Hydro) Register(handler EventHandler) {
    44  	h.Map.Set(handler.Typ(), handler)
    45  }
    46  
    47  // Recover starts a disaster recovery, which will replay all the events.
    48  func (h *Hydro) Recover(ctx context.Context) {
    49  	ch, _ := h.store.Scan([]byte(eventPrefix))
    50  	events := []HydroEvent{}
    51  	logger := log.WithFunc("wal.hydro.Recover")
    52  
    53  	for {
    54  		scanEntry, ok := <-ch
    55  		if !ok {
    56  			logger.Warn(ctx, "noting have to restore, wal recover closed")
    57  			break
    58  		}
    59  
    60  		event, err := h.decodeEvent(scanEntry)
    61  		if err != nil {
    62  			logger.Error(ctx, err, "decode event error")
    63  			continue
    64  		}
    65  		events = append(events, event)
    66  	}
    67  
    68  	for _, event := range events {
    69  		handler, ok := h.getEventHandler(event.Type)
    70  		if !ok {
    71  			logger.Warn(ctx, "no such event handler for %s", event.Type)
    72  			continue
    73  		}
    74  
    75  		if err := h.recover(ctx, handler, event); err != nil {
    76  			logger.Errorf(ctx, err, "handle event %d (%s) failed", event.ID, event.Type)
    77  			continue
    78  		}
    79  	}
    80  }
    81  
    82  // Log records a log item.
    83  func (h *Hydro) Log(eventyp string, item any) (Commit, error) {
    84  	handler, ok := h.getEventHandler(eventyp)
    85  	if !ok {
    86  		return nil, errors.Wrap(coretypes.ErrInvaildWALEventType, eventyp)
    87  	}
    88  
    89  	bs, err := handler.Encode(item) // TODO 2 times encode is necessary?
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	var ID uint64
    95  	if ID, err = h.store.NextSequence(); err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	event := NewHydroEvent(ID, eventyp, bs)
   100  	if bs, err = event.Encode(); err != nil {
   101  		return nil, coretypes.ErrInvaildWALEvent
   102  	}
   103  
   104  	if err = h.store.Put(event.Key(), bs); err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	return func() error {
   109  		return h.store.Delete(event.Key())
   110  	}, nil
   111  }
   112  
   113  func (h *Hydro) recover(ctx context.Context, handler EventHandler, event HydroEvent) error {
   114  	item, err := handler.Decode(event.Item)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	del := func() error {
   120  		return h.store.Delete(event.Key())
   121  	}
   122  
   123  	switch handle, err := handler.Check(ctx, item); {
   124  	case err != nil:
   125  		return err
   126  	case !handle:
   127  		return del()
   128  	default:
   129  		if err := handler.Handle(ctx, item); err != nil {
   130  			return err
   131  		}
   132  	}
   133  	return del()
   134  }
   135  
   136  func (h *Hydro) getEventHandler(eventyp string) (EventHandler, bool) {
   137  	handler, ok := h.Map.Get(eventyp)
   138  	if !ok {
   139  		return nil, ok
   140  	}
   141  	return handler, ok
   142  }
   143  
   144  func (h *Hydro) decodeEvent(scanEntry kv.ScanEntry) (event HydroEvent, err error) {
   145  	if err = scanEntry.Error(); err != nil {
   146  		return
   147  	}
   148  
   149  	key, value := scanEntry.Pair()
   150  	if err = json.Unmarshal(value, &event); err != nil {
   151  		return
   152  	}
   153  
   154  	event.ID, err = parseHydroEventID(key)
   155  	return
   156  }