Author Archives: youenzeng

rethrow exception and keeps original StackTrace

Published / by youenzeng / Leave a Comment

Expceiton rethrow

最近在改一个项目的bug,反射调用一段代码出错,结果在Log里面看不到完整的错误. 原因是Log里面记录的是抛出的Exception,而不是内部Exception. 当我修改为抛出内部Exception时,发现记录下来Exception的StackTrace是在抛出Exception的地方,而不是引发Exception的地方, 即Exception的StackTrace被修改了.

为了重现这个问题,添加一个dll

    class Processor
    {
        public string Runner(DateTime dateTime)
        {
            if (dateTime.Year > 2058)
                return "Hell! It's about time!";

            throw new Exception("Hold on..");
        }
    }

调用代码

class PluginRunner
    {
        public void ExecuteCore()
        {
            Assembly assembly = Assembly.LoadFrom("PluginTest.dll");

            object obj = assembly.CreateInstance("PluginTest.Processor");
            MethodInfo m = obj.GetType().GetMethod("Runner");

            if (m != null)
            {
                object[] methodParams = new object[] { DateTime.Now };
                var result = m.Invoke(obj, methodParams);
            }
        }
    }
        static void Main(string[] args)
        {
            PluginRunner runner = new PluginRunner();
            try
            {
                runner.ExecuteCore();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"exception stacktrace:" + Environment.NewLine + ex.StackTrace + Environment.NewLine);
                throw;
            }

        }

由于调用方的异常抛出,此时, 记录异常为m.Invoke的堆栈,结果如下:

exception stacktrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Canary.PluginRunner.ExecuteCore() in xxx\Canary\PluginRunner.cs:line 23
at Canary.Program.Main(String[] args) in xxx\Canary\Program.cs:line 19

随后,尝试抛出InnerException

                try
                {
                    var result = m.Invoke(obj, methodParams);
                }
                catch (Exception ex)
                {
                    if (ex.InnerException != null)
                    {
                        // ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                        throw ex.InnerException;
                    }
                    throw ex;
                }

得到的是Invoke调用方的异常,结果如下:

exception stacktrace:
   at Canary.PluginRunner.ExecuteCore() in xxx\Canary\PluginRunner.cs:line 33
   at Canary.Program.Main(String[] args) in xxx\Canary\Program.cs:line 19

但是在断点出查看时at PluginTest.Processor.Runner(DateTime dateTime),异常在抛出后被修改了.

解决办法:
第一种是使用.NET 4.5中引入的ExceptionDispatchInfo,如官方文档所说,这种方式会保留原始堆栈.这个class的引入应该初衷是为了配合Task调用的AggregateException.
The ExceptionDispatchInfo object stores the stack trace information and Watson information that the exception contains at the point where it is captured. The exception can be thrown at another time and possibly on another thread by calling the ExceptionDispatchInfo.Throw method. The exception is thrown as if it had flowed from the point where it was captured to the point where the Throw method is called.
代码如下:

ExceptionDispatchInfo.Capture(ex.InnerException).Throw();

此时异常如下,原始异常被追加上去了:

exception stacktrace:
   at PluginTest.Processor.Runner(DateTime dateTime)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Canary.PluginRunner.ExecuteCore() in xxx\Canary\PluginRunner.cs:line 32
   at Canary.Program.Main(String[] args) in xxx\Canary\Program.cs:line 19

SO大佬在2010年指出这是一个Windows CLR的限制:
This is a well known limitation in the Windows version of the CLR. It uses Windows' built-in support for exception handling (SEH). Problem is, it is stack frame based and a method has only one stack frame. You can easily solve the problem by moving the inner try/catch block into another helper method, thus creating another stack frame. Another consequence of this limitation is that the JIT compiler won't inline any method that contains a try statement.

那么Linux下呢, 我在WSL下看了看是确实没有这个问题.

➜  CanaryNetCore git:(master) ✗ dotnet run

Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Exception: Hold on..
   at PluginTestCore.Processor.Runner(DateTime dateTime)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at CanaryNetCore.PluginRunner.ExecuteCore() in /mnt/c/DevLab/Canary/CanaryNetCore/PluginRunner.cs:line 33
   at CanaryNetCore.Program.Main(String[] args) in /mnt/c/DevLab/Canary/CanaryNetCore/Program.cs:line 18

➜  CanaryNetCore git:(master) ✗ cat cat PluginRunner.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading.Tasks;

namespace CanaryNetCore
{
    class PluginRunner
    {
        public void ExecuteCore()
        {
            Assembly assembly = Assembly.LoadFrom("PluginTestCore.dll");

            object obj = assembly.CreateInstance("PluginTestCore.Processor");
            MethodInfo m = obj.GetType().GetMethod("Runner");

            if (m != null)
            {
                object[] methodParams = new object[] { DateTime.Now };
                try
                {
                    var result = m.Invoke(obj, methodParams);
                }
                catch (Exception ex)
                {
                    //if (ex.InnerException != null)
                    //{
                    //    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                    //}
                    throw;
                }

            }
        }
    }
}

第二种方法是直接throw,在外面处理InnerException, 由于我不想修改外部代码, 所以使用了第一种方法.
The recommended way to re-throw an exception is to simply use the throw statement in C# and the Throw statement in Visual Basic without including an expression. This ensures that all call stack information is preserved when the exception is propagated to the caller.

参考:
1. https://stackoverflow.com/questions/57383/in-c-how-can-i-rethrow-innerexception-without-losing-stack-trace
2. https://msdn.microsoft.com/en-us/library/system.runtime.exceptionservices.exceptiondispatchinfo%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
3. https://msdn.microsoft.com/en-us/library/system.exception(v=vs.110).aspx

595mm in daily life

Published / by youenzeng / Leave a Comment

595mm

最近搬家,需要买一些家电.由于厨房只有一道约60公分的空间,在买家电的时候关注了一下尺寸. 惊奇的发现很多家电都有一个相似的尺寸:595mm.

  • 单开门冰箱
  • 滚筒洗衣机,干衣机
  • 洗碗机

我想这肯定是装修时有意如此设计的,留下一道60公分的空间来放置家电.也就是说多年前这个宽度已经成为行业标准,且各个家电行业的设计都在遵循这个标准. 由此想到计算机的硬件接口设计也是如此, 外部接口USB/SATA/VGA&HDMI多年只在进化且保持向下兼容.

不过计算机内部的接口就没有那么’友好’了,CPU/内存针脚隔代基本不兼容. 反之推想,各种电器内部也肯定经过了多重改进/改良.

都是在条条框框里面进化.

Git bash alias config

Published / by youenzeng / Leave a Comment

SourceTree是一个功能很强大的Git GUI工具,但是就是太慢,且在Windows下会出现莫名占用一个核心CPU。于是开始折腾Git bash,目前基本够用。其中alias部分的配置文件如下,供参考,放到 /username/.gitconfig里面即可。

[alias]
l = log --pretty=format:"%C(yellow)%h\ %ad%Cred%d\ %Creset%s%Cblue\ [%cn]" --decorate --date=short
a = add
ap = add -p
c = commit --verbose
ca = commit -a --verbose
cm = commit -m
cam = commit -a -m
m = commit --amend --verbose
d = diff
ds = diff --stat
dc = diff --cached
s = status
ss = status -s
co = checkout
cob = checkout -b
# list branches sorted by last modified
b = "!git for-each-ref --sort='-authordate' --format='%(authordate)%09%(objectname:short)%09%(refname)' refs/heads | sed -e 's-refs/heads/--'"
lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all
lg = !"git lg1"
# list aliases
la = "!git config -l | grep alias | cut -c 7-"

其中我最喜欢的是git lg,附图2张,右键查看大图。
git lg
git lg2