注:本文仅针对 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,不然还真不知道,也算是收获不小啊。