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 }