feat: adds serializable isolation
This commit is contained in:
parent
a81afd772b
commit
a88d378151
50
main.go
50
main.go
@ -26,6 +26,7 @@ type Store struct {
|
||||
type Transaction struct {
|
||||
store *Store
|
||||
readTS Timestamp
|
||||
readSet map[string]struct{}
|
||||
writeBuffer map[string]string
|
||||
committed bool
|
||||
}
|
||||
@ -65,11 +66,13 @@ func (s *Store) Begin() *Transaction {
|
||||
return &Transaction{
|
||||
store: s,
|
||||
readTS: readTS,
|
||||
readSet: make(map[string]struct{}),
|
||||
writeBuffer: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Transaction) Read(key string) (string, bool) {
|
||||
tx.readSet[key] = struct{}{}
|
||||
if val, ok := tx.writeBuffer[key]; ok {
|
||||
return val, true
|
||||
}
|
||||
@ -85,27 +88,40 @@ func (tx *Transaction) Commit() error {
|
||||
return fmt.Errorf("transaction already committed")
|
||||
}
|
||||
|
||||
tx.store.mu.Lock()
|
||||
defer tx.store.mu.Unlock()
|
||||
s := tx.store
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Conflict detection
|
||||
getLatestTS := func(key string) Timestamp {
|
||||
if h, ok := s.data[key]; ok && len(h.Versions) > 0 {
|
||||
return h.Versions[len(h.Versions)-1].Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Write-write Conflict detection
|
||||
for key := range tx.writeBuffer {
|
||||
history, exists := tx.store.data[key]
|
||||
if !exists {
|
||||
if getLatestTS(key) > tx.readTS {
|
||||
return fmt.Errorf("write-write conflict on key: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Read-write conflict detection (excluding keys we're writing)
|
||||
for key := range tx.readSet {
|
||||
if _, isWriting := tx.writeBuffer[key]; isWriting {
|
||||
continue
|
||||
}
|
||||
latest := history.Versions[len(history.Versions)-1]
|
||||
if latest.Timestamp > tx.readTS {
|
||||
return fmt.Errorf("write-write conflict on key '%s'", key)
|
||||
if getLatestTS(key) > tx.readTS {
|
||||
return fmt.Errorf("read-write conflict on key: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
commitTS := tx.store.nextTimestamp()
|
||||
for key, val := range tx.writeBuffer {
|
||||
history, exists := tx.store.data[key]
|
||||
history, exists := s.data[key]
|
||||
if !exists {
|
||||
history = &KeyHistory{}
|
||||
tx.store.data[key] = history
|
||||
s.data[key] = history
|
||||
}
|
||||
history.Versions = append(history.Versions, VersionedValue{
|
||||
Value: val,
|
||||
@ -139,10 +155,9 @@ func main() {
|
||||
fmt.Println("tx2 reads foo: not found")
|
||||
}
|
||||
|
||||
// Transaction 3: Conflicting write
|
||||
// Transaction 3: Conflicting write — modifies "foo" after tx2 read it
|
||||
tx3 := store.Begin()
|
||||
tx3.Write("foo", "baz")
|
||||
// tx2 committed first, so tx3 will conflict if tx2 also wrote to "foo"
|
||||
err = tx3.Commit()
|
||||
if err != nil {
|
||||
fmt.Println("tx3 commit error:", err)
|
||||
@ -150,11 +165,21 @@ func main() {
|
||||
fmt.Println("tx3 committed")
|
||||
}
|
||||
|
||||
// Now tx2 tries to commit after reading a value that's been modified — should conflict
|
||||
tx2.Write("bar", "new") // tx2 tries to do something unrelated, but it read "foo"
|
||||
err = tx2.Commit()
|
||||
if err != nil {
|
||||
fmt.Println("tx2 commit error (expected read-write conflict):", err)
|
||||
} else {
|
||||
fmt.Println("tx2 committed (unexpected!)")
|
||||
}
|
||||
|
||||
// Transaction 4: Should read latest "foo"
|
||||
tx4 := store.Begin()
|
||||
val, _ = tx4.Read("foo")
|
||||
fmt.Printf("tx4 reads foo: %s\n", val)
|
||||
|
||||
// Additional write-write conflict test (unchanged)
|
||||
tx5 := store.Begin()
|
||||
tx6 := store.Begin()
|
||||
|
||||
@ -172,3 +197,4 @@ func main() {
|
||||
fmt.Println("tx6 committed (unexpectedly)")
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user