Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

985 lines
22 KiB

  1. /*++
  2. Copyright (c) 1995-1996 Microsoft Corporation
  3. Module Name :
  4. dirmon.cxx
  5. Abstract:
  6. This module includes definitions of functions and variables
  7. for CDirMonitor and CDirMonitorEntry object
  8. Author:
  9. Charles Grant ( cgrant ) April-1997
  10. Revision History:
  11. --*/
  12. /************************************************************
  13. * Include Headers
  14. ************************************************************/
  15. #include <iis.h>
  16. #include "dbgutil.h"
  17. #include "dirmon.h"
  18. //
  19. // CDirMonitorEntry
  20. //
  21. #define DEFAULT_BUFFER_SIZE 512
  22. VOID
  23. WINAPI
  24. DirMonOverlappedCompletionRoutine(
  25. DWORD dwErrorCode,
  26. DWORD dwNumberOfBytesTransfered,
  27. LPOVERLAPPED lpOverlapped
  28. )
  29. {
  30. CDirMonitor::OverlappedCompletionRoutine( dwErrorCode,
  31. dwNumberOfBytesTransfered,
  32. lpOverlapped );
  33. }
  34. VOID
  35. CDirMonitor::OverlappedCompletionRoutine(
  36. DWORD dwErrorCode,
  37. DWORD dwNumberOfBytesTransfered,
  38. LPOVERLAPPED lpOverlapped
  39. )
  40. {
  41. PVOID pvContext;
  42. pvContext = CONTAINING_RECORD( lpOverlapped,
  43. CDirMonitorEntry,
  44. m_ovr );
  45. DBG_ASSERT( pvContext != NULL );
  46. CDirMonitor::DirMonitorCompletionFunction( pvContext,
  47. dwNumberOfBytesTransfered,
  48. dwErrorCode,
  49. lpOverlapped );
  50. }
  51. CDirMonitorEntry::CDirMonitorEntry() :
  52. m_cDirRefCount(0),
  53. m_cIORefCount(0),
  54. m_hDir(INVALID_HANDLE_VALUE),
  55. m_dwNotificationFlags(0),
  56. m_pszPath(NULL),
  57. m_cPathLength(0),
  58. m_pDirMonitor(NULL),
  59. m_cBufferSize(0),
  60. m_pbBuffer(NULL),
  61. m_fInCleanup(FALSE),
  62. m_fWatchSubdirectories(FALSE)
  63. /*++
  64. Routine Description:
  65. CDirMonitorEntry constructor
  66. Arguments:
  67. None
  68. Return Value:
  69. Nothing
  70. --*/
  71. {
  72. }
  73. CDirMonitorEntry::~CDirMonitorEntry(
  74. VOID
  75. )
  76. /*++
  77. Routine Description:
  78. CDirMonitorEntry destructor
  79. Arguments:
  80. None
  81. Return Value:
  82. Nothing
  83. --*/
  84. {
  85. HANDLE hDir;
  86. IF_DEBUG( DIRMON )
  87. {
  88. DBGPRINTF((DBG_CONTEXT, "[CDirMonitorEntry] Destructor\n"));
  89. }
  90. // We should only be destroyed when
  91. // our ref counts have gone to 0
  92. DBG_ASSERT(m_cDirRefCount == 0);
  93. DBG_ASSERT(m_cIORefCount == 0);
  94. //
  95. // We really ought to have closed the handle by now
  96. //
  97. if (m_hDir != INVALID_HANDLE_VALUE) {
  98. DBGPRINTF(( DBG_CONTEXT, "~CDirMonitorEntry: open handle %p\n",
  99. m_hDir ));
  100. hDir = m_hDir;
  101. m_hDir = INVALID_HANDLE_VALUE;
  102. CloseHandle( hDir );
  103. }
  104. if (m_pDirMonitor != NULL)
  105. {
  106. m_pDirMonitor->RemoveEntry(this);
  107. m_pDirMonitor = NULL;
  108. }
  109. m_cPathLength = 0;
  110. if ( m_pszPath != NULL )
  111. {
  112. LocalFree( m_pszPath );
  113. m_pszPath = NULL;
  114. }
  115. if (m_pbBuffer != NULL)
  116. {
  117. LocalFree(m_pbBuffer);
  118. m_cBufferSize = 0;
  119. }
  120. }
  121. BOOL
  122. CDirMonitorEntry::Init(
  123. DWORD cBufferSize = DEFAULT_BUFFER_SIZE
  124. )
  125. /*++
  126. Routine Description:
  127. Initialize the dir montior entry.
  128. Arguments:
  129. cBufferSize - Initial size of buffer used to store change notifications
  130. Return Value:
  131. TRUE if success, otherwise FALSE
  132. --*/
  133. {
  134. // Don't allow a 0 length buffer
  135. if (cBufferSize == 0)
  136. {
  137. return FALSE;
  138. }
  139. DBG_ASSERT( m_pbBuffer == NULL );
  140. m_pbBuffer = (BYTE *) LocalAlloc( LPTR, cBufferSize );
  141. if (m_pbBuffer != NULL)
  142. {
  143. m_cBufferSize = cBufferSize;
  144. return TRUE;
  145. }
  146. else
  147. {
  148. // Unable to allocate buffer
  149. return FALSE;
  150. }
  151. }
  152. BOOL
  153. CDirMonitorEntry::RequestNotification(
  154. VOID
  155. )
  156. /*++
  157. Routine Description:
  158. Request ATQ to monitor directory changes for the directory handle
  159. associated with this entry
  160. Arguments:
  161. None
  162. Return Value:
  163. TRUE if success, otherwise FALSE
  164. --*/
  165. {
  166. BOOL fResult = FALSE;
  167. DWORD cbRead = 0;
  168. IF_DEBUG( DIRMON )
  169. {
  170. DBGPRINTF((DBG_CONTEXT, "[CDirMonitorEntry] Request change notification\n"));
  171. }
  172. DBG_ASSERT(m_pDirMonitor);
  173. // Reset the overlapped io structure
  174. memset(&m_ovr, 0, sizeof(m_ovr));
  175. // Increase the ref count in advance
  176. IOAddRef();
  177. // Request notification of directory changes
  178. fResult = ReadDirectoryChangesW( m_hDir,
  179. m_pbBuffer,
  180. m_cBufferSize,
  181. m_fWatchSubdirectories,
  182. m_dwNotificationFlags,
  183. &cbRead,
  184. &m_ovr,
  185. NULL );
  186. if (!fResult)
  187. {
  188. // ReadDirChanges failed so
  189. // release the ref count we did in advance
  190. // Might cause IO ref count to go to 0
  191. IORelease();
  192. }
  193. return fResult;
  194. }
  195. BOOL
  196. CDirMonitorEntry::Cleanup(
  197. VOID
  198. )
  199. /*++
  200. Routine Description:
  201. Cleans up resource and determines if the caller need to delete
  202. the Directory Monitor Entry instance.
  203. Arguments:
  204. None
  205. Return Value:
  206. TRUE if the caller is responsible for deleting the object
  207. This will be the case if there are no pending Asynch IO requests
  208. --*/
  209. {
  210. BOOL fDeleteNeeded = FALSE;
  211. BOOL fHandleClosed = FALSE;
  212. HANDLE hDir = INVALID_HANDLE_VALUE;
  213. DBG_ASSERT(m_cDirRefCount == 0);
  214. BOOL fInCleanup = (BOOL) InterlockedExchange((long *) &m_fInCleanup, TRUE);
  215. if ( fInCleanup )
  216. {
  217. DBG_ASSERT(FALSE);
  218. return FALSE;
  219. }
  220. // Get the IO ref count BEFORE we close the handle
  221. DWORD cIORefCount = m_cIORefCount;
  222. if (m_hDir != INVALID_HANDLE_VALUE)
  223. {
  224. // If we have a pending AtqReadDirectoryChanges,
  225. // closing the directory handle will cause a call back from ATQ.
  226. // The call back should relase the final refcount on the object
  227. // which should result in its deletion
  228. hDir = m_hDir;
  229. m_hDir = INVALID_HANDLE_VALUE;
  230. fHandleClosed = CloseHandle( hDir );
  231. }
  232. // If there were no pending Asynch IO operations or if we failed
  233. // to close the handle, then the caller will be responsible for
  234. // deleting this object.
  235. if (cIORefCount == 0 || fHandleClosed == FALSE)
  236. {
  237. fDeleteNeeded = TRUE;
  238. }
  239. return fDeleteNeeded;
  240. }
  241. BOOL
  242. CDirMonitorEntry::ResetDirectoryHandle(
  243. VOID
  244. )
  245. /*++
  246. Routine Description:
  247. Opens a new directory handle for the path,
  248. and closes the old ones. We want to be able to do this so we
  249. can change the size of the buffer passed in ReadDirectoryChangesW.
  250. Arguments:
  251. None
  252. Return Value:
  253. TRUE if the handles were succesfully reopened
  254. FALSE otherwise
  255. --*/
  256. {
  257. // BUGBUG - Beta2 HACK
  258. return FALSE;
  259. /*
  260. // We'd better have a directory path available to try this
  261. if (m_pszPath == NULL)
  262. {
  263. return FALSE;
  264. }
  265. // Get a new handle to the directory
  266. HANDLE hDir = CreateFileW( m_pszPath,
  267. FILE_LIST_DIRECTORY,
  268. FILE_SHARE_READ |
  269. FILE_SHARE_WRITE |
  270. FILE_SHARE_DELETE,
  271. NULL,
  272. OPEN_EXISTING,
  273. FILE_FLAG_BACKUP_SEMANTICS |
  274. FILE_FLAG_OVERLAPPED,
  275. NULL );
  276. if ( hDir == INVALID_HANDLE_VALUE )
  277. {
  278. // We couldn't open another handle on the directory,
  279. // leave the current handle and ATQ context alone
  280. return FALSE;
  281. }
  282. //
  283. // Associate handle with thread pool's completion port
  284. //
  285. if ( !ThreadPoolBindIoCompletionCallback( hDir,
  286. DirMonOverlappedCompletionRoutine,
  287. 0 ) )
  288. {
  289. // We couldn't get a new ATQ context. Close our new handle.
  290. // We leave the objects current handle and ATQ context alone
  291. CloseHandle(hDir);
  292. return FALSE;
  293. }
  294. // We have the new handle and ATQ context so we close
  295. // and replace the old ones.
  296. if ( m_hDir != INVALID_HANDLE_VALUE )
  297. {
  298. CloseHandle( m_hDir );
  299. }
  300. m_hDir = hDir;
  301. return TRUE;
  302. */
  303. }
  304. BOOL
  305. CDirMonitorEntry::SetBufferSize(
  306. DWORD cBufferSize
  307. )
  308. /*++
  309. Routine Description:
  310. Sets the size of the buffer used for storing change notification records
  311. Arguments:
  312. cBufferSize new size for the buffer.
  313. Return Value:
  314. TRUE if the size of the buffer was succesfully set
  315. FALSE otherwise
  316. Note
  317. When a call to ReadDirectoryChangesW is made, the size of the buffer is set in
  318. the data associated with the directory handle and is not changed on subsequent
  319. calls to ReadDirectoryChangesW. To make use of the new buffer size the directory
  320. handle must be closed and a new handle opened (see ResetDirectoryHandle())
  321. --*/
  322. {
  323. // We should never be called if the buffer doesn't already exist
  324. DBG_ASSERT(m_pbBuffer);
  325. // Don't allow the buffer to be set to 0
  326. if (cBufferSize == 0)
  327. {
  328. return FALSE;
  329. }
  330. VOID *pbBuffer = LocalReAlloc( m_pbBuffer,
  331. cBufferSize,
  332. LMEM_MOVEABLE );
  333. if (pbBuffer == NULL)
  334. {
  335. // Re-allocation failed, stuck with the same size buffer
  336. return FALSE;
  337. }
  338. else
  339. {
  340. // Re-allocation succeded, update the member variables
  341. m_pbBuffer = (BYTE *) pbBuffer;
  342. m_cBufferSize = cBufferSize;
  343. return TRUE;
  344. }
  345. }
  346. //
  347. // CDirMonitor
  348. //
  349. CDirMonitor::CDirMonitor()
  350. : CTypedHashTable<CDirMonitor, CDirMonitorEntry, const WCHAR*>("DirMon")
  351. /*++
  352. Routine Description:
  353. CDirMonitor constructor
  354. Arguments:
  355. None
  356. Return Value:
  357. Nothing
  358. --*/
  359. {
  360. ThreadPoolInitialize( 0 ); // Use the process default stack size
  361. INITIALIZE_CRITICAL_SECTION( &m_csSerialComplLock );
  362. m_cRefs = 1;
  363. m_fShutdown = FALSE;
  364. }
  365. CDirMonitor::~CDirMonitor()
  366. /*++
  367. Routine Description:
  368. CDirMonitor destructor
  369. Arguments:
  370. None
  371. Return Value:
  372. Nothing
  373. --*/
  374. {
  375. DeleteCriticalSection(&m_csSerialComplLock);
  376. ThreadPoolTerminate();
  377. }
  378. BOOL
  379. CDirMonitor::Monitor(
  380. CDirMonitorEntry * pDME,
  381. LPWSTR pszDirectory,
  382. BOOL fWatchSubDirectories,
  383. DWORD dwNotificationFlags
  384. )
  385. /*++
  386. Routine Description:
  387. Create a monitor entry for the specified path
  388. Arguments:
  389. pszDirectory - directory to monitor
  390. pCtxt - Context of path is being monitored
  391. pszDirectory - name of directory to monitor
  392. fWatchSubDirectories - whether to get notifications for subdirectories
  393. dwNotificationFlags - which activities to be notified of
  394. Return Value:
  395. TRUE if success, otherwise FALSE
  396. Remarks:
  397. Caller should have a lock on the CDirMonitor
  398. Not compatible with WIN95
  399. --*/
  400. {
  401. HANDLE hDirectoryFile = INVALID_HANDLE_VALUE;
  402. LK_RETCODE lkrc;
  403. IF_DEBUG( DIRMON )
  404. {
  405. DBGPRINTF((DBG_CONTEXT, "[CDirMonitor] Monitoring new CDirMonitorEntry\n"));
  406. }
  407. // Must have a directory monitor entry and a string
  408. // containing the directory path
  409. if (!pDME || !pszDirectory)
  410. {
  411. SetLastError(ERROR_INVALID_PARAMETER);
  412. return FALSE;
  413. }
  414. // Make copy of pszDirectory for the entry to hang on to
  415. pDME->m_cPathLength = (DWORD) wcslen(pszDirectory);
  416. pDME->m_pszPath = (LPWSTR)LocalAlloc( LPTR,
  417. ( pDME->m_cPathLength + 1 ) * sizeof( WCHAR ) );
  418. if (pDME->m_pszPath==NULL)
  419. {
  420. pDME->m_cPathLength = 0;
  421. return FALSE;
  422. }
  423. memcpy( pDME->m_pszPath,
  424. pszDirectory,
  425. ( pDME->m_cPathLength + 1 ) * sizeof( WCHAR ) );
  426. pDME->Init();
  427. // Open the directory handle
  428. hDirectoryFile = CreateFileW(
  429. pszDirectory,
  430. FILE_LIST_DIRECTORY,
  431. FILE_SHARE_READ |
  432. FILE_SHARE_WRITE |
  433. FILE_SHARE_DELETE,
  434. NULL,
  435. OPEN_EXISTING,
  436. FILE_FLAG_BACKUP_SEMANTICS |
  437. FILE_FLAG_OVERLAPPED,
  438. NULL );
  439. if ( hDirectoryFile == INVALID_HANDLE_VALUE )
  440. {
  441. // Cleanup
  442. LocalFree(pDME->m_pszPath);
  443. pDME->m_pszPath = NULL;
  444. pDME->m_cPathLength = 0;
  445. return FALSE;
  446. }
  447. else
  448. {
  449. // Store the handle so we can close it on cleanup
  450. pDME->m_hDir = hDirectoryFile;
  451. // Set the flags for the type of notifications we want
  452. // and if we should watch subdirectories or just the root
  453. pDME->m_dwNotificationFlags = dwNotificationFlags;
  454. pDME->m_fWatchSubdirectories = fWatchSubDirectories;
  455. // Get an ATQ context for this handle
  456. // and register our completion call back function
  457. if ( ThreadPoolBindIoCompletionCallback(
  458. hDirectoryFile,
  459. DirMonOverlappedCompletionRoutine,
  460. 0 ) )
  461. {
  462. // Insert this entry into the list of active entries
  463. lkrc = InsertEntry(pDME);
  464. if (lkrc == LK_SUCCESS)
  465. {
  466. // Ask for notification if this directory has changes
  467. if (!pDME->RequestNotification())
  468. {
  469. // Couldn't register for change notification
  470. // Clean up resources
  471. RemoveEntry(pDME);
  472. pDME->m_hDir = INVALID_HANDLE_VALUE;
  473. CloseHandle( hDirectoryFile );
  474. LocalFree(pDME->m_pszPath);
  475. pDME->m_pszPath = NULL;
  476. pDME->m_cPathLength = 0;
  477. return FALSE;
  478. }
  479. }
  480. else
  481. {
  482. //
  483. // Could not add to hash table. Cleanup
  484. //
  485. CloseHandle(hDirectoryFile);
  486. pDME->m_hDir = INVALID_HANDLE_VALUE;
  487. LocalFree(pDME->m_pszPath);
  488. pDME->m_pszPath = NULL;
  489. pDME->m_cPathLength = 0;
  490. //
  491. // If it has been added from underneath us, indicate so
  492. //
  493. if ( lkrc == LK_KEY_EXISTS )
  494. {
  495. SetLastError( ERROR_ALREADY_EXISTS );
  496. }
  497. return FALSE;
  498. }
  499. }
  500. else
  501. {
  502. // Failed to add handle to ATQ, clean up
  503. CloseHandle(hDirectoryFile);
  504. pDME->m_hDir = INVALID_HANDLE_VALUE;
  505. LocalFree(pDME->m_pszPath);
  506. pDME->m_pszPath = NULL;
  507. pDME->m_cPathLength = 0;
  508. return FALSE;
  509. }
  510. }
  511. return TRUE;
  512. }
  513. VOID
  514. CDirMonitor::DirMonitorCompletionFunction(
  515. PVOID pCtxt,
  516. DWORD dwBytesWritten,
  517. DWORD dwCompletionStatus,
  518. OVERLAPPED *
  519. )
  520. /*++
  521. Routine Description:
  522. Static member function called by ATQ to signal directory changes
  523. Arguments:
  524. pCtxt - CDirMonitorEntry*
  525. dwBytesWritten - # bytes returned by ReadDirectoryChanges
  526. dwCompletionStatus - status of request to ReadDirectoryChanges
  527. pOvr - OVERLAPPED as specified in call to ReadDirectoryChanges
  528. Return Value:
  529. Nothing
  530. --*/
  531. {
  532. IF_DEBUG( DIRMON )
  533. {
  534. DBGPRINTF((DBG_CONTEXT, "[CDirMonitor] Notification call-back begining. Status %d\n", dwCompletionStatus));
  535. }
  536. CDirMonitorEntry* pDirMonitorEntry = reinterpret_cast<CDirMonitorEntry*>(pCtxt);
  537. DBG_ASSERT(pDirMonitorEntry);
  538. // Safety add ref, this should guarentee that the DME is not deleted
  539. // while we are still processing the callback
  540. pDirMonitorEntry->IOAddRef();
  541. // Release for the current Asynch operation
  542. // Should not send IO ref count to 0
  543. DBG_REQUIRE(pDirMonitorEntry->IORelease());
  544. BOOL fRequestNotification = FALSE;
  545. // There has been a change in the directory we were monitoring
  546. // carry out whatever work we need to do.
  547. if (!pDirMonitorEntry->m_fInCleanup)
  548. {
  549. pDirMonitorEntry->m_pDirMonitor->SerialComplLock();
  550. // BUGBUG Under stress ActOnNotification has been initiating a chain
  551. // of events leading to an AV. For Beta 3 we think we can ignore
  552. // these AV. For the final product we need to rework the critical
  553. // sections for the template manager and
  554. // the include file table.
  555. __try
  556. {
  557. fRequestNotification = pDirMonitorEntry->ActOnNotification(dwCompletionStatus, dwBytesWritten);
  558. }
  559. __except( EXCEPTION_EXECUTE_HANDLER )
  560. {
  561. // We should never get here
  562. DBG_ASSERT(FALSE);
  563. }
  564. // If ActOnNotification returned TRUE, then make another Asynch
  565. // notification request.
  566. if (fRequestNotification)
  567. {
  568. fRequestNotification = pDirMonitorEntry->RequestNotification();
  569. }
  570. pDirMonitorEntry->m_pDirMonitor->SerialComplUnlock();
  571. }
  572. // Remove safety ref count, may cause IO ref count to go to 0
  573. pDirMonitorEntry->IORelease();
  574. IF_DEBUG( DIRMON )
  575. {
  576. DBGPRINTF((DBG_CONTEXT, "[CDirMonitor] Notification call-back ending\n"));
  577. }
  578. }
  579. CDirMonitorEntry *
  580. CDirMonitor::FindEntry(
  581. LPWSTR pszPath
  582. )
  583. /*++
  584. Routine Description:
  585. Searches the list of entries for the specified path
  586. Arguments:
  587. pszPath - file path, including file name
  588. Return Value:
  589. pointer to the entry, allready addref'd
  590. --*/
  591. {
  592. DBG_ASSERT(pszPath);
  593. CDirMonitorEntry *pDME = NULL;
  594. //
  595. // Lock to prevent aga finding an entry that could be deleted by someone
  596. //
  597. ReadLock();
  598. FindKey(pszPath, &pDME);
  599. if (pDME != NULL)
  600. {
  601. if (pDME->m_fInCleanup)
  602. {
  603. // Don't hand back a DME that is being shutdown
  604. pDME = NULL;
  605. }
  606. else
  607. {
  608. // We found a valid DME which we are going to hand to the caller
  609. pDME->AddRef();
  610. }
  611. }
  612. ReadUnlock();
  613. return pDME;
  614. }
  615. LK_RETCODE
  616. CDirMonitor::InsertEntry(
  617. CDirMonitorEntry *pDME
  618. )
  619. /*++
  620. Routine Description:
  621. Insert an entry into the list of entries for the monitor
  622. Arguments:
  623. pDME - entry to insert
  624. Return Value:
  625. LK returncode
  626. --*/
  627. {
  628. DBG_ASSERT(pDME);
  629. LK_RETCODE lkResult;
  630. //
  631. // If we're cleaning up the table, don't let anyone else add stuff to
  632. // it
  633. //
  634. if ( m_fShutdown )
  635. {
  636. return LK_NOT_INITIALIZED;
  637. }
  638. pDME->m_pDirMonitor = this;
  639. lkResult = InsertRecord(pDME, false);
  640. if (lkResult == LK_SUCCESS)
  641. {
  642. DBGPRINTF((DBG_CONTEXT, "[CDirMonitor] Inserting directory (DME %08x) %ws\n", pDME, pDME->m_pszPath));
  643. AddRef();
  644. }
  645. else
  646. {
  647. pDME->m_pDirMonitor = NULL;
  648. }
  649. return lkResult;
  650. }
  651. LK_RETCODE
  652. CDirMonitor::RemoveEntry(
  653. CDirMonitorEntry *pDME
  654. )
  655. /*++
  656. Routine Description:
  657. Deletes an entry from the list of entries for the monitor
  658. Arguments:
  659. pDME - entry to delete
  660. Return Value:
  661. None
  662. --*/
  663. {
  664. DBG_ASSERT(pDME);
  665. //
  666. // Delete the entry from the hash table
  667. //
  668. LK_RETCODE lkResult = DeleteRecord(pDME);
  669. pDME->m_pDirMonitor = NULL;
  670. DBGPRINTF((DBG_CONTEXT, "[CDirMonitor] Removed DME(%08x), directory %ws\n", pDME, pDME->m_pszPath));
  671. //
  672. // Release the DME's reference on the DirMonitor object.
  673. //
  674. // It seems obvious, but we must have the Release() occur AFTER the
  675. // removal of the hash-table entry. This is because the loop check
  676. // in CDirMonitor::Cleanup() can complete while DeleteKey() is still
  677. // doing stuff to the table. I mention this since DirMon cleanup is an
  678. // often-butchered code path.
  679. //
  680. Release();
  681. return lkResult;
  682. }
  683. VOID
  684. CDirMonitor::Cleanup(
  685. VOID
  686. )
  687. /*++
  688. Routine Description:
  689. Pauses while all entries are cleaned up
  690. Arguments:
  691. None
  692. Return Value:
  693. None
  694. --*/
  695. {
  696. //
  697. // Don't let anyone else try to insert into the hashtable
  698. //
  699. m_fShutdown = TRUE;
  700. //
  701. // Sleep until the hash table is empty and all contained
  702. // CDirMonitorEntry's are destroyed
  703. //
  704. while (Size() > 0 || m_cRefs != 1)
  705. {
  706. //
  707. // At least one DME is still active, sleep and try again
  708. //
  709. Sleep(200);
  710. }
  711. }