
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
我封装了一个 Mysql 连接池,目的是始终维持着一个 mysql connector 对象,然后把使用过的语句都进行预编译。
然后,重复执行 sql 的时候,始终能够使用预编译的 sql。
具体的实现请看:https://github.com/ahfuzhang/QiWa.framework/blob/v0.9.1/src/Mysql/DbConnectionPool.cs
然后网站总是会重现一个问题:一段时间无法再访问后,第一个请求会出现 500 错误。
我抓到的堆栈信息如下:
at System.Net.Security.SslStream.<<WriteSingleChunk>g__CompleteWriteAsync|166_1>d`1.MoveNext() + 0x88
--- End of stack trace from previous location
--- at System.Net.Security.SslStream.<WriteAsyncInternal>d__173`1.MoveNext() + 0x2e1
--- End of stack trace from previous location
--- at MySqlConnector.Protocol.Serialization.StreamByteHandler.<<WriteBytesAsync>g__DoWriteBytesAsync|7_0>d.MoveNext() + 0x90 --- End of stack trace from previous location
--- at MySqlConnector.Protocol.Serialization.ProtocolUtility.<WritePayloadAsync>d__2.MoveNext() + 0xfe
--- End of stack trace from previous location
--- at MySqlConnector.Core.ServerSession.<SendReplyAsync>d__122.MoveNext() + 0x200
--- End of stack trace from previous location
--- at MySqlConnector.Core.CommandExecutor.<ExecuteReaderAsync>d__0.MoveNext() + 0x585
--- End of stack trace from previous location
--- at MySqlConnector.MySqlCommand.<ExecuteReaderAsync>d__87.MoveNext() + 0x1d5
--- End of stack trace from previous location
--- at QiWa.Mysql.MySqlCommandWrapper.<ExecuteReaderAsync>d__12.MoveNext() + 0x5f
--- End of stack trace from previous location
--- at QiWa.Mysql.DbConnection`3.<ExecuteReaderAsync>d__16.MoveNext() + 0x499
--- End of stack trace from previous location
--- at Generated.CSharpBackend.API.V1.Menu.Rpc.LoadMenuTreeContext.<RunAsync>d__3.MoveNext() + 0x495
--- End of stack trace from previous location
--- at Generated.CSharpBackend.API.V1.Menu.Rpc.MenuService.<CallMethod>d__11`2.MoveNext() + 0x4bb
QiWa.Mysql.MySqlCommandWrapper. 这个位置是我的框架的代码。
这部分代码的写法如下:
try
{
var reader = await cmd!.ExecuteReaderAsync(ct).ConfigureAwait(false);
await using (reader.ConfigureAwait(false))
{
long rowCount = 0;
while (await reader.ReadAsync(ct).ConfigureAwait(true))
{
var rowErr = eachRowFunc(reader);
if (rowErr.Err())
{
return (rowCount, rowErr);
}
rowCount++;
}
return (rowCount, default);
}
}
catch (MySqlException ex)
{
return (0,
Error.WithLoc(
(int)ErrorCodes.ExecuteMySqlExceptionError,
$"[MySqlException]ExecuteReaderAsync: {ex.Message}"
)
);
}
catch (OperationCanceledException)
{
_disableReuse = true; // 如果发生超时,则这条连接可能一直在使用之中,所以不再重用这个连接对象
return (0, Error.WithLoc((int)ErrorCodes.ExecuteTimeoutError, "[OperationCanceledException]cmd.ExecuteReaderAsync timeout"));
}
catch (IOException exIO)
{
_disableReuse = true;
return (0, Error.WithLoc((int)ErrorCodes.ExecuteIOExceptionError, $"[IOException]cmd.ExecuteReaderAsync,ex={exIO.Message}"));
}
catch (Exception exUnknown)
{
_disableReuse = true;
return (0, Error.WithLoc((int)ErrorCodes.ExecuteUnknownExceptionError, $"[Exception]cmd.ExecuteReaderAsync,ex={exUnknown.Message}"));
}
可以看到,最后一个 catch 中已经加上了 catch (Exception),理论上无论抛出什么异常都会被抓到。
神奇的地方是,http 入口的 try catch 又抓住了这个异常。这部分代码是:
// 调用业务
try
{
long start = Stopwatch.GetTimestamp();
err = await ctx.RunAsync().ConfigureAwait(true);
counters.Latency.ReportLatency(start);
}
catch (Exception ex)
{
// 打日志
ctx.L!.Warn(
Field.Int64("error_code"u8, 65535),
Field.String("message"u8, ex.Message),
Field.String("stack_trace"u8, ex.StackTrace ?? "")
);
// 数据上报
Interlocked.Increment(ref counters.ExceptionsTotal);
context.Response.StatusCode = 500;
return (null, Error.WithLoc(65535, "exception"));
}
** 为什么内层的 try-catch 抓不住异常,而外层的又抓到了 **
我的 AOT 编译参数如下:
# 保留 NativeAOT 调试元数据,方便分析 k8s 中的 core dump。
AOT_CORE_DUMP_FLAGS = \
-p:DebugSymbols=true \
-p:DebugType=portable \
-p:DebuggerSupport=true \
-p:StackTraceSupport=true \
-p:IlcGenerateMapFile=true
RID = linux-x64
TFM = net10.0
PRJ=csharp_backend
BUILD_DIR=../../build/Release/linux/amd64/
AOT_OBJ_DIR=./obj/Release/$(TFM)/$(RID)
AOT_NATIVE_OBJ_DIR=$(AOT_OBJ_DIR)/native
build_server:
cd server/csharp_backend && \
dotnet restore $(PRJ).csproj -r $(RID) && \
dotnet clean $(PRJ).csproj -r $(RID) -c Release && \
rm -fdr $(BUILD_DIR)/* && \
dotnet publish $(PRJ).csproj \
-r $(RID) \
-p:DefineConstants=UNIX \
-p:AllowUnsafeBlocks=true \
-p:PublishAot=true \
-p:StripSymbols=false \
-p:StaticLinkedRuntime=true \
-p:StaticExecutable=true \
-p:PositionIndependentExecutable=false \
-p:InvariantGlobalization=true \
-p:OptimizationPreference=Size \
-p:CppCompilerAndLinker=clang \
-p:EventSourceSupport=true \
-p:EmbedAllSources=true \
$(AOT_CORE_DUMP_FLAGS) \
--self-contained true \
-c Release -o $(BUILD_DIR) && \
cp -f $(AOT_NATIVE_OBJ_DIR)/$(PRJ).map.xml $(BUILD_DIR) && \
cp -f $(AOT_OBJ_DIR)/$(PRJ).pdb $(BUILD_DIR) && \
cp csharp_backend.yaml $(BUILD_DIR)
已经过去两星期了,一个简单的 mysql 连接池,仍然还是会在 AOT 编译模式下发生 coredump 或者抛异常。
文章摘自:https://www.cnblogs.com/ahfuzhang/p/21920545

