Changes to CIMOM Internals for Synchronous Enumeration
Rev 1.0, raymcc, 14-Jan-99.
Rev 1.1, sanjes, 17-Jul-99 updated registry value that cache size is retrieved from and updated Reset description.
Rev 1.2, sanjes, 24-Jul-99 updated section on internal indicates to cover retries, overflow buffer, processing during next and new registry entries.
Rev 1.3, sanjes, 25-Jul-99 added WBEM_E_CLIENT_TOO_SLOW error and removed need for entries in self-registration object.
Rev 1.4, sanjes, 27-Jul-99 default overflow timeout bumped up to 60 seconds.
Rev 1.5, sanjes, 30-Jul-99 Removed WBEM_E_CLIENT_TOO_SLOW and timeouts.
Rev 1.6, sanjes, 3-Aug-99 Added note for Provider implementors (#8).
1.0 Introduction
Due to various RAID entries and sheer stupidity of caching large amounts of data in CIMOM as the result of a query, this memo details changes to CIMOM internals to prevent such caching while maintaining the look-and-feel of full sychronous enumerations and queries.
There is no change in the API documentation or the user experience except for timing-related issues.
The affected APIs from IWbemServices are CreateClassEnum, CreateInstanceEnum, and ExecQuery. No other APIs or the async versions are affected.
The functionality is specifically targeted at the internal implementation of IEnumWbemClassObject.
2.0 The Problem
The problem is that synchronous enumerations specified with the default flags of 0 for CreateClassEnum, CreateInstanceEnum, and ExecQuery have undesirable behavior. The entire result set is (a) cached in memory of CIMOM, and (b) the entire result is generated before the call unblocks, which is undesirable if the user in fact aborts the enumeration early on or in fact cancels it.
The amount of memory consumption can be tremendous. The primary problem is that synchronous enumerations can fail completely whereas the semisync or async versions will succeed.
While there are flags to overcome this, they are unfortunately not the default and the semantics are slightly different anyway.
3.0 The Solution
The solution is the retain the semantics but to change the internal delivery mechanism to due limited caching and to synchronize with the user's retrieval of information.
Therefore, the following behavior will be introduced
[1] The internal enumerator-sink currently blocks until the last object arrives into the sink, and then it sets an event. This will be changed such that it blocks until the arrival of the first object and then sets the event.
[2] During internal delivery fo the enumerator-sink, currently the objects are simply added to an array immediately. If a Next function is concurrently being processed, we will add objects to the array up to the number of objects requested. If a Next is not being processed, or we have received enough objects to satisfy the request to Next, we will continue to add objects up to a predefined Maximum Cache Size (obtained from ConfigMgr). Once the cache size is exceeded, the Indicate will block and retry, waiting for a predefined Retry Interval (obtained from ConfigMgr). On the next retry if the cache size is still exceeded, we will begin adding additional objects until we exceed a predefined overflow buffer size (obtained from ConfigMgr). Once we fill the overflow buffer, the Indicate will internally block until it is either able to place an object into the enumerator or the enumerator is destroyed. Each time Next(), Skip(), and NextAsync() are called, they will trigger an event that will cause the Indicate to try and place the object in the enumerator. In addition, each time we perform an internal Indicate, we will check for low-memory conditions. If these are reached, we will fail the enumeration with WBEM_E_OUT_OF_MEMORY. The cache buffer size and overflow buffer size are all configurable in the registry as DWORD values. The value names are: "MaxEnumCacheSize" and "MaxEnumOverflowSize". These are all obtained from ConfigMgr, which if it does not find the values in the registry, will write them out with the default values. The defaults are MaxEnumCacheSize=0x80000 and MaxEnumOverflowSize=0x20000.
[3] Remove the 20 meg test currently in place for the sync enumerator.
[4] Ensure that if Release is called before the thing is finished, things unblock properly internally and the appropriate cancellations are issued.
[5] As the user calls Next or NextAsync, the objects are removed from the internal array and Released. There will be no caching. If the user calls Next for more objects than are available, the call will block until the requested number is available or until there are no more objects, at which point it is permissible to return fewer objects than requested. Because of previous documented semantics and existing code, it is not permitted to return fewer objects than requested except at the end of an enumeration sequence.
[6] The caller may issue a Reset call. For resettable enumerators, as objects are indicated in, we also store AddRef'd pointers to the objects in an array datamember. If the operation completes before the number of cached objects exceeds the max cache size (obtained from the ConfigMgr), the call to Reset() will simply readd the objects from the cache array, into the array from which objects are delivered to the user. If we exceed the cache size, we clear out the cache, and if Reset() is called, we will have to resubmit the operation. This means that the original call type and IWbemContext object and the class name or query must be retained by the enumerator so that it can be reissued to rebuild the result set. Since the caller is proving a security context at the time of a Reset call, it is possible to propagate security into the reissue of ExecQuery, CreateClassEnum, or CreateInstanceEnum. The implementation needs to cleanly cancel any pending deliveries to the internal enumerator-sink and reissue the request to a different sink internally so that the result sets from the previous request don't get mixed with the new one.
[7] Skip calls should block until the required number of objects have been received from the internal sink and discarded.
[8] IMPORTANT - Because the call to Indicate can now block, provider implementors must ensure that their call to Indicate is not blocked. In other words, when they call Indicate they should ensure that their code is not blocking other clients from accessing their provider, or a deadlock condition can occur. This needs to be documented.