blob: 04202f654a93f45719da3382cb35edc56b037e60 [file] [log] [blame]
// Copyright 2011 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)
#include "pagespeed/kernel/thread/pthread_shared_mem.h"
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cerrno>
#include <cstddef>
#include <map>
#include <utility>
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_shared_mem.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace net_instaweb {
namespace {
// This implementation relies on readonly copies of old memory and shared R/W
// mappings being kept across a fork. It simply stashes addresses of
// shared mmap segments into a map where kid processes can pick them up.
// close() a fd logging failure and dealing with EINTR.
void CheckedClose(int fd, MessageHandler* message_handler) {
while (close(fd) != 0) {
if (errno != EINTR) {
message_handler->Message(kWarning, "Problem closing SHM segment fd:%d",
errno);
return;
}
}
}
// Unlike PthreadMutex this doesn't own the lock, but rather refers to an
// external one.
class PthreadSharedMemMutex : public AbstractMutex {
public:
explicit PthreadSharedMemMutex(pthread_mutex_t* external_mutex)
: external_mutex_(external_mutex) {}
virtual bool TryLock() {
return (pthread_mutex_trylock(external_mutex_) == 0);
}
virtual void Lock() {
pthread_mutex_lock(external_mutex_);
}
virtual void Unlock() {
pthread_mutex_unlock(external_mutex_);
}
private:
pthread_mutex_t* external_mutex_;
DISALLOW_COPY_AND_ASSIGN(PthreadSharedMemMutex);
};
class PthreadSharedMemSegment : public AbstractSharedMemSegment {
public:
// We will be representing memory mapped in the [base, base + size) range.
PthreadSharedMemSegment(char* base, size_t size, MessageHandler* handler)
: base_(base),
size_(size) {
}
virtual ~PthreadSharedMemSegment() {
}
virtual volatile char* Base() {
return base_;
}
virtual size_t SharedMutexSize() const {
return sizeof(pthread_mutex_t);
}
virtual bool InitializeSharedMutex(size_t offset, MessageHandler* handler) {
pthread_mutexattr_t attr;
if (pthread_mutexattr_init(&attr) != 0) {
handler->Message(kError, "pthread_mutexattr_init failed with errno:%d",
errno);
return false;
}
if (pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) != 0) {
pthread_mutexattr_destroy(&attr);
handler->Message(
kError, "pthread_mutexattr_setpshared failed with errno:%d", errno);
return false;
}
if (pthread_mutex_init(MutexPtr(offset), &attr) != 0) {
pthread_mutexattr_destroy(&attr);
handler->Message(kError, "pthread_mutex_init failed with errno:%d",
errno);
return false;
}
pthread_mutexattr_destroy(&attr);
return true;
}
virtual AbstractMutex* AttachToSharedMutex(size_t offset) {
return new PthreadSharedMemMutex(MutexPtr(offset));
}
private:
pthread_mutex_t* MutexPtr(size_t offset) {
return reinterpret_cast<pthread_mutex_t*>(base_ + offset);
}
char* const base_;
const size_t size_;
DISALLOW_COPY_AND_ASSIGN(PthreadSharedMemSegment);
};
pthread_mutex_t segment_bases_lock = PTHREAD_MUTEX_INITIALIZER;
} // namespace
size_t PthreadSharedMem::s_instance_count_ = 0;
PthreadSharedMem::SegmentBaseMap* PthreadSharedMem::segment_bases_ = NULL;
PthreadSharedMem::PthreadSharedMem() {
instance_number_ = ++s_instance_count_;
}
PthreadSharedMem::~PthreadSharedMem() {
}
size_t PthreadSharedMem::SharedMutexSize() const {
return sizeof(pthread_mutex_t);
}
AbstractSharedMemSegment* PthreadSharedMem::CreateSegment(
const GoogleString& name, size_t size, MessageHandler* handler) {
GoogleString prefixed_name = PrefixSegmentName(name);
// Create the memory
int fd = open("/dev/zero", O_RDWR);
if (fd == -1) {
handler->Message(
kError, "Unable to create SHM segment %s, open of /dev/zero failed "
"with errno=%d.", prefixed_name.c_str(), errno);
return NULL;
}
// map it
char* base = reinterpret_cast<char*>(
mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
int mmap_errno = errno;
CheckedClose(fd, handler);
if (base == MAP_FAILED) {
handler->Message(
kError, "Unable to create SHM segment %s, mmap failed with errno=%d.",
prefixed_name.c_str(), mmap_errno);
return NULL;
}
SegmentBaseMap* bases = AcquireSegmentBases();
(*bases)[prefixed_name] = std::make_pair(base, size);
UnlockSegmentBases();
return new PthreadSharedMemSegment(base, size, handler);
}
AbstractSharedMemSegment* PthreadSharedMem::AttachToSegment(
const GoogleString& name, size_t size, MessageHandler* handler) {
GoogleString prefixed_name = PrefixSegmentName(name);
SegmentBaseMap* bases = AcquireSegmentBases();
SegmentBaseMap::const_iterator i = bases->find(prefixed_name);
if (i == bases->end()) {
handler->Message(kError, "Unable to find SHM segment %s to attach to.",
prefixed_name.c_str());
UnlockSegmentBases();
return NULL;
}
char* base = i->second.first;
DCHECK_EQ(size, i->second.second);
UnlockSegmentBases();
return new PthreadSharedMemSegment(base, size, handler);
}
void PthreadSharedMem::DestroySegment(const GoogleString& name,
MessageHandler* handler) {
GoogleString prefixed_name = PrefixSegmentName(name);
// Note that in the process state children will not see any mutations
// we make here, so it acts mostly for checking in that case.
SegmentBaseMap* bases = AcquireSegmentBases();
SegmentBaseMap::iterator i = bases->find(prefixed_name);
if (i != bases->end()) {
// Note that we must munmap the segment here in order to not leak like crazy
// for things like apache2ctrl graceful (and similar nginx configuration).
munmap(i->second.first, i->second.second);
bases->erase(i);
if (bases->empty()) {
delete segment_bases_;
segment_bases_ = NULL;
}
} else {
handler->Message(kError, "Attempt to destroy unknown SHM segment %s.",
prefixed_name.c_str());
}
UnlockSegmentBases();
}
PthreadSharedMem::SegmentBaseMap* PthreadSharedMem::AcquireSegmentBases() {
PthreadSharedMemMutex lock(&segment_bases_lock);
lock.Lock();
if (segment_bases_ == NULL) {
segment_bases_ = new SegmentBaseMap();
}
return segment_bases_;
}
void PthreadSharedMem::UnlockSegmentBases() {
PthreadSharedMemMutex lock(&segment_bases_lock);
lock.Unlock();
}
GoogleString PthreadSharedMem::PrefixSegmentName(const GoogleString& name) {
GoogleString res;
StrAppend(&res, "[", IntegerToString(instance_number_), "]", name);
return res;
}
void PthreadSharedMem::Terminate() {
// Clean up the local memory associated with the maps to shared memory
// storage.
PthreadSharedMemMutex lock(&segment_bases_lock);
lock.Lock();
if (segment_bases_ != NULL) {
delete segment_bases_;
segment_bases_ = NULL;
}
lock.Unlock();
}
} // namespace net_instaweb