diff options
Diffstat (limited to 'src/lib/util/memory_segment.h')
-rw-r--r-- | src/lib/util/memory_segment.h | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/lib/util/memory_segment.h b/src/lib/util/memory_segment.h new file mode 100644 index 0000000..c9ae97f --- /dev/null +++ b/src/lib/util/memory_segment.h @@ -0,0 +1,333 @@ +// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef MEMORY_SEGMENT_H +#define MEMORY_SEGMENT_H + +#include <exceptions/exceptions.h> + +#include <utility> + +#include <stdlib.h> + +namespace isc { +namespace util { + +/// \brief Exception that can be thrown when constructing a MemorySegment +/// object. +class MemorySegmentOpenError : public Exception { +public: + MemorySegmentOpenError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// \brief Exception that is thrown, when allocating space in a MemorySegment +/// results in growing the underlying segment. +/// +/// See MemorySegment::allocate() for details. +class MemorySegmentGrown : public Exception { +public: + MemorySegmentGrown(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// \brief General error that can be thrown by a MemorySegment +/// implementation. +class MemorySegmentError : public Exception { +public: + MemorySegmentError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// \brief Memory Segment Class +/// +/// This class specifies an interface for allocating memory segments. +/// It's intended to provide a unified interface, whether the underlying +/// memory is local to a specific process or is sharable by multiple +/// processes. +/// +/// This is an abstract class and a real implementation such as +/// MemorySegmentLocal should be used in code. +class MemorySegment { +public: + /// \brief Destructor + virtual ~MemorySegment() {} + + /// \brief Allocate/acquire a fragment of memory. + /// + /// The source of the memory is dependent on the implementation used. + /// + /// Depending on the implementation details, it may have to grow the + /// internal memory segment (again, in an implementation dependent way) + /// to allocate the required size of memory. In that case the + /// implementation must grow the internal segment sufficiently so the + /// next call to allocate() for the same size will succeed, and throw + /// a \c MemorySegmentGrown exception (not really allocating the memory + /// yet). + /// + /// An application that uses this memory segment abstraction to allocate + /// memory should expect this exception, and should normally catch it + /// at an appropriate layer (which may be immediately after a call to + /// \c allocate() or a bit higher layer). It should interpret the + /// exception as any raw address that belongs to the segment may have + /// been remapped and must be re-fetched via an already established + /// named address using the \c getNamedAddress() method. + /// + /// The intended use case of \c allocate() with the \c MemorySegmentGrown + /// exception is to build a complex object that would internally require + /// multiple calls to \c allocate(): + /// + /// \code + /// ComplicatedStuff* stuff = NULL; + /// while (!stuff) { // this must eventually succeed or result in bad_alloc + /// try { + /// // create() is a factory method that takes a memory segment + /// // and calls allocate() on it multiple times. create() + /// // provides an exception guarantee that any intermediately + /// // allocated memory will be properly deallocate()-ed on + /// // exception. + /// stuff = ComplicatedStuff::create(mem_segment); + /// } catch (const MemorySegmentGrown&) { /* just try again */ } + /// } + /// \endcode + /// + /// This way, \c create() can be written as if each call to \c allocate() + /// always succeeds. + /// + /// Alternatively, or in addition to this, we could introduce a "no throw" + /// version of this method with a way to tell the caller the reason of + /// any failure (whether it's really out of memory or just due to growing + /// the segment). That would be more convenient if the caller wants to + /// deal with the failures on a per-call basis rather than as a set + /// of calls like in the above example. At the moment, we don't expect + /// to have such use-cases, so we only provide the exception + /// version. + /// + /// \throw std::bad_alloc The implementation cannot allocate the + /// requested storage. + /// \throw MemorySegmentGrown The memory segment doesn't have sufficient + /// space for the requested size and has grown internally. + /// \throw MemorySegmentError An attempt was made to allocate + /// storage on a read-only memory segment. + /// + /// \param size The size of the memory requested in bytes. + /// \return Returns pointer to the memory allocated. + virtual void* allocate(size_t size) = 0; + + /// \brief Free/release a segment of memory. + /// + /// This method may throw <code>isc::OutOfRange</code> if \c size is + /// not equal to the originally allocated size. \c size could be + /// used by some implementations such as a slice allocator, where + /// freeing memory also requires the size to be specified. We also + /// use this argument in some implementations to test if all allocated + /// memory was deallocated properly. + /// + /// Specific implementation may also throw \c MemorySegmentError if it + /// encounters violation of implementation specific restrictions. + /// + /// In general, however, this method must succeed and exception free + /// as long as the caller passes valid parameters (\c ptr specifies + /// memory previously allocated and \c size is correct). + /// + /// \throw OutOfRange The passed size doesn't match the allocated memory + /// size (when identifiable for the implementation). + /// \throw MemorySegmentError Failure of implementation specific + /// validation. + /// + /// \param ptr Pointer to the block of memory to free/release. This + /// should be equal to a value returned by <code>allocate()</code>. + /// \param size The size of the memory to be freed in bytes. This + /// should be equal to the number of bytes originally allocated. + virtual void deallocate(void* ptr, size_t size) = 0; + + /// \brief Check if all allocated memory was deallocated. + /// + /// \return Returns <code>true</code> if all allocated memory (including + /// names associated by memory addresses by \c setNamedAddress()) was + /// deallocated, <code>false</code> otherwise. + virtual bool allMemoryDeallocated() const = 0; + + /// \brief Associate specified address in the segment with a given name. + /// + /// This method establishes an association between the given name and + /// the address in an implementation specific way. The stored address + /// is retrieved by the name later by calling \c getNamedAddress(). + /// If the underlying memory segment is sharable by multiple processes, + /// the implementation must ensure the portability of the association; + /// if a process gives an address in the shared segment a name, another + /// process that shares the same segment should be able to retrieve the + /// corresponding address by that name (in such cases the real address + /// may be different between these two processes). + /// + /// Some names are reserved for internal use by this class. If such + /// a name is passed to this method, an \c isc::InvalidParameter + /// exception will be thrown. See \c validateName() method for details. + /// + /// \c addr must be 0 (NULL) or an address that belongs to this segment. + /// The latter case means it must be the return value of a previous call + /// to \c allocate(). The actual implementation is encouraged to detect + /// violation of this restriction and signal it with an exception, but + /// it's not an API requirement. It's generally the caller's + /// responsibility to meet the restriction. Note that NULL is allowed + /// as \c addr even if it wouldn't be considered to "belong to" the + /// segment in its normal sense; it can be used to indicate that memory + /// has not been allocated for the specified name. A subsequent call + /// to \c getNamedAddress() will return NamedAddressResult(true, NULL) + /// for that name. + /// + /// \note Naming an address is intentionally separated from allocation + /// so that, for example, one module of a program can name a memory + /// region allocated by another module of the program. + /// + /// There can be an existing association for the name; in that case the + /// association will be overridden with the newly given address. + /// + /// While normally unexpected, it's possible that available space in the + /// segment is not sufficient to allocate a space (if not already exist) + /// for the specified name in the segment. In that case, if possible, the + /// implementation should try to grow the internal segment and retry + /// establishing the association. The implementation should throw + /// std::bad_alloc if even reasonable attempts of retry still fail. + /// + /// This method should normally return false, but if the internal segment + /// had to grow to store the given name, it must return true. The + /// application should interpret it just like the case of + /// \c MemorySegmentGrown exception thrown from the \c allocate() method. + /// + /// \note The behavior in case the internal segment grows is different + /// from that of \c allocate(). This is intentional. In intended use + /// cases (for the moment) this method will be called independently, + /// rather than as part of a set of allocations. It's also expected + /// that other raw memory addresses (which would have been invalidated + /// due to the change to the segment) won't be referenced directly + /// immediately after this call. So, the caller should normally be able + /// to call this method as mostly never-fail one (except in case of real + /// memory exhaustion) and ignore the return value. + /// + /// \throw std::bad_alloc Allocation of a segment space for the given name + /// failed. + /// \throw InvalidParameter name is NULL, empty ("") or begins with + /// an underscore ('_'). + /// \throw MemorySegmentError Failure of implementation specific + /// validation. + /// + /// \param name A C string to be associated with \c addr. Must not be NULL. + /// \param addr A memory address returned by a prior call to \c allocate. + /// \return true if the internal segment has grown to allocate space for + /// the name; false otherwise (see above). + bool setNamedAddress(const char* name, void* addr) { + // This public method implements common validation. The actual + // work specific to the derived segment is delegated to the + // corresponding protected method. + validateName(name); + return (setNamedAddressImpl(name, addr)); + } + + /// \brief Type definition for result returned by getNamedAddress() + typedef std::pair<bool, void*> NamedAddressResult; + + /// \brief Return the address in the segment that has the given name. + /// + /// This method returns the memory address in the segment corresponding + /// to the specified \c name. The name and address must have been + /// associated by a prior call to \c setNameAddress(). If no address + /// associated with the given name is found, it returns NULL. + /// + /// Some names are reserved for internal use by this class. If such + /// a name is passed to this method, an \c isc::InvalidParameter + /// exception will be thrown. See \c validateName() method for details. + /// + /// This method should generally be considered exception free, but there + /// can be a small chance it throws, depending on the internal + /// implementation (e.g., if it converts the name to std::string), so the + /// API doesn't guarantee that property. In general, if this method + /// throws it should be considered a fatal condition. + /// + /// \throw InvalidParameter name is NULL, empty ("") or begins with + /// an underscore ('_'). + /// + /// \param name A C string of which the segment memory address is to be + /// returned. Must not be NULL. + /// \return An std::pair containing a bool (set to true if the name + /// was found, or false otherwise) and the address associated with + /// the name (which is undefined if the name was not found). + NamedAddressResult getNamedAddress(const char* name) const { + // This public method implements common validation. The actual + // work specific to the derived segment is delegated to the + // corresponding protected method. + validateName(name); + return (getNamedAddressImpl(name)); + } + + /// \brief Delete a name previously associated with a segment address. + /// + /// This method deletes the association of the given \c name to + /// a corresponding segment address previously established by + /// \c setNamedAddress(). If there is no association for the given name + /// this method returns false; otherwise it returns true. + /// + /// Some names are reserved for internal use by this class. If such + /// a name is passed to this method, an \c isc::InvalidParameter + /// exception will be thrown. See \c validateName() method for details. + /// + /// See \c getNamedAddress() about exception consideration. + /// + /// \throw InvalidParameter name is NULL, empty ("") or begins with + /// an underscore ('_'). + /// \throw MemorySegmentError Failure of implementation specific + /// validation. + /// + /// \param name A C string of which the segment memory address is to be + /// deleted. Must not be NULL. + bool clearNamedAddress(const char* name) { + // This public method implements common validation. The actual + // work specific to the derived segment is delegated to the + // corresponding protected method. + validateName(name); + return (clearNamedAddressImpl(name)); + } + +private: + /// \brief Validate the passed name. + /// + /// This method validates the passed name (for name/address pairs) + /// and throws \c InvalidParameter if the name fails + /// validation. Otherwise, it does nothing. + /// + /// \throw InvalidParameter name is NULL, empty ("") or begins with + /// an underscore ('_'). + static void validateName(const char* name) { + if (!name) { + isc_throw(InvalidParameter, "NULL is invalid for a name."); + } else if (*name == '\0') { + isc_throw(InvalidParameter, "Empty names are invalid."); + } else if (*name == '_') { + isc_throw(InvalidParameter, + "Names beginning with '_' are reserved for " + "internal use only."); + } + } + +protected: + /// \brief Implementation of setNamedAddress beyond common validation. + virtual bool setNamedAddressImpl(const char* name, void* addr) = 0; + + /// \brief Implementation of getNamedAddress beyond common validation. + virtual NamedAddressResult getNamedAddressImpl(const char* name) const = 0; + + /// \brief Implementation of clearNamedAddress beyond common validation. + virtual bool clearNamedAddressImpl(const char* name) = 0; +}; + +} // namespace util +} // namespace isc + +#endif // MEMORY_SEGMENT_H + +// Local Variables: +// mode: c++ +// End: |