// bdlt_timetablecache.h -*-C++-*- #ifndef INCLUDED_BDLT_TIMETABLECACHE #define INCLUDED_BDLT_TIMETABLECACHE #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide an efficient cache for read-only 'bdlt::Timetable' objects. // //@CLASSES: // bdlt::TimetableCache: cache for read-only timetables loaded on demand // //@SEE_ALSO: bdlt_timetable, bdlt_timetableloader // //@DESCRIPTION: This component defines the 'bdlt::TimetableCache' class, a // cache for read-only 'bdlt::Timetable' objects. The 'bdlt::TimetableCache' // class defines two methods for fetching timetables from the cache: a // manipulator called 'getTimetable' and an accessor called 'lookupTimetable'. // Timetables are identified by name using C-style strings, and both retrieval // methods return a 'bsl::shared_ptr<const bdlt::Timetable>'. // // The first time a timetable is requested from the cache using the // 'getTimetable' manipulator, the identified timetable is loaded into the // cache using the loader that was supplied upon construction of the cache (see // 'bdlt_timetableloader'); a reference to that newly-loaded timetable is then // returned. Subsequent requests for the same timetable, using either the // 'getTimetable' or 'lookupTimetable' method, are efficiently satisfied by // returning references to the cached instance. The 'lookupTimetable' accessor // differs from the 'getTimetable' manipulator in that when a request is made // through the accessor for a timetable that is *not* present in the cache, the // timetable is not loaded as a side-effect. In this case, an empty // 'bsl::shared_ptr<const bdlt::Timetable>' is returned instead, which is // effectively a null pointer. Note that the timetable-naming convention in // effect for a given cache is determined by the concrete loader supplied at // construction of the cache. // // Timetables stored in a cache can be explicitly invalidated; the 'invalidate' // method is used to invalidate a single timetable and 'invalidateAll' // invalidates all timetables in the cache. Invalidated timetables are removed // from the cache. However, a timetable that has been invalidated in the cache // remains valid to all outstanding references to it, obtained via earlier // calls to the 'getTimetable' and 'lookupTimetable' methods, until all of // those references have been destroyed. Note that a subsequent request, using // the 'getTimetable' manipulator, for a timetable that has been invalidated // incurs the overhead of once again loading that timetable into the cache. // // Timetables can also be invalidated on the basis of a timeout. To use this // feature of 'bdlt::TimetableCache', a 'bsls::TimeInterval' timeout must be // supplied at construction. When a timeout is in effect for a cache, requests // for a timetable from the cache using the 'getTimetable' manipulator may // incur the reloading of the timetable if the one in the cache has expired // (i.e., the time interval defined by the timeout value has elapsed since the // timetable was last loaded). In the case of the 'lookupTimetable' accessor, // an empty 'bsl::shared_ptr<const bdlt::Timetable>' is returned if the // requested timetable is found to have expired. // ///Thread Safety ///------------- // The 'bdlt::TimetableCache' class is fully thread-safe (see // 'bsldoc_glossary') provided that the allocator supplied at construction and // the default allocator in effect during the lifetime of cache objects are // both fully thread-safe. // ///Usage ///----- // The following example illustrates how to use a 'bdlt::TimetableCache'. // ///Example 1: Using a 'bdlt::TimetableCache' /// - - - - - - - - - - - - - - - - - - - - // This example shows basic use of a 'bdlt::TimetableCache' object. // // In this example, we assume a hypothetical timetable loader, // 'MyTimetableLoader', the details of which are not important other than that // it supports timetables identified by "ZERO", "ONE", and "TWO". Furthermore, // the value of the initial transition code for each of these timetables is // given by the timetable's name (e.g., if 'Z' has the value of the timetable // identified as "ZERO", then '0 == Z.initialTransitionCode()'). // // First, we create a timetable loader, an instance of 'MyTimetableLoader', and // use it, in turn, to create a cache. For the purposes of this example, it is // sufficient to let the cache use the default allocator: //.. // MyTimetableLoader loader; // bdlt::TimetableCache cache(&loader); //.. // Next, we retrieve the timetable 'twoA', identified by "TWO", verify that the // loading of that timetable into the cache was successful ('twoA.get()' is // non-null), and verify that 2 is the value of the initial transition code for // timetable "TWO": //.. // bsl::shared_ptr<const bdlt::Timetable> twoA = cache.getTimetable("TWO"); // // assert(twoA.get()); // assert(2 == twoA->initialTransitionCode()); //.. // Then, we fetch the timetable identified by "ONE", this time verifying that 1 // is the value of the initial transition code for the "ONE" timetable: //.. // bsl::shared_ptr<const bdlt::Timetable> oneA = cache.getTimetable("ONE"); // // assert(oneA.get()); // assert(1 == oneA->initialTransitionCode()); //.. // Next, we retrieve the "ONE" timetable again, this time via the // 'lookupTimetable' accessor, and note that the request is satisfied by the // timetable that is already in the cache: //.. // const bdlt::TimetableCache& readonlyCache = cache; // // bsl::shared_ptr<const bdlt::Timetable> oneB = // readonlyCache.lookupTimetable("ONE"); // // assert(oneA.get() == oneB.get()); //.. // Then, we invalidate the "TWO" timetable in the cache and immediately fetch // it again. The call to 'invalidate' removed the "TWO" timetable from the // cache, so it had to be reloaded into the cache to satisfy the request: //.. // int numInvalidated = cache.invalidate("TWO"); // assert(1 == numInvalidated); // // bsl::shared_ptr<const bdlt::Timetable> twoB = cache.getTimetable("TWO"); // // assert(twoB.get() != twoA.get()); // assert(twoB.get()); // assert(2 == twoB->initialTransitionCode()); //.. // Next, all timetables in the cache are invalidated, then reloaded: //.. // numInvalidated = cache.invalidateAll(); // assert(2 == numInvalidated); // // bsl::shared_ptr<const bdlt::Timetable> twoC = cache.getTimetable("TWO"); // // assert(twoC.get() != twoA.get()); // assert(twoC.get() != twoB.get()); // assert(twoC.get()); // assert(2 == twoC->initialTransitionCode()); // // bsl::shared_ptr<const bdlt::Timetable> oneC = cache.getTimetable("ONE"); // // assert(oneC.get() != oneA.get()); // assert(oneC.get() != oneB.get()); // assert(oneC.get()); // assert(1 == oneC->initialTransitionCode()); //.. // Now, verify that references to timetables that were invalidated in the cache // are still valid for clients that obtained references to them before they // were made invalid: //.. // assert(1 == oneA->initialTransitionCode()); // assert(1 == oneB->initialTransitionCode()); // // assert(2 == twoA->initialTransitionCode()); // assert(2 == twoB->initialTransitionCode()); //.. // When 'twoA', 'twoB', 'oneA', and 'oneB' go out of scope, the resources used // by the timetables to which they refer are automatically reclaimed. // // Finally, using the 'lookupTimetable' accessor, we attempt to retrieve a // timetable that has not yet been loaded into the cache, but that we *know* to // be supported by the timetable loader. Since the 'lookupTimetable' accessor // does not load timetables into the cache as a side-effect, the request fails: //.. // bsl::shared_ptr<const bdlt::Timetable> zero = // readonlyCache.lookupTimetable("ZERO"); // // assert(!zero.get()); //.. // ///Example 2: A Timetable Cache with a Timeout /// - - - - - - - - - - - - - - - - - - - - - // This second example shows the effects on a 'bdlt::TimetableCache' object // that is constructed to have a timeout value. Note that the following // snippets of code assume a platform-independent 'sleepSeconds' method that // sleeps for the specified number of seconds. // // First, we create a timetable loader and a timetable cache. The cache is // constructed to have a timeout of 3 seconds. Of course, such a short timeout // is inappropriate for production use, but it is necessary for illustrating // the effects of a timeout in this example. As in example 1 (above), we again // let the cache use the default allocator: //.. // MyTimetableLoader loader; // bdlt::TimetableCache cache(&loader, bsls::TimeInterval(3, 0)); // const bdlt::TimetableCache& readonlyCache = cache; //.. // Next, we retrieve the timetable identified by "ZERO" from the cache: //.. // bsl::shared_ptr<const bdlt::Timetable> zeroA = cache.getTimetable("ZERO"); // // assert(zeroA.get()); //.. // Next, we sleep for 2 seconds before retrieving the "ONE" timetable: //.. // sleepSeconds(2); // // bsl::shared_ptr<const bdlt::Timetable> oneA = cache.getTimetable("ONE"); // // assert(oneA.get()); //.. // Next, we sleep for 2 more seconds before attempting to retrieve the "ZERO" // timetable again, this time using the 'lookupTimetable' accessor. Since the // cumulative sleep time exceeds the timeout value established for the cache // when it was constructed, the "ZERO" timetable has expired; hence, it has // been removed from the cache: //.. // sleepSeconds(2); // // bsl::shared_ptr<const bdlt::Timetable> zeroB = // readonlyCache.lookupTimetable("ZERO"); // // assert(!zeroB.get()); //.. // Next, we verify that the "ONE" timetable is still available in the cache: //.. // bsl::shared_ptr<const bdlt::Timetable> oneB = // readonlyCache.lookupTimetable("ONE"); // // assert(oneA.get() == oneB.get()); //.. // Finally, we sleep for an additional 2 seconds and verify that the "ONE" // timetable has also expired: //.. // sleepSeconds(2); // // bsl::shared_ptr<const bdlt::Timetable> oneC = // readonlyCache.lookupTimetable("ONE"); // // assert(!oneC.get()); //.. #include <bdlscm_version.h> #include <bdlt_timetable.h> #include <bdlt_datetime.h> #include <bdlt_datetimeinterval.h> #include <bslma_allocator.h> #include <bslma_usesbslmaallocator.h> #include <bslmf_integralconstant.h> #include <bslmt_mutex.h> #include <bsls_timeinterval.h> #include <bsl_map.h> #include <bsl_memory.h> // 'bsl::shared_ptr' #include <bsl_string.h> namespace BloombergLP { namespace bdlt { class TimetableLoader; class TimetableCache_Entry; // ========================== // class TimetableCache_Entry // ========================== // IMPLEMENTATION NOTE: The Sun Studio 12.3 compiler does not support 'map's // holding types that are incomplete at the point of declaration of a data // member. Other compilers allow us to complete 'TimetableCache_Entry' at a // later point in the code, but before any operation (such as 'insert') that // would require the type to be complete. If we did not have to support this // compiler, this whole class could be defined in the .cpp file; as it stands, // it *must* be defined before class 'TimetableCache'. class TimetableCache_Entry { // This class defines the type of objects that are inserted into the // timetable cache. Each entry contains a shared pointer to a read-only // timetable and the time at which that timetable was loaded. Note that an // explicit allocator is *required* to create an entry object. // DATA bsl::shared_ptr<const Timetable> d_ptr; // shared pointer to // out-of-place instance Datetime d_loadTime; // time when timetable was // loaded public: // CREATORS TimetableCache_Entry(); // Create an empty cache entry object. Note that an empty cache entry // is never actually inserted into the cache. TimetableCache_Entry(Timetable *timetable, const Datetime& loadTime, bslma::Allocator *allocator); // Create a cache entry object for managing the specified 'timetable' // that was loaded at the specified 'loadTime' using the specified // 'allocator'. The behavior is undefined unless 'timetable' uses // 'allocator' to obtain memory. TimetableCache_Entry(const TimetableCache_Entry& original); // Create a cache entry object having the value of the specified // 'original' object. ~TimetableCache_Entry(); // Destroy this cache entry object. // MANIPULATORS TimetableCache_Entry& operator=(const TimetableCache_Entry& rhs); // Assign to this cache entry object the value of the specified 'rhs' // object, and return a reference providing modifiable access to this // object. // ACCESSORS bsl::shared_ptr<const Timetable> get() const; // Return a shared pointer providing non-modifiable access to the // timetable referred to by this cache entry object. Datetime loadTime() const; // Return the time at which the timetable referred to by this cache // entry object was loaded. }; // ==================== // class TimetableCache // ==================== class TimetableCache { // This class implements an efficient cache of *read-only* // 'bdlt::Timetable' objects that are loaded into the cache, using a // timetable loader supplied at construction, as a side-effect of the // 'getTimetable' manipulator. Timetables in the cache can be invalidated, // and removed from the cache via the 'invalidate' and 'invalidateAll' // methods. In addition, timetables in the cache can be made to expire // based on a timeout that may be optionally supplied at construction. The // 'bsl::shared_ptr<const bdlt::Timetable>' objects returned from the // 'getTimetable' and 'lookupTimetable' methods allow for the safe removal // of timetables from the cache that may still have outstanding references // to them. // // This container is *exception* *neutral* with no guarantee of rollback: // if an exception is thrown during the invocation of a method on a // pre-existing instance, the container is left in a valid state, but its // value is undefined. In no event is memory leaked. // // This class is fully thread-safe (see 'bsldoc_glossary'). // DATA mutable bsl::map<bsl::string, TimetableCache_Entry> d_cache; // cache of (name, handle) pairs TimetableLoader *d_loader_p; // timetable loader (held, not // owned) DatetimeInterval d_timeOut; // timeout value; ignored unless // 'd_hasTimeOutFlag' is 'true' bool d_hasTimeOutFlag; // 'true' if this cache has a // timeout value and 'false' // otherwise mutable bslmt::Mutex d_lock; // guard access to cache bslma::Allocator *d_allocator_p; // memory allocator (held, not // owned) private: // PRIVATE TYPES typedef bsl::map<bsl::string, TimetableCache_Entry>::iterator CacheIterator; typedef bsl::map<bsl::string, TimetableCache_Entry>::const_iterator ConstCacheIterator; private: // NOT IMPLEMENTED TimetableCache(const TimetableCache&); TimetableCache& operator=(const TimetableCache&); public: // CREATORS explicit TimetableCache(TimetableLoader *loader, bslma::Allocator *basicAllocator = 0); // Create an empty timetable cache that uses the specified 'loader' to // load timetables on demand and has no timeout. Optionally specify a // 'basicAllocator' used to supply memory. If 'basicAllocator' is 0, // the currently installed default allocator is used. Timetables // loaded into this cache remain valid for retrieval until they have // been explicitly invalidated (via either the 'invalidate' or // 'invalidateAll' methods), or until this object is destroyed. The // behavior is undefined unless 'loader' remains valid throughout the // lifetime of this cache. TimetableCache(TimetableLoader *loader, const bsls::TimeInterval& timeout, bslma::Allocator *basicAllocator = 0); // Create an empty timetable cache that uses the specified 'loader' to // load timetables on demand and has the specified 'timeout' interval // indicating the length of time that timetables remain valid for // subsequent retrieval from the cache after they have been loaded. // Optionally specify a 'basicAllocator' used to supply memory. If // 'basicAllocator' is 0, the currently installed default allocator is // used. The behavior is undefined unless // 'bsls::TimeInterval() <= timeout <= bsls::TimeInterval(INT_MAX, 0)', // and 'loader' remains valid throughout the lifetime of this cache. // Note that a 'timeout' value of 0 indicates that a timetable will be // loaded into the cache by *each* (successful) call to the // 'getTimetable' method. ~TimetableCache(); // Destroy this object. // MANIPULATORS bsl::shared_ptr<const Timetable> getTimetable(const char *timetableName); // Return a shared pointer providing non-modifiable access to the // timetable having the specified 'timetableName' in this timetable // cache, loading the timetable into the cache using the loader that // was supplied at construction if the timetable is not already present // in the cache or if the timetable has expired (i.e., per a timeout // optionally supplied at construction). If the loader fails, whether // in loading a timetable for the first time or in reloading a // timetable that has expired, return an empty shared pointer. int invalidate(const char *timetableName); // Invalidate the timetable having the specified 'timetableName' in // this timetable cache, and remove it from the cache. If a timetable // having 'timetableName' is not present in this cache, this method has // no effect. Return the number of timetables that were invalidated. // Note that a timetable that has been invalidated in the cache remains // valid to all outstanding references to it, obtained via earlier // calls to the 'getTimetable' and 'lookupTimetable' methods, until all // of those references have been destroyed. int invalidateAll(); // Invalidate all timetables in this timetable cache, and remove them // from the cache. Return the number of timetables that were // invalidated. Note that a timetable that has been invalidated in the // cache remains valid to all outstanding references to it, obtained // via earlier calls to the 'getTimetable' and 'lookupTimetable' // methods, until all of those references have been destroyed. // ACCESSORS bsl::shared_ptr<const Timetable> lookupTimetable(const char *timetableName) const; // Return a shared pointer providing non-modifiable access to the // timetable having the specified 'timetableName' in this timetable // cache. If the timetable having 'timetableName' is not found in the // cache, or if the timetable has expired (i.e., per a timeout // optionally supplied at construction), return an empty shared // pointer. Datetime lookupLoadTime(const char *timetableName) const; // Return the datetime, in Coordinated Universal Time (UTC), at which // the timetable having the specified 'timetableName' was loaded into // this timetable cache. If the timetable having 'timetableName' is // not found in the cache, or if the timetable has expired (i.e., per a // timeout optionally supplied at construction), return 'Datetime()'. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ } // close package namespace } // close enterprise namespace // TRAITS namespace BloombergLP { namespace bslma { template <> struct UsesBslmaAllocator<bdlt::TimetableCache> : bsl::true_type {}; } // close namespace bslma } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2018 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------