You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RavenDB超时配置未生效,集成测试因索引过期偶发失败

解决RavenDB集成测试中索引超时的偶发失败问题

问题背景

我们在Docker中部署了ravendb/ravendb:5.3.104-ubuntu.20.04-x64用于自动化集成测试,使用的C#客户端版本是RavenDB.Client 5.4.1。尽管已经为DocumentStore配置了10分钟的索引等待超时,但测试仍会偶发在毫秒级内因索引过期抛出OperationCanceledException,导致测试不稳定。

核心问题分析

从异常栈和现有配置来看,问题根源集中在以下几点:

  1. 版本不兼容:服务器版本(5.3.x)和客户端版本(5.4.x)不一致,RavenDB对版本匹配要求严格,版本差异会导致超时参数在客户端-服务器交互中解析异常。
  2. 同步等待逻辑冲突:OnSessionCreated中通过Thread.Sleep循环轮询索引状态的同步阻塞逻辑,不仅会阻塞Session创建流程,还会与客户端内置的异步等待机制冲突,提前触发取消信号。
  3. 全局请求超时未配置:仅设置了索引等待超时,但RequestExecutor的全局HTTP请求超时仍为默认值,可能比索引等待超时更短,导致请求提前被终止。
  4. CancellationToken被提前取消:异步操作(如SaveChangesAsync)传入的CancellationToken可能被测试框架或业务逻辑提前取消,覆盖了你配置的超时参数。

解决方案

1. 对齐客户端与服务器版本

将C#客户端版本降级到5.3.x,与服务器版本完全一致,彻底避免版本差异导致的参数传递问题。

2. 修复DocumentStore配置

移除冲突的同步等待逻辑,同时配置全局请求超时确保覆盖索引等待时长:

public static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(10);

private IDocumentStore CreateDocumentStore()
{
    var store = new DocumentStore()
    {
        Urls = new[] { RavenUrl },
        Database = TestDatabaseName,
        Conventions = new DocumentConventions
        {
            WaitForIndexesAfterSaveChangesTimeout = DefaultTimeout,
            WaitForNonStaleResultsTimeout = DefaultTimeout,
        }
    };

    store.Initialize();
    // 配置全局请求超时,确保比索引等待超时更长,避免HTTP请求提前终止
    store.RequestExecutor.Timeout = DefaultTimeout.Add(TimeSpan.FromMinutes(1));

    ConfigureDocumentStoreConventions(store);

    store.OnBeforeQuery += (_, args) =>
    {
        args.QueryCustomization.WaitForNonStaleResults(DefaultTimeout);
    };

    _newDatabaseWasCreated = EnsureDatabaseExists(store, TestDatabaseName);
    if (_newDatabaseWasCreated)
    {
        SeedDefaultDocuments(store);
        // 种子数据写入后,显式等待所有索引完成
        WaitForAllIndexesToBeNonStale(store);
    }

    return store;
}

// 封装安全的索引等待逻辑,带超时控制
private void WaitForAllIndexesToBeNonStale(IDocumentStore store)
{
    var startTime = DateTime.UtcNow;
    while (DateTime.UtcNow - startTime < DefaultTimeout)
    {
        var stats = store.Maintenance.ForDatabase(TestDatabaseName)
            .Send(new GetStatisticsOperation());
        
        if (stats.StaleIndexes.Length == 0)
            return;
            
        Thread.Sleep(500);
    }
    throw new TimeoutException("等待索引变为非过期状态超时");
}

3. 确保异步操作的CancellationToken不被提前取消

在测试中的异步操作(如SaveChangesAsync)中,避免传入测试框架自带的短超时CancellationToken,改用CancellationToken.None或延长测试框架的超时时间:

// 示例:在Repository中使用CancellationToken.None确保不被提前取消
public async Task Update(Guid entityId, Action<ItSystemRelationship> applyChanges)
{
    using var session = _store.OpenAsyncSession();
    var entity = await session.LoadAsync<ItSystemRelationship>(entityId, CancellationToken.None);
    applyChanges(entity);
    await session.SaveChangesAsync(CancellationToken.None);
}

4. 写入后显式等待索引(可选)

对于关键的写入操作,可以在SaveChanges后显式调用索引等待方法,确保索引完全更新后再执行查询:

await session.SaveChangesAsync(CancellationToken.None);
// 等待所有受影响的索引完成
await session.Advanced.WaitForIndexesAfterSaveChangesAsync(DefaultTimeout, CancellationToken.None);

异常原因补充

你遇到的OperationCanceledException发生在服务器端的JsonContextPool分配阶段,说明服务器端的操作被取消信号终止,这通常是因为客户端的HTTP请求超时,而非索引等待超时本身。因此配置RequestExecutor的全局超时是解决问题的关键。

内容的提问来源于stack exchange,提问作者LLL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 07:23:14