成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Golang Failpoint 的設(shè)計(jì)與實(shí)現(xiàn)

shiyang6017 / 906人閱讀

摘要:因?yàn)橥粋€(gè)包下面的所有都在同一個(gè)命名空間,所以需要小心命名來(lái)避免命名沖突,這里有一些推薦的規(guī)則來(lái)改善這種情況保證名字在包內(nèi)是唯一的。

作者:龍恒

對(duì)于一個(gè)大型復(fù)雜的系統(tǒng)來(lái)說(shuō),通常包含多個(gè)模塊或多個(gè)組件構(gòu)成,模擬各個(gè)子系統(tǒng)的故障是測(cè)試中必不可少的環(huán)節(jié),并且這些故障模擬必須做到無(wú)侵入地集成到自動(dòng)化測(cè)試系統(tǒng)中,通過(guò)在自動(dòng)化測(cè)試中自動(dòng)激活這些故障點(diǎn)來(lái)模擬故障,并觀測(cè)最終結(jié)果是否符合預(yù)期結(jié)果來(lái)判斷系統(tǒng)的正確性和穩(wěn)定性。如果在一個(gè)分布式系統(tǒng)中需要專門(mén)請(qǐng)一位同事來(lái)插拔網(wǎng)線來(lái)模擬網(wǎng)絡(luò)異常,一個(gè)存儲(chǔ)系統(tǒng)中需要通過(guò)破壞硬盤(pán)來(lái)模擬磁盤(pán)損壞,昂貴的測(cè)試成本會(huì)讓測(cè)試成為一場(chǎng)災(zāi)難,并且難以模擬一些需要精細(xì)化控制的的測(cè)試。所以我們需要一些自動(dòng)化的方式來(lái)進(jìn)行確定性的故障測(cè)試。

Failpoint 項(xiàng)目 就是為此而生,它是 FreeBSD failpoints 的 Golang 實(shí)現(xiàn),允許在代碼中注入錯(cuò)誤或異常行為, 并由環(huán)境變量或代碼動(dòng)態(tài)激活來(lái)觸發(fā)這些異常行為。Failpoint 能用于各種復(fù)雜系統(tǒng)中模擬錯(cuò)誤處理來(lái)提高系統(tǒng)的容錯(cuò)性、正確性和穩(wěn)定性,比如:

微服務(wù)中某個(gè)服務(wù)出現(xiàn)隨機(jī)延遲、某個(gè)服務(wù)不可用。

存儲(chǔ)系統(tǒng)磁盤(pán) IO 延遲增加、IO 吞吐量過(guò)低、落盤(pán)時(shí)間長(zhǎng)。

調(diào)度系統(tǒng)中出現(xiàn)熱點(diǎn),某個(gè)調(diào)度指令失敗。

充值系統(tǒng)中模擬第三方重復(fù)請(qǐng)求充值成功回調(diào)接口。

游戲開(kāi)發(fā)中模擬玩家網(wǎng)絡(luò)不穩(wěn)定、掉幀、延遲過(guò)大等,以及各種異常輸入(外掛請(qǐng)求)情況下系統(tǒng)是否正確工作。

……

為什么要重復(fù)造輪子?

Etcd 團(tuán)隊(duì)在 2016 年開(kāi)發(fā)了 gofail 極大地簡(jiǎn)化了錯(cuò)誤注入,為 Golang 生態(tài)做出了巨大貢獻(xiàn)。我們?cè)?2018 年已經(jīng)引入了 gofail 進(jìn)行錯(cuò)誤注入測(cè)試,但是我們?cè)谑褂弥邪l(fā)現(xiàn)了一些功能性以及便利性的問(wèn)題,所以我們決定造一個(gè)更好的「輪子」。

如何使用 gofail

使用注釋在程序中注入一個(gè) failpoint:

// gofail: var FailIfImportedChunk int
// if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) {
// rc.checkpointsWg.Done()
// rc.checkpointsWg.Wait()
// panic("forcing failure due to FailIfImportedChunk")
// }
// goto RETURN1

// gofail: RETURN1:

