Fast implementation of Git in pure Go

objectstore/mix: Separate

runxiyu.tngl.sh 78779168 a9684e72

verified
+305 -257
+51
objectstore/mix/bytes.go
··· 1 + package mix 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + 7 + "codeberg.org/lindenii/furgit/objectid" 8 + "codeberg.org/lindenii/furgit/objectstore" 9 + "codeberg.org/lindenii/furgit/objecttype" 10 + ) 11 + 12 + // ReadBytesFull reads a full serialized object from one backend that has it. 13 + func (mix *Mix) ReadBytesFull(id objectid.ObjectID) ([]byte, error) { 14 + for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 15 + full, err := backend.ReadBytesFull(id) 16 + if err == nil { 17 + mix.touchBackend(backend) 18 + 19 + return full, nil 20 + } 21 + 22 + if errors.Is(err, objectstore.ErrObjectNotFound) { 23 + continue 24 + } 25 + 26 + return nil, fmt.Errorf("objectstore: backend %d read bytes full: %w", i, err) 27 + } 28 + 29 + return nil, objectstore.ErrObjectNotFound 30 + } 31 + 32 + // ReadBytesContent reads an object's type and content bytes from one backend 33 + // that has it. 34 + func (mix *Mix) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) { 35 + for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 36 + ty, content, err := backend.ReadBytesContent(id) 37 + if err == nil { 38 + mix.touchBackend(backend) 39 + 40 + return ty, content, nil 41 + } 42 + 43 + if errors.Is(err, objectstore.ErrObjectNotFound) { 44 + continue 45 + } 46 + 47 + return objecttype.TypeInvalid, nil, fmt.Errorf("objectstore: backend %d read bytes content: %w", i, err) 48 + } 49 + 50 + return objecttype.TypeInvalid, nil, objectstore.ErrObjectNotFound 51 + }
+30
objectstore/mix/close.go
··· 1 + package mix 2 + 3 + import ( 4 + "errors" 5 + 6 + "codeberg.org/lindenii/furgit/objectstore" 7 + ) 8 + 9 + // Close closes all backends and joins close errors. 10 + func (mix *Mix) Close() error { 11 + mix.mu.RLock() 12 + 13 + backends := make([]objectstore.Store, 0, len(mix.backendNodeByStore)) 14 + for node := mix.backendHead; node != nil; node = node.next { 15 + backends = append(backends, node.backend) 16 + } 17 + 18 + mix.mu.RUnlock() 19 + 20 + var errs []error 21 + 22 + for _, backend := range backends { 23 + err := backend.Close() 24 + if err != nil { 25 + errs = append(errs, err) 26 + } 27 + } 28 + 29 + return errors.Join(errs...) 30 + }
+30
objectstore/mix/header.go
··· 1 + package mix 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + 7 + "codeberg.org/lindenii/furgit/objectid" 8 + "codeberg.org/lindenii/furgit/objectstore" 9 + "codeberg.org/lindenii/furgit/objecttype" 10 + ) 11 + 12 + // ReadHeader reads object header data from one backend that has it. 13 + func (mix *Mix) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) { 14 + for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 15 + ty, size, err := backend.ReadHeader(id) 16 + if err == nil { 17 + mix.touchBackend(backend) 18 + 19 + return ty, size, nil 20 + } 21 + 22 + if errors.Is(err, objectstore.ErrObjectNotFound) { 23 + continue 24 + } 25 + 26 + return objecttype.TypeInvalid, 0, fmt.Errorf("objectstore: backend %d read header: %w", i, err) 27 + } 28 + 29 + return objecttype.TypeInvalid, 0, objectstore.ErrObjectNotFound 30 + }
-257
objectstore/mix/mix.go
··· 3 3 package mix 4 4 5 5 import ( 6 - "errors" 7 - "fmt" 8 - "io" 9 6 "sync" 10 7 11 - "codeberg.org/lindenii/furgit/objectid" 12 8 "codeberg.org/lindenii/furgit/objectstore" 13 - "codeberg.org/lindenii/furgit/objecttype" 14 9 ) 15 10 16 11 // Mix queries multiple object databases with an MRU backend preference. ··· 21 16 backendTail *backendNode 22 17 backendNodeByStore map[objectstore.Store]*backendNode 23 18 } 24 - 25 - // New creates a Mix from backends. 26 - func New(backends ...objectstore.Store) *Mix { 27 - nodeByStore := make(map[objectstore.Store]*backendNode, len(backends)) 28 - 29 - var ( 30 - head *backendNode 31 - tail *backendNode 32 - ) 33 - 34 - for _, backend := range backends { 35 - if backend == nil { 36 - continue 37 - } 38 - 39 - node := &backendNode{ 40 - backend: backend, 41 - prev: tail, 42 - } 43 - if tail != nil { 44 - tail.next = node 45 - } 46 - 47 - if head == nil { 48 - head = node 49 - } 50 - 51 - tail = node 52 - nodeByStore[backend] = node 53 - } 54 - 55 - return &Mix{ 56 - backendHead: head, 57 - backendTail: tail, 58 - backendNodeByStore: nodeByStore, 59 - } 60 - } 61 - 62 - // ReadBytesFull reads a full serialized object from one backend that has it. 63 - func (mix *Mix) ReadBytesFull(id objectid.ObjectID) ([]byte, error) { 64 - for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 65 - full, err := backend.ReadBytesFull(id) 66 - if err == nil { 67 - mix.touchBackend(backend) 68 - 69 - return full, nil 70 - } 71 - 72 - if errors.Is(err, objectstore.ErrObjectNotFound) { 73 - continue 74 - } 75 - 76 - return nil, fmt.Errorf("objectstore: backend %d read bytes full: %w", i, err) 77 - } 78 - 79 - return nil, objectstore.ErrObjectNotFound 80 - } 81 - 82 - // ReadBytesContent reads an object's type and content bytes from one backend 83 - // that has it. 84 - func (mix *Mix) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) { 85 - for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 86 - ty, content, err := backend.ReadBytesContent(id) 87 - if err == nil { 88 - mix.touchBackend(backend) 89 - 90 - return ty, content, nil 91 - } 92 - 93 - if errors.Is(err, objectstore.ErrObjectNotFound) { 94 - continue 95 - } 96 - 97 - return objecttype.TypeInvalid, nil, fmt.Errorf("objectstore: backend %d read bytes content: %w", i, err) 98 - } 99 - 100 - return objecttype.TypeInvalid, nil, objectstore.ErrObjectNotFound 101 - } 102 - 103 - // ReadReaderFull reads a full serialized object stream from one backend that 104 - // has it. 105 - func (mix *Mix) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) { 106 - for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 107 - reader, err := backend.ReadReaderFull(id) 108 - if err == nil { 109 - mix.touchBackend(backend) 110 - 111 - return reader, nil 112 - } 113 - 114 - if errors.Is(err, objectstore.ErrObjectNotFound) { 115 - continue 116 - } 117 - 118 - return nil, fmt.Errorf("objectstore: backend %d read reader full: %w", i, err) 119 - } 120 - 121 - return nil, objectstore.ErrObjectNotFound 122 - } 123 - 124 - // ReadReaderContent reads an object's type, declared content length, and 125 - // content stream from one backend that has it. 126 - func (mix *Mix) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) { 127 - for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 128 - ty, size, reader, err := backend.ReadReaderContent(id) 129 - if err == nil { 130 - mix.touchBackend(backend) 131 - 132 - return ty, size, reader, nil 133 - } 134 - 135 - if errors.Is(err, objectstore.ErrObjectNotFound) { 136 - continue 137 - } 138 - 139 - return objecttype.TypeInvalid, 0, nil, fmt.Errorf("objectstore: backend %d read reader content: %w", i, err) 140 - } 141 - 142 - return objecttype.TypeInvalid, 0, nil, objectstore.ErrObjectNotFound 143 - } 144 - 145 - // ReadSize reads object content length from one backend that has it. 146 - func (mix *Mix) ReadSize(id objectid.ObjectID) (int64, error) { 147 - for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 148 - size, err := backend.ReadSize(id) 149 - if err == nil { 150 - mix.touchBackend(backend) 151 - 152 - return size, nil 153 - } 154 - 155 - if errors.Is(err, objectstore.ErrObjectNotFound) { 156 - continue 157 - } 158 - 159 - return 0, fmt.Errorf("objectstore: backend %d read size: %w", i, err) 160 - } 161 - 162 - return 0, objectstore.ErrObjectNotFound 163 - } 164 - 165 - // ReadHeader reads object header data from one backend that has it. 166 - func (mix *Mix) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) { 167 - for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 168 - ty, size, err := backend.ReadHeader(id) 169 - if err == nil { 170 - mix.touchBackend(backend) 171 - 172 - return ty, size, nil 173 - } 174 - 175 - if errors.Is(err, objectstore.ErrObjectNotFound) { 176 - continue 177 - } 178 - 179 - return objecttype.TypeInvalid, 0, fmt.Errorf("objectstore: backend %d read header: %w", i, err) 180 - } 181 - 182 - return objecttype.TypeInvalid, 0, objectstore.ErrObjectNotFound 183 - } 184 - 185 - // Close closes all backends and joins close errors. 186 - func (mix *Mix) Close() error { 187 - mix.mu.RLock() 188 - 189 - backends := make([]objectstore.Store, 0, len(mix.backendNodeByStore)) 190 - for node := mix.backendHead; node != nil; node = node.next { 191 - backends = append(backends, node.backend) 192 - } 193 - 194 - mix.mu.RUnlock() 195 - 196 - var errs []error 197 - 198 - for _, backend := range backends { 199 - err := backend.Close() 200 - if err != nil { 201 - errs = append(errs, err) 202 - } 203 - } 204 - 205 - return errors.Join(errs...) 206 - } 207 - 208 - type backendNode struct { 209 - backend objectstore.Store 210 - prev *backendNode 211 - next *backendNode 212 - } 213 - 214 - func (mix *Mix) firstBackend() objectstore.Store { 215 - mix.mu.RLock() 216 - defer mix.mu.RUnlock() 217 - 218 - if mix.backendHead == nil { 219 - return nil 220 - } 221 - 222 - return mix.backendHead.backend 223 - } 224 - 225 - func (mix *Mix) nextBackend(current objectstore.Store) objectstore.Store { 226 - mix.mu.RLock() 227 - defer mix.mu.RUnlock() 228 - 229 - node := mix.backendNodeByStore[current] 230 - if node == nil || node.next == nil { 231 - return nil 232 - } 233 - 234 - return node.next.backend 235 - } 236 - 237 - func (mix *Mix) touchBackend(backend objectstore.Store) { 238 - if backend == nil { 239 - return 240 - } 241 - 242 - if !mix.mu.TryLock() { 243 - return 244 - } 245 - defer mix.mu.Unlock() 246 - 247 - node := mix.backendNodeByStore[backend] 248 - if node == nil || node == mix.backendHead { 249 - return 250 - } 251 - 252 - if node.prev != nil { 253 - node.prev.next = node.next 254 - } 255 - 256 - if node.next != nil { 257 - node.next.prev = node.prev 258 - } 259 - 260 - if mix.backendTail == node { 261 - mix.backendTail = node.prev 262 - } 263 - 264 - node.prev = nil 265 - 266 - node.next = mix.backendHead 267 - if mix.backendHead != nil { 268 - mix.backendHead.prev = node 269 - } 270 - 271 - mix.backendHead = node 272 - if mix.backendTail == nil { 273 - mix.backendTail = node 274 - } 275 - }
+72
objectstore/mix/mru.go
··· 1 + package mix 2 + 3 + import "codeberg.org/lindenii/furgit/objectstore" 4 + 5 + type backendNode struct { 6 + backend objectstore.Store 7 + prev *backendNode 8 + next *backendNode 9 + } 10 + 11 + func (mix *Mix) firstBackend() objectstore.Store { 12 + mix.mu.RLock() 13 + defer mix.mu.RUnlock() 14 + 15 + if mix.backendHead == nil { 16 + return nil 17 + } 18 + 19 + return mix.backendHead.backend 20 + } 21 + 22 + func (mix *Mix) nextBackend(current objectstore.Store) objectstore.Store { 23 + mix.mu.RLock() 24 + defer mix.mu.RUnlock() 25 + 26 + node := mix.backendNodeByStore[current] 27 + if node == nil || node.next == nil { 28 + return nil 29 + } 30 + 31 + return node.next.backend 32 + } 33 + 34 + func (mix *Mix) touchBackend(backend objectstore.Store) { 35 + if backend == nil { 36 + return 37 + } 38 + 39 + if !mix.mu.TryLock() { 40 + return 41 + } 42 + defer mix.mu.Unlock() 43 + 44 + node := mix.backendNodeByStore[backend] 45 + if node == nil || node == mix.backendHead { 46 + return 47 + } 48 + 49 + if node.prev != nil { 50 + node.prev.next = node.next 51 + } 52 + 53 + if node.next != nil { 54 + node.next.prev = node.prev 55 + } 56 + 57 + if mix.backendTail == node { 58 + mix.backendTail = node.prev 59 + } 60 + 61 + node.prev = nil 62 + 63 + node.next = mix.backendHead 64 + if mix.backendHead != nil { 65 + mix.backendHead.prev = node 66 + } 67 + 68 + mix.backendHead = node 69 + if mix.backendTail == nil { 70 + mix.backendTail = node 71 + } 72 + }
+40
objectstore/mix/new.go
··· 1 + package mix 2 + 3 + import "codeberg.org/lindenii/furgit/objectstore" 4 + 5 + // New creates a Mix from backends. 6 + func New(backends ...objectstore.Store) *Mix { 7 + nodeByStore := make(map[objectstore.Store]*backendNode, len(backends)) 8 + 9 + var ( 10 + head *backendNode 11 + tail *backendNode 12 + ) 13 + 14 + for _, backend := range backends { 15 + if backend == nil { 16 + continue 17 + } 18 + 19 + node := &backendNode{ 20 + backend: backend, 21 + prev: tail, 22 + } 23 + if tail != nil { 24 + tail.next = node 25 + } 26 + 27 + if head == nil { 28 + head = node 29 + } 30 + 31 + tail = node 32 + nodeByStore[backend] = node 33 + } 34 + 35 + return &Mix{ 36 + backendHead: head, 37 + backendTail: tail, 38 + backendNodeByStore: nodeByStore, 39 + } 40 + }
+53
objectstore/mix/reader.go
··· 1 + package mix 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "io" 7 + 8 + "codeberg.org/lindenii/furgit/objectid" 9 + "codeberg.org/lindenii/furgit/objectstore" 10 + "codeberg.org/lindenii/furgit/objecttype" 11 + ) 12 + 13 + // ReadReaderFull reads a full serialized object stream from one backend that 14 + // has it. 15 + func (mix *Mix) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) { 16 + for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 17 + reader, err := backend.ReadReaderFull(id) 18 + if err == nil { 19 + mix.touchBackend(backend) 20 + 21 + return reader, nil 22 + } 23 + 24 + if errors.Is(err, objectstore.ErrObjectNotFound) { 25 + continue 26 + } 27 + 28 + return nil, fmt.Errorf("objectstore: backend %d read reader full: %w", i, err) 29 + } 30 + 31 + return nil, objectstore.ErrObjectNotFound 32 + } 33 + 34 + // ReadReaderContent reads an object's type, declared content length, and 35 + // content stream from one backend that has it. 36 + func (mix *Mix) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) { 37 + for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 38 + ty, size, reader, err := backend.ReadReaderContent(id) 39 + if err == nil { 40 + mix.touchBackend(backend) 41 + 42 + return ty, size, reader, nil 43 + } 44 + 45 + if errors.Is(err, objectstore.ErrObjectNotFound) { 46 + continue 47 + } 48 + 49 + return objecttype.TypeInvalid, 0, nil, fmt.Errorf("objectstore: backend %d read reader content: %w", i, err) 50 + } 51 + 52 + return objecttype.TypeInvalid, 0, nil, objectstore.ErrObjectNotFound 53 + }
+29
objectstore/mix/size.go
··· 1 + package mix 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + 7 + "codeberg.org/lindenii/furgit/objectid" 8 + "codeberg.org/lindenii/furgit/objectstore" 9 + ) 10 + 11 + // ReadSize reads object content length from one backend that has it. 12 + func (mix *Mix) ReadSize(id objectid.ObjectID) (int64, error) { 13 + for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) { 14 + size, err := backend.ReadSize(id) 15 + if err == nil { 16 + mix.touchBackend(backend) 17 + 18 + return size, nil 19 + } 20 + 21 + if errors.Is(err, objectstore.ErrObjectNotFound) { 22 + continue 23 + } 24 + 25 + return 0, fmt.Errorf("objectstore: backend %d read size: %w", i, err) 26 + } 27 + 28 + return 0, objectstore.ErrObjectNotFound 29 + }