diff --git a/main.go b/main.go index 28ed9be..85d3258 100644 --- a/main.go +++ b/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)") } } +