// gofail: var FailIfStatusBecomes int
// if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes {
// rc.checkpointsWg.Done()
// rc.checkpointsWg.Wait()
// panic("forcing failure due to FailIfStatusBecomes")
// }
// goto RETURN2

// gofail: RETURN2:

使用 gofail enable 轉(zhuǎn)換后的代碼:

if vFailIfImportedChunk, __fpErr := __fp_FailIfImportedChunk.Acquire(); __fpErr == nil { defer __fp_FailIfImportedChunk.Release(); FailIfImportedChunk, __fpTypeOK := vFailIfImportedChunk.(int); if !__fpTypeOK { goto __badTypeFailIfImportedChunk} 
    if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) {
        rc.checkpointsWg.Done()
        rc.checkpointsWg.Wait()
        panic("forcing failure due to FailIfImportedChunk")
    }
    goto RETURN1; __badTypeFailIfImportedChunk: __fp_FailIfImportedChunk.BadType(vFailIfImportedChunk, "int"); };

/* gofail-label */ RETURN1:

if vFailIfStatusBecomes, __fpErr := __fp_FailIfStatusBecomes.Acquire(); __fpErr == nil { defer __fp_FailIfStatusBecomes.Release(); FailIfStatusBecomes, __fpTypeOK := vFailIfStatusBecomes.(int); if !__fpTypeOK { goto __badTypeFailIfStatusBecomes} 
    if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes {
        rc.checkpointsWg.Done()
        rc.checkpointsWg.Wait()
        panic("forcing failure due to FailIfStatusBecomes")
    }
    goto RETURN2; __badTypeFailIfStatusBecomes: __fp_FailIfStatusBecomes.BadType(vFailIfStatusBecomes, "int"); };

/* gofail-label */ RETURN2:

gofail 使用中遇到的問(wèn)題

使用注釋的方式在代碼中注入 failpoint,代碼容易出錯(cuò),并且沒(méi)有編譯器檢測(cè)。

只能全局生效,大型項(xiàng)目為了縮短自動(dòng)化測(cè)試的時(shí)間會(huì)引入并行測(cè)試,不同并行任務(wù)之間會(huì)存在干擾。

需要寫(xiě)一些 hack 代碼來(lái)避免一些不必要的錯(cuò)誤日志,比如如上代碼,必須要寫(xiě) // goto RETURN2// gofail: RETURN2:,并且中間必須添加一個(gè)空行,至于原因可以看 generated code 邏輯。

我們要設(shè)計(jì)一個(gè)什么樣子的 failpoint? 理想的 failpoint 實(shí)現(xiàn)應(yīng)該是什么樣子?

理想中的 failpoint 應(yīng)該是使用代碼定義并且對(duì)業(yè)務(wù)邏輯無(wú)侵入,如果在一個(gè)支持宏的語(yǔ)言中 (比如 Rust),我們可以定義一個(gè) fail_point 宏來(lái)定義 failpoint:

fail_point!("transport_on_send_store", |sid| if let Some(sid) = sid {
    let sid: u64 = sid.parse().unwrap();
    if sid == store_id {
        self.raft_client.wl().addrs.remove(&store_id);
    }
})

但是我們遇到了一些問(wèn)題:

Golang 并不支持 macro 語(yǔ)言特性。

Golang 不支持編譯器插件。

Golang tags 也不能提供一個(gè)比較優(yōu)雅的實(shí)現(xiàn) (go build --tag="enable-failpoint-a")。

Failpoint 設(shè)計(jì)準(zhǔn)則

使用 Golang 代碼定義 failpoint,而不是注釋或其他形式。

Failpoint 代碼不應(yīng)該有任何額外開(kāi)銷:

不能影響正常功能邏輯,不能對(duì)功能代碼有任何侵入。

注入 failpoint 代碼之后不能導(dǎo)致性能回退。

Failpoint 代碼最終不能出現(xiàn)在最終發(fā)行的二進(jìn)制文件中。

Failpoint 代碼必須是易讀、易寫(xiě)并且能引入編譯器檢測(cè)。

最終生成的代碼必須具有可讀性。

生成代碼中,功能邏輯代碼的行號(hào)不能發(fā)生變化(便于調(diào)試)。

支持并行測(cè)試,可以通過(guò) context.Context 控制一個(gè)某個(gè)具體的 failpoint 是否激活。

Golang 如何實(shí)現(xiàn)一個(gè)類似 failpoint 宏?

宏的本質(zhì)是什么?如果追本溯源,發(fā)現(xiàn)其實(shí)可以通過(guò) AST 重寫(xiě)在 Golang 中實(shí)現(xiàn)滿足以上條件的 failpoint,原理如下圖所示:

對(duì)于任何一個(gè) Golang 代碼的源文件,可以通過(guò)解析出這個(gè)文件的語(yǔ)法樹(shù),遍歷整個(gè)語(yǔ)法樹(shù),找出所有 failpoint 注入點(diǎn),然后對(duì)語(yǔ)法樹(shù)重寫(xiě),轉(zhuǎn)換成想要的邏輯。

相關(guān)概念 Failpoint

Failpoint 是一個(gè)代碼片段,并且僅在對(duì)應(yīng)的 failpoint name 激活的情況下才會(huì)執(zhí)行,如果通過(guò) failpoint.Disable("failpoint-name-for-demo") 禁用后, 那么對(duì)應(yīng)的的 failpoint 永遠(yuǎn)不會(huì)觸發(fā)。所有 failpoiint 代碼片段不會(huì)編譯到最終的二進(jìn)制文件中,比如我們模擬文件系統(tǒng)權(quán)限控制:

func saveTo(path string) error {
    failpoint.Inject("mock-permission-deny", func() error {
         // It"s OK to access outer scope variable
         return fmt.Errorf("mock permission deny: %s", path)
    })
}
Marker 函數(shù)

AST 重寫(xiě)階段標(biāo)記需要被重寫(xiě)的部分,主要有以下功能:

提示 Rewriter 重寫(xiě)為一個(gè)相等的 IF 語(yǔ)句。

標(biāo)記函數(shù)的參數(shù)是重寫(xiě)過(guò)程中需要用到的參數(shù)。

標(biāo)記函數(shù)是一個(gè)空函數(shù),編譯過(guò)程會(huì)被 inline,進(jìn)一步被消除。

標(biāo)記函數(shù)中注入的 failpoint 是一個(gè)閉包,如果閉包訪問(wèn)外部作用于變量,閉包語(yǔ)法允許捕獲外部作用域變量,不會(huì)出現(xiàn)編譯錯(cuò)誤, 同時(shí)轉(zhuǎn)換后的的代碼是一個(gè) IF 語(yǔ)句,IF 語(yǔ)句訪問(wèn)外部作用域變量不會(huì)產(chǎn)生任何問(wèn)題,所以閉包捕獲只是為了語(yǔ)法合法,最終不會(huì)有任何額外開(kāi)銷。

簡(jiǎn)單、易讀、易寫(xiě)。

引入編譯器檢測(cè),如果 Marker 函數(shù)的參數(shù)不正確,程序不能通過(guò)編譯的,進(jìn)而保證轉(zhuǎn)換后的代碼正確性。

目前支持的 Marker 函數(shù)列表:

func Inject(fpname string, fpblock func(val Value)) {}

func InjectContext(fpname string, ctx context.Context, fpblock func(val Value)) {}

func Break(label ...string) {}

func Goto(label string) {}

func Continue(label ...string) {}

func Fallthrough() {}

func Return(results ...interface{}) {}

func Label(label string) {}

如何在你的程序中使用 failpoint 進(jìn)行注入?

最簡(jiǎn)單的方式是使用 failpoint.Inject 在調(diào)用的地方注入一個(gè) failpoint,最終 failpoint.Inject 調(diào)用會(huì)重寫(xiě)為一個(gè) IF 語(yǔ)句, 其中 mock-io-error 用來(lái)判斷是否觸發(fā),failpoint-closure 中的邏輯會(huì)在觸發(fā)后執(zhí)行。 比如我們?cè)谝粋€(gè)讀取文件的函數(shù)中注入一個(gè) IO 錯(cuò)誤:

failpoint.Inject("mock-io-error", func(val failpoint.Value) error {
    return fmt.Errorf("mock error: %v", val.(string))
})

最終轉(zhuǎn)換后的代碼如下:

if ok, val := failpoint.Eval(_curpkg_("mock-io-error")); ok {
    return fmt.Errorf("mock error: %v", val.(string))
}

通過(guò) failpoint.Enable("mock-io-error", "return("disk error")") 激活程序中的 failpoint,如果需要給 failpoint.Value 賦一個(gè)自定義的值,則需要傳入一個(gè) failpoint expression,比如這里 return("disk error"),更多語(yǔ)法可以參考 failpoint語(yǔ)法。

閉包可以為 nil ,比如 failpoint.Enable("mock-delay", "sleep(1000)"),目的是在注入點(diǎn)休眠一秒,不需要執(zhí)行額外的邏輯。

failpoint.Inject("mock-delay", nil)
failpoint.Inject("mock-delay", func(){})

最終會(huì)產(chǎn)生以下代碼:

failpoint.Eval(_curpkg_("mock-delay"))
failpoint.Eval(_curpkg_("mock-delay"))

如果我們只想在 failpoint 中執(zhí)行一個(gè) panic,不需要接收 failpoint.Value,則我們可以在閉包的參數(shù)中忽略這個(gè)值。 例如:

failpoint.Inject("mock-panic", func(_ failpoint.Value) error {
    panic("mock panic")
})
// OR
failpoint.Inject("mock-panic", func() error {
    panic("mock panic")
})

最佳實(shí)踐是以下這樣:

failpoint.Enable("mock-panic", "panic")
failpoint.Inject("mock-panic", nil)
// GENERATED CODE
failpoint.Eval(_curpkg_("mock-panic"))

為了可以在并行測(cè)試中防止不同的測(cè)試任務(wù)之間的干擾,可以在 context.Context 中包含一個(gè)回調(diào)函數(shù),用于精細(xì)化控制 failpoint 的激活與關(guān)閉

failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) {
    fmt.Println("unit-test", val)
})

轉(zhuǎn)換后的代碼:

if ok, val := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); ok {
    fmt.Println("unit-test", val)
}

使用 failpoint.WithHook 的示例

func (s *dmlSuite) TestCRUDParallel() {
    sctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
        return ctx.Value(fpname) != nil // Determine by ctx key
    })
    insertFailpoints = map[string]struct{} {
        "insert-record-fp": {},
        "insert-index-fp": {},
        "on-duplicate-fp": {},
    }
    ictx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
        _, found := insertFailpoints[fpname] // Only enables some failpoints.
        return found
    })
    deleteFailpoints = map[string]struct{} {
        "tikv-is-busy-fp": {},
        "fetch-tso-timeout": {},
    }
    dctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
        _, found := deleteFailpoints[fpname] // Only disables failpoints. 
        return !found
    })
    // other DML parallel test cases.
    s.RunParallel(buildSelectTests(sctx))
    s.RunParallel(buildInsertTests(ictx))
    s.RunParallel(buildDeleteTests(dctx))
}

如果我們?cè)谘h(huán)中使用 failpoint,可能我們會(huì)使用到其他的 Marker 函數(shù)

failpoint.Label("outer")
for i := 0; i < 100; i++ {
    inner:
        for j := 0; j < 1000; j++ {
            switch rand.Intn(j) + i {
            case j / 5:
                failpoint.Break()
            case j / 7:
                failpoint.Continue("outer")
            case j / 9:
                failpoint.Fallthrough()
            case j / 10:
                failpoint.Goto("outer")
            default:
                failpoint.Inject("failpoint-name", func(val failpoint.Value) {
                    fmt.Println("unit-test", val.(int))
                    if val == j/11 {
                        failpoint.Break("inner")
                    } else {
                        failpoint.Goto("outer")
                    }
                })
        }
    }
}

以上代碼最終會(huì)重寫(xiě)為如下代碼:

outer:
    for i := 0; i < 100; i++ {
    inner:
        for j := 0; j < 1000; j++ {
            switch rand.Intn(j) + i {
            case j / 5:
                break
            case j / 7:
                continue outer
            case j / 9:
                fallthrough
            case j / 10:
                goto outer
            default:
                if ok, val := failpoint.Eval(_curpkg_("failpoint-name")); ok {
                    fmt.Println("unit-test", val.(int))
                    if val == j/11 {
                        break inner
                    } else {
                        goto outer
                    }
                }
            }
        }
    }

對(duì)于為什么會(huì)有 label, break, continue 和 fallthrough 相關(guān) Marker 函數(shù)保持疑問(wèn),為什么不直接使用關(guān)鍵字?

Golang 中如果某個(gè)變量或則標(biāo)簽未使用,是不能通過(guò)編譯的。

label1: // compiler error: unused label1
    failpoint.Inject("failpoint-name", func(val failpoint.Value) {
        if val.(int) == 1000 {
            goto label1 // illegal to use goto here
        }
        fmt.Println("unit-test", val)
    })

break 和 continue 只能在循環(huán)上下文中使用,在閉包中使用。

一些復(fù)雜的注入示例

示例一:在 IF 語(yǔ)句的 INITIAL 和 CONDITIONAL 中注入 failpoint

if a, b := func() {
    failpoint.Inject("failpoint-name", func(val failpoint.Value) {
        fmt.Println("unit-test", val)
    })
}, func() int { return rand.Intn(200) }(); b > func() int {
    failpoint.Inject("failpoint-name", func(val failpoint.Value) int {
        return val.(int)
    })
    return rand.Intn(3000)
}() && b < func() int {
    failpoint.Inject("failpoint-name-2", func(val failpoint.Value) {
        return rand.Intn(val.(int))
    })
    return rand.Intn(6000)
}() {
    a()
    failpoint.Inject("failpoint-name-3", func(val failpoint.Value) {
        fmt.Println("unit-test", val)
    })
}

上面的代碼最終會(huì)被重寫(xiě)為:

if a, b := func() {
    if ok, val := failpoint.Eval(_curpkg_("failpoint-name")); ok {
        fmt.Println("unit-test", val)
    }
}, func() int { return rand.Intn(200) }(); b > func() int {
    if ok, val := failpoint.Eval(_curpkg_("failpoint-name")); ok {
        return val.(int)
    }
    return rand.Intn(3000)
}() && b < func() int {
    if ok, val := failpoint.Eval(_curpkg_("failpoint-name-2")); ok {
        return rand.Intn(val.(int))
    }
    return rand.Intn(6000)
}() {
    a()
    if ok, val := failpoint.Eval(_curpkg_("failpoint-name-3")); ok {
        fmt.Println("unit-test", val)
    }
}

示例二:在 SELECT 語(yǔ)句的 CASE 中注入 failpoint 來(lái)動(dòng)態(tài)控制某個(gè) case 是否被阻塞

func (s *StoreService) ExecuteStoreTask() {
    select {
    case <-func() chan *StoreTask {
        failpoint.Inject("priority-fp", func(_ failpoint.Value) {
            return make(chan *StoreTask)
        })
        return s.priorityHighCh
    }():
        fmt.Println("execute high priority task")

    case <- s.priorityNormalCh:
        fmt.Println("execute normal priority task")

    case <- s.priorityLowCh:
        fmt.Println("execute normal low task")
    }
}

上面的代碼最終會(huì)被重寫(xiě)為:

func (s *StoreService) ExecuteStoreTask() {
    select {
    case <-func() chan *StoreTask {
        if ok, _ := failpoint.Eval(_curpkg_("priority-fp")); ok {
            return make(chan *StoreTask)
        })
        return s.priorityHighCh
    }():
        fmt.Println("execute high priority task")

    case <- s.priorityNormalCh:
        fmt.Println("execute normal priority task")

    case <- s.priorityLowCh:
        fmt.Println("execute normal low task")
    }
}

示例三:動(dòng)態(tài)注入 SWITCH CASE

switch opType := operator.Type(); {
case opType == "balance-leader":
    fmt.Println("create balance leader steps")

case opType == "balance-region":
    fmt.Println("create balance region steps")

case opType == "scatter-region":
    fmt.Println("create scatter region steps")

case func() bool {
    failpoint.Inject("dynamic-op-type", func(val failpoint.Value) bool {
        return strings.Contains(val.(string), opType)
    })
    return false
}():
    fmt.Println("do something")

default:
    panic("unsupported operator type")
}

以上代碼最終會(huì)重寫(xiě)為如下代碼:

switch opType := operator.Type(); {
case opType == "balance-leader":
    fmt.Println("create balance leader steps")

case opType == "balance-region":
    fmt.Println("create balance region steps")

case opType == "scatter-region":
    fmt.Println("create scatter region steps")

case func() bool {
    if ok, val := failpoint.Eval(_curpkg_("dynamic-op-type")); ok {
        return strings.Contains(val.(string), opType)
    }
    return false
}():
    fmt.Println("do something")

default:
    panic("unsupported operator type")
}

除了上面的例子之外,還可以寫(xiě)的更加復(fù)雜的情況:

循環(huán)的 INITIAL 語(yǔ)句, CONDITIONAL 表達(dá)式,以及 POST 語(yǔ)句

FOR RANGE 語(yǔ)句

SWITCH INITIAL 語(yǔ)句

Slice 的構(gòu)造和索引

結(jié)構(gòu)體動(dòng)態(tài)初始化

……

實(shí)際上,任何你可以調(diào)用函數(shù)的地方都可以注入 failpoint,所以請(qǐng)發(fā)揮你的想象力。

Failpoint 命名最佳實(shí)踐

上面生成的代碼中會(huì)自動(dòng)添加一個(gè) _curpkg_ 調(diào)用在 failpoint-name 上,是因?yàn)槊质侨值?,為了避免命名沖突,所以會(huì)在最終的名字包包名,_curpkg_ 相當(dāng)一個(gè)宏,在運(yùn)行的時(shí)候自動(dòng)使用包名進(jìn)行展開(kāi)。你并不需要在自己的應(yīng)用程序中實(shí)現(xiàn) _curpkg_,它在 failpoint-ctl enable 的自動(dòng)生成以及自動(dòng)添加,并在 failpoint-ctl disable 的時(shí)候被刪除。

package ddl // ddl’s parent package is `github.com/pingcap/tidb`

func demo() {
    // _curpkg_("the-original-failpoint-name") will be expanded as `github.com/pingcap/tidb/ddl/the-original-failpoint-name`
    if ok, val := failpoint.Eval(_curpkg_("the-original-failpoint-name")); ok {...}
}

因?yàn)橥粋€(gè)包下面的所有 failpoint 都在同一個(gè)命名空間,所以需要小心命名來(lái)避免命名沖突,這里有一些推薦的規(guī)則來(lái)改善這種情況:

保證名字在包內(nèi)是唯一的。

使用一個(gè)自解釋的名字。

可以通過(guò)環(huán)境變量來(lái)激活 failpoint:

 GO_FAILPOINTS="github.com/pingcap/tidb/ddl/renameTableErr=return(100);github.com/pingcap/tidb/planner/core/illegalPushDown=return(true);github.com/pingcap/pd/server/schedulers/balanceLeaderFailed=return(true)"

致謝

感謝 gofail 提供最初實(shí)現(xiàn),給我們提供了靈感,讓我們能站在巨人的肩膀上對(duì) failpoint 進(jìn)行迭代。

感謝 FreeBSD 定義 語(yǔ)法規(guī)范。

最后,歡迎大家和我們交流討論,一起完善 Failpoint 項(xiàng)目。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/17999.html

相關(guān)文章

  • TiKV 源碼解析(五)fail-rs 介紹

    摘要:作者張博康本文為源碼解析系列的第五篇,為大家介紹在測(cè)試中使用的周邊庫(kù)。而對(duì)于行為的情況會(huì)特殊一些,在中并不做實(shí)際的動(dòng)作,而是返回并通過(guò)傳參給閉包產(chǎn)生自定義的返回值。 作者:張博康 本文為 TiKV 源碼解析系列的第五篇,為大家介紹 TiKV 在測(cè)試中使用的周邊庫(kù) fail-rs。 fail-rs 的設(shè)計(jì)啟發(fā)于 FreeBSD 的 failpoints,由 Rust 實(shí)現(xiàn)。通過(guò)代碼或者環(huán)...

    enali 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<