由string.IndexOf引起的ASP.Net应用挂起

最近有一个有趣的案例,我们发现String.IndexOf会造成ASP.Net应用挂起。

 

问题描述:

通常这种ASP.Net应用程序将挂起,因此IIS工作进程需要重新启动使得应用程序能够正常运行。即使当负载比较低的时候也会发生这种情况。

 

排除故障:

像以前一样,我们抓获挂起dump,我们可以看到几乎请求都会被GC阻止。下面是一个示例调用堆栈。

 

ntdll!ZwWaitForSingleObject

kernel32!WaitForSingleObjectEx

mscorwks!CLREventWaitHelper

mscorwks!CLREvent::WaitEx

mscorwks!SVR::gc_heap::wait_for_gc_done

mscorwks!SVR::gc_heap::try_allocate_more_space

Some customer code here

System_Web_ni!System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)

 

但是,如果去查看一下GC线程,发现他们根本不在GC状态。之后我们发现线程48禁用了PreEmptive GC,不允许GC来暂停它。所以GC必须等到这个线程结束。

 

PreEmptive Lock

ID OSID ThreadOBJ State GC GC Alloc Context Domain Count APT Exception

24 1 1e48 000000000015f280 1808220 Enabled 00000000902c9e50:00000000902c9e70 00000000001bbbe0 1 MTA (Threadpool Worker)

……………………

48 8 2070 00000000091043e0 180b222 Disabled 0000000000000000:0000000000000000 00000000001bbbe0 1 MTA (Threadpool Worker)

………………..

线程48正在替换strings。

 

mscorwks!COMStringBuffer::LocalIndexOfString

mscorwks!COMString::ReplaceString

MyAssembly!MyFunction.RemoveSpaces(System.String)

然后线程 48占用了所有CPU.

 0:048> !runaway

 User Mode Time

 Thread Time

 48:2070 0 days 0:01:02.468

 52:20b8 0 days 0:00:16.359

 34:1ef0 0 days 0:00:16.171

  ….

如果我们dump出字符串,它是非常小的字符串,其中只包含200个字符。替换字符串里面的东西不应该花费这么长的时间。代码下面,它试图用单个空格替换所有连续的空格。其实,字符串中已经没有连续空格。此外,在这里我们不需要while循环,因为String.Replace会返回一个新字符串,其中在这个新字符串当前所有与指定的字符串匹配的字符串会被替换为指定字符串。此时我怀疑while循环是无法终止的。

if ((str != null) && (str.Length != 0))

  {

 while (str.IndexOf(" ") != -1) // as long as there are two continuous spaces

  {

 str = str.Replace(" ", " "); //replace it with single space

  }

看看此字符串的十六进制值,我找到了异常:字符串里面有一个特殊的字符0xfffd。

 

0:048> dd 801fc6d8

00000000`801fc6d8 0041004d 00200058 0020fffd 00680054

 

此后我用下面代码重现了问题。.IndexOf总是返回0x0而不是所希望的-1。

 

char [] test= { '\u0020' , '\ufffd' , '\u0020' };

string testStr = newstring (test);

int i = testStr.IndexOf( " " );

0xFFFD是一个特殊的Unicode字符。U+FFFD(“ �​”)是未知或不可打印字符的替换字符。为安全起见,在一个Unicode文本中的无效字节被替换为U+ FFFD,即Unicode替换字符。例如,字符串“Ad\ xD800min\ xDC00istrator“将改为“广告\ xFFFDmin\ xFFFDistrator“这个字符串解码为“Ad�min�istrator“,其中“ �​”是Unicode替换字符0xFFFD。

原因在于“String.Replace”执行了顺序的(culture-insensitive)搜索,这里当Unicode标值一样时,两个字符是被认为是等价的。但是“IndexOf(string)”方法用当前的culture执行了case-sensitive和culture-sensitive的字符搜索。为了解决这个无限循环的问题, 我们应该使用string.IndexOf("AA", StringComparison.Ordinal);

        staticvoid Main( string [] args)

        {

char [] test = { '\u0020' , '\ufffd' , '\u0020' };   // A string " "

string testStr = newstring (test);

int i = testStr.IndexOf( " ", StringComparison.Ordinal );     //This returns 0x0 which means “ ”(two spaces) was found at position 0x0

while (i != -1)

                {

                    testStr = testStr.Replace( " " , " " );   //replace two continuous spaces with single space.

                    i = testStr.IndexOf( " ", StringComparison.Ordinal );

                }

        }

 

可以参考“Remarks”一栏获取更多相关信息:

String::IndexOf Method (String)

https://msdn.microsoft.com/en-us/library/k8b1470s(v=VS.90).aspx

 

String.Replace Method (String, String)

https://msdn.microsoft.com/en-us/library/fk49wtc1(v=VS.80).aspx