Add document ETag optimistic concurrency (#16)

* Add ETag optimistic concurrency for document replace

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Expose precondition error code header

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Stop Badger GC before closing datastore

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Pijus Kamandulis
2026-06-05 23:26:25 +03:00
committed by GitHub
parent 05e8cd2842
commit 36fd7f48cc
5 changed files with 126 additions and 11 deletions
+4
View File
@@ -35,3 +35,7 @@ var UnknownErrorResponse = gin.H{"message": "Unknown error"}
var NotFoundResponse = gin.H{"message": "NotFound"}
var ConflictResponse = gin.H{"message": "Conflict"}
var BadRequestResponse = gin.H{"message": "BadRequest"}
var PreconditionFailedResponse = gin.H{
"code": "PreconditionFailed",
"message": "Operation cannot be performed because one of the specified precondition is not met.",
}
@@ -12,8 +12,10 @@ import (
)
type BadgerDataStore struct {
db *badger.DB
gcTicker *time.Ticker
db *badger.DB
gcTicker *time.Ticker
gcDone chan struct{}
gcStopped chan struct{}
}
type BadgerDataStoreOptions struct {
@@ -36,8 +38,10 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
gcTicker := time.NewTicker(5 * time.Minute)
ds := &BadgerDataStore{
db: db,
gcTicker: gcTicker,
db: db,
gcTicker: gcTicker,
gcDone: make(chan struct{}),
gcStopped: make(chan struct{}),
}
ds.initializeDataStore(options.InitialDataFilePath)
@@ -50,7 +54,8 @@ func NewBadgerDataStore(options BadgerDataStoreOptions) *BadgerDataStore {
func (r *BadgerDataStore) Close() {
if r.gcTicker != nil {
r.gcTicker.Stop()
r.gcTicker = nil
close(r.gcDone)
<-r.gcStopped
}
r.db.Close()
@@ -63,11 +68,19 @@ func (r *BadgerDataStore) DumpToJson() (string, error) {
}
func (r *BadgerDataStore) runGarbageCollector() {
for range r.gcTicker.C {
again:
err := r.db.RunValueLogGC(0.7)
if err == nil {
goto again
defer close(r.gcStopped)
for {
select {
case <-r.gcTicker.C:
for {
err := r.db.RunValueLogGC(0.7)
if err != nil {
break
}
}
case <-r.gcDone:
return
}
}
}