bes  Updated for version 3.20.13
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 
29 namespace agg_util {
30 
31 AggMemberDatasetDimensionCache *AggMemberDatasetDimensionCache::d_instance = nullptr;
32 bool AggMemberDatasetDimensionCache::d_enabled = true;
33 
34 const string AggMemberDatasetDimensionCache::CACHE_DIR_KEY = "NCML.DimensionCache.directory";
35 const string AggMemberDatasetDimensionCache::PREFIX_KEY = "NCML.DimensionCache.prefix";
36 const string AggMemberDatasetDimensionCache::SIZE_KEY = "NCML.DimensionCache.size";
37 
42 unsigned 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 
65 string 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 
86 string 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 
110 string 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 
134 AggMemberDatasetDimensionCache::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 
159 AggMemberDatasetDimensionCache::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 
180 AggMemberDatasetDimensionCache *
181 AggMemberDatasetDimensionCache::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 
209 AggMemberDatasetDimensionCache *
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 
235 void 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
244 AggMemberDatasetDimensionCache::~AggMemberDatasetDimensionCache()
245 {
246  // Nothing to do here....
247 }
248 #endif
249 
260 bool 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  try {
318  // If the object in the cache is not valid, remove it. The read_lock will
319  // then fail and the code will drop down to the create_and_lock() call.
320  // is_valid() tests for a non-zero length cache file (cache_file_name) and
321  // for the source data file (local_id) with a newer LMT than the cache file.
322  if (!is_valid(cache_file_name, local_id)) {
323  BESDEBUG("cache",
324  "AggMemberDatasetDimensionCache::loadDimensionCache() - File is not valid. Purging file from cache. filename: "
325  << cache_file_name << endl);
326  purge_file(cache_file_name);
327  }
328 
329  if (get_read_lock(cache_file_name, fd)) {
330  BESDEBUG("cache",
331  "AggMemberDatasetDimensionCache::loadDimensionCache() - Dimension cache file exists. Loading dimension cache from file: "
332  << cache_file_name << endl);
333 
334  ifstream istrm(cache_file_name.c_str());
335  if (!istrm)
336  throw libdap::InternalErr(__FILE__, __LINE__,
337  "Could not open '" + cache_file_name + "' to read cached dimensions.");
338 
339  amd->loadDimensionCache(istrm);
340 
341  istrm.close();
342 
343 
344  }
345  else {
346  // If here, the cache_file_name could not be locked for read access, or it was out of date.
347  // So we are going to (re)build the cache file.
348 
349  // We need to build the DDS object and extract the dimensions.
350  // We do not lock before this operation because it may take a _long_ time and
351  // we don't want to monopolize the cache while we do it.
353 
354  // Now, we try to make an empty cache file and get an exclusive lock on it.
355  if (create_and_lock(cache_file_name, fd)) {
356  // Woohoo! We got the exclusive lock on the new cache file.
357  BESDEBUG("cache", prolog << "Created and locked cache file: " << cache_file_name << endl);
358 
359  // Now we open it (again) using the more friendly ostream API.
360  ofstream ostrm(cache_file_name.c_str());
361  if (!ostrm)
362  throw libdap::InternalErr(__FILE__, __LINE__,
363  "Could not open '" + cache_file_name + "' to write cached response.");
364 
365  // Save the dimensions to the cache file.
366  amd->saveDimensionCache(ostrm);
367 
368  // And close the cache file;s ostream.
369  ostrm.close();
370 
371  // Change the exclusive lock on the new file to a shared lock. This keeps
372  // other processes from purging the new file and ensures that the reading
373  // process can use it.
375 
376  // Now update the total cache size info and purge if needed. The new file's
377  // name is passed into the purge method because this process cannot detect its
378  // own lock on the file.
379  unsigned long long size = update_cache_info(cache_file_name);
380  if (cache_too_big(size))
381  update_and_purge(cache_file_name);
382  }
383  // get_read_lock() returns immediately if the file does not exist,
384  // but blocks waiting to get a shared lock if the file does exist.
385  else if (get_read_lock(cache_file_name, fd)) {
386  // If we got here then someone else rebuilt the cache file before we could do it.
387  // That's OK, and since we already built the DDS we have all of the cache info in memory
388  // from directly accessing the source dataset(s), so we need to do nothing more,
389  // Except send a debug statement so we can see that this happened.
390  BESDEBUG("cache",
391  "AggMemberDatasetDimensionCache::loadDimensionCache() - Couldn't create and lock cache file, But I got a read lock. "
392  "Cache file may have been rebuilt by another process. "
393  "Cache file: " << cache_file_name << endl);
394  }
395  else {
396  throw libdap::InternalErr(__FILE__, __LINE__,
397  "AggMemberDatasetDimensionCache::loadDimensionCache() - Cache error during function invocation.");
398  }
399  }
400 
401  BESDEBUG("cache", "AggMemberDatasetDimensionCache::loadDimensionCache() - unlocking and closing cache file "
402  << cache_file_name << endl);
403  unlock_and_close(cache_file_name);
404  }
405  catch (...) {
406  BESDEBUG("cache", prolog << "caught exception, unlocking cache and re-throw." << endl);
407  unlock_cache();
408  throw;
409  }
410 
411  BESDEBUG("cache", prolog << "END (local_id=`" << local_id << "')" << endl);
412 }
413 
414 } /* 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)
exception thrown if internal error encountered
static std::string lowercase(const std::string &s)
Definition: BESUtil.cc:254
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:801
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:340
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:71
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
Helper class for temporarily hijacking an existing dhi to load a DDX response for one particular file...