/* * stacktester.go * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "bytes" "encoding/binary" "encoding/hex" "fmt" "log" "math/big" "os" "reflect" "runtime" "sort" "strconv" "strings" "sync" "time" "github.com/apple/foundationdb/bindings/go/src/fdb" "github.com/apple/foundationdb/bindings/go/src/fdb/tuple" ) const verbose bool = false var trMap = map[string]fdb.Transaction{} var trMapLock = sync.RWMutex{} // Make tuples sortable by byte-order type byBytes []tuple.Tuple func (b byBytes) Len() int { return len(b) } func (b byBytes) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b byBytes) Less(i, j int) bool { return bytes.Compare(b[i].Pack(), b[j].Pack()) < 0 } func int64ToBool(i int64) bool { switch i { case 0: return false default: return true } } type stackEntry struct { item interface{} idx int } type StackMachine struct { prefix []byte trName string stack []stackEntry lastVersion int64 threads sync.WaitGroup verbose bool de *DirectoryExtension } func newStackMachine(prefix []byte, verbose bool) *StackMachine { sm := StackMachine{verbose: verbose, prefix: prefix, de: newDirectoryExtension(), trName: string(prefix[:])} return &sm } func (sm *StackMachine) waitAndPop() (ret stackEntry) { defer func() { if r := recover(); r != nil { switch r := r.(type) { case fdb.Error: ret.item = []byte(tuple.Tuple{[]byte("ERROR"), []byte(fmt.Sprintf("%d", r.Code))}.Pack()) default: panic(r) } } }() ret, sm.stack = sm.stack[len(sm.stack)-1], sm.stack[:len(sm.stack)-1] switch el := ret.item.(type) { case []byte: ret.item = el case int64, uint64, *big.Int, string, bool, tuple.UUID, float32, float64, tuple.Tuple, tuple.Versionstamp: ret.item = el case fdb.Key: ret.item = []byte(el) case fdb.FutureNil: el.MustGet() ret.item = []byte("RESULT_NOT_PRESENT") case fdb.FutureByteSlice: v := el.MustGet() if v != nil { ret.item = v } else { ret.item = []byte("RESULT_NOT_PRESENT") } case fdb.FutureKey: ret.item = []byte(el.MustGet()) case nil: default: log.Fatalf("Don't know how to pop stack element %v %T\n", el, el) } return } func (sm *StackMachine) popSelector() fdb.KeySelector { sel := fdb.KeySelector{fdb.Key(sm.waitAndPop().item.([]byte)), int64ToBool(sm.waitAndPop().item.(int64)), int(sm.waitAndPop().item.(int64))} return sel } func (sm *StackMachine) popKeyRange() fdb.KeyRange { kr := fdb.KeyRange{fdb.Key(sm.waitAndPop().item.([]byte)), fdb.Key(sm.waitAndPop().item.([]byte))} return kr } func (sm *StackMachine) popRangeOptions() fdb.RangeOptions { ro := fdb.RangeOptions{Limit: int(sm.waitAndPop().item.(int64)), Reverse: int64ToBool(sm.waitAndPop().item.(int64)), Mode: fdb.StreamingMode(sm.waitAndPop().item.(int64) + 1)} return ro } func (sm *StackMachine) popPrefixRange() fdb.ExactRange { er, e := fdb.PrefixRange(sm.waitAndPop().item.([]byte)) if e != nil { panic(e) } return er } func (sm *StackMachine) pushRange(idx int, sl []fdb.KeyValue, prefixFilter []byte) { var t tuple.Tuple = make(tuple.Tuple, 0, len(sl)*2) for _, kv := range sl { if prefixFilter == nil || bytes.HasPrefix(kv.Key, prefixFilter) { t = append(t, kv.Key) t = append(t, kv.Value) } } sm.store(idx, []byte(t.Pack())) } func (sm *StackMachine) store(idx int, item interface{}) { sm.stack = append(sm.stack, stackEntry{item, idx}) } func tupleToString(t tuple.Tuple) string { var buffer bytes.Buffer buffer.WriteByte('(') for i, el := range t { if i > 0 { buffer.WriteString(", ") } switch el := el.(type) { case int64, uint64: buffer.WriteString(fmt.Sprintf("%d", el)) case *big.Int: buffer.WriteString(fmt.Sprintf("%s", el)) case []byte: buffer.WriteString(fmt.Sprintf("%+q", string(el))) case string: buffer.WriteString(fmt.Sprintf("%+q", el)) case bool: buffer.WriteString(fmt.Sprintf("%t", el)) case tuple.UUID: buffer.WriteString(hex.EncodeToString(el[:])) case float32, float64: buffer.WriteString(fmt.Sprintf("%f", el)) case nil: buffer.WriteString("nil") case tuple.Tuple: buffer.WriteString(tupleToString(el)) default: log.Fatalf("Don't know how to stringify tuple elemement %v %T\n", el, el) } } buffer.WriteByte(')') return buffer.String() } func (sm *StackMachine) dumpStack() { for i := len(sm.stack) - 1; i >= 0; i-- { fmt.Printf(" %d.", sm.stack[i].idx) el := sm.stack[i].item switch el := el.(type) { case int64, uint64: fmt.Printf(" %d", el) case *big.Int: fmt.Printf(" %s", el) case fdb.FutureNil: fmt.Printf(" FutureNil") case fdb.FutureByteSlice: fmt.Printf(" FutureByteSlice") case fdb.FutureKey: fmt.Printf(" FutureKey") case []byte: fmt.Printf(" %+q", string(el)) case fdb.Key: fmt.Printf(" %+q", string(el)) case string: fmt.Printf(" %+q", el) case bool: fmt.Printf(" %t", el) case tuple.Tuple: fmt.Printf(" %s", tupleToString(el)) case tuple.UUID: fmt.Printf(" %s", hex.EncodeToString(el[:])) case float32, float64: fmt.Printf(" %f", el) case nil: fmt.Printf(" nil") default: log.Fatalf("Don't know how to dump stack element %v %T\n", el, el) } if i != 0 { fmt.Printf(",") } } } func (sm *StackMachine) executeMutation(t fdb.Transactor, f func(fdb.Transaction) (interface{}, error), isDB bool, idx int) { _, e := t.Transact(f) if e != nil { panic(e) } if isDB { sm.store(idx, []byte("RESULT_NOT_PRESENT")) } } func (sm *StackMachine) checkWatches(watches [4]fdb.FutureNil, expected bool) bool { for _, watch := range watches { if watch.IsReady() || expected { e := watch.Get() if e != nil { switch e := e.(type) { case fdb.Error: tr, tr_error := db.CreateTransaction() if tr_error != nil { panic(tr_error) } tr.OnError(e).MustGet() default: panic(e) } } if !expected { return false } } } return true } func (sm *StackMachine) testWatches() { for { _, e := db.Transact(func(tr fdb.Transaction) (interface{}, error) { tr.Set(fdb.Key("w0"), []byte("0")) tr.Set(fdb.Key("w2"), []byte("2")) tr.Set(fdb.Key("w3"), []byte("3")) return nil, nil }) if e != nil { panic(e) } var watches [4]fdb.FutureNil _, e = db.Transact(func(tr fdb.Transaction) (interface{}, error) { watches[0] = tr.Watch(fdb.Key("w0")) watches[1] = tr.Watch(fdb.Key("w1")) watches[2] = tr.Watch(fdb.Key("w2")) watches[3] = tr.Watch(fdb.Key("w3")) tr.Set(fdb.Key("w0"), []byte("0")) tr.Clear(fdb.Key("w1")) return nil, nil }) if e != nil { panic(e) } time.Sleep(5 * time.Second) if !sm.checkWatches(watches, false) { continue } _, e = db.Transact(func(tr fdb.Transaction) (interface{}, error) { tr.Set(fdb.Key("w0"), []byte("a")) tr.Set(fdb.Key("w1"), []byte("b")) tr.Clear(fdb.Key("w2")) tr.BitXor(fdb.Key("w3"), []byte("\xff\xff")) return nil, nil }) if e != nil { panic(e) } if sm.checkWatches(watches, true) { return } } } func (sm *StackMachine) testLocality() { _, e := db.Transact(func(tr fdb.Transaction) (interface{}, error) { tr.Options().SetTimeout(60 * 1000) tr.Options().SetReadSystemKeys() boundaryKeys, e := db.LocalityGetBoundaryKeys(fdb.KeyRange{fdb.Key(""), fdb.Key("\xff\xff")}, 0, 0) if e != nil { panic(e) } for i := 0; i < len(boundaryKeys)-1; i++ { start := boundaryKeys[i] end := tr.GetKey(fdb.LastLessThan(boundaryKeys[i+1])).MustGet() startAddresses := tr.LocalityGetAddressesForKey(start).MustGet() endAddresses := tr.LocalityGetAddressesForKey(end).MustGet() for _, address1 := range startAddresses { found := false for _, address2 := range endAddresses { if address1 == address2 { found = true break } } if !found { panic("Locality not internally consistent.") } } } return nil, nil }) if e != nil { panic(e) } } func (sm *StackMachine) logStack(entries map[int]stackEntry, prefix []byte) { _, e := db.Transact(func(tr fdb.Transaction) (interface{}, error) { for index, el := range entries { var keyt tuple.Tuple keyt = append(keyt, int64(index)) keyt = append(keyt, int64(el.idx)) pk := append(prefix, keyt.Pack()...) var valt tuple.Tuple valt = append(valt, el.item) pv := valt.Pack() vl := 40000 if len(pv) < vl { vl = len(pv) } tr.Set(fdb.Key(pk), pv[:vl]) } return nil, nil }) if e != nil { panic(e) } return } func (sm *StackMachine) currentTransaction() fdb.Transaction { trMapLock.RLock() tr := trMap[sm.trName] trMapLock.RUnlock() return tr } func (sm *StackMachine) newTransactionWithLockHeld() { tr, e := db.CreateTransaction() if e != nil { panic(e) } trMap[sm.trName] = tr } func (sm *StackMachine) newTransaction() { trMapLock.Lock() sm.newTransactionWithLockHeld() trMapLock.Unlock() } func (sm *StackMachine) switchTransaction(name []byte) { sm.trName = string(name[:]) trMapLock.RLock() _, present := trMap[sm.trName] trMapLock.RUnlock() if !present { trMapLock.Lock() _, present = trMap[sm.trName] if !present { sm.newTransactionWithLockHeld() } trMapLock.Unlock() } } func (sm *StackMachine) processInst(idx int, inst tuple.Tuple) { defer func() { if r := recover(); r != nil { switch r := r.(type) { case fdb.Error: sm.store(idx, []byte(tuple.Tuple{[]byte("ERROR"), []byte(fmt.Sprintf("%d", r.Code))}.Pack())) default: panic(r) } } }() var e error op := inst[0].(string) if sm.verbose { fmt.Printf("%d. Instruction is %s (%v)\n", idx, op, sm.prefix) fmt.Printf("Stack from [") sm.dumpStack() fmt.Printf(" ] (%d)\n", len(sm.stack)) } var t fdb.Transactor var rt fdb.ReadTransactor var isDB bool switch { case strings.HasSuffix(op, "_SNAPSHOT"): rt = sm.currentTransaction().Snapshot() op = op[:len(op)-9] case strings.HasSuffix(op, "_DATABASE"): t = db rt = db op = op[:len(op)-9] isDB = true default: t = sm.currentTransaction() rt = sm.currentTransaction() } switch { case op == "PUSH": sm.store(idx, inst[1]) case op == "DUP": entry := sm.stack[len(sm.stack)-1] sm.store(entry.idx, entry.item) case op == "EMPTY_STACK": sm.stack = []stackEntry{} sm.stack = make([]stackEntry, 0) case op == "SWAP": idx := sm.waitAndPop().item.(int64) sm.stack[len(sm.stack)-1], sm.stack[len(sm.stack)-1-int(idx)] = sm.stack[len(sm.stack)-1-int(idx)], sm.stack[len(sm.stack)-1] case op == "POP": sm.stack = sm.stack[:len(sm.stack)-1] case op == "SUB": var x, y *big.Int switch x1 := sm.waitAndPop().item.(type) { case *big.Int: x = x1 case int64: x = big.NewInt(x1) case uint64: x = new(big.Int) x.SetUint64(x1) } switch y1 := sm.waitAndPop().item.(type) { case *big.Int: y = y1 case int64: y = big.NewInt(y1) case uint64: y = new(big.Int) y.SetUint64(y1) } sm.store(idx, x.Sub(x, y)) case op == "CONCAT": str1 := sm.waitAndPop().item str2 := sm.waitAndPop().item switch str1.(type) { case string: sm.store(idx, str1.(string)+str2.(string)) case []byte: sm.store(idx, append(str1.([]byte), str2.([]byte)...)) default: panic("Invalid CONCAT parameter") } case op == "NEW_TRANSACTION": sm.newTransaction() case op == "USE_TRANSACTION": sm.switchTransaction(sm.waitAndPop().item.([]byte)) case op == "ON_ERROR": sm.store(idx, sm.currentTransaction().OnError(fdb.Error{int(sm.waitAndPop().item.(int64))})) case op == "GET_READ_VERSION": _, e = rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) { sm.lastVersion = rtr.GetReadVersion().MustGet() sm.store(idx, []byte("GOT_READ_VERSION")) return nil, nil }) if e != nil { panic(e) } case op == "SET": key := fdb.Key(sm.waitAndPop().item.([]byte)) value := sm.waitAndPop().item.([]byte) sm.executeMutation(t, func(tr fdb.Transaction) (interface{}, error) { tr.Set(key, value) return nil, nil }, isDB, idx) case op == "LOG_STACK": prefix := sm.waitAndPop().item.([]byte) entries := make(map[int]stackEntry) for len(sm.stack) > 0 { entries[len(sm.stack)-1] = sm.waitAndPop() if len(entries) == 100 { sm.logStack(entries, prefix) entries = make(map[int]stackEntry) } } sm.logStack(entries, prefix) case op == "GET": key := fdb.Key(sm.waitAndPop().item.([]byte)) res, e := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) { return rtr.Get(key), nil }) if e != nil { panic(e) } sm.store(idx, res.(fdb.FutureByteSlice)) case op == "COMMIT": sm.store(idx, sm.currentTransaction().Commit()) case op == "RESET": sm.currentTransaction().Reset() case op == "CLEAR": key := fdb.Key(sm.waitAndPop().item.([]byte)) sm.executeMutation(t, func(tr fdb.Transaction) (interface{}, error) { tr.Clear(key) return nil, nil }, isDB, idx) case op == "SET_READ_VERSION": sm.currentTransaction().SetReadVersion(sm.lastVersion) case op == "WAIT_FUTURE": entry := sm.waitAndPop() sm.store(entry.idx, entry.item) case op == "GET_COMMITTED_VERSION": sm.lastVersion, e = sm.currentTransaction().GetCommittedVersion() if e != nil { panic(e) } sm.store(idx, []byte("GOT_COMMITTED_VERSION")) case op == "GET_APPROXIMATE_SIZE": approximateSize := sm.currentTransaction().GetApproximateSize().MustGet() var x *big.Int = big.NewInt(approximateSize) sm.store(idx, x) case op == "GET_VERSIONSTAMP": sm.store(idx, sm.currentTransaction().GetVersionstamp()) case op == "GET_KEY": sel := sm.popSelector() prefix := sm.waitAndPop().item.([]byte) res, e := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) { return rtr.GetKey(sel).MustGet(), nil }) if e != nil { panic(e) } key := res.(fdb.Key) if bytes.HasPrefix(key, prefix) { sm.store(idx, key) } else if bytes.Compare(key, prefix) < 0 { sm.store(idx, prefix) } else { s, e := fdb.Strinc(prefix) if e != nil { panic(e) } sm.store(idx, s) } case strings.HasPrefix(op, "GET_RANGE"): var r fdb.Range switch op[9:] { case "_STARTS_WITH": r = sm.popPrefixRange() case "_SELECTOR": r = fdb.SelectorRange{sm.popSelector(), sm.popSelector()} case "": r = sm.popKeyRange() } ro := sm.popRangeOptions() var prefix []byte = nil if op[9:] == "_SELECTOR" { prefix = sm.waitAndPop().item.([]byte) } res, e := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) { return rtr.GetRange(r, ro).GetSliceOrPanic(), nil }) if e != nil { panic(e) } sm.pushRange(idx, res.([]fdb.KeyValue), prefix) case strings.HasPrefix(op, "CLEAR_RANGE"): var er fdb.ExactRange switch op[11:] { case "_STARTS_WITH": er = sm.popPrefixRange() case "": er = sm.popKeyRange() } sm.executeMutation(t, func(tr fdb.Transaction) (interface{}, error) { tr.ClearRange(er) return nil, nil }, isDB, idx) case op == "TUPLE_PACK": var t tuple.Tuple count := sm.waitAndPop().item.(int64) for i := 0; i < int(count); i++ { t = append(t, sm.waitAndPop().item) } sm.store(idx, []byte(t.Pack())) case op == "TUPLE_PACK_WITH_VERSIONSTAMP": var t tuple.Tuple prefix := sm.waitAndPop().item.([]byte) c := sm.waitAndPop().item.(int64) for i := 0; i < int(c); i++ { t = append(t, sm.waitAndPop().item) } packed, err := t.PackWithVersionstamp(prefix) if err != nil && strings.Contains(err.Error(), "No incomplete") { sm.store(idx, []byte("ERROR: NONE")) } else if err != nil { sm.store(idx, []byte("ERROR: MULTIPLE")) } else { sm.store(idx, []byte("OK")) sm.store(idx, packed) } case op == "TUPLE_UNPACK": t, e := tuple.Unpack(fdb.Key(sm.waitAndPop().item.([]byte))) if e != nil { panic(e) } for _, el := range t { sm.store(idx, []byte(tuple.Tuple{el}.Pack())) } case op == "TUPLE_SORT": count := sm.waitAndPop().item.(int64) tuples := make([]tuple.Tuple, count) for i := 0; i < int(count); i++ { tuples[i], e = tuple.Unpack(fdb.Key(sm.waitAndPop().item.([]byte))) if e != nil { panic(e) } } sort.Sort(byBytes(tuples)) for _, t := range tuples { sm.store(idx, t.Pack()) } case op == "ENCODE_FLOAT": val_bytes := sm.waitAndPop().item.([]byte) var val float32 binary.Read(bytes.NewBuffer(val_bytes), binary.BigEndian, &val) sm.store(idx, val) case op == "ENCODE_DOUBLE": val_bytes := sm.waitAndPop().item.([]byte) var val float64 binary.Read(bytes.NewBuffer(val_bytes), binary.BigEndian, &val) sm.store(idx, val) case op == "DECODE_FLOAT": val := sm.waitAndPop().item.(float32) var ibuf bytes.Buffer binary.Write(&ibuf, binary.BigEndian, val) sm.store(idx, ibuf.Bytes()) case op == "DECODE_DOUBLE": val := sm.waitAndPop().item.(float64) var ibuf bytes.Buffer binary.Write(&ibuf, binary.BigEndian, val) sm.store(idx, ibuf.Bytes()) case op == "TUPLE_RANGE": var t tuple.Tuple count := sm.waitAndPop().item.(int64) for i := 0; i < int(count); i++ { t = append(t, sm.waitAndPop().item) } bk, ek := t.FDBRangeKeys() sm.store(idx, []byte(bk.FDBKey())) sm.store(idx, []byte(ek.FDBKey())) case op == "START_THREAD": newsm := newStackMachine(sm.waitAndPop().item.([]byte), verbose) sm.threads.Add(1) go func() { newsm.Run() sm.threads.Done() }() case op == "WAIT_EMPTY": prefix := sm.waitAndPop().item.([]byte) er, e := fdb.PrefixRange(prefix) if e != nil { panic(e) } db.Transact(func(tr fdb.Transaction) (interface{}, error) { v := tr.GetRange(er, fdb.RangeOptions{}).GetSliceOrPanic() if len(v) != 0 { panic(fdb.Error{1020}) } return nil, nil }) sm.store(idx, []byte("WAITED_FOR_EMPTY")) case op == "READ_CONFLICT_RANGE": e = sm.currentTransaction().AddReadConflictRange(fdb.KeyRange{fdb.Key(sm.waitAndPop().item.([]byte)), fdb.Key(sm.waitAndPop().item.([]byte))}) if e != nil { panic(e) } sm.store(idx, []byte("SET_CONFLICT_RANGE")) case op == "WRITE_CONFLICT_RANGE": e = sm.currentTransaction().AddWriteConflictRange(fdb.KeyRange{fdb.Key(sm.waitAndPop().item.([]byte)), fdb.Key(sm.waitAndPop().item.([]byte))}) if e != nil { panic(e) } sm.store(idx, []byte("SET_CONFLICT_RANGE")) case op == "READ_CONFLICT_KEY": e = sm.currentTransaction().AddReadConflictKey(fdb.Key(sm.waitAndPop().item.([]byte))) if e != nil { panic(e) } sm.store(idx, []byte("SET_CONFLICT_KEY")) case op == "WRITE_CONFLICT_KEY": e = sm.currentTransaction().AddWriteConflictKey(fdb.Key(sm.waitAndPop().item.([]byte))) if e != nil { panic(e) } sm.store(idx, []byte("SET_CONFLICT_KEY")) case op == "ATOMIC_OP": opname := strings.Replace(strings.Title(strings.Replace(strings.ToLower(sm.waitAndPop().item.(string)), "_", " ", -1)), " ", "", -1) key := fdb.Key(sm.waitAndPop().item.([]byte)) ival := sm.waitAndPop().item value := ival.([]byte) sm.executeMutation(t, func(tr fdb.Transaction) (interface{}, error) { reflect.ValueOf(tr).MethodByName(opname).Call([]reflect.Value{reflect.ValueOf(key), reflect.ValueOf(value)}) return nil, nil }, isDB, idx) case op == "DISABLE_WRITE_CONFLICT": sm.currentTransaction().Options().SetNextWriteNoWriteConflictRange() case op == "CANCEL": sm.currentTransaction().Cancel() case op == "UNIT_TESTS": db.Options().SetLocationCacheSize(100001) db.Options().SetMaxWatches(10001) db.Options().SetDatacenterId("dc_id") db.Options().SetMachineId("machine_id") db.Options().SetTransactionTimeout(100000) db.Options().SetTransactionTimeout(0) db.Options().SetTransactionMaxRetryDelay(100) db.Options().SetTransactionRetryLimit(10) db.Options().SetTransactionRetryLimit(-1) db.Options().SetSnapshotRywEnable() db.Options().SetSnapshotRywDisable() if !fdb.IsAPIVersionSelected() { log.Fatal("API version should be selected") } apiVersion := fdb.MustGetAPIVersion() if apiVersion == 0 { log.Fatal("API version is 0") } e1 := fdb.APIVersion(apiVersion + 1) if e1 != nil { fdbE := e1.(fdb.Error) if fdbE.Code != 2201 { panic(e1) } } else { log.Fatal("Was not stopped from selecting two API versions") } e2 := fdb.APIVersion(apiVersion - 1) if e2 != nil { fdbE := e2.(fdb.Error) if fdbE.Code != 2201 { panic(e2) } } else { log.Fatal("Was not stopped from selecting two API versions") } fdb.MustAPIVersion(apiVersion) _, e := db.Transact(func(tr fdb.Transaction) (interface{}, error) { tr.Options().SetPrioritySystemImmediate() tr.Options().SetPriorityBatch() tr.Options().SetCausalReadRisky() tr.Options().SetCausalWriteRisky() tr.Options().SetReadYourWritesDisable() tr.Options().SetReadSystemKeys() tr.Options().SetAccessSystemKeys() tr.Options().SetTimeout(60 * 1000) tr.Options().SetRetryLimit(50) tr.Options().SetMaxRetryDelay(100) tr.Options().SetUsedDuringCommitProtectionDisable() tr.Options().SetDebugTransactionIdentifier("my_transaction") tr.Options().SetLogTransaction() tr.Options().SetReadLockAware() tr.Options().SetLockAware() return tr.Get(fdb.Key("\xff")).MustGet(), nil }) if e != nil { panic(e) } sm.testWatches() sm.testLocality() case strings.HasPrefix(op, "DIRECTORY_"): sm.de.processOp(sm, op[10:], isDB, idx, t, rt) default: log.Fatalf("Unhandled operation %s\n", string(inst[0].([]byte))) } if sm.verbose { fmt.Printf(" to [") sm.dumpStack() fmt.Printf(" ] (%d)\n\n", len(sm.stack)) } runtime.Gosched() } func (sm *StackMachine) Run() { r, e := db.Transact(func(tr fdb.Transaction) (interface{}, error) { return tr.GetRange(tuple.Tuple{sm.prefix}, fdb.RangeOptions{}).GetSliceOrPanic(), nil }) if e != nil { panic(e) } instructions := r.([]fdb.KeyValue) for i, kv := range instructions { inst, _ := tuple.Unpack(fdb.Key(kv.Value)) if sm.verbose { fmt.Printf("Instruction %d\n", i) } sm.processInst(i, inst) } sm.threads.Wait() } var db fdb.Database func main() { var clusterFile string prefix := []byte(os.Args[1]) if len(os.Args) > 3 { clusterFile = os.Args[3] } var e error var apiVersion int apiVersion, e = strconv.Atoi(os.Args[2]) if e != nil { log.Fatal(e) } if fdb.IsAPIVersionSelected() { log.Fatal("API version already selected") } e = fdb.APIVersion(apiVersion) if e != nil { log.Fatal(e) } if fdb.MustGetAPIVersion() != apiVersion { log.Fatal("API version not equal to value selected") } db, e = fdb.OpenDatabase(clusterFile) if e != nil { log.Fatal(e) } sm := newStackMachine(prefix, verbose) sm.Run() }