bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
AggMemberDatasetDimensionCache.cc
1#pragma clang diagnostic push
2#pragma ide diagnostic ignored "EmptyDeclOrStmt"
3/*
4 * AggMemberDatasetDimensionCache.cc
5 *
6 * Created on: Sep 25, 2015
7 * Author: ndp
8 */
9
10#include "config.h"
11
12#include "AggMemberDatasetDimensionCache.h"
13#include "AggMemberDataset.h"
14#include <string>
15#include <sstream>
16#include <sys/stat.h>
17
18#include <libdap/util.h>
19#include "BESInternalError.h"
20#include "BESUtil.h"
21#include "BESDebug.h"
22#include "TheBESKeys.h"
23
24#define BES_DATA_ROOT "BES.Data.RootDirectory"
25#define BES_CATALOG_ROOT "BES.Catalog.catalog.RootDirectory"
26
27#define prolog "AggMemberDatasetDimensionCache::" << __func__ << "() - "
28
29namespace agg_util {
30
31AggMemberDatasetDimensionCache *AggMemberDatasetDimensionCache::d_instance = nullptr;
32bool AggMemberDatasetDimensionCache::d_enabled = true;
33
34const string AggMemberDatasetDimensionCache::CACHE_DIR_KEY = "NCML.DimensionCache.directory";
35const string AggMemberDatasetDimensionCache::PREFIX_KEY = "NCML.DimensionCache.prefix";
36const string AggMemberDatasetDimensionCache::SIZE_KEY = "NCML.DimensionCache.size";
37
42unsigned long AggMemberDatasetDimensionCache::getCacheSizeFromConfig()
43{
44 bool found;
45 string size;
46 unsigned long size_in_megabytes = 0;
47 TheBESKeys::TheKeys()->get_value(SIZE_KEY, size, found);
48 if (found) {
49 std::istringstream iss(size);
50 iss >> size_in_megabytes;
51 }
52 else {
53 string msg = "[ERROR] AggMemberDatasetDimensionCache::getCacheSize() - The BES Key " + SIZE_KEY +
54 " is not set! It MUST be set to utilize the NcML Dimension Cache. ";
55 BESDEBUG("cache", msg << endl);
56 throw BESInternalError(msg, __FILE__, __LINE__);
57 }
58 return size_in_megabytes;
59}
60
65string AggMemberDatasetDimensionCache::getCacheDirFromConfig()
66{
67 bool found;
68 string subdir;
69 TheBESKeys::TheKeys()->get_value(CACHE_DIR_KEY, subdir, found);
70
71 if (!found) {
72 string msg = "[ERROR] AggMemberDatasetDimensionCache::getSubDirFromConfig() - The BES Key " + CACHE_DIR_KEY +
73 " is not set! It MUST be set to utilize the NcML Dimension Cache. ";
74 BESDEBUG("cache", msg << endl);
75 throw BESInternalError(msg, __FILE__, __LINE__);
76 }
77
78 return subdir;
79}
80
81
86string AggMemberDatasetDimensionCache::getDimCachePrefixFromConfig()
87{
88 bool found;
89 string prefix;
90 TheBESKeys::TheKeys()->get_value(PREFIX_KEY, prefix, found);
91 if (found) {
92 prefix = BESUtil::lowercase(prefix);
93 }
94 else {
95 string msg = "[ERROR] AggMemberDatasetDimensionCache::getResultPrefix() - The BES Key " + PREFIX_KEY +
96 " is not set! It MUST be set to utilize the NcML Dimension Cache. ";
97 BESDEBUG("cache", msg << endl);
98 throw BESInternalError(msg, __FILE__, __LINE__);
99 }
100
101 return prefix;
102}
103
104
110string AggMemberDatasetDimensionCache::getBesDataRootDirFromConfig()
111{
112 bool found;
113 string cacheDir;
114 TheBESKeys::TheKeys()->get_value(BES_CATALOG_ROOT, cacheDir, found);
115 if (!found) {
116 TheBESKeys::TheKeys()->get_value(BES_DATA_ROOT, cacheDir, found);
117 if (!found) {
118 string msg =
119 ((string) "[ERROR] AggMemberDatasetDimensionCache::getStoredResultsDir() - Neither the BES Key ") +
120 BES_CATALOG_ROOT +
121 "or the BES key " + BES_DATA_ROOT +
122 " have been set! One MUST be set to utilize the NcML Dimension Cache. ";
123 BESDEBUG("cache", msg << endl);
124 throw BESInternalError(msg, __FILE__, __LINE__);
125 }
126 }
127 return cacheDir;
128
129}
130
134AggMemberDatasetDimensionCache::AggMemberDatasetDimensionCache()
135{
136 BESDEBUG("cache", "AggMemberDatasetDimensionCache::AggMemberDatasetDimensionCache() - BEGIN" << endl);
137
138 d_dimCacheDir = getCacheDirFromConfig();
139 d_dataRootDir = getBesDataRootDirFromConfig();
140
141 d_dimCacheFilePrefix = getDimCachePrefixFromConfig();
142 d_maxCacheSize = getCacheSizeFromConfig();
143
144 BESDEBUG("cache",
145 "AggMemberDatasetDimensionCache() - Stored results cache configuration params: " << d_dimCacheDir << ", "
146 << d_dimCacheFilePrefix
147 << ", " << d_maxCacheSize
148 << endl);
149
150 initialize(d_dimCacheDir, d_dimCacheFilePrefix, d_maxCacheSize);
151
152 BESDEBUG("cache", "AggMemberDatasetDimensionCache::AggMemberDatasetDimensionCache() - END" << endl);
153
154}
155
159AggMemberDatasetDimensionCache::AggMemberDatasetDimensionCache(const string &data_root_dir, const string &cache_dir,
160 const string &prefix, unsigned long long size)
161{
162
163 BESDEBUG("cache", "AggMemberDatasetDimensionCache::AggMemberDatasetDimensionCache() - BEGIN" << endl);
164
165 d_dataRootDir = data_root_dir;
166 d_dimCacheDir = cache_dir;
167 d_dimCacheFilePrefix = prefix;
168 d_maxCacheSize = size;
169
170 initialize(d_dimCacheDir, d_dimCacheFilePrefix, d_maxCacheSize);
171
172 BESDEBUG("cache", "AggMemberDatasetDimensionCache::AggMemberDatasetDimensionCache() - END" << endl);
173}
174
175
181AggMemberDatasetDimensionCache::get_instance(const string &data_root_dir, const string &cache_dir,
182 const string &result_file_prefix, unsigned long long max_cache_size)
183{
184 if (d_enabled && d_instance == nullptr && libdap::dir_exists(cache_dir)) {
185 d_instance = new AggMemberDatasetDimensionCache(data_root_dir, cache_dir, result_file_prefix,
186 max_cache_size);
187 d_enabled = d_instance->cache_enabled();
188 if (!d_enabled) {
189 delete d_instance;
190 d_instance = nullptr;
191 BESDEBUG("cache", prolog << "Cache is DISABLED" << endl);
192 }
193 else {
194#ifdef HAVE_ATEXIT
195 atexit(delete_instance);
196#endif
197 BESDEBUG("cache", prolog << "Cache is ENABLED" << endl);
198 }
199 }
200
201 return d_instance;
202}
203
211{
212 if (d_enabled && d_instance == 0) {
213 d_instance = new AggMemberDatasetDimensionCache();
214 d_enabled = d_instance->cache_enabled();
215 if (!d_enabled) {
216 delete d_instance;
217 d_instance = nullptr;
218 BESDEBUG("cache", prolog << "Cache is DISABLED" << endl);
219 }
220 else {
221#ifdef HAVE_ATEXIT
222 atexit(delete_instance);
223#endif
224 BESDEBUG("cache", prolog << "Cache is ENABLED" << endl);
225 }
226 }
227
228 return d_instance;
229}
230
231
235void AggMemberDatasetDimensionCache::delete_instance()
236{
237 BESDEBUG("cache", prolog << "Deleting singleton BESStoredDapResultCache instance." << endl);
238 delete d_instance;
239 d_instance = nullptr;
240}
241
242
243#if 0
244AggMemberDatasetDimensionCache::~AggMemberDatasetDimensionCache()
245{
246 // Nothing to do here....
247}
248#endif
249
260bool AggMemberDatasetDimensionCache::is_valid(const string &cache_file_name, const string &local_id) const
261{
262 // If the cached response is zero bytes in size, it's not valid.
263 // (hmmm...)
264 string datasetFileName = BESUtil::assemblePath(d_dataRootDir, local_id, true);
265
266 off_t entry_size = 0;
267 time_t entry_time = 0;
268 struct stat buf;
269 if (stat(cache_file_name.c_str(), &buf) == 0) {
270 entry_size = buf.st_size;
271 entry_time = buf.st_mtime;
272 }
273 else {
274 return false;
275 }
276
277 if (entry_size == 0)
278 return false;
279
280 time_t dataset_time = entry_time;
281 if (stat(datasetFileName.c_str(), &buf) == 0) {
282 dataset_time = buf.st_mtime;
283 }
284
285 // Trick: if the d_dataset is not a file, stat() returns error and
286 // the times stay equal and the code uses the cache entry.
287
288 // TODO Fix this so that the code can get a LMT from the correct handler.
289 // TODO Consider adding a getLastModified() method to the libdap::DDS object to support this
290 // TODO The DDS may be expensive to instantiate - I think the handler may be a better location
291 // for an LMT method, if we can access the handler when/where needed.
292 if (dataset_time > entry_time)
293 return false;
294
295 return true;
296}
297
298
306{
307 BESDEBUG("cache", "AggMemberDatasetDimensionCache::loadDimensionCache() - BEGIN" << endl);
308
309 // Get the cache filename for this thing, mangle name.
310 string local_id = amd->getLocation();
311 BESDEBUG("cache", "AggMemberDatasetDimensionCache::loadDimensionCache() - local resource id: " << local_id << endl);
312 string cache_file_name = get_cache_file_name(local_id, true);
313 BESDEBUG("cache",
314 "AggMemberDatasetDimensionCache::loadDimensionCache() - cache_file_name: " << cache_file_name << endl);
315
316 int fd;
317#if 0
318 try {
319#endif
320 // If the object in the cache is not valid, remove it. The read_lock will
321 // then fail and the code will drop down to the create_and_lock() call.
322 // is_valid() tests for a non-zero length cache file (cache_file_name) and
323 // for the source data file (local_id) with a newer LMT than the cache file.
324 if (!is_valid(cache_file_name, local_id)) {
325 BESDEBUG("cache",
326 "AggMemberDatasetDimensionCache::loadDimensionCache() - File is not valid. Purging file from cache. filename: "
327 << cache_file_name << endl);
328 purge_file(cache_file_name);
329 }
330
331 if (get_read_lock(cache_file_name, fd)) {
332 BESDEBUG("cache",
333 "AggMemberDatasetDimensionCache::loadDimensionCache() - Dimension cache file exists. Loading dimension cache from file: "
334 << cache_file_name << endl);
335
336 ifstream istrm(cache_file_name.c_str());
337 if (!istrm)
338 throw libdap::InternalErr(__FILE__, __LINE__,
339 "Could not open '" + cache_file_name + "' to read cached dimensions.");
340
341 amd->loadDimensionCache(istrm);
342
343 istrm.close();
344
345
346 }
347 else {
348 // If here, the cache_file_name could not be locked for read access, or it was out of date.
349 // So we are going to (re)build the cache file.
350
351 // We need to build the DDS object and extract the dimensions.
352 // We do not lock before this operation because it may take a _long_ time and
353 // we don't want to monopolize the cache while we do it.
355
356 // Now, we try to make an empty cache file and get an exclusive lock on it.
357 if (create_and_lock(cache_file_name, fd)) {
358 // Woohoo! We got the exclusive lock on the new cache file.
359 BESDEBUG("cache", prolog << "Created and locked cache file: " << cache_file_name << endl);
360
361 // Now we open it (again) using the more friendly ostream API.
362 ofstream ostrm(cache_file_name.c_str());
363 if (!ostrm)
364 throw libdap::InternalErr(__FILE__, __LINE__,
365 "Could not open '" + cache_file_name + "' to write cached response.");
366
367 // Save the dimensions to the cache file.
368 amd->saveDimensionCache(ostrm);
369
370 // And close the cache file;s ostream.
371 ostrm.close();
372
373 // Change the exclusive lock on the new file to a shared lock. This keeps
374 // other processes from purging the new file and ensures that the reading
375 // process can use it.
377
378 // Now update the total cache size info and purge if needed. The new file's
379 // name is passed into the purge method because this process cannot detect its
380 // own lock on the file.
381 unsigned long long size = update_cache_info(cache_file_name);
382 if (cache_too_big(size))
383 update_and_purge(cache_file_name);
384 }
385 // get_read_lock() returns immediately if the file does not exist,
386 // but blocks waiting to get a shared lock if the file does exist.
387 else if (get_read_lock(cache_file_name, fd)) {
388 // If we got here then someone else rebuilt the cache file before we could do it.
389 // That's OK, and since we already built the DDS we have all of the cache info in memory
390 // from directly accessing the source dataset(s), so we need to do nothing more,
391 // Except send a debug statement so we can see that this happened.
392 BESDEBUG("cache",
393 "AggMemberDatasetDimensionCache::loadDimensionCache() - Couldn't create and lock cache file, But I got a read lock. "
394 "Cache file may have been rebuilt by another process. "
395 "Cache file: " << cache_file_name << endl);
396 }
397 else {
398 throw libdap::InternalErr(__FILE__, __LINE__,
399 "AggMemberDatasetDimensionCache::loadDimensionCache() - Cache error during function invocation.");
400 }
401 }
402
403 BESDEBUG("cache", "AggMemberDatasetDimensionCache::loadDimensionCache() - unlocking and closing cache file "
404 << cache_file_name << endl);
405 unlock_and_close(cache_file_name);
406#if 0
407}
408 catch (...) {
409 BESDEBUG("cache", prolog << "caught exception, unlocking cache and re-throw." << endl);
410 unlock_cache();
411 throw;
412 }
413#endif
414
415 BESDEBUG("cache", prolog << "END (local_id=`" << local_id << "')" << endl);
416}
417
418} /* namespace agg_util */
void initialize(const std::string &cache_dir, const std::string &prefix, unsigned long long size)
Initialize an instance of FileLockingCache.
virtual void unlock_and_close(const std::string &target)
virtual unsigned long long update_cache_info(const std::string &target)
Update the cache info file to include 'target'.
virtual bool create_and_lock(const std::string &target, int &fd)
Create a file in the cache and lock it for write access.
virtual void exclusive_to_shared_lock(int fd)
Transfer from an exclusive lock to a shared lock.
virtual bool get_read_lock(const std::string &target, int &fd)
Get a read-only lock on the file if it exists.
virtual void purge_file(const std::string &file)
Purge a single file from the cache.
virtual bool cache_too_big(unsigned long long current_size) const
look at the cache size; is it too large? Look at the cache size and see if it is too big.
virtual void update_and_purge(const std::string &new_file)
Purge files from the cache.
virtual std::string get_cache_file_name(const std::string &src, bool mangle=true)
static std::string lowercase(const std::string &s)
Definition BESUtil.cc:257
static std::string assemblePath(const std::string &firstPart, const std::string &secondPart, bool leadingSlash=false, bool trailingSlash=false)
Assemble path fragments making sure that they are separated by a single '/' character.
Definition BESUtil.cc:804
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
static TheBESKeys * TheKeys()
Access to the singleton.
Definition TheBESKeys.cc:85
static AggMemberDatasetDimensionCache * get_instance()
virtual void loadDimensionCache(std::istream &istr)=0
const std::string & getLocation() const
virtual void saveDimensionCache(std::ostream &ostr)=0
virtual void fillDimensionCacheByUsingDDS()=0
STL class.
STL class.
Helper class for temporarily hijacking an existing dhi to load a DDX response for one particular file...