Compare commits

...

1 Commits
master ... si

Author SHA1 Message Date
a88d378151
feat: adds serializable isolation 2025-05-10 13:57:57 -06:00

50
main.go
View File

@ -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)")
}
}