Source code of Windows XP (NT5)
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.

1016 lines
18 KiB

  1. /*++
  2. Copyright (c) 1999 Microsoft Corporation
  3. Module Name :
  4. usercache.cxx
  5. Abstract:
  6. Implements the common code shared by all the caches
  7. (file, meta, uri, token, ul-cached-response, etc)
  8. Author:
  9. Bilal Alam (balam) 11-Nov-2000
  10. Environment:
  11. Win32 - User Mode
  12. Project:
  13. ULW3.DLL
  14. --*/
  15. #include "precomp.hxx"
  16. #include "usercache.hxx"
  17. #include "cachehint.hxx"
  18. //static
  19. void
  20. CACHE_ENTRY_HASH::AddRefRecord(
  21. CACHE_ENTRY * pCacheEntry,
  22. int nIncr
  23. )
  24. /*++
  25. Routine Description:
  26. Dereference and possibly delete the cache entry. Note the destructor
  27. for the cache entry is private. Only this code should ever delete
  28. the cache entry
  29. Arguments:
  30. None
  31. Return Value:
  32. None
  33. --*/
  34. {
  35. DBG_ASSERT( pCacheEntry->CheckSignature() );
  36. if ( nIncr == +1 )
  37. {
  38. pCacheEntry->ReferenceCacheEntry();
  39. }
  40. else if ( nIncr == -1 )
  41. {
  42. pCacheEntry->SetFlushed();
  43. pCacheEntry->QueryCache()->IncFlushes();
  44. pCacheEntry->QueryCache()->DecEntriesCached();
  45. pCacheEntry->DereferenceCacheEntry();
  46. }
  47. else
  48. {
  49. DBG_ASSERT( FALSE );
  50. }
  51. }
  52. //
  53. // CACHE_ENTRY definitions
  54. //
  55. CACHE_ENTRY::CACHE_ENTRY(
  56. OBJECT_CACHE * pObjectCache
  57. )
  58. {
  59. _cRefs = 1;
  60. _fFlushed = FALSE;
  61. _fCached = FALSE;
  62. _cConfiguredTTL = pObjectCache->QueryConfiguredTTL();
  63. _cTTL = _cConfiguredTTL;
  64. _pObjectCache = pObjectCache;
  65. _pDirmonInvalidator = NULL;
  66. _dwSignature = CACHE_ENTRY_SIGNATURE;
  67. }
  68. CACHE_ENTRY::~CACHE_ENTRY(
  69. VOID
  70. )
  71. {
  72. DBG_ASSERT( _cRefs == 0 );
  73. if ( _pDirmonInvalidator != NULL )
  74. {
  75. _pDirmonInvalidator->Release();
  76. _pDirmonInvalidator = NULL;
  77. }
  78. if ( _fFlushed )
  79. {
  80. DBG_ASSERT( QueryCache() != NULL );
  81. QueryCache()->DecActiveFlushedEntries();
  82. }
  83. _dwSignature = CACHE_ENTRY_SIGNATURE_FREE;
  84. }
  85. VOID
  86. CACHE_ENTRY::ReferenceCacheEntry(
  87. VOID
  88. )
  89. /*++
  90. Routine Description:
  91. Reference the given entry (duh)
  92. Arguments:
  93. None
  94. Return Value:
  95. None
  96. --*/
  97. {
  98. LONG cRefs;
  99. cRefs = InterlockedIncrement( &_cRefs );
  100. DBG_ASSERT( QueryCache() != NULL );
  101. QueryCache()->DoReferenceTrace( this, cRefs );
  102. }
  103. VOID
  104. CACHE_ENTRY::DereferenceCacheEntry(
  105. VOID
  106. )
  107. /*++
  108. Routine Description:
  109. Dereference and possibly delete the cache entry. Note the destructor
  110. for the cache entry is private. Only this code should ever delete
  111. the cache entry
  112. Arguments:
  113. None
  114. Return Value:
  115. None
  116. --*/
  117. {
  118. LONG cRefs;
  119. OBJECT_CACHE * pObjectCache = QueryCache();
  120. DBG_ASSERT( pObjectCache != NULL );
  121. cRefs = InterlockedDecrement( &_cRefs );
  122. pObjectCache->DoReferenceTrace( this, cRefs );
  123. if ( cRefs == 0 )
  124. {
  125. delete this;
  126. }
  127. }
  128. HRESULT
  129. CACHE_ENTRY::AddDirmonInvalidator(
  130. DIRMON_CONFIG * pDirmonConfig
  131. )
  132. /*++
  133. Routine Description:
  134. Setup dirmon invalidation for this cache entry
  135. Arguments:
  136. pDirmonConfig - path/token for use in monitoring directory
  137. Return Value:
  138. HRESULT
  139. --*/
  140. {
  141. CDirMonitorEntry * pDME = NULL;
  142. HRESULT hr;
  143. hr = QueryCache()->AddDirmonInvalidator( pDirmonConfig, &pDME );
  144. if ( FAILED( hr ) )
  145. {
  146. return hr;
  147. }
  148. DBG_ASSERT( pDME != NULL );
  149. //
  150. // Cleanup any old dir monitor entry
  151. //
  152. if ( _pDirmonInvalidator != NULL )
  153. {
  154. _pDirmonInvalidator->Release();
  155. }
  156. _pDirmonInvalidator = pDME;
  157. return NO_ERROR;
  158. }
  159. BOOL
  160. CACHE_ENTRY::Checkout(
  161. VOID
  162. )
  163. /*++
  164. Routine Description:
  165. Checkout a cache entry
  166. Arguments:
  167. None
  168. Return Value:
  169. TRUE if the checkout was successful, else FALSE
  170. --*/
  171. {
  172. ReferenceCacheEntry();
  173. if ( QueryIsFlushed() )
  174. {
  175. DereferenceCacheEntry();
  176. QueryCache()->IncMisses();
  177. return FALSE;
  178. }
  179. else
  180. {
  181. QueryCache()->IncHits();
  182. return TRUE;
  183. }
  184. }
  185. BOOL
  186. CACHE_ENTRY::QueryIsOkToFlushTTL(
  187. VOID
  188. )
  189. /*++
  190. Routine Description:
  191. Called when the cache scavenger is invoked. This routine returns whether
  192. it is OK to flush this entry due to TTL
  193. Arguments:
  194. None
  195. Return Value:
  196. TRUE if it is OK to flush by TTL, else FALSE
  197. --*/
  198. {
  199. //
  200. // Only do the TTL thing if the hash table holds the only reference to
  201. // the cache entry. We can be loose with this check as this is just an
  202. // optimization to prevent overzealous flushing
  203. //
  204. if ( _cRefs > 1 )
  205. {
  206. return FALSE;
  207. }
  208. if ( InterlockedDecrement( &_cTTL ) == 0 )
  209. {
  210. //
  211. // TTL has expired. However, we let the cache entry override this
  212. // expiry it wants to. Check that now
  213. //
  214. //
  215. // Entry be gone!
  216. //
  217. return TRUE;
  218. }
  219. else
  220. {
  221. return FALSE;
  222. }
  223. }
  224. BOOL
  225. CACHE_ENTRY::QueryIsOkToFlushMetadata(
  226. WCHAR * pszMetaPath,
  227. DWORD cchMetaPath
  228. )
  229. /*++
  230. Routine Description:
  231. Called whem metadata has changed. This routine returns whether the
  232. current cache entry should be flushed
  233. Arguments:
  234. pszMetaPath - Metabase path which changed
  235. cchMetaPath - Size of metabase path
  236. Return Value:
  237. TRUE if we should flush
  238. --*/
  239. {
  240. BOOL fRet;
  241. DBG_ASSERT( pszMetaPath != NULL );
  242. DBG_ASSERT( cchMetaPath != 0 );
  243. if ( pszMetaPath[ cchMetaPath - 1 ] == L'/' )
  244. {
  245. cchMetaPath--;
  246. }
  247. DBG_ASSERT( QueryCache()->QuerySupportsMetadataFlush() );
  248. if ( QueryMetadataPath() == NULL )
  249. {
  250. fRet = TRUE;
  251. }
  252. else if ( QueryMetadataPath()->QueryCCH() < cchMetaPath )
  253. {
  254. fRet = FALSE;
  255. }
  256. else if ( _wcsnicmp( QueryMetadataPath()->QueryStr(),
  257. pszMetaPath,
  258. cchMetaPath ) == 0 )
  259. {
  260. fRet = TRUE;
  261. }
  262. else
  263. {
  264. fRet = FALSE;
  265. }
  266. return fRet;
  267. }
  268. //
  269. // OBJECT_CACHE definitions
  270. //
  271. OBJECT_CACHE::OBJECT_CACHE(
  272. VOID
  273. )
  274. /*++
  275. Routine Description:
  276. Create an object cache. Obviously all the app-specific goo will be
  277. initialized in the derived class
  278. Arguments:
  279. None
  280. Return Value:
  281. None
  282. --*/
  283. {
  284. _hTimer = NULL;
  285. _pHintManager = NULL;
  286. _cmsecScavengeTime = 0;
  287. _cmsecTTL = 0;
  288. _dwSupportedInvalidation = 0;
  289. _cCacheHits = 0;
  290. _cCacheMisses = 0;
  291. _cCacheFlushes = 0;
  292. _cActiveFlushedEntries = 0;
  293. _cFlushCalls = 0;
  294. _cEntriesCached = 0;
  295. _cTotalEntriesCached = 0;
  296. _cPerfCacheHits = 0;
  297. _cPerfCacheMisses = 0;
  298. _cPerfCacheFlushes = 0;
  299. _cPerfFlushCalls = 0;
  300. _cPerfTotalEntriesCached = 0;
  301. #if DBG
  302. _pTraceLog = CreateRefTraceLog( 2000, 0 );
  303. #else
  304. _pTraceLog = NULL;
  305. #endif
  306. InitializeListHead( &_listEntry );
  307. _dwSignature = OBJECT_CACHE_SIGNATURE;
  308. }
  309. OBJECT_CACHE::~OBJECT_CACHE(
  310. VOID
  311. )
  312. {
  313. if ( _hTimer != NULL )
  314. {
  315. DeleteTimerQueueTimer( NULL,
  316. _hTimer,
  317. INVALID_HANDLE_VALUE );
  318. _hTimer = NULL;
  319. }
  320. //
  321. // So why do I set the free sig here instead of at the top? Because
  322. // waiting for the timer queue to go away may cause a timer completion
  323. // to fire and we don't want an assert there.
  324. //
  325. _dwSignature = OBJECT_CACHE_SIGNATURE_FREE;
  326. if ( _pHintManager != NULL )
  327. {
  328. delete _pHintManager;
  329. _pHintManager = NULL;
  330. }
  331. if ( _pTraceLog != NULL )
  332. {
  333. DestroyRefTraceLog( _pTraceLog );
  334. _pTraceLog = NULL;
  335. }
  336. }
  337. //static
  338. VOID
  339. WINAPI
  340. OBJECT_CACHE::ScavengerCallback(
  341. PVOID pParam,
  342. BOOLEAN TimerOrWaitFired
  343. )
  344. {
  345. OBJECT_CACHE * pObjectCache;
  346. pObjectCache = (OBJECT_CACHE*) pParam;
  347. DBG_ASSERT( pObjectCache != NULL );
  348. DBG_ASSERT( pObjectCache->CheckSignature() );
  349. pObjectCache->FlushByTTL();
  350. }
  351. //static
  352. LK_PREDICATE
  353. OBJECT_CACHE::CacheFlushByTTL(
  354. CACHE_ENTRY * pCacheEntry,
  355. VOID * pvState
  356. )
  357. /*++
  358. Routine Description:
  359. Determine whether given entry should be deleted due to TTL
  360. Arguments:
  361. pCacheEntry - Cache entry to check
  362. pvState - Pointer to cache
  363. Return Value:
  364. LKP_PERFORM - do the delete,
  365. LKP_NO_ACTION - do nothing
  366. --*/
  367. {
  368. OBJECT_CACHE * pCache = (OBJECT_CACHE*) pvState;
  369. DBG_ASSERT( pCache != NULL );
  370. DBG_ASSERT( pCache->CheckSignature() );
  371. DBG_ASSERT( pCacheEntry != NULL );
  372. DBG_ASSERT( pCacheEntry->CheckSignature() );
  373. if ( pCacheEntry->QueryIsOkToFlushTTL() )
  374. {
  375. return LKP_PERFORM;
  376. }
  377. else
  378. {
  379. return LKP_NO_ACTION;
  380. }
  381. }
  382. //static
  383. LK_PREDICATE
  384. OBJECT_CACHE::CacheFlushByDirmon(
  385. CACHE_ENTRY * pCacheEntry,
  386. VOID * pvState
  387. )
  388. /*++
  389. Routine Description:
  390. Determine whether given entry should be deleted due to dir mon
  391. Arguments:
  392. pCacheEntry - Cache entry to check
  393. pvState - STRU of path which changed
  394. Return Value:
  395. LKP_PERFORM - do the delete,
  396. LKP_NO_ACTION - do nothing
  397. --*/
  398. {
  399. STRU * pstrPath = (STRU*) pvState;
  400. OBJECT_CACHE * pCache;
  401. DBG_ASSERT( pCacheEntry != NULL );
  402. DBG_ASSERT( pCacheEntry->CheckSignature() );
  403. pCache = pCacheEntry->QueryCache();
  404. DBG_ASSERT( pCache->CheckSignature() );
  405. if ( pCacheEntry->QueryIsOkToFlushDirmon( pstrPath->QueryStr(),
  406. pstrPath->QueryCCH() ) )
  407. {
  408. return LKP_PERFORM;
  409. }
  410. else
  411. {
  412. return LKP_NO_ACTION;
  413. }
  414. }
  415. //static
  416. LK_PREDICATE
  417. OBJECT_CACHE::CacheFlushByMetadata(
  418. CACHE_ENTRY * pCacheEntry,
  419. VOID * pvState
  420. )
  421. /*++
  422. Routine Description:
  423. Determine whether given entry should be deleted due to metadata change
  424. Arguments:
  425. pCacheEntry - Cache entry to check
  426. pvState - STRU with metapath which changed
  427. Return Value:
  428. LKP_PERFORM - do the delete,
  429. LKP_NO_ACTION - do nothing
  430. --*/
  431. {
  432. STRU * pstrPath = (STRU*) pvState;
  433. OBJECT_CACHE * pCache;
  434. DBG_ASSERT( pCacheEntry != NULL );
  435. DBG_ASSERT( pCacheEntry->CheckSignature() );
  436. pCache = pCacheEntry->QueryCache();
  437. DBG_ASSERT( pCache->CheckSignature() );
  438. if ( pCacheEntry->QueryIsOkToFlushMetadata( pstrPath->QueryStr(),
  439. pstrPath->QueryCCH() ) )
  440. {
  441. return LKP_PERFORM;
  442. }
  443. else
  444. {
  445. return LKP_NO_ACTION;
  446. }
  447. }
  448. VOID
  449. OBJECT_CACHE::FlushByTTL(
  450. VOID
  451. )
  452. /*++
  453. Routine Description:
  454. Flush any inactive cache entries who have outlived they TTL
  455. Arguments:
  456. None
  457. Return Value:
  458. None
  459. --*/
  460. {
  461. IncFlushCalls();
  462. //
  463. // Iterate the hash table, deleting expired itmes
  464. //
  465. _hashTable.DeleteIf( CacheFlushByTTL, this );
  466. }
  467. VOID
  468. OBJECT_CACHE::DoDirmonInvalidationFlush(
  469. WCHAR * pszPath
  470. )
  471. /*++
  472. Routine Description:
  473. Flush all appropriate entries due to dirmon change notification
  474. Arguments:
  475. pszPath - Path that changed
  476. Return Value:
  477. None
  478. --*/
  479. {
  480. STACK_STRU( strPath, 256 );
  481. IncFlushCalls();
  482. if ( SUCCEEDED( strPath.Copy( pszPath ) ) )
  483. {
  484. _hashTable.DeleteIf( CacheFlushByDirmon, (VOID*) &strPath );
  485. }
  486. }
  487. VOID
  488. OBJECT_CACHE::DoMetadataInvalidationFlush(
  489. WCHAR * pszMetaPath
  490. )
  491. /*++
  492. Routine Description:
  493. Flush all appropriate entries due to metadata change notification
  494. Arguments:
  495. pszMetaPath - Metabase path which changed
  496. Return Value:
  497. None
  498. --*/
  499. {
  500. STACK_STRU( strPath, 256 );
  501. IncFlushCalls();
  502. if ( SUCCEEDED( strPath.Copy( pszMetaPath ) ) )
  503. {
  504. _hashTable.DeleteIf( CacheFlushByMetadata, (VOID*) &strPath );
  505. }
  506. }
  507. HRESULT
  508. OBJECT_CACHE::SetCacheConfiguration(
  509. DWORD cmsecScavengeTime,
  510. DWORD cmsecTTL,
  511. DWORD dwSupportedInvalidation,
  512. CACHE_HINT_CONFIG * pCacheHintConfig
  513. )
  514. /*++
  515. Routine Description:
  516. Do the general cache initialization here
  517. Arguments:
  518. cmsecScavengeTime - How often should a scavenger be run for this cache
  519. (should be no larger than the TTL expected for
  520. entries in this cache)
  521. cmsecTTL - TTL for entries in this cache
  522. dwSupportedInvalidation - How can the cache be invalidated?
  523. pCacheHintConfig - Cache hint configuration (NULL for no cache hints)
  524. Return Value:
  525. HRESULT
  526. --*/
  527. {
  528. BOOL fRet;
  529. HRESULT hr;
  530. DBG_ASSERT( cmsecTTL >= cmsecScavengeTime );
  531. //
  532. // Create a timer which fires every cmsecScavengeTime
  533. //
  534. DBG_ASSERT( cmsecScavengeTime != 0 );
  535. _cmsecScavengeTime = cmsecScavengeTime;
  536. fRet = CreateTimerQueueTimer( &_hTimer,
  537. NULL,
  538. OBJECT_CACHE::ScavengerCallback,
  539. this,
  540. _cmsecScavengeTime,
  541. _cmsecScavengeTime,
  542. WT_EXECUTELONGFUNCTION );
  543. if ( !fRet )
  544. {
  545. return HRESULT_FROM_WIN32( GetLastError() );
  546. }
  547. _cmsecTTL = cmsecTTL;
  548. _dwSupportedInvalidation = dwSupportedInvalidation;
  549. //
  550. // Should we setup a cache hint table
  551. //
  552. if ( pCacheHintConfig != NULL )
  553. {
  554. _pHintManager = new CACHE_HINT_MANAGER;
  555. if ( _pHintManager == NULL )
  556. {
  557. hr = HRESULT_FROM_WIN32( GetLastError() );
  558. DeleteTimerQueueTimer( NULL,
  559. _hTimer,
  560. INVALID_HANDLE_VALUE );
  561. _hTimer = NULL;
  562. return hr;
  563. }
  564. hr = _pHintManager->Initialize( pCacheHintConfig );
  565. if ( FAILED( hr ) )
  566. {
  567. delete _pHintManager;
  568. _pHintManager = NULL;
  569. DeleteTimerQueueTimer( NULL,
  570. _hTimer,
  571. INVALID_HANDLE_VALUE );
  572. _hTimer = NULL;
  573. return hr;
  574. }
  575. }
  576. return NO_ERROR;
  577. }
  578. HRESULT
  579. OBJECT_CACHE::FindCacheEntry(
  580. CACHE_KEY * pCacheKey,
  581. CACHE_ENTRY ** ppCacheEntry,
  582. BOOL * pfShouldCache
  583. )
  584. /*++
  585. Routine Description:
  586. Lookup key in cache
  587. Arguments:
  588. pCacheKey - Cache key to lookup
  589. ppCacheEntry - Points to cache entry on success
  590. pfShouldCache - Provides a hint if possible on whether we should cache
  591. (can be NULL indicating no hint is needed)
  592. Return Value:
  593. HRESULT
  594. --*/
  595. {
  596. LK_RETCODE lkrc;
  597. HRESULT hr;
  598. CACHE_ENTRY * pCacheEntry = NULL;
  599. if ( ppCacheEntry == NULL ||
  600. pCacheKey == NULL )
  601. {
  602. DBG_ASSERT( FALSE );
  603. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  604. }
  605. *ppCacheEntry = NULL;
  606. //
  607. // First do a lookup
  608. //
  609. lkrc = _hashTable.FindKey( pCacheKey, &pCacheEntry );
  610. if ( lkrc == LK_SUCCESS )
  611. {
  612. //
  613. // If this entry has been flushed, then it really isn't a hit
  614. //
  615. if ( pCacheEntry->QueryIsFlushed() )
  616. {
  617. pCacheEntry->DereferenceCacheEntry();
  618. }
  619. else
  620. {
  621. IncHits();
  622. DBG_ASSERT( pCacheEntry != NULL );
  623. *ppCacheEntry = pCacheEntry;
  624. return NO_ERROR;
  625. }
  626. }
  627. IncMisses();
  628. //
  629. // Is a hint requested?
  630. //
  631. if ( pfShouldCache != NULL )
  632. {
  633. *pfShouldCache = TRUE;
  634. if ( _pHintManager != NULL )
  635. {
  636. hr = _pHintManager->ShouldCacheEntry( pCacheKey,
  637. pfShouldCache );
  638. //
  639. // Ignore error
  640. //
  641. }
  642. }
  643. return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
  644. }
  645. HRESULT
  646. OBJECT_CACHE::FlushCacheEntry(
  647. CACHE_KEY * pCacheKey
  648. )
  649. /*++
  650. Routine Description:
  651. Flush given cache key
  652. Arguments:
  653. pCacheKey - Key to flush
  654. Return Value:
  655. HRESULT
  656. --*/
  657. {
  658. LK_RETCODE lkrc;
  659. HRESULT hr;
  660. CACHE_ENTRY * pCacheEntry;
  661. if ( pCacheKey == NULL )
  662. {
  663. DBG_ASSERT( FALSE );
  664. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  665. }
  666. //
  667. // First do a lookup
  668. //
  669. lkrc = _hashTable.FindKey( pCacheKey, &pCacheEntry );
  670. if ( lkrc == LK_SUCCESS )
  671. {
  672. DBG_ASSERT( pCacheEntry != NULL );
  673. if ( !pCacheEntry->QueryIsFlushed() )
  674. {
  675. _hashTable.DeleteRecord( pCacheEntry );
  676. }
  677. pCacheEntry->DereferenceCacheEntry();
  678. }
  679. return NO_ERROR;
  680. }
  681. HRESULT
  682. OBJECT_CACHE::AddCacheEntry(
  683. CACHE_ENTRY * pCacheEntry
  684. )
  685. /*++
  686. Routine Description:
  687. Lookup key in cache
  688. Arguments:
  689. pCacheEntry - Points to cache entry on success
  690. Return Value:
  691. HRESULT
  692. --*/
  693. {
  694. LK_RETCODE lkrc;
  695. if ( pCacheEntry == NULL )
  696. {
  697. DBG_ASSERT( FALSE );
  698. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  699. }
  700. DBG_ASSERT( pCacheEntry->QueryCached() == FALSE );
  701. //
  702. // Now try to insert into hash table
  703. //
  704. pCacheEntry->SetCached( TRUE );
  705. lkrc = _hashTable.InsertRecord( pCacheEntry );
  706. if ( lkrc == LK_SUCCESS )
  707. {
  708. IncEntriesCached();
  709. }
  710. else
  711. {
  712. pCacheEntry->SetCached( FALSE );
  713. }
  714. return NO_ERROR;
  715. }
  716. HRESULT
  717. OBJECT_CACHE::AddDirmonInvalidator(
  718. DIRMON_CONFIG * pDirmonConfig,
  719. CDirMonitorEntry ** ppDME
  720. )
  721. /*++
  722. Routine Description:
  723. Add dirmon invalidator for this cache
  724. Arguments:
  725. pDirmonConfig - Configuration of dir monitor
  726. ppDME - filled with dir monitor entry to be attached to cache entry
  727. Return Value:
  728. HRESULT
  729. --*/
  730. {
  731. HRESULT hr = NO_ERROR;
  732. if ( !QuerySupportsDirmonSpecific() &&
  733. !QuerySupportsDirmonFlush() )
  734. {
  735. return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  736. }
  737. //
  738. // Start monitoring
  739. //
  740. DBG_ASSERT( g_pCacheManager != NULL );
  741. hr = g_pCacheManager->MonitorDirectory( pDirmonConfig,
  742. ppDME );
  743. if ( FAILED( hr ) )
  744. {
  745. return hr;
  746. }
  747. DBG_ASSERT( *ppDME != NULL );
  748. return NO_ERROR;
  749. }