blob: 5632afd40fa5893b0a0c969543ad784fb55c4eaa [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/shared_string.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_annotations.h"
#include "pagespeed/kernel/cache/file_cache.h"
#include "pagespeed/kernel/sharedmem/shared_mem_cache_data.h"
namespace net_instaweb {
class AbstractSharedMem;
class AbstractSharedMemSegment;
class Hasher;
class MessageHandler;
class SharedMemCacheDump;
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.
//
// Callers who want checkpointing need to call RegisterSnapshotFileCache().
//
// 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.
//
// If a file cache was set this restores any snapshotted sectors to shared
// memory.
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();
// Tries to dump the contents the specified sector to *dest, aborts early if a
// different thread is already working on it, and returns whether it was
// successful. To make sure only one thread ends up dumping the sector it
// compares the last_checkpoint_ms provided to the one in the sector, and only
// continues with the dump if they match. After a successful dump, it updates
// the last_checkpoint_ms in the sector to the current time.
//
// Note: other accesses to the sector will be locked out for the duration.
bool AddSectorToSnapshot(int sector_num, int64 last_checkpoint_ms,
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 StringPiece& marshaled,
SharedMemCacheDump* out);
virtual void Get(const GoogleString& key, Callback* callback);
virtual void Put(const GoogleString& key, const 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();
// Use the specified FileCache for loading and storing snapshots. This may be
// called multiple times with different FileCaches but we pick one. If the
// cache has the same path we were constructed with, use that. Otherwise, to
// handle the default shm cache case, use the cache with the path that comes
// first alphabetically.
void RegisterSnapshotFileCache(FileCache* potential_file_cache,
int checkpoint_interval_sec);
StringPiece snapshot_path() const { return snapshot_path_; }
FileCache* file_cache() const { return file_cache_; }
int64 GetLastWriteMsForTesting(int sector_num);
void SetLastWriteMsForTesting(int sector_num, int64 last_checkpoint_ms);
void WriteOutSnapshotForTesting(int sector_num, int64 last_checkpoint_ms) {
WriteOutSnapshotFromWorkerThread(sector_num, last_checkpoint_ms);
}
private:
class WriteOutSnapshotFunction;
// Describes potential placements of a key
struct Position {
int sector;
SharedMemCacheData::EntryNum keys[kAssociativity];
};
bool InitCache(bool parent);
// PutRawHash can be used in either realtime mode or in restore mode. In
// realtime mode (checkpoint_ok=true) a put can trigger a checkpoint and
// last_use_timestamp_ms should be the current time. In restore mode, a put
// shouldn't checkpointing because we're in the middle of restoring a
// checkpoint and last_use_timestamp_ms should be the timestamp to restore for
// the entry.
void PutRawHash(const GoogleString& raw_hash, int64 last_use_timestamp_ms,
const SharedString& value, bool checkpoint_ok);
// Finish a get, with the entry matching and sector lock held. Releases lock
// while performing the read, but takes it again before returning.
CacheInterface::KeyState GetFromEntry(
const GoogleString& key,
SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num,
Callback* str) EXCLUSIVE_LOCKS_REQUIRED(sector->mutex());
// Finish a put into the given entry. Lock is expected to be held at entry,
// will still be held when done. The hash in the entry must also be already
// correct at time of call.
void PutIntoEntry(SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num,
int64 last_use_timestamp_ms, const SharedString& value)
EXCLUSIVE_LOCKS_REQUIRED(sector->mutex());
// Finish a delete, with the entry matching and sector lock held.
void DeleteEntry(SharedMemCacheData::Sector<kBlockSize>* sector,
SharedMemCacheData::EntryNum entry_num)
EXCLUSIVE_LOCKS_REQUIRED(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());
// Restore snapshots from the file cache, if set.
void RestoreFromDisk();
// Helper for PutRawHash that decides whether to call ScheduleSnapshot and
// then makes the call if appropriate.
void ScheduleSnapshotIfNecessary(bool checkpoint_ok,
int64 last_use_timestamp_ms,
int64 last_checkpoint_ms,
int sector_num);
// Write out the specified sector to the file cache so it will survive process
// restarts. We pass along last_checkpoint_ms so that if another thread or
// process succeeds at snapshotting before we do we can abort quickly.
void ScheduleSnapshot(int sector_num, int64 last_checkpoint_ms);
// ScheduleSnapshot asks the FileCache's slow worker thread to call this.
void WriteOutSnapshotFromWorkerThread(int sector_num,
int64 last_checkpoint_ms);
// Key to store the snapshot of this sector under. If two SharedMemCaches
// have the same cache key it's safe to restore a snapshot dumped from one
// into the other.
GoogleString SnapshotCacheKey(int sector_num) const;
AbstractSharedMem* shm_runtime_;
const Hasher* hasher_;
Timer* timer_;
GoogleString filename_;
int num_sectors_;
int entries_per_sector_;
int blocks_per_sector_;
int checkpoint_interval_sec_;
MessageHandler* handler_;
GoogleString snapshot_path_;
FileCache* file_cache_;
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_