优化你的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   02413704 System.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           ///   <summary>
 2           ///  将对象转换为Int32类型
 3           ///   </summary>
 4           ///   <param name="str"> 要转换的字符串 </param>
 5           ///   <param name="defValue"> 缺省值 </param>
 6           ///   <returns> 转换后的int类型结果 </returns>
 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 < CachedCodeEntry >  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<CachedCodeEntry> 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,不然还真不知道,也算是收获不小啊。

 

 

 

你可能感兴趣的:(discuz)