bes Updated for version 3.20.13
BESFileLockingCache.cc
1// This file was originally part of bes, A C++ back-end server
2// implementation framework for the OPeNDAP Data Access Protocol.
3// Copied to libdap. This is used to cache responses built from
4// functional CE expressions.
5
6// Moved back to the BES. 6/11/13 jhrg
7
8// Copyright (c) 2012 OPeNDAP, Inc
9// Author: James Gallagher <jgallagher@opendap.org>
10// Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
11//
12// This library is free software; you can redistribute it and/or
13// modify it under the terms of the GNU Lesser General Public
14// License as published by the Free Software Foundation; either
15// version 2.1 of the License, or (at your option) any later version.
16//
17// This library is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20// Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public
23// License along with this library; if not, write to the Free Software
24// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25//
26// You can contact University Corporation for Atmospheric Research at
27// 3080 Center Green Drive, Boulder, CO 80301
28
29#include "config.h"
30
31#include <sys/file.h>
32#include <sys/stat.h>
33#include <unistd.h>
34#include <dirent.h>
35#include <fcntl.h>
36
37#ifdef HAVE_STDLIB_H
38#include <stdlib.h>
39#endif
40
41#include <string>
42#include <sstream>
43#include <vector>
44#include <cstring>
45#include <cerrno>
46
47#include "BESInternalError.h"
48
49#include "BESUtil.h"
50#include "BESDebug.h"
51#include "BESLog.h"
52
53#include "BESFileLockingCache.h"
54
55// Symbols used with BESDEBUG.
56#define CACHE "cache"
57#define LOCK "cache-lock"
58#define LOCK_STATUS "cache-lock-status"
59
60#define CACHE_CONTROL "cache_control"
61
62#define prolog std::string("BESFileLockingCache::").append(__func__).append("() - ")
63
64using namespace std;
65
66// conversion factor
67static const unsigned long long BYTES_PER_MEG = 1048576ULL;
68
69// Max cache size in megs, so we can check the user input and warn.
70// 2^64 / 2^20 == 2^44
71static const unsigned long long MAX_CACHE_SIZE_IN_MEGABYTES = (1ULL << 44);
72
94BESFileLockingCache::BESFileLockingCache(const string &cache_dir, const string &prefix, unsigned long long size) :
95 d_cache_dir(cache_dir), d_prefix(prefix), d_max_cache_size_in_bytes(size), d_target_size(0), d_cache_info(""),
96 d_cache_info_fd(-1)
97{
98 m_initialize_cache_info();
99}
100
116void BESFileLockingCache::initialize(const string &cache_dir, const string &prefix, unsigned long long size)
117{
118 d_cache_dir = cache_dir;
119 d_prefix = prefix;
120 d_max_cache_size_in_bytes = size; // converted later on to bytes
121
122 m_initialize_cache_info();
123}
124
125static inline string get_errno()
126{
127 char *s_err = strerror(errno);
128 if (s_err)
129 return s_err;
130 else
131 return "Unknown error.";
132}
133
134// Build a lock of a certain type.
135//
136// Using whence == SEEK_SET with start and len set to zero means lock the whole file.
137// jhrg 9/8/18
138static inline struct flock *lock(int type)
139{
140 static struct flock lock;
141 lock.l_type = type;
142 lock.l_whence = SEEK_SET;
143 lock.l_start = 0;
144 lock.l_len = 0;
145 lock.l_pid = getpid();
146
147 return &lock;
148}
149
150inline void BESFileLockingCache::m_record_descriptor(const string &file, int fd)
151{
152 BESDEBUG(LOCK, prolog << "Recording descriptor: " << file << ", " << fd << endl);
153
154 d_locks.insert(std::pair<string, int>(file, fd));
155}
156
157inline int BESFileLockingCache::m_remove_descriptor(const string &file)
158{
159 BESDEBUG(LOCK, prolog << "d_locks size: " << d_locks.size() << endl);
160
161 FilesAndLockDescriptors::iterator i = d_locks.find(file);
162 if (i == d_locks.end()) return -1;
163
164 int fd = i->second;
165 d_locks.erase(i);
166
167 BESDEBUG(LOCK, prolog << "Found file descriptor [" << fd << "] for file: " << file << endl);
168
169 return fd;
170}
171
172#if USE_GET_SHARED_LOCK
173inline int BESFileLockingCache::m_find_descriptor(const string &file)
174{
175 BESDEBUG(LOCK, prolog << "d_locks size: " << d_locks.size() << endl);
176
177 FilesAndLockDescriptors::iterator i = d_locks.find(file);
178 if (i == d_locks.end()) return -1;
179
180 BESDEBUG(LOCK, prolog << "Found file descriptor [" << i->second << "] for file: " << file << endl);
181
182 return i->second; // return the file descriptor bound to 'file'
183}
184#endif
185
191static string lockStatus(const int fd)
192{
193 struct flock lock_query;
194
195 lock_query.l_type = F_WRLCK; /* Test for any lock on any part of a file. */
196 lock_query.l_start = 0;
197 lock_query.l_whence = SEEK_SET;
198 lock_query.l_len = 0;
199 lock_query.l_pid = 0;
200
201 int ret = fcntl(fd, F_GETLK, &lock_query);
202
203 stringstream ss;
204 ss << endl;
205 if (ret == -1) {
206 ss << "fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << " errno[" << errno << "]: "
207 << strerror(errno) << endl;
208 }
209 else {
210 ss << "fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << endl;
211 }
212
213 ss << "lock_info.l_len: " << lock_query.l_len << endl;
214 ss << "lock_info.l_pid: " << lock_query.l_pid << endl;
215 ss << "lock_info.l_start: " << lock_query.l_start << endl;
216
217 string type;
218 switch (lock_query.l_type) {
219 case F_RDLCK:
220 type = "F_RDLCK";
221 break;
222 case F_WRLCK:
223 type = "F_WRLCK";
224 break;
225 case F_UNLCK:
226 type = "F_UNLCK";
227 break;
228
229 }
230
231 ss << "lock_info.l_type: " << type << endl;
232 ss << "lock_info.l_whence: " << lock_query.l_whence << endl;
233
234 return ss.str();
235}
236
242static void unlock(int fd)
243{
244 if (fcntl(fd, F_SETLK, lock(F_UNLCK)) == -1) {
245 throw BESInternalError(prolog + "An error occurred trying to unlock the file: " + get_errno(), __FILE__, __LINE__);
246 }
247
248 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(fd) << endl);
249
250 if (close(fd) == -1) throw BESInternalError(prolog + "Could not close the (just) unlocked file.", __FILE__, __LINE__);
251
252 BESDEBUG(LOCK, prolog << "File Closed. fd: " << fd << endl);
253}
254
274bool BESFileLockingCache::m_check_ctor_params()
275{
276 // Should this really be a fatal error? What about just not
277 // using the cache in this case or writing out a warning message
278 // to the log. jhrg 10/23/15
279 //
280 // Yes, leave this as a fatal error and handle the case when cache_dir is
281 // empty in code that specializes this class. Those child classes are
282 // all singletons and their get_instance() methods need to return null
283 // when caching is turned off. You cannot do that here without throwing
284 // and we don't want to throw an exception for every call to a child's
285 // get_instance() method just because someone doesn't want to use a cache.
286 // jhrg 9/27/16
287 BESDEBUG(CACHE, prolog << "BEGIN" << endl);
288
289 if (d_cache_dir.empty()) {
290 BESDEBUG(CACHE, prolog << "The cache directory was not specified. CACHE IS DISABLED." << endl);
291
292 disable();
293 return false;
294 }
295
296
297 int status = mkdir(d_cache_dir.c_str(), 0775);
298 // If there is an error and it's not that the dir already exists,
299 // throw an exception.
300 if (status == -1 && errno != EEXIST) {
301 string err = prolog + "The cache directory " + d_cache_dir + " could not be created: " + strerror(errno);
302 throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
303 }
304
305 if (d_prefix.empty()) {
306 string err = prolog + "The cache file prefix was not specified, must not be empty";
307 throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
308 }
309
310 // I changed this from '<=' to '<' since the code now uses a cache size
311 // of zero to indicate that the cache will never be purged. The other
312 // size-related methods all still work. Since the field is unsigned,
313 // testing for '< 0' is pointless. Later on in this code the value is capped
314 // at MAX_CACHE_SIZE_IN_MEGABYTES (set in this file), which is 2^44.
315 // jhrg 2.28.18
316#if 0
317 if (d_max_cache_size_in_bytes < 0) {
318 string err = "The cache size was not specified, must be greater than zero";
319 throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
320 }
321#endif
322
323 BESDEBUG(CACHE,
324 "BESFileLockingCache::" << __func__ << "() -" <<
325 " d_cache_dir: " << d_cache_dir <<
326 " d_prefix: " << d_prefix <<
327 " d_max_cache_size_in_bytes: " << d_max_cache_size_in_bytes << endl);
328
329 enable();
330 return true;
331}
332
342static bool createLockedFile(const string &file_name, int &ref_fd)
343{
344 BESDEBUG(LOCK, prolog << "BEGIN file: " << file_name <<endl);
345
346 int fd;
347 if ((fd = open(file_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666)) < 0) {
348 switch (errno) {
349 case EEXIST:
350 return false;
351
352 default:
353 throw BESInternalError(file_name + ": " + get_errno(), __FILE__, __LINE__);
354 }
355 }
356
357 struct flock *l = lock(F_WRLCK);
358 // F_SETLKW == set lock, blocking
359 if (fcntl(fd, F_SETLKW, l) == -1) {
360 close(fd);
361 ostringstream oss;
362 oss << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
363 throw BESInternalError(oss.str(), __FILE__, __LINE__);
364 }
365
366 BESDEBUG(LOCK, prolog << "END file: " << file_name <<endl);
367
368 // Success
369 ref_fd = fd;
370 return true;
371}
372
382bool BESFileLockingCache::m_initialize_cache_info()
383{
384 BESDEBUG(CACHE, prolog << "BEGIN" << endl);
385
386 // The value set in configuration files, etc., is the size in megabytes. The private
387 // variable holds the size in bytes (converted below).
388 d_max_cache_size_in_bytes = min(d_max_cache_size_in_bytes, MAX_CACHE_SIZE_IN_MEGABYTES);
389 d_max_cache_size_in_bytes *= BYTES_PER_MEG;
390 d_target_size = d_max_cache_size_in_bytes * 0.8;
391
392 BESDEBUG(CACHE, prolog << "d_max_cache_size_in_bytes: "
393 << d_max_cache_size_in_bytes << " d_target_size: "<<d_target_size<< endl);
394
395 bool status = m_check_ctor_params(); // Throws BESError on error; otherwise sets the cache_enabled() property
396 if (status) {
397 d_cache_info = BESUtil::assemblePath(d_cache_dir, d_prefix + CACHE_CONTROL, true);
398
399 BESDEBUG(CACHE, prolog << "d_cache_info: " << d_cache_info << endl);
400
401 // See if we can create it. If so, that means it doesn't exist. So make it and
402 // set the cache initial size to zero.
403 if (createLockedFile(d_cache_info, d_cache_info_fd)) {
404 // initialize the cache size to zero
405 unsigned long long size = 0;
406 if (write(d_cache_info_fd, &size, sizeof(unsigned long long)) != sizeof(unsigned long long))
407 throw BESInternalError(prolog + "Could not write size info to the cache info file `" + d_cache_info + "`",
408 __FILE__,
409 __LINE__);
410
411 // This leaves the d_cache_info_fd file descriptor open
412 unlock_cache();
413 }
414 else {
415 if ((d_cache_info_fd = open(d_cache_info.c_str(), O_RDWR)) == -1) {
416 throw BESInternalError(prolog + "Failed to open cache info file: " + d_cache_info + " errno: " + get_errno(), __FILE__, __LINE__);
417 }
418 }
419
420 BESDEBUG(CACHE,prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
421 }
422
423 BESDEBUG(CACHE,prolog << "END [" << "CACHE IS " << (cache_enabled()?"ENABLED]":"DISABLED]") << endl);
424
425 return status;
426}
427
428static const string chars_excluded_from_filenames = "<>=,/()\\\"\':? []()$";
429
444string BESFileLockingCache::get_cache_file_name(const string &src, bool mangle)
445{
446 // Old way of building String, retired 10/02/2015 - ndp
447 // Return d_cache_dir + "/" + d_prefix + BESFileLockingCache::DAP_CACHE_CHAR + target;
448 BESDEBUG(CACHE, prolog << "src: '" << src << "' mangle: "<< mangle << endl);
449
450 string target = get_cache_file_prefix() + src;
451
452 if (mangle) {
453 string::size_type pos = target.find_first_of(chars_excluded_from_filenames);
454 while (pos != string::npos) {
455 target.replace(pos, 1, "#", 1);
456 pos = target.find_first_of(chars_excluded_from_filenames);
457 }
458 }
459
460 if (target.length() > 254) {
461 ostringstream msg;
462 msg << prolog << "Cache filename is longer than 254 characters (name length: ";
463 msg << target.length() << ", name: " << target;
464 throw BESInternalError(msg.str(), __FILE__, __LINE__);
465 }
466
467 target = BESUtil::assemblePath(get_cache_directory(), target, true);
468
469 BESDEBUG(CACHE, prolog << "target: '" << target << "'" << endl);
470
471 return target;
472}
473
474#if USE_GET_SHARED_LOCK
488static bool getSharedLock(const string &file_name, int &ref_fd)
489{
490 BESDEBUG(LOCK, prolog << "Acquiring cache read lock for " << file_name <<endl);
491
492 int fd;
493 if ((fd = open(file_name.c_str(), O_RDONLY)) < 0) {
494 switch (errno) {
495 case ENOENT:
496 return false;
497
498 default:
499 throw BESInternalError(get_errno(), __FILE__, __LINE__);
500 }
501 }
502
503 struct flock *l = lock(F_RDLCK);
504 if (fcntl(fd, F_SETLKW, l) == -1) {
505 close(fd);
506 ostringstream oss;
507 oss << prolog << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
508 throw BESInternalError(oss.str(), __FILE__, __LINE__);
509 }
510
511 BESDEBUG(LOCK, prolog << "SUCCESS Read Lock Acquired For " << file_name <<endl);
512
513 // Success
514 ref_fd = fd;
515 return true;
516}
517#endif
518
537bool BESFileLockingCache::get_read_lock(const string &target, int &fd)
538{
540
541 bool status = true;
542
543#if USE_GET_SHARED_LOCK
544 status = getSharedLock(target, fd);
545
546 if (status) m_record_descriptor(target, fd);
547#else
548 fd = m_find_descriptor(target);
549 // fd == -1 --> The file is not currently open
550 if ((fd == -1) && (fd = open(target.c_str(), O_RDONLY)) < 0) {
551 switch (errno) {
552 case ENOENT:
553 return false; // The file does not exist
554
555 default:
556 throw BESInternalError(get_errno(), __FILE__, __LINE__);
557 }
558 }
559
560 // The file might be open for writing, so setting a read lock is
561 // not possible.
562 struct flock *l = lock(F_RDLCK);
563 if (fcntl(fd, F_SETLKW, l) == -1) {
564 return false; // cannot get the lock
565 }
566
567 m_record_descriptor(target, fd);
568#endif
569
570 unlock_cache();
571
572 return status;
573}
574
592bool BESFileLockingCache::create_and_lock(const string &target, int &fd)
593{
595
596 bool status = createLockedFile(target, fd);
597
598 BESDEBUG(LOCK, prolog << "target: " << target << " (status: " << status << ", fd: " << fd << ")" << endl);
599
600 if (status) m_record_descriptor(target, fd);
601
602 unlock_cache();
603
604 return status;
605}
606
623{
624 struct flock lock;
625 lock.l_type = F_RDLCK;
626 lock.l_whence = SEEK_SET;
627 lock.l_start = 0;
628 lock.l_len = 0;
629 lock.l_pid = getpid();
630
631 if (fcntl(fd, F_SETLKW, &lock) == -1) {
632 throw BESInternalError(get_errno(), __FILE__, __LINE__);
633 }
634
635 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(fd) << endl);
636}
637
647{
648 BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
649
650 if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_WRLCK)) == -1) {
651 throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
652 __LINE__);
653 }
654
655 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
656}
657
662{
663 BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
664
665 if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_RDLCK)) == -1) {
666 throw BESInternalError(prolog + "An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
667 __LINE__);
668 }
669
670 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
671}
672
679{
680 BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
681
682 if (fcntl(d_cache_info_fd, F_SETLK, lock(F_UNLCK)) == -1) {
683 throw BESInternalError(prolog +"An error occurred trying to unlock the cache-control file" + get_errno(), __FILE__,
684 __LINE__);
685 }
686
687 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
688}
689
705void BESFileLockingCache::unlock_and_close(const string &file_name)
706{
707 BESDEBUG(LOCK, prolog << "BEGIN file: " << file_name << endl);
708
709 int fd = m_remove_descriptor(file_name); // returns -1 when no more files desp. remain
710 while (fd != -1) {
711 unlock(fd);
712 fd = m_remove_descriptor(file_name);
713 }
714
715 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
716 BESDEBUG(LOCK, prolog << "END file: "<< file_name<< endl);
717}
718
729unsigned long long BESFileLockingCache::update_cache_info(const string &target)
730{
731 unsigned long long current_size;
732 try {
734
735 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
736 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
737
738 // read the size from the cache info file
739 if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
740 throw BESInternalError(prolog + "Could not get read size info from the cache info file!", __FILE__, __LINE__);
741
742 struct stat buf;
743 int statret = stat(target.c_str(), &buf);
744 if (statret == 0)
745 current_size += buf.st_size;
746 else
747 throw BESInternalError(prolog + "Could not read the size of the new file: " + target + " : " + get_errno(), __FILE__,
748 __LINE__);
749
750 BESDEBUG(CACHE, prolog << "cache size updated to: " << current_size << endl);
751
752 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
753 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
754
755 if (write(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
756 throw BESInternalError(prolog + "Could not write size info from the cache info file!", __FILE__, __LINE__);
757
758 unlock_cache();
759 }
760 catch (...) {
761 unlock_cache();
762 throw;
763 }
764
765 return current_size;
766}
767
772bool BESFileLockingCache::cache_too_big(unsigned long long current_size) const
773{
774 return current_size > d_max_cache_size_in_bytes;
775}
776
785{
786 unsigned long long current_size;
787 try {
789
790 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
791 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
792 // read the size from the cache info file
793 if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
794 throw BESInternalError(prolog + "Could not get read size info from the cache info file!", __FILE__, __LINE__);
795
796 unlock_cache();
797 }
798 catch (...) {
799 unlock_cache();
800 throw;
801 }
802
803 return current_size;
804}
805
806static bool entry_op(cache_entry &e1, cache_entry &e2)
807{
808 return e1.time < e2.time;
809}
810
812unsigned long long BESFileLockingCache::m_collect_cache_dir_info(CacheFiles &contents)
813{
814 DIR *dip = opendir(d_cache_dir.c_str());
815 if (!dip) throw BESInternalError(prolog + "Unable to open cache directory " + d_cache_dir, __FILE__, __LINE__);
816
817 struct dirent *dit;
818 vector<string> files;
819 // go through the cache directory and collect all of the files that
820 // start with the matching prefix
821 while ((dit = readdir(dip)) != NULL) {
822 string dirEntry = dit->d_name;
823 if (dirEntry.compare(0, d_prefix.length(), d_prefix) == 0 && dirEntry != d_cache_info) {
824 files.push_back(d_cache_dir + "/" + dirEntry);
825 }
826 }
827
828 closedir(dip);
829
830 unsigned long long current_size = 0;
831 struct stat buf;
832 for (vector<string>::iterator file = files.begin(); file != files.end(); ++file) {
833 if (stat(file->c_str(), &buf) == 0) {
834 current_size += buf.st_size;
835 cache_entry entry;
836 entry.name = *file;
837 entry.size = buf.st_size;
838 entry.time = buf.st_atime;
839 // Sanity check; Removed after initial testing since some files might be zero bytes
840#if 0
841 if (entry.size == 0)
842 throw BESInternalError("Zero-byte file found in cache. " + *file, __FILE__, __LINE__);
843#endif
844 contents.push_back(entry);
845 }
846 }
847
848 // Sort so smaller (older) times are first.
849 contents.sort(entry_op);
850
851 return current_size;
852}
853
870bool BESFileLockingCache::get_exclusive_lock_nb(const string &file_name, int &ref_fd)
871{
872 BESDEBUG(LOCK, prolog << "BEGIN filename: " << file_name <<endl);
873
874 int fd;
875 if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
876 switch (errno) {
877 case ENOENT:
878 return false;
879
880 default:
881 throw BESInternalError(prolog + "errno: " + get_errno(), __FILE__, __LINE__);
882 }
883 }
884
885 struct flock *l = lock(F_WRLCK);
886 if (fcntl(fd, F_SETLK, l) == -1) {
887 switch (errno) {
888 case EAGAIN:
889 case EACCES:
890 BESDEBUG(LOCK,prolog << "exit (false): " << file_name << " by: " << l->l_pid << endl);
891 close(fd);
892 return false;
893
894 default: {
895 close(fd);
896 ostringstream oss;
897 oss << prolog << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
898 throw BESInternalError(oss.str(), __FILE__, __LINE__);
899 }
900 }
901 }
902
903
904 // Success
905 m_record_descriptor(file_name,fd);
906 ref_fd = fd;
907 BESDEBUG(LOCK, prolog << "END filename: " << file_name <<endl);
908 return true;
909}
910
911
927void BESFileLockingCache::update_and_purge(const string &new_file)
928{
929 BESDEBUG(CACHE, prolog << "Starting the purge" << endl);
930
931 if (is_unlimited()) {
932 BESDEBUG(CACHE, prolog << "Size is set to unlimited, so no need to purge." << endl);
933 return;
934 }
935
936 try {
938
939 CacheFiles contents;
940 unsigned long long computed_size = m_collect_cache_dir_info(contents);
941#if 0
942 if (BESISDEBUG( "cache_contents" )) {
943 BESDEBUG(CACHE, "BEFORE Purge " << computed_size/BYTES_PER_MEG << endl );
944 CacheFiles::iterator ti = contents.begin();
945 CacheFiles::iterator te = contents.end();
946 for (; ti != te; ti++) {
947 BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
948 }
949 }
950#endif
951 BESDEBUG(CACHE, prolog << "Current and target size (in MB) "
952 << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
953
954 // This deletes files and updates computed_size
955 if (cache_too_big(computed_size)) {
956
957 // d_target_size is 80% of the maximum cache size.
958 // Grab the first which is the oldest in terms of access time.
959 CacheFiles::iterator i = contents.begin();
960 while (i != contents.end() && computed_size > d_target_size) {
961 // Grab an exclusive lock but do not block - if another process has the file locked
962 // just move on to the next file. Also test to see if the current file is the file
963 // this process just added to the cache - don't purge that!
964 int cfile_fd;
965 if (i->name != new_file && get_exclusive_lock_nb(i->name, cfile_fd)) {
966 BESDEBUG(CACHE, prolog << "purge: " << i->name << " removed." << endl);
967
968 if (unlink(i->name.c_str()) != 0)
969 throw BESInternalError(
970 prolog + "Unable to purge the file " + i->name + " from the cache: " + get_errno(), __FILE__,
971 __LINE__);
972
973 unlock(cfile_fd);
974 computed_size -= i->size;
975 }
976 ++i;
977
978 BESDEBUG(CACHE,prolog << "Current and target size (in MB) "
979 << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
980 }
981 }
982
983 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
984 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
985
986 if (write(d_cache_info_fd, &computed_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
987 throw BESInternalError(prolog + "Could not write size info to the cache info file!", __FILE__, __LINE__);
988#if 0
989 if (BESISDEBUG( "cache_contents" )) {
990 contents.clear();
991 computed_size = m_collect_cache_dir_info(contents);
992 BESDEBUG(CACHE, "AFTER Purge " << computed_size/BYTES_PER_MEG << endl );
993 CacheFiles::iterator ti = contents.begin();
994 CacheFiles::iterator te = contents.end();
995 for (; ti != te; ti++) {
996 BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
997 }
998 }
999#endif
1000 unlock_cache();
1001 }
1002 catch (...) {
1003 unlock_cache();
1004 throw;
1005 }
1006}
1007
1024bool BESFileLockingCache::get_exclusive_lock(const string &file_name, int &ref_fd)
1025{
1026 BESDEBUG(LOCK, prolog << "BEGIN filename: " << file_name <<endl);
1027
1028 int fd;
1029 if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
1030 switch (errno) {
1031 case ENOENT:
1032 return false;
1033
1034 default:
1035 {
1036 stringstream msg;
1037 msg << prolog << "FAILED to open file: " << file_name << " errno: " << get_errno();
1038 BESDEBUG(LOCK, msg.str() << endl);
1039 throw BESInternalError(msg.str() , __FILE__, __LINE__);
1040 }
1041 }
1042 }
1043
1044 struct flock *l = lock(F_WRLCK);
1045 if (fcntl(fd, F_SETLKW, l) == -1) { // F_SETLKW == blocking lock
1046 close(fd);
1047 ostringstream oss;
1048 oss << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
1049 throw BESInternalError(oss.str(), __FILE__, __LINE__);
1050 }
1051
1052 // Success
1053 m_record_descriptor(file_name,fd);
1054 ref_fd = fd;
1055
1056 BESDEBUG(LOCK, prolog << "END filename: " << file_name <<endl);
1057 return true;
1058}
1059
1070void BESFileLockingCache::purge_file(const string &file)
1071{
1072 BESDEBUG(CACHE, prolog << "Starting the purge" << endl);
1073
1074 try {
1076
1077 // Grab an exclusive lock on the file
1078 int cfile_fd;
1079 if (get_exclusive_lock(file, cfile_fd)) {
1080 // Get the file's size
1081 unsigned long long size = 0;
1082 struct stat buf;
1083 if (stat(file.c_str(), &buf) == 0) {
1084 size = buf.st_size;
1085 }
1086
1087 BESDEBUG(CACHE, prolog << "file: " << file << " removed." << endl);
1088
1089 if (unlink(file.c_str()) != 0)
1090 throw BESInternalError(prolog + "Unable to purge the file " + file + " from the cache: " + get_errno(), __FILE__,
1091 __LINE__);
1092
1093 unlock(cfile_fd);
1094
1095 unsigned long long cache_size = get_cache_size() - size;
1096
1097 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
1098 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
1099
1100 if (write(d_cache_info_fd, &cache_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
1101 throw BESInternalError(prolog + "Could not write size info to the cache info file!", __FILE__, __LINE__);
1102 }
1103
1104 unlock_cache();
1105 }
1106 catch (...) {
1107 unlock_cache();
1108 throw;
1109 }
1110}
1111
1121bool BESFileLockingCache::dir_exists(const string &dir)
1122{
1123 struct stat buf;
1124
1125 return (stat(dir.c_str(), &buf) == 0) && (buf.st_mode & S_IFDIR);
1126}
1127
1136void BESFileLockingCache::dump(ostream &strm) const
1137{
1138 strm << BESIndent::LMarg << prolog << "(" << (void *) this << ")" << endl;
1139 BESIndent::Indent();
1140 strm << BESIndent::LMarg << "cache dir: " << d_cache_dir << endl;
1141 strm << BESIndent::LMarg << "prefix: " << d_prefix << endl;
1142 strm << BESIndent::LMarg << "size (bytes): " << d_max_cache_size_in_bytes << endl;
1143 BESIndent::UnIndent();
1144}
Base exception class for the BES with basic string message.
Definition: BESError.h:59
virtual void dump(std::ostream &strm) const
dumps information about this object
virtual unsigned long long get_cache_size()
Get the cache size.
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)
const std::string get_cache_directory()
bool is_unlimited() const
Is this cache allowed to store as much as it wants?
virtual unsigned long long update_cache_info(const std::string &target)
Update the cache info file to include 'target'.
virtual void lock_cache_write()
void enable()
Enable the cache.
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.
static bool dir_exists(const std::string &dir)
const std::string get_cache_file_prefix()
virtual void lock_cache_read()
virtual bool get_exclusive_lock(const std::string &target, int &fd)
void disable()
Disable the cache.
virtual bool get_exclusive_lock_nb(const std::string &target, int &fd)
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 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