客户端的不当使用或ServerObject的设计不良,一次服务会常时间执行或长生不死,这常时间占据服务器的CPU和资源,影响服务器对别的客户的服务,因此,需要设定一个服务超时时间,如1分钟、10分钟,超过这个时间的调用就会被强制终止。
如何终止?当然是终止服务的线程,但千万不要使用TerminateThread这个API,它太暴力,使线程没有机会执行清理工作,外面也不知道这个线程已经被终止,典型的现象就是客户端收不到任何错误,并一直等下去...
如何完全终止线程,有人说要靠线程内部检查某个值,然后满足条件就自己终止,话需不错,假设线程都是我们在服务器自己创建的,因此可以加入这样的代码,但是,如果线程中的某一条语句本身要很长时间呢,这个线程还是要等到这个语句执行完毕才有机会来检查超时条件,但这个时候可能已经超时好多好多了,所以这个方法也不行。
唯一的方法是使用GetThreadContext和SetThreadContext,挂起线程,截获线程的快照,在线程中插入一段代码(如Abort),然后恢复线程执行。但有时候一次不会成功,因为你插入的代码可能恰好在try...except里面,没有引起异常,所以可能要多来几次。
procedure InjectProc_AbortThread;
begin
//举发异常,使正常释放资源
Abort;
// Raise Exception.Create(S_CallTimeout);
// finally
// EndThread(STATUS_TIMEOUT);
// end;
end;
procedure SafeAbortThread(AThreadHandle:THandle);
var
vThreadContext:TContext;
pStack:PDWORD;
vWaitResult:Cardinal;
vSuspendCount:Cardinal;
vEnjected:Boolean;
vWaitTime:integer;
begin
//在工作线程插入Abort指令,可能这个指令恰好被try except拦掉,
//所以不停的测试
//等待其执行完毕,不会马上停止,有时对数据库的访问要等近1分钟才出现异常
Sleep(0);
vWaitResult:=Windows.WaitForSingleObject(AThreadHandle,0);
repeat
case vWaitResult of
WAIT_FAILED:
begin
tmErrorLog.ErrorLog.Log(SysErrorMessage(Windows.GetLastError),nil,nil);
break;
end;
WAIT_OBJECT_0:
begin
break;
end;
else
begin
vEnjected:=false;
Windows.SuspendThread(AThreadHandle);
try
vThreadContext.ContextFlags:=CONTEXT_FULL;
if Windows.GetThreadContext(AThreadHandle,vThreadContext) then
begin
if (Cardinal(vThreadContext.Eip)<$70000000) then
begin
pStack:=Pointer(vThreadContext.Esp);
Dec(pStack);
if not IsBadWritePtr(pStack,4) then
begin
vThreadContext.Esp:=DWORD(pStack);
pStack^:=vThreadContext.Eip;
vThreadContext.Eip:=DWORD(@InjectProc_AbortThread);
vThreadContext.ContextFlags:=CONTEXT_CONTROL;
vEnjected:=SetThreadContext(AThreadHandle,vThreadContext);
end;
end;
end
else break;
finally
repeat
vSuspendCount:=Windows.ResumeThread(AThreadHandle);
until (vSuspendCount<=1) or (vSuspendCount=$FFFFFFFF);
end;
//交出本线程执行时间
Sleep(0);
//检查工作线程执行情况
if vEnjected then vWaitTime:=100
else vWaitTime:=0;
vWaitResult:=Windows.WaitForSingleObject(AThreadHandle,vWaitTime);
end;
end;
until false;
end;