blob: 5adae911c35c3c0b5dbf30047b16af73107c5912 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.
*/
// Author: morlovich@google.com (Maksim Orlovich)
#ifndef PAGESPEED_KERNEL_SHAREDMEM_SHARED_MEM_CACHE_H_
#define PAGESPEED_KERNEL_SHAREDMEM_SHARED_MEM_CACHE_H_
#include <cstddef>
#include <vector>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/cache_interface.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/thread_annotations.h"
#include "pagespeed/kernel/sharedmem/shared_mem_cache_data.h"
namespace net_instaweb {
class AbstractSharedMem;
class AbstractSharedMemSegment;
class Hasher;
class MessageHandler;
class SharedMemCacheDump;
class SharedString;
class Timer;
// Abstract interface for a cache.
template<size_t kBlockSize>
class SharedMemCache : public CacheInterface {
public:
static const int kAssociativity = 4; // Note: changing this requires changing
// code of ExtractPosition as well.
// Initializes the cache's settings, but does not actually touch the shared
// memory --- you must call Initialize or Attach (and handle them potentially
// returning false) to do so. The filename parameter will be used to identify
// the shared memory segment, so distinct caches should use distinct values.
//
// Precondition: hasher's raw mode must produce 13 bytes or more.
SharedMemCache(AbstractSharedMem* shm_runtime, const GoogleString& filename,
Timer* timer, const Hasher* hasher, int sectors,
int entries_per_sector, int blocks_per_sector,
MessageHandler* handler);
virtual ~SharedMemCache();
// Sets up our shared state for use of all child processes/threads. Returns
// whether successful. This should be called exactly once for every cache
// in the root process, before forking.
bool Initialize();
// Connects to already initialized state from a child process. It must be
// called once for every cache in every child process (that is, post-fork).
// Returns whether successful.
bool Attach();
// This should be called from the root process as it is about to exit, when
// no further children are expected to start.
static void GlobalCleanup(AbstractSharedMem* shm_runtime,
const GoogleString& filename,
MessageHandler* message_handler);
// Computes how many entries and blocks per sector a cache with total size
// 'size_kb' and 'sectors' should have if there are about 'block_entry_ratio'
// worth of blocks of data per every entry. You probably want to underestimate
// this ratio somewhat, since having extra entries can reduce conflicts.
// Also outputs size_cap, which is the limit on object size for the resulting
// cache.
static void ComputeDimensions(int64 size_kb,
int block_entry_ratio,
int sectors,
int* entries_per_sector_out,
int* blocks_per_sector_out,
int64* size_cap_out);
// Returns the largest size of an object this cache can store.
size_t MaxValueSize() const {
return (blocks_per_sector_ * kBlockSize) / 8;
}
// Returns some statistics as plaintext.
// TODO(morlovich): Potentially periodically push these to the main
// Statistics system (or pull to it from these).
GoogleString DumpStats();
// Saves information on sector contents to *dest. You can call this multiple
// times with the same dest.
// Note: other accesses to the sector will be locked out for the duration.
void AddSectorToSnapshot(int sector_num, SharedMemCacheDump* dest);
// Restores entries stored in the dump into this cache. The dump
// may contain multiple sectors.
void RestoreSnapshot(const SharedMemCacheDump& dump);
// Encode/Decode SharedMemCacheDump objects.
static void MarshalSnapshot(const SharedMemCacheDump& dump,
GoogleString* out);
static void DemarshalSnapshot(const GoogleString& marshaled,
SharedMemCacheDump* out);
virtual void Get(const GoogleString& key, Callback* callback);
virtual void Put(const GoogleString& key, SharedString* value);
virtual void Delete(const GoogleString& key);
static GoogleString FormatName();
virtual GoogleString Name() const { return FormatName();}
virtual bool IsBlocking() const { return true; }
virtual bool IsHealthy() const { return true; }
virtual void ShutDown() {
// TODO(morlovich): Implement
}
// Sanity check the cache data structures.
void SanityCheck();
private:
// Describes potential placements of a key
struct Position {
int sector;
SharedMemCacheData::EntryNum keys[kAssociativity];
};
bool InitCache(bool parent);
void PutRawHash(const GoogleString& raw_hash, int64 last_use_timestamp_ms,
SharedString* value);
// Finish a get, with the entry matching and sector lock held.
// Releases lock when done.
void GetFromEntry(const GoogleString& key,
SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num, Callback* callback)
UNLOCK_FUNCTION(sector->mutex());
// Finish a put into the given entry. Lock is expected to be held at entry,
// will be released when done. The hash in the entry must also be already
// correct at time of entry.
void PutIntoEntry(SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num,
int64 last_use_timestamp_ms, SharedString* value)
EXCLUSIVE_LOCKS_REQUIRED(sector->mutex())
UNLOCK_FUNCTION(sector->mutex());
// Finish a delete, with the entry matching and sector lock held.
// Releases lock when done.
void DeleteEntry(SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num)
UNLOCK_FUNCTION(sector->mutex());
// Attempts to allocate at least the given number of blocks, and appends any
// blocks it manages to allocate to *blocks. Returns whether successful.
//
// Note that in case of failure, some blocks may still have been allocated,
// so the caller may have to clean them up. When successful, this method may
// allocate more memory than is requested.
bool TryAllocateBlocks(SharedMemCacheData::Sector<kBlockSize>* sector,
int goal, SharedMemCacheData::BlockVector* blocks)
EXCLUSIVE_LOCKS_REQUIRED(sector->mutex());
// Marks the given entry free in the directory, and unlinks it from the LRU.
// Note that this does not touch the entry's blocks.
void MarkEntryFree(SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num);
// Marks entry as having been recently used, and updates timestamp.
void TouchEntry(SharedMemCacheData::Sector<kBlockSize>* sector,
int64 last_use_timestamp_ms,
SharedMemCacheData::EntryNum entry_num);
// Returns true if the entry can be written (in particular meaning it's not
// opened by someone else)
bool Writeable(const SharedMemCacheData::CacheEntry* entry);
bool KeyMatch(SharedMemCacheData::CacheEntry* entry,
const GoogleString& raw_hash);
GoogleString ToRawHash(const GoogleString& key);
// Given a hash, tells what sector and what entries in it to check.
void ExtractPosition(const GoogleString& raw_hash, Position* out_pos);
// Makes sure we have exclusive write access to the entry, with no concurrent
// readers. Must be called with sector lock held.
void EnsureReadyForWriting(SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::CacheEntry* entry)
EXCLUSIVE_LOCKS_REQUIRED(sector->mutex());
AbstractSharedMem* shm_runtime_;
const Hasher* hasher_;
Timer* timer_;
GoogleString filename_;
int num_sectors_;
int entries_per_sector_;
int blocks_per_sector_;
MessageHandler* handler_;
scoped_ptr<AbstractSharedMemSegment> segment_;
std::vector<SharedMemCacheData::Sector<kBlockSize>*> sectors_;
GoogleString name_;
DISALLOW_COPY_AND_ASSIGN(SharedMemCache);
};
} // namespace net_instaweb
#endif // PAGESPEED_KERNEL_SHAREDMEM_SHARED_MEM_CACHE_H_