Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions changelog.d/unreleased/1714.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: fixed
issues:
- 1714
affected:
- src/CodeIndex/Database/DbReader.cs
- src/CodeIndex/Mcp/McpServer.cs
- src/CodeIndex/Mcp/McpToolHandlers.cs
---

## English

- **MCP DbReader lifetimes are now disposed deterministically (#1714)** - per-request readers and the index-project signal reader release their per-reader caches when the call finishes.

## 日本語

- **MCP の DbReader lifetime を決定的に破棄するようにしました (#1714)** - リクエスト単位の reader と index-project の signal reader が、呼び出し終了時に reader 内キャッシュを解放します。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/1716.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 1716
affected:
- src/CodeIndex/Cli/ProgramRunner.cs
---

## English

- **MCP server startup now handles loop failures before process exit (#1716)** - stdio and HTTP MCP entry points log unexpected failures and flush stdout/stderr before returning an error exit code.

## 日本語

- **MCP server 起動経路がループ失敗をプロセス終了前に処理するようにしました (#1716)** - stdio / HTTP MCP の entry point は予期しない失敗をログに残し、stdout/stderr を flush してからエラー終了します。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/1720.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 1720
affected:
- src/CodeIndex/Mcp/McpToolHandlers.cs
---

## English

- **MCP index now reports per-file failures instead of silently swallowing them (#1720)** - index responses include `failed_count` and a capped failure list when individual files fail.

## 日本語

- **MCP index がファイル単位の失敗を黙殺せず報告するようにしました (#1720)** - 個別ファイルの処理に失敗した場合、応答に `failed_count` と上限付きの失敗一覧を含めます。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/2010.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 2010
affected:
- src/CodeIndex/Database/DbDebug.cs
---

## English

- **DbDebug now preserves row-read exception chains (#2010)** - debug dumps include exception chains captured while snapshotting row fields and a root-cause line for faster diagnosis.

## 日本語

- **DbDebug が row read 例外チェーンを保持するようにしました (#2010)** - 行フィールドの snapshot 中に捕捉した例外チェーンと root-cause 行を debug dump に含め、原因特定を容易にします。
40 changes: 37 additions & 3 deletions src/CodeIndex/Cli/ProgramRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1451,8 +1451,25 @@ private static int RunMcp(string[] cmdArgs, string appVersion)
if (string.Equals(transport, "http", StringComparison.OrdinalIgnoreCase))
return RunMcpHttp(server, listenSpec ?? DefaultMcpHttpListen);

server.RunAsync().GetAwaiter().GetResult();
return CommandExitCodes.Success;
try
{
server.RunAsync().GetAwaiter().GetResult();
return CommandExitCodes.Success;
}
catch (OperationCanceledException)
{
Console.Out.Flush();
Console.Error.Flush();
return CommandExitCodes.CancelledBySignal;
}
catch (Exception ex)
{
GlobalToolLog.Error("mcp_server_failed " + GlobalToolLog.FormatExceptionChain(ex));
Console.Error.WriteLine($"Error: MCP server failed ({ex.GetType().Name}: {ex.Message}).");
Console.Out.Flush();
Console.Error.Flush();
return CommandExitCodes.DatabaseError;
}
}
finally
{
Expand Down Expand Up @@ -1516,7 +1533,24 @@ private static int RunMcpHttp(McpServer server, string listenSpec)
else
Console.Error.WriteLine($"[cdidx-mcp] HTTP transport listening on {resolved.Prefix} (bearer auth required).");

server.RunAsync(transport, cts.Token).GetAwaiter().GetResult();
try
{
server.RunAsync(transport, cts.Token).GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
Console.Out.Flush();
Console.Error.Flush();
return CommandExitCodes.CancelledBySignal;
}
catch (Exception ex)
{
GlobalToolLog.Error("mcp_http_server_failed " + GlobalToolLog.FormatExceptionChain(ex));
Console.Error.WriteLine($"Error: MCP HTTP server failed ({ex.GetType().Name}: {ex.Message}).");
Console.Out.Flush();
Console.Error.Flush();
return CommandExitCodes.DatabaseError;
}
}
}
finally
Expand Down
37 changes: 37 additions & 0 deletions src/CodeIndex/Database/DbDebug.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public static class DbDebug
[ThreadStatic]
private static List<(string Name, string Value)>? _lastRow;
[ThreadStatic]
private static List<string>? _lastRowReadExceptionChains;
[ThreadStatic]
private static List<Exception>? _lastRowReadExceptions;
[ThreadStatic]
private static bool _hasContext;
[ThreadStatic]
private static List<QueryProfileEntry>? _profileEntries;
Expand Down Expand Up @@ -169,6 +173,8 @@ public static void ResetContext()
_lastSql = null;
_lastParams = null;
_lastRow = null;
_lastRowReadExceptionChains = null;
_lastRowReadExceptions = null;
_hasContext = false;
}

Expand Down Expand Up @@ -314,6 +320,9 @@ internal static void SnapshotRow(SqliteDataReader reader)
catch (Exception ex)
{
row.Add((name, $"<error: {ex.GetType().Name}: {ex.Message}>"));
(_lastRowReadExceptions ??= new List<Exception>()).Add(ex);
(_lastRowReadExceptionChains ??= new List<string>())
.Add($"[{name}]\n{GlobalToolLog.FormatExceptionChain(ex, includeStacks: mode == DebugMode.Unsafe)}");
}
}
_lastRow = row;
Expand Down Expand Up @@ -356,6 +365,15 @@ public static void DumpToStderr(Exception ex)
foreach (var (name, value) in _lastRow)
sb.AppendLine($" [{name}] = {value}");
}
if (_lastRowReadExceptionChains is { Count: > 0 })
{
sb.AppendLine("Row read exception chains:");
foreach (var chain in _lastRowReadExceptionChains)
sb.AppendLine(chain);
}
var rootCause = GetDeepestExceptionIncludingRowReads(ex);
if (_lastRowReadExceptionChains is { Count: > 0 })
sb.AppendLine($"Root cause: {rootCause.GetType().Name}: {rootCause.Message}");
if (mode != DebugMode.Unsafe && ex.StackTrace != null)
{
sb.AppendLine("Stack:");
Expand All @@ -365,6 +383,25 @@ public static void DumpToStderr(Exception ex)
Console.Error.Write(sb.ToString());
}

private static Exception GetDeepestException(Exception ex)
{
var current = ex;
while (current.InnerException != null)
current = current.InnerException;
return current;
}

private static Exception GetDeepestExceptionIncludingRowReads(Exception ex)
{
var deepest = GetDeepestException(ex);
if (_lastRowReadExceptions is not { Count: > 0 })
return deepest;

foreach (var rowException in _lastRowReadExceptions)
deepest = GetDeepestException(rowException);
return deepest;
}

private static string FormatValue(object? value, DebugMode mode, string? valueName = null)
{
if (value is null || value is DBNull)
Expand Down
27 changes: 26 additions & 1 deletion src/CodeIndex/Database/DbReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public readonly record struct SqlGraphContractSignal(
/// Handles read/query operations against the database for search, symbols, and files.
/// 検索・シンボル・ファイル一覧などのDB読み取り操作を担当する。
/// </summary>
public partial class DbReader
public partial class DbReader : IDisposable
{
public const string VerifyFoldReadyRowsEnvironmentVariable = "CDIDX_VERIFY_FOLD_READY_ROWS";
internal const int MaxReferenceKindAggregateCharacters = 16 * 1024;
Expand Down Expand Up @@ -65,6 +65,7 @@ public partial class DbReader
private readonly Dictionary<string, List<CSharpContainingTypeCandidate>> _csharpTypeContainingTypesByName = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, HashSet<string>> _csharpInheritedContainingTypesByQualifiedName = new(StringComparer.Ordinal);
private readonly Dictionary<string, CSharpContainingTypeScope?> _csharpContainingTypeScopeByQualifiedName = new(StringComparer.Ordinal);
private bool _disposed;
private HashSet<string>? _csharpGlobalUsingStaticTargets;
private HashSet<string>? _csharpGlobalUsingNamespaces;
private Dictionary<string, CSharpUsingAliasScope>? _csharpGlobalUsingAliasesByName;
Expand Down Expand Up @@ -563,6 +564,30 @@ private DbReader(
(_indexNewerThanReader, _indexNewerThanReaderReason) = DetectNewerThanReaderContracts(_conn, userVersion);
}

public void Dispose()
{
if (_disposed)
return;

_disposed = true;
_csharpUsingStaticScopesByPath.Clear();
_csharpNamespaceScopesByPath.Clear();
_csharpContainingTypeScopesByPath.Clear();
_csharpUsingNamespaceScopesByPath.Clear();
_csharpUsingAliasScopesByPath.Clear();
_activeCSharpTypeNamespacesByPathLine.Clear();
_activeCSharpContainingTypeScopesByPathLine.Clear();
_activeCSharpUsingStaticTargetsByPathLine.Clear();
_csharpConstantPatternContainersByMemberName.Clear();
_csharpTypeNamespacesByName.Clear();
_csharpTypeContainingTypesByName.Clear();
_csharpInheritedContainingTypesByQualifiedName.Clear();
_csharpContainingTypeScopeByQualifiedName.Clear();
_csharpGlobalUsingStaticTargets = null;
_csharpGlobalUsingNamespaces = null;
_csharpGlobalUsingAliasesByName = null;
}

/// <summary>
/// Per-request CancellationToken plumbed in by the caller (e.g. the MCP server). Methods
/// that loop over SQLite rows can check this between batches to bail out promptly on
Expand Down
4 changes: 2 additions & 2 deletions src/CodeIndex/Mcp/McpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2518,7 +2518,7 @@ private JsonNode WithDbReader(JsonNode? id, JsonNode? args, Func<DbReader, JsonN
{
using var isolatedDb = new DbContext(_dbPath);
isolatedDb.TryMigrateForRead();
var isolatedReader = new DbReader(isolatedDb, requestToken);
using var isolatedReader = new DbReader(isolatedDb, requestToken);
isolatedReader.IncludeGenerated = args?["includeGenerated"]?.GetValue<bool>() ?? false;
return isolatedReader.RunWithGeneratedScope(() => action(isolatedReader));
}
Expand All @@ -2539,7 +2539,7 @@ private JsonNode WithDbReader(JsonNode? id, JsonNode? args, Func<DbReader, JsonN
// MCP ツール呼び出しごとの schema 再走査を排除し (issue #1565)、
// per-request cancellation token を reader に渡して SQLite 作業が
// shutdown / 切断を観測できるようにする (#1567)。
var reader = new DbReader(db, requestToken);
using var reader = new DbReader(db, requestToken);
reader.IncludeGenerated = args?["includeGenerated"]?.GetValue<bool>() ?? false;
return reader.RunWithGeneratedScope(() => action(reader));
}
Expand Down
42 changes: 37 additions & 5 deletions src/CodeIndex/Mcp/McpToolHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2899,6 +2899,7 @@ void WriteProjectRootOnce()
if (purged > 0 && hadCSharpStaticInterfaceContractsBeforePurge)
csharpWorkspace = csharpWorkspace with { HasStaticInterfaceContracts = true };
int processed = 0, skipped = 0, errors = 0;
var failures = new List<IndexFileFailure>();
var reusedHotspotFamilyLanguages = new HashSet<string>(StringComparer.Ordinal);

foreach (var filePath in files)
Expand Down Expand Up @@ -2970,9 +2971,10 @@ void WriteProjectRootOnce()
txn.Commit();
}
}
catch
catch (Exception ex)
{
errors++;
failures.Add(BuildIndexFileFailure(projectPath, filePath, ex, "delete_skipped_binary"));
}
}
catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException)
Expand All @@ -2991,9 +2993,10 @@ void WriteProjectRootOnce()
txn.Commit();
}
}
catch
catch (Exception cleanupEx)
{
errors++;
failures.Add(BuildIndexFileFailure(projectPath, filePath, cleanupEx, "delete_missing_file"));
}
}
catch (OperationCanceledException) when (requestToken.IsCancellationRequested)
Expand All @@ -3002,11 +3005,12 @@ void WriteProjectRootOnce()
writer.ClearBatchInProgress();
throw;
}
catch
catch (Exception ex)
{
if (fileBatchMarked)
writer.ClearBatchInProgress();
errors++;
failures.Add(BuildIndexFileFailure(projectPath, filePath, ex, "index_file"));
}
processed++;
EmitProgressNotification(progressToken, processed, files.Count);
Expand Down Expand Up @@ -3160,7 +3164,8 @@ void WriteProjectRootOnce()
["skipped"] = skipped,
["purged"] = purged,
["unknown_extension_file_count"] = scanResult.UnknownExtensionFiles.Count,
["errors"] = errors
["errors"] = errors,
["failed_count"] = failures.Count
},
["sql_graph_contract_ready"] = sqlGraphContractReadyAfter,
["csharp_symbol_name_ready"] = csharpSymbolNameReadyAfter,
Expand All @@ -3170,9 +3175,28 @@ void WriteProjectRootOnce()
["fold_ready"] = foldReadyAfter,
["fold_ready_reason"] = foldReadyReason
};
if (failures.Count > 0)
{
var failureArray = new JsonArray();
foreach (var failure in failures.Take(50))
{
failureArray.Add(new JsonObject
{
["path"] = failure.Path,
["stage"] = failure.Stage,
["exception_type"] = failure.ExceptionType,
["message"] = failure.Message,
});
}
structured["failed_count"] = failures.Count;
structured["failures"] = failureArray;
if (failures.Count > 50)
structured["failures_truncated"] = failures.Count - 50;
GlobalToolLog.Error($"mcp_index_file_failures count={failures.Count} first_path='{failures[0].Path}' first_error='{failures[0].ExceptionType}: {failures[0].Message}'");
}
if (!sqlGraphContractReadyAfter)
{
var signalReader = new DbReader(writer.Connection);
using var signalReader = new DbReader(writer.Connection);
AddSqlGraphContractSignal(structured, signalReader.GetSqlGraphContractSignal());
}
return CreateToolResult(id,
Expand All @@ -3188,6 +3212,14 @@ void WriteProjectRootOnce()
structured);
}

private static IndexFileFailure BuildIndexFileFailure(string projectPath, string filePath, Exception ex, string stage)
{
var relativePath = FileIndexer.NormalizePathSeparators(Path.GetRelativePath(projectPath, filePath));
return new IndexFileFailure(relativePath, stage, ex.GetType().Name, ex.Message);
}

private sealed record IndexFileFailure(string Path, string Stage, string ExceptionType, string Message);

private JsonNode ExecuteBackfillFold(JsonNode? id, JsonNode? progressToken = null)
{
if (!DbContext.TryValidateExistingCodeIndexDb(_dbPath, out var validationMessage, out var isNotFound))
Expand Down
Loading