优化你的DiscuzNT3.0,让它跑起来(6)在线人数和Regex.IsMatch()引发的hang

注:本文仅针对 DiscuzNT3.0, sqlserver 2000版本,其他版本请勿对号入座。
你没看错标题,的确是 在线人数和Regex.IsMatch()引发的hang。事情是这样的,就在今天我们的论坛出现的挂起问题,当时刚好赶上了抓dump文件。于是就有了今天这篇文章。
我们先用windbg看看论坛当时在干什么吧。
1. 打开文件,运行 .load sos, 因为是hang,所以当然是要运行 !syncblk , 下面是运行结果:
0:000> .load sos
0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
202 000fe87c 1 1 1c3d2c98 d28 48 02413704System.Collections.Generic.LinkedList`1[[System.Text.RegularExpressions.CachedCodeEntry, System]]
-----------------------------
Total 419
CCW 2
RCW 0
ComClassFactory 0
Free 354
从上面看到,持有锁的线程是48 ,持有的对象是System.Collections.Generic.LinkedList,

2. 我们看看线程48在干什么, ~48s 切换到线程,!clrstack 看看执行的代码。
0:000> ~48s
eax=000006d7 ebx=1c3d2c98 ecx=000fe87c edx=044ec5c3 esi=00000354 edi=00000000
eip=7c9585ec esp=1d8eeee4 ebp=1d8eef54 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c9585ec c3 ret
0:048> !clrstack
OS Thread Id: 0xd28 (48)
ESP EIP
1d8ef060 7c9585ec [GCFrame: 1d8ef060]
1d8ef0fc 7c9585ec [HelperMethodFrame: 1d8ef0fc] System.Threading.Monitor.Enter(System.Object)
1d8ef150 7a4aed85 System.Text.RegularExpressions.Regex.LookupCachedAndUpdate(System.String)
1d8ef180 7a4aec91 System.Text.RegularExpressions.Regex..ctor(System.String, System.Text.RegularExpressions.RegexOptions, Boolean)
1d8ef1b4 7a4bf480 System.Text.RegularExpressions.Regex.IsMatch(System.String, System.String)
1d8ef1c4 1ba6c3cc Discuz.Common.TypeConverter.StrToInt(System.String, Int32)
1d8ef1e0 1ba6f6c2 Discuz.Common.TypeConverter.ObjectToInt(System.Object)
1d8ef1e4 1d50a215 Discuz.Data.OnlineUsers.LoadSingleOnlineUser(System.Data.IDataReader)
1d8ef1f4 1d50a111 Discuz.Data.OnlineUsers.GetOnlineUserCollection()
1d8ef204 1d509f73 Discuz.Forum.OnlineUsers.GetOnlineUserCollection(Int32 ByRef, Int32 ByRef, Int32 ByRef, Int32 ByRef)
1d8ef25c 1d30b291 Discuz.Web.website.ShowPage()
1d8ef274 1ba6d837 Discuz.Forum.PageBase..ctor()
1d8ef318 1d37e2d1 Discuz.Web.website..ctor()
1d8ef324 1d37e0f0 ASP.aspx_2_website_aspx..ctor()
。。。。。。这里省略若干字
--------------------------
我们从上面看到程序调用了
Discuz.Common.TypeConverter.StrToInt() 这个方法,然后进入
System.Text.RegularExpressions.dll,最后停留在
System.Text.RegularExpressions.Regex.LookupCachedAndUpdate() 方法, 我们从dnt3.0的程序一步步来看看。
1 /// 2 /// 将对象转换为Int32类型 3 /// 4 /// 要转换的字符串
5 /// 缺省值
6 /// 转换后的int类型结果 7 public static int StrToInt(string str, int defValue) 8 { 9 if (string.IsNullOrEmpty(str) || str.Trim().Length >= 11 || !Regex.IsMatch(str.Trim(), @"^([-]|[0-9])[0-9]*(\.\w*)?$")) 10 return defValue; 11 12 int rv; 13 if (Int32.TryParse(str, out rv)) 14 return rv; 15 16 return Convert.ToInt32(StrToFloat(str, defValue));
17 }
请出reflector
1 public static bool IsMatch(string input, string pattern) 2 { 3 return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
4 }
继续reflector
1 private Regex(string pattern, RegexOptions options, bool useCache) 2 { 3 CachedCodeEntry cachedAndUpdate = null; 4 string threeLetterWindowsLanguageName = null; 5 if (pattern == null) 6 { 7 throw new ArgumentNullException("pattern"); 8 } 9 if ((options < RegexOptions.None) || ((((int) options) >> 10) != 0)) 10 { 11 throw new ArgumentOutOfRangeException("options"); 12 } 13 if (((options & RegexOptions.ECMAScript) != RegexOptions.None) && ((options & ~(RegexOptions.CultureInvariant | RegexOptions.ECMAScript | RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase)) != RegexOptions.None)) 14 { 15 throw new ArgumentOutOfRangeException("options"); 16 } 17 if ((options & RegexOptions.CultureInvariant) != RegexOptions.None) 18 { 19 threeLetterWindowsLanguageName = CultureInfo.InvariantCulture.ThreeLetterWindowsLanguageName; 20 } 21 else 22 { 23 threeLetterWindowsLanguageName = CultureInfo.CurrentCulture.ThreeLetterWindowsLanguageName; 24 } 25 string[] strArray = new string[] { ((int) options).ToString(NumberFormatInfo.InvariantInfo), ":", threeLetterWindowsLanguageName, ":", pattern }; 26 string key = string.Concat(strArray); 27 cachedAndUpdate = LookupCachedAndUpdate(key); 28 this.pattern = pattern; 29 this.roptions = options; 30 if (cachedAndUpdate == null) 31 { 32 RegexTree t = RegexParser.Parse(pattern, this.roptions); 33 this.capnames = t._capnames; 34 this.capslist = t._capslist; 35 this.code = RegexWriter.Write(t); 36 this.caps = this.code._caps; 37 this.capsize = this.code._capsize; 38 this.InitializeReferences(); 39 t = null; 40 if (useCache) 41 { 42 cachedAndUpdate = this.CacheCode(key); 43 } 44 } 45 else 46 { 47 this.caps = cachedAndUpdate._caps; 48 this.capnames = cachedAndUpdate._capnames; 49 this.capslist = cachedAndUpdate._capslist; 50 this.capsize = cachedAndUpdate._capsize; 51 this.code = cachedAndUpdate._code; 52 this.factory = cachedAndUpdate._factory; 53 this.runnerref = cachedAndUpdate._runnerref; 54 this.replref = cachedAndUpdate._replref; 55 this.refsInitialized = true; 56 } 57 if (this.UseOptionC() && (this.factory == null)) 58 { 59 this.factory = this.Compile(this.code, this.roptions); 60 if (useCache && (cachedAndUpdate != null)) 61 { 62 cachedAndUpdate.AddCompiled(this.factory); 63 } 64 this.code = null; 65 }
66 }
这个代码量较大,找到关键点 LookupCachedAndUpdate
private static CachedCodeEntry LookupCachedAndUpdate(string key) { lock (livecode) { for (LinkedListNode node = livecode.First; node != null; node = node.Next) { if (node.Value._key == key) { livecode.Remove(node); livecode.AddFirst(node); return node.Value; } } } return null;
}
终于找到这个lock的对象了,看看他是什么类型的,和我们通过windbg看到的一样吗
internal static LinkedList livecode;
果然一样, 这下应该放心了,就是这里的lock引起了hang,但是我们应该经常用Regex.IsMatch()的,也没见引起这个问题啊,为什么这里???
我们看看在线人数有多少,如果在线人数比较多,访问的人数也多,那可能性就很大了,我们来看看到底有多少人在线。
运行 !dso,看看本线程对象。
0:048> !dso
OS Thread Id: 0xd28 (48)
ESP/REG Object Name
1d8ef094 02413704 System.Collections.Generic.LinkedList`1[[System.Text.RegularExpressions.CachedCodeEntry, System]]
1d8ef0b8 074b8c54 System.String 0:CHS:^([-]|[0-9])[0-9]*(\.\w*)?$
1d8ef0bc 074b94ac System.String 114.247.10.127
1d8ef0d0 074b94ac System.String 114.247.10.127
1d8ef150 02413704 System.Collections.Generic.LinkedList`1[[System.Text.RegularExpressions.CachedCodeEntry, System]]
1d8ef170 074b8c20 System.Text.RegularExpressions.Regex
1d8ef184 0a3fcd1c System.String ^([-]|[0-9])[0-9]*(\.\w*)?$
1d8ef190 074b8c54 System.String 0:CHS:^([-]|[0-9])[0-9]*(\.\w*)?$
1d8ef19c 074b8c08 System.String -1
1d8ef1a0 074b8c20 System.Text.RegularExpressions.Regex
1d8ef1a4 0a3fcd1c System.String ^([-]|[0-9])[0-9]*(\.\w*)?$
1d8ef1b4 068e2dc4 Discuz.Common.Generic.List`1[[Discuz.Entity.OnlineUserInfo, Discuz.Entity]]
1d8ef1b8 074b8c08 System.String -1
1d8ef1d4 074b8bac Discuz.Entity.OnlineUserInfo
1d8ef1d8 068e2f64 System.Data.SqlClient.SqlDataReader
。。。。。。省略若干字
----------------------
找到上面的
Discuz.Common.Generic.List 的地址
068e2dc4 , 运行 !do
068e2dc4
0:048> !do 068e2dc4
Name: Discuz.Common.Generic.List`1[[Discuz.Entity.OnlineUserInfo, Discuz.Entity]]
MethodTable: 1ceb7084
EEClass: 1bb39dd0
Size: 28(0x1c) bytes
(C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\9932c999\b8dfff59\assembly\dl3\21614a1e\486ed665_6d88ca01\Discuz.Common.DLL)
Fields:
MT Field Offset Type VT Attr Value Name
79124228 400099d 4 System.Object[] 0 instance 0f1b254c _items
790fed1c 400099e c System.Int32 0 instance 472 _size
790fed1c 400099f 10 System.Int32 0 instance 472 _version
790f9c18 40009a0 8 System.Object 0 instance 00000000 _syncRoot
79124228 40009a1 0 System.Object[] 0 shared static _emptyArray
>> Domain:Value dynamic statics NYI
000e1008:NotInit dynamic statics NYI
00107038:NotInit <<
790fed1c 400000c 14 System.Int32 0 instance 0 _fixedsize
从上面的size可以看到在线人数是472人,就是说这个48这个线程要lock 472 次,如果有n个人访问那后面的人真的要等不少时候了。
话说StrToInt()这个方法为什么要用Regex.IsMatch()呢,string 转换成 int 一般 int.TryParse()也足够了。不过从这里我才发现Regex.IsMatch() 里面原来还有个lock,不然还真不知道,也算是收获不小啊。


Tags: 

延伸阅读

最新评论

发表评论