应用程序挂起于SysUtils - >退出时DoneMonitorSupport [英] Application hangs in SysUtils -> DoneMonitorSupport on exit

查看:92
本文介绍了应用程序挂起于SysUtils - >退出时DoneMonitorSupport的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我已经追踪到系统单元,找到程序进入无限的地方循环。它位于 SysUtils 行19868 - > DoneMonitorSupport - > CleanEventList

 重复直到InterlockedCompareExchange(EventCache [I] .Lock,1,0)= 0; 

我已经在线搜索了一个解决方案,并发现了几个QC报告:





不幸的是,这些似乎与我的情况无关,因为我不使用 TThreadList TMonitor



我很确定我的所有线程都已经完成,并且已经被销毁,因为所有线程都继承自保持创建/销毁计数的基线程。



有没有人遇到类似的行为?你知道发现根本原因可能在哪里的任何策略?

解决方案

我一直在看这个 TMonitor 锁实现,我终于做了一个有趣的发现。对于一些戏剧,我会先告诉你这些锁的工作原理。



当你打电话给任何 TMonitor TObject 的函数中,创建了一个 TMonitor 记录的新实例,并将该实例分配给一个 MonitorFld 在对象本身内。使用 InterlockedCompareExchangePointer 以线程安全的方式进行此作业。因为这个技巧, TObject 只包含一个指针大小的数据支持 TMonitor ,它不' t包含完整的TMonitor结构。这是一件好事。



这个 TMonitor 结构包含许多记录。我们将从 FLockCount:Integer 字段开始。当第一个线程在任何对象上使用 TMonitor.Enter()时,此组合锁定计数器字段的值为零。再次使用 InterlockedCompareExchange 方法,获取锁定并启动计数器。调用线程没有锁定,没有上下文切换,因为这一切都在进行中。



当第二个线程尝试 TMonitor.Enter()同一个对象,首先尝试锁定会失败。 Delphi遵循以下两种策略:




  • 如果开发人员使用 TMonitor.SetSpinCount()设置一些旋转,那么Delphi会做一个忙等待循环,旋转给定的次数。这对于小锁来说非常好,因为它允许获取锁而不执行上下文切换。

  • 如果自旋计数过期(或者没有自旋计数),并且默认情况下,自旋计数零), TMonitor.Enter()将启动对 TMonitor.GetEvent()返回的事件的等待。换句话说,它不会忙 - 等待浪费CPU周期。记住 TMonitor.GetEvent(),因为这非常重要。



我们有一个线程获取锁和一个尝试获取锁的线程,但现在正在等待 TMonitor.GetEvent 返回的事件。当第一个线程调用 TMonitor.Exit()它会注意到(通过 FLockCount 字段)至少有另一个线程阻塞。所以它立即脉冲通常是以前分配的事件(调用 TMonitor.GetEvent())。但是由于这两个线程,调用 TMonitor.Exit()和调用 TMonitor.Enter()可能实际上调用 TMonitor.GetEvent()同时,在$ code> TMonitor.GetEvent()以确保仅分配一个事件,与操作顺序无关。



对于更多有趣的时刻,我们将现在深入研究 TMonitor.GetEvent()的工作原理。这个东西住在 System 单元(你知道,我们不能重新编译的单元),但事实证明它将实际分配事件的义务委托给另一个单元,通过 System.MonitorSupport 指针。这指向 TMonitorSupport 类型的记录,其中声明了5个函数指针:




  • NewSyncObject - 为同步目的分配一个新的事件

  • FreeSyncObject - 释放事件分配给同步目的

  • NewWaitObject - 为等待操作分配一个新的事件

  • FreeWaitObject - 释放等待事件

  • WaitAndOrSignalObject - 好..等待或信号。



同时也可以看出, NewXYZ 函数返回的对象可能是任何东西,因为它们只用于调用 WaitXYZ 和相应调用 FreeXyzObject 。这些功能在 SysUtils 中实现的方式旨在为这些锁提供最少量的锁定和上下文切换;因为这些对象本身(由 NewSyncObject NewWaitObject 返回)不是直接由 CreateEvent(),但指向 SyncEventCacheArray 中的记录。更进一步的是,直到必要时才会创建实际的Windows事件。因为 SyncEventCacheArray 中的记录包含几个记录:




  • TSyncEventItem.Lock - 这告诉Delphi,而不是现在正在使用Lock,而

  • TSyncEventItem .Event - 如果需要等待,这将保存用于同步的实际事件。



当应用程序终止时, SysUtils.DoneMonitorSupport 会超过 SyncEventCacheArray 中的所有记录,并等待锁定变为零,即等待锁停止被任何东西使用。理论上来说,只要这个锁不是零,那么至少有一个线程可能会使用锁 - 所以要做的事情就是要等待,以免造成AccessViolations错误。我们终于得到了我们目前的问题:在中的HANGING SysUtils.DoneMonitorSupport



为什么应用程序可能挂起在SysUtils中。 DoneMonitorSupport即使所有的线程正常终止?



因为至少有一个事件是使用 NewSyncObject NewWaitObject 未使用相应的 FreeSyncObject FreeWaitObject 释放。然后我们返回到 TMonitor.GetEvent()例程。它分配的事件保存在对应于用于的对象的 TMonitor 记录中。TMonitor.Enter() 。指向该记录的指针只保留在该对象的实例数据中,并保存在应用程序的整个生命周期中。搜索字段名称 FLockEvent ,我们在 System.pas 文件中找到:

 程序TMonitor.Destroy; 
begin
if(MonitorSupport<> nil)和(FLockEvent<> nil)then
MonitorSupport.FreeSyncObject(FLockEvent);
Dispose(@Self);
结束

并在此处调用该记录 - 析构函数: procedure TObject.CleanupInstance



换句话说,只有当用于同步的对象被释放时,最终的同步事件才被释放! / em>



OP的问题的答案:



应用程序挂起,因为至少有一个OBJECT用于 TMonitor.Enter()未被释放。



可能的解决方案:



不幸的是我不喜欢这个。这是不对的,我的意思是罚款不释放一个小对象应该是一个小的内存泄漏,不是一个挂起的应用程序!这对于服务应用程序来说尤其糟糕,因为服务可能永远挂起,而不是完全关闭,但无法响应任何请求。



Delphi团队的解决方案?他们不应该挂在 SysUtils 单元的完成代码中,无论如何。他们应该忽略锁定并移动关闭事件句柄。在这个阶段(SysUtils单元的最终确定),如果仍然有代码运行在一些线程中,它的形状真的很糟糕,因为大多数单元已经完成,它并没有运行在被设计为运行的环境中。



对于delphi用户?我们可以用我们自己的版本替换 MonitorSupport ,而不是在完成时间内进行广泛的测试。


I am writing a very thread intensive application that hangs when it exits.

I've traced into the system units and found the place where the program enters an infinite loop. It's in SysUtils line 19868 -> DoneMonitorSupport -> CleanEventList:

repeat until InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0;

I've searched for a solution online and found a couple of QC reports:

Unfortunately, these don't seem to relate to my situation as I don't use either TThreadList or TMonitor.

I'm pretty sure that all my threads have finished and have been destroyed as that all inherit from a base thread that keeps a create/destroy count.

Has anybody come across similar behaviour before? Do you know of any strategies for discovering where the root cause may lie?

解决方案

I've been looking at how the TMonitor locks are implemented, and I finally made an interesting discovery. For a bit of drama, I'll first tell you how the locks work.

When you call any TMonitor function on an TObject, a new instance of the TMonitor record is created and that instance is assigned to a MonitorFld inside the object itself. This assignment is made in a thread-safe way, using InterlockedCompareExchangePointer. Because of this trick the TObject only contains one pointer-size amount of data for the support of TMonitor, it doesn't contain the full TMonitor structure. And that's a good thing.

This TMonitor structure contains a number of records. We'll start with the FLockCount: Integer field. When the first thread uses TMonitor.Enter() on any object, this combined lock-counter field will have the value ZERO. Again using a InterlockedCompareExchange method the lock is acquired and the counter is initiated. There will be no locking for the calling thread, no context-switch since this is all done in-process.

When the second thread tries to TMonitor.Enter() the same object, it's first attempt to lock will fail. When that happens Delphi follows two strategies:

  • If the developer used TMonitor.SetSpinCount() to set a number of "spins", then Delphi will do a busy-wait loop, spinning the given number of times. That's very nice for tiny locks because it allows acquiring the lock without doing a context-switch.
  • If the spin-count expires (or there's no spin-count, and by default the spin count zero), TMonitor.Enter() will initiate a Wait on the event returned by TMonitor.GetEvent(). In other words it will not busy-wait wasting CPU cycles. Remember the TMonitor.GetEvent() because that's very important.

Let's say we've got a thread that acquired the lock and a thread that tried to acquire the lock but is now waiting on the event returned by TMonitor.GetEvent. When the first thread calls TMonitor.Exit() it will notice (via the FLockCount field) that there is at least one other thread blocking. So it immediately pulses what should normally be the previously allocated event (calls TMonitor.GetEvent()). But since the two threads, the one that calls TMonitor.Exit() and the one that called TMonitor.Enter() might actually call TMonitor.GetEvent() at the same time, tehre are a couple more tricks inside TMonitor.GetEvent() to make sure that only one event is allocated, irrelevant of the order of operations.

For a few more fun moments we'll now delve into the way the TMonitor.GetEvent() works. This thing lives inside the System unit (you know, the one we can't recompile to play with), but it turns out it delegates the duty of actually allocated the Event to an other unit, through the System.MonitorSupport pointer. That points to a record of type TMonitorSupport that declares 5 function pointers:

  • NewSyncObject - allocates a new Event for Synchronization purposes
  • FreeSyncObject - deallocates the Event allocated for Synchronization purposes
  • NewWaitObject - allocates a new Event for Wait operations
  • FreeWaitObject - deallocates that Wait event
  • WaitAndOrSignalObject - well.. waits or signals.

It also turns out that the objects returned by the NewXYZ functions could be anything, because they're only used for the call to WaitXYZ and for the corresponding call to FreeXyzObject. The way those functions are implemented in SysUtils is designed to provide those locks with a minimum amount of locking and context-switching; Because of that the objects themselves (returned by NewSyncObject and NewWaitObject) are not directly the Events returned by CreateEvent(), but pointers to records in the SyncEventCacheArray. It goes even further, actual Windows Events are not created until required. Because of that the records in the SyncEventCacheArray contains a couple of records:

  • TSyncEventItem.Lock - this tells Delphi rather the Lock is being used for anything right now or not and
  • TSyncEventItem.Event - this holds the actual Event that'll be used for synchronization, if waiting is required.

When the application terminates, the SysUtils.DoneMonitorSupport goes over all the records in the SyncEventCacheArray and waits for the Lock to become ZERO, ie, waits for the lock to stop being used by anything. Theoretically, as long as that lock is NOT Zero, at least one thread out there might be using the lock - so the sane thing to do would be to wait, in order to NOT cause AccessViolations errors. And we finally got to our current question: HANGING in SysUtils.DoneMonitorSupport

Why an application might Hang in SysUtils.DoneMonitorSupport even if all it's threads terminated properly?

Because at least one Event allocated using any one of NewSyncObject or NewWaitObject was not freed using it's corresponding FreeSyncObject or FreeWaitObject. And we go back to the TMonitor.GetEvent() routine. The Event it allocates is saved in the TMonitor record that corresponds to the object that was used for TMonitor.Enter(). The pointer to that record is only kept in that object's instance data, and is kept there for the life of the application. Searching for the name of the field, FLockEvent, we find this in the System.pas file:

procedure TMonitor.Destroy;
begin
  if (MonitorSupport <> nil) and (FLockEvent <> nil) then
    MonitorSupport.FreeSyncObject(FLockEvent);
  Dispose(@Self);
end;

and a call to that record-destructor in here: procedure TObject.CleanupInstance.

In other words, the final sync-event is only released when the object that was used for synchronization is freed!

Answer to OP's question:

The application hangs because at least one OBJECT that was used for TMonitor.Enter() was not freed.

Possible solutions:

Unfortunately I don't like this. It's not right, I mean the penalty for not freeing a small object should be a small memory leak, not a hanging application! This is especially bad for Service applications where a service might simply hang for ever, not fully shut down but unable to respond to any request.

The solutions for the Delphi team? They should NOT hang in the finalization code of the SysUtils unit, no-matter-what. They should probably ignore the Lock and move to closing the Event handle. At that stage (finalization of the SysUtils unit), if there's still code running in some thread, it's in a real bad shape as most of the units got finalized, it's not running in the environment it was designed to run in.

For the delphi users? We can replace the MonitorSupport with our own version, one that doesn't do those extensive tests at finalization time.

这篇关于应用程序挂起于SysUtils - &gt;退出时DoneMonitorSupport的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