diff options
author | Eliot Horowitz <eliot@10gen.com> | 2011-12-24 15:33:26 -0500 |
---|---|---|
committer | Eliot Horowitz <eliot@10gen.com> | 2011-12-24 15:33:45 -0500 |
commit | ae1ecd9c786911f9f1f0242f0f7d702b3e5dfeba (patch) | |
tree | 92f8e1649e6f080b251ff5f1763679a72eb59b34 /src/mongo/util | |
parent | dfa4cd7e2cf109b072440155fabc08a93c8045a0 (diff) | |
download | mongo-ae1ecd9c786911f9f1f0242f0f7d702b3e5dfeba.tar.gz |
bulk move of code to src/ SERVER-4551
Diffstat (limited to 'src/mongo/util')
124 files changed, 17961 insertions, 0 deletions
diff --git a/src/mongo/util/admin_access.h b/src/mongo/util/admin_access.h new file mode 100644 index 00000000000..bb882b2b4c5 --- /dev/null +++ b/src/mongo/util/admin_access.h @@ -0,0 +1,52 @@ +/** @file admin_access.h + */ + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + /* + * An AdminAccess is an interface class used to determine if certain users have + * priviledges to a given resource. + * + */ + class AdminAccess { + public: + virtual ~AdminAccess() { } + + /** @return if there are any priviledge users. This should not + * block for long and throw if can't get a lock if needed. + */ + virtual bool haveAdminUsers() const = 0; + + /** @return priviledged user with this name. This should not block + * for long and throw if can't get a lock if needed + */ + virtual BSONObj getAdminUser( const string& username ) const = 0; + }; + + class NoAdminAccess : public AdminAccess { + public: + virtual ~NoAdminAccess() { } + + virtual bool haveAdminUsers() const { return false; } + virtual BSONObj getAdminUser( const string& username ) const { return BSONObj(); } + }; + +} // namespace mongo diff --git a/src/mongo/util/alignedbuilder.cpp b/src/mongo/util/alignedbuilder.cpp new file mode 100644 index 00000000000..b2e0461b733 --- /dev/null +++ b/src/mongo/util/alignedbuilder.cpp @@ -0,0 +1,141 @@ +// @file alignedbuilder.cpp + +/** +* Copyright (C) 2009 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" +#include "alignedbuilder.h" + +namespace mongo { + + AlignedBuilder::AlignedBuilder(unsigned initSize) { + _len = 0; + _malloc(initSize); + uassert(13584, "out of memory AlignedBuilder", _p._allocationAddress); + } + + BOOST_STATIC_ASSERT(sizeof(void*) == sizeof(size_t)); + + /** reset for a re-use. shrinks if > 128MB */ + void AlignedBuilder::reset() { + _len = 0; + RARELY { + const unsigned sizeCap = 128*1024*1024; + if (_p._size > sizeCap) + _realloc(sizeCap, _len); + } + } + + /** reset with a hint as to the upcoming needed size specified */ + void AlignedBuilder::reset(unsigned sz) { + _len = 0; + unsigned Q = 32 * 1024 * 1024 - 1; + unsigned want = (sz+Q) & (~Q); + if( _p._size == want ) { + return; + } + if( _p._size > want ) { + if( _p._size <= 64 * 1024 * 1024 ) + return; + bool downsize = false; + RARELY { downsize = true; } + if( !downsize ) + return; + } + _realloc(want, _len); + } + + void AlignedBuilder::mallocSelfAligned(unsigned sz) { + assert( sz == _p._size ); + void *p = malloc(sz + Alignment - 1); + _p._allocationAddress = p; + size_t s = (size_t) p; + size_t sold = s; + s += Alignment - 1; + s = (s/Alignment)*Alignment; + assert( s >= sold ); // begining + assert( (s + sz) <= (sold + sz + Alignment - 1) ); //end + _p._data = (char *) s; + } + + /* "slow"/infrequent portion of 'grow()' */ + void NOINLINE_DECL AlignedBuilder::growReallocate(unsigned oldLen) { + dassert( _len > _p._size ); + unsigned a = _p._size; + assert( a ); + while( 1 ) { + if( a < 128 * 1024 * 1024 ) + a *= 2; + else if( sizeof(int*) == 4 ) + a += 32 * 1024 * 1024; + else + a += 64 * 1024 * 1024; + DEV if( a > 256*1024*1024 ) { + log() << "dur AlignedBuilder too big, aborting in _DEBUG build" << endl; + abort(); + } + wassert( a <= 256*1024*1024 ); + assert( a <= 512*1024*1024 ); + if( _len < a ) + break; + } + _realloc(a, oldLen); + } + + void AlignedBuilder::_malloc(unsigned sz) { + _p._size = sz; +#if defined(_WIN32) + void *p = VirtualAlloc(0, sz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + _p._allocationAddress = p; + _p._data = (char *) p; +#elif defined(__linux__) + // in theory #ifdef _POSIX_VERSION should work, but it doesn't on OS X 10.4, and needs to be testeed on solaris. + // so for now, linux only for this. + void *p = 0; + int res = posix_memalign(&p, Alignment, sz); + massert(13524, "out of memory AlignedBuilder", res == 0); + _p._allocationAddress = p; + _p._data = (char *) p; +#else + mallocSelfAligned(sz); + assert( ((size_t) _p._data) % Alignment == 0 ); +#endif + } + + void AlignedBuilder::_realloc(unsigned newSize, unsigned oldLen) { + // posix_memalign alignment is not maintained on reallocs, so we can't use realloc(). + AllocationInfo old = _p; + _malloc(newSize); + assert( oldLen <= _len ); + memcpy(_p._data, old._data, oldLen); + _free(old._allocationAddress); + } + + void AlignedBuilder::_free(void *p) { +#if defined(_WIN32) + VirtualFree(p, 0, MEM_RELEASE); +#else + free(p); +#endif + } + + void AlignedBuilder::kill() { + _free(_p._allocationAddress); + _p._allocationAddress = 0; + _p._data = 0; + } + +} diff --git a/src/mongo/util/alignedbuilder.h b/src/mongo/util/alignedbuilder.h new file mode 100644 index 00000000000..1d246a9d78e --- /dev/null +++ b/src/mongo/util/alignedbuilder.h @@ -0,0 +1,125 @@ +// @file alignedbuilder.h + +/** +* Copyright (C) 2009 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../bson/stringdata.h" + +namespace mongo { + + /** a page-aligned BufBuilder. */ + class AlignedBuilder { + public: + AlignedBuilder(unsigned init_size); + ~AlignedBuilder() { kill(); } + + /** reset with a hint as to the upcoming needed size specified */ + void reset(unsigned sz); + + /** reset for a re-use. shrinks if > 128MB */ + void reset(); + + /** note this may be deallocated (realloced) if you keep writing or reset(). */ + const char* buf() const { return _p._data; } + + /** leave room for some stuff later + @return offset in the buffer that was our current position + */ + size_t skip(unsigned n) { + unsigned l = len(); + grow(n); + return l; + } + + /** if buffer grows pointer no longer valid */ + char* atOfs(unsigned ofs) { return _p._data + ofs; } + + /** if buffer grows pointer no longer valid */ + char* cur() { return _p._data + _len; } + + void appendChar(char j) { + *((char*)grow(sizeof(char))) = j; + } + void appendNum(char j) { + *((char*)grow(sizeof(char))) = j; + } + void appendNum(short j) { + *((short*)grow(sizeof(short))) = j; + } + void appendNum(int j) { + *((int*)grow(sizeof(int))) = j; + } + void appendNum(unsigned j) { + *((unsigned*)grow(sizeof(unsigned))) = j; + } + void appendNum(bool j) { + *((bool*)grow(sizeof(bool))) = j; + } + void appendNum(double j) { + *((double*)grow(sizeof(double))) = j; + } + void appendNum(long long j) { + *((long long*)grow(sizeof(long long))) = j; + } + void appendNum(unsigned long long j) { + *((unsigned long long*)grow(sizeof(unsigned long long))) = j; + } + + void appendBuf(const void *src, size_t len) { memcpy(grow((unsigned) len), src, len); } + + template<class T> + void appendStruct(const T& s) { appendBuf(&s, sizeof(T)); } + + void appendStr(const StringData &str , bool includeEOO = true ) { + const unsigned len = str.size() + ( includeEOO ? 1 : 0 ); + assert( len < (unsigned) BSONObjMaxUserSize ); + memcpy(grow(len), str.data(), len); + } + + /** @return the in-use length */ + unsigned len() const { return _len; } + + private: + static const unsigned Alignment = 8192; + + /** returns the pre-grow write position */ + inline char* grow(unsigned by) { + unsigned oldlen = _len; + _len += by; + if (MONGO_unlikely( _len > _p._size )) { + growReallocate(oldlen); + } + return _p._data + oldlen; + } + + void growReallocate(unsigned oldLenInUse); + void kill(); + void mallocSelfAligned(unsigned sz); + void _malloc(unsigned sz); + void _realloc(unsigned newSize, unsigned oldLenInUse); + void _free(void*); + + struct AllocationInfo { + char *_data; + void *_allocationAddress; + unsigned _size; + } _p; + unsigned _len; // bytes in use + }; + +} diff --git a/src/mongo/util/allocator.h b/src/mongo/util/allocator.h new file mode 100644 index 00000000000..a642e7cab56 --- /dev/null +++ b/src/mongo/util/allocator.h @@ -0,0 +1,39 @@ +// allocator.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + + inline void * ourmalloc(size_t size) { + void *x = malloc(size); + if ( x == 0 ) dbexit( EXIT_OOM_MALLOC , "malloc fails"); + return x; + } + + inline void * ourrealloc(void *ptr, size_t size) { + void *x = realloc(ptr, size); + if ( x == 0 ) dbexit( EXIT_OOM_REALLOC , "realloc fails"); + return x; + } + +#define MONGO_malloc mongo::ourmalloc +#define malloc MONGO_malloc +#define MONGO_realloc mongo::ourrealloc +#define realloc MONGO_realloc + +} // namespace mongo diff --git a/src/mongo/util/array.h b/src/mongo/util/array.h new file mode 100644 index 00000000000..12822252fd7 --- /dev/null +++ b/src/mongo/util/array.h @@ -0,0 +1,127 @@ +// array.h + +/* + * Copyright 2010 10gen 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. + */ + +namespace mongo { + + /* + * simple array class that does no allocations + * same api as vector + * fixed buffer, so once capacity is exceeded, will assert + * meant to be-reused with clear() + */ + template<typename T> + class FastArray { + public: + FastArray( int capacity=10000 ) + : _capacity( capacity ) , _size(0) , _end(this,capacity) { + _data = new T[capacity]; + } + + ~FastArray() { + delete[] _data; + } + + void clear() { + _size = 0; + } + + T& operator[]( int x ) { + assert( x >= 0 && x < _capacity ); + return _data[x]; + } + + T& getNext() { + return _data[_size++]; + } + + void push_back( const T& t ) { + assert( _size < _capacity ); + _data[_size++] = t; + } + + void sort( int (*comp)(const void *, const void *) ) { + qsort( _data , _size , sizeof(T) , comp ); + } + + int size() { + return _size; + } + + bool hasSpace() { + return _size < _capacity; + } + class iterator { + public: + iterator() { + _it = 0; + _pos = 0; + } + + iterator( FastArray * it , int pos=0 ) { + _it = it; + _pos = pos; + } + + bool operator==(const iterator& other ) const { + return _pos == other._pos; + } + + bool operator!=(const iterator& other ) const { + return _pos != other._pos; + } + + void operator++() { + _pos++; + } + + T& operator*() { + return _it->_data[_pos]; + } + + string toString() const { + stringstream ss; + ss << _pos; + return ss.str(); + } + private: + FastArray * _it; + int _pos; + + friend class FastArray; + }; + + + iterator begin() { + return iterator(this); + } + + iterator end() { + _end._pos = _size; + return _end; + } + + + private: + int _capacity; + int _size; + + iterator _end; + + T * _data; + }; +} diff --git a/src/mongo/util/assert_util.cpp b/src/mongo/util/assert_util.cpp new file mode 100644 index 00000000000..2199cb1ce11 --- /dev/null +++ b/src/mongo/util/assert_util.cpp @@ -0,0 +1,213 @@ +// assert_util.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "assert_util.h" +#include "assert.h" +//#include "file.h" +#include <cmath> +using namespace std; + +#ifndef _WIN32 +#include <cxxabi.h> +#include <sys/file.h> +#endif + +//#include "../bson/bson.h" +#include "../db/jsobj.h" + +namespace mongo { + + AssertionCount assertionCount; + + AssertionCount::AssertionCount() + : regular(0),warning(0),msg(0),user(0),rollovers(0) { + } + + void AssertionCount::rollover() { + rollovers++; + regular = 0; + warning = 0; + msg = 0; + user = 0; + } + + void AssertionCount::condrollover( int newvalue ) { + static int max = (int)pow( 2.0 , 30 ); + if ( newvalue >= max ) + rollover(); + } + + bool DBException::traceExceptions = false; + + void ExceptionInfo::append( BSONObjBuilder& b , const char * m , const char * c ) const { + if ( msg.empty() ) + b.append( m , "unknown assertion" ); + else + b.append( m , msg ); + + if ( code ) + b.append( c , code ); + } + + string getDbContext(); + + /* "warning" assert -- safe to continue, so we don't throw exception. */ + NOINLINE_DECL void wasserted(const char *msg, const char *file, unsigned line) { + static bool rateLimited; + static time_t lastWhen; + static unsigned lastLine; + if( lastLine == line && time(0)-lastWhen < 5 ) { + if( rateLimited++ == 0 ) { + log() << "rate limiting wassert" << endl; + } + return; + } + lastWhen = time(0); + lastLine = line; + + problem() << "warning assertion failure " << msg << ' ' << file << ' ' << dec << line << endl; + sayDbContext(); + raiseError(0,msg && *msg ? msg : "wassertion failure"); + assertionCount.condrollover( ++assertionCount.warning ); +#if defined(_DEBUG) || defined(_DURABLEDEFAULTON) || defined(_DURABLEDEFAULTOFF) + // this is so we notice in buildbot + log() << "\n\n***aborting after wassert() failure in a debug/test build\n\n" << endl; + abort(); +#endif + } + + NOINLINE_DECL void asserted(const char *msg, const char *file, unsigned line) { + assertionCount.condrollover( ++assertionCount.regular ); + problem() << "Assertion failure " << msg << ' ' << file << ' ' << dec << line << endl; + sayDbContext(); + raiseError(0,msg && *msg ? msg : "assertion failure"); + stringstream temp; + temp << "assertion " << file << ":" << line; + AssertionException e(temp.str(),0); + breakpoint(); +#if defined(_DEBUG) || defined(_DURABLEDEFAULTON) || defined(_DURABLEDEFAULTOFF) + // this is so we notice in buildbot + log() << "\n\n***aborting after assert() failure as this is a debug/test build\n\n" << endl; + abort(); +#endif + throw e; + } + + NOINLINE_DECL void verifyFailed( int msgid ) { + assertionCount.condrollover( ++assertionCount.regular ); + problem() << "Assertion failure " << msgid << endl; + sayDbContext(); + raiseError(0,"assertion failure"); + stringstream temp; + temp << msgid; + AssertionException e(temp.str(),0); + breakpoint(); +#if defined(_DEBUG) || defined(_DURABLEDEFAULTON) || defined(_DURABLEDEFAULTOFF) + // this is so we notice in buildbot + log() << "\n\n***aborting after verify() failure in a debug/test build\n\n" << endl; + abort(); +#endif + throw e; + } + + void uassert_nothrow(const char *msg) { + raiseError(0,msg); + } + + void uasserted(int msgid , const string &msg) { + uasserted(msgid, msg.c_str()); + } + + NOINLINE_DECL void uasserted(int msgid, const char *msg) { + assertionCount.condrollover( ++assertionCount.user ); + LOG(1) << "User Assertion: " << msgid << ":" << msg << endl; + raiseError(msgid,msg); + throw UserException(msgid, msg); + } + + void msgasserted(int msgid, const string &msg) { + msgasserted(msgid, msg.c_str()); + } + + NOINLINE_DECL void msgasserted(int msgid, const char *msg) { + assertionCount.condrollover( ++assertionCount.warning ); + tlog() << "Assertion: " << msgid << ":" << msg << endl; + raiseError(msgid,msg && *msg ? msg : "massert failure"); + breakpoint(); + printStackTrace(); + throw MsgAssertionException(msgid, msg); + } + + NOINLINE_DECL void msgassertedNoTrace(int msgid, const char *msg) { + assertionCount.condrollover( ++assertionCount.warning ); + log() << "Assertion: " << msgid << ":" << msg << endl; + raiseError(msgid,msg && *msg ? msg : "massert failure"); + throw MsgAssertionException(msgid, msg); + } + + NOINLINE_DECL void streamNotGood( int code , string msg , std::ios& myios ) { + stringstream ss; + // errno might not work on all systems for streams + // if it doesn't for a system should deal with here + ss << msg << " stream invalid: " << errnoWithDescription(); + throw UserException( code , ss.str() ); + } + + string errnoWithPrefix( const char * prefix ) { + stringstream ss; + if ( prefix ) + ss << prefix << ": "; + ss << errnoWithDescription(); + return ss.str(); + } + + string demangleName( const type_info& typeinfo ) { +#ifdef _WIN32 + return typeinfo.name(); +#else + int status; + + char * niceName = abi::__cxa_demangle(typeinfo.name(), 0, 0, &status); + if ( ! niceName ) + return typeinfo.name(); + + string s = niceName; + free(niceName); + return s; +#endif + } + + NOINLINE_DECL ErrorMsg::ErrorMsg(const char *msg, char ch) { + int l = strlen(msg); + assert( l < 128); + memcpy(buf, msg, l); + char *p = buf + l; + p[0] = ch; + p[1] = 0; + } + + NOINLINE_DECL ErrorMsg::ErrorMsg(const char *msg, unsigned val) { + int l = strlen(msg); + assert( l < 128); + memcpy(buf, msg, l); + char *p = buf + l; + sprintf(p, "%u", val); + } + +} + diff --git a/src/mongo/util/assert_util.h b/src/mongo/util/assert_util.h new file mode 100644 index 00000000000..2e6b2a9732a --- /dev/null +++ b/src/mongo/util/assert_util.h @@ -0,0 +1,275 @@ +// assert_util.h + +/* Copyright 2009 10gen 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. + */ + + +#pragma once + +#include "../db/lasterror.h" + +// MONGO_NORETURN undefed at end of file +#ifdef __GNUC__ +# define MONGO_NORETURN __attribute__((__noreturn__)) +#else +# define MONGO_NORETURN +#endif + +namespace mongo { + + enum CommonErrorCodes { + DatabaseDifferCaseCode = 13297 , + SendStaleConfigCode = 13388 , + RecvStaleConfigCode = 9996 + }; + + class AssertionCount { + public: + AssertionCount(); + void rollover(); + void condrollover( int newValue ); + + int regular; + int warning; + int msg; + int user; + int rollovers; + }; + + extern AssertionCount assertionCount; + + struct ExceptionInfo { + ExceptionInfo() : msg(""),code(-1) {} + ExceptionInfo( const char * m , int c ) + : msg( m ) , code( c ) { + } + ExceptionInfo( const string& m , int c ) + : msg( m ) , code( c ) { + } + void append( BSONObjBuilder& b , const char * m = "$err" , const char * c = "code" ) const ; + string toString() const { stringstream ss; ss << "exception: " << code << " " << msg; return ss.str(); } + bool empty() const { return msg.empty(); } + + void reset(){ msg = ""; code=-1; } + + string msg; + int code; + }; + + /** helper class that builds error strings. lighter weight than a StringBuilder, albeit less flexible. + NOINLINE_DECL used in the constructor implementations as we are assuming this is a cold code path when used. + + example: + throw UserException(123, ErrorMsg("blah", num_val)); + */ + class ErrorMsg { + public: + ErrorMsg(const char *msg, char ch); + ErrorMsg(const char *msg, unsigned val); + operator string() const { return buf; } + private: + char buf[256]; + }; + + class DBException; + string causedBy( const DBException& e ); + string causedBy( const string& e ); + + class DBException : public std::exception { + public: + DBException( const ExceptionInfo& ei ) : _ei(ei) { traceIfNeeded(*this); } + DBException( const char * msg , int code ) : _ei(msg,code) { traceIfNeeded(*this); } + DBException( const string& msg , int code ) : _ei(msg,code) { traceIfNeeded(*this); } + virtual ~DBException() throw() { } + + virtual const char* what() const throw() { return _ei.msg.c_str(); } + virtual int getCode() const { return _ei.code; } + + virtual void appendPrefix( stringstream& ss ) const { } + virtual void addContext( const string& str ) { + _ei.msg = str + causedBy( _ei.msg ); + } + + virtual string toString() const { + stringstream ss; ss << getCode() << " " << what(); return ss.str(); + return ss.str(); + } + + const ExceptionInfo& getInfo() const { return _ei; } + + static void traceIfNeeded( const DBException& e ) { + if( traceExceptions && ! inShutdown() ){ + warning() << "DBException thrown" << causedBy( e ) << endl; + printStackTrace(); + } + } + + static bool traceExceptions; + + protected: + ExceptionInfo _ei; + }; + + class AssertionException : public DBException { + public: + + AssertionException( const ExceptionInfo& ei ) : DBException(ei) {} + AssertionException( const char * msg , int code ) : DBException(msg,code) {} + AssertionException( const string& msg , int code ) : DBException(msg,code) {} + + virtual ~AssertionException() throw() { } + + virtual bool severe() { return true; } + virtual bool isUserAssertion() { return false; } + + /* true if an interrupted exception - see KillCurrentOp */ + bool interrupted() { + return _ei.code == 11600 || _ei.code == 11601; + } + }; + + /* UserExceptions are valid errors that a user can cause, like out of disk space or duplicate key */ + class UserException : public AssertionException { + public: + UserException(int c , const string& m) : AssertionException( m , c ) {} + + virtual bool severe() { return false; } + virtual bool isUserAssertion() { return true; } + virtual void appendPrefix( stringstream& ss ) const { ss << "userassert:"; } + }; + + class MsgAssertionException : public AssertionException { + public: + MsgAssertionException( const ExceptionInfo& ei ) : AssertionException( ei ) {} + MsgAssertionException(int c, const string& m) : AssertionException( m , c ) {} + virtual bool severe() { return false; } + virtual void appendPrefix( stringstream& ss ) const { ss << "massert:"; } + }; + + void asserted(const char *msg, const char *file, unsigned line) MONGO_NORETURN; + void wasserted(const char *msg, const char *file, unsigned line); + void verifyFailed( int msgid ); + + /** a "user assertion". throws UserAssertion. logs. typically used for errors that a user + could cause, such as duplicate key, disk full, etc. + */ + void uasserted(int msgid, const char *msg) MONGO_NORETURN; + void uasserted(int msgid , const string &msg); + + /** reported via lasterror, but don't throw exception */ + void uassert_nothrow(const char *msg); + + /** msgassert and massert are for errors that are internal but have a well defined error text string. + a stack trace is logged. + */ + void msgassertedNoTrace(int msgid, const char *msg) MONGO_NORETURN; + inline void msgassertedNoTrace(int msgid, const string& msg) { msgassertedNoTrace( msgid , msg.c_str() ); } + void msgasserted(int msgid, const char *msg) MONGO_NORETURN; + void msgasserted(int msgid, const string &msg); + + /* convert various types of exceptions to strings */ + inline string causedBy( const char* e ){ return (string)" :: caused by :: " + e; } + inline string causedBy( const DBException& e ){ return causedBy( e.toString().c_str() ); } + inline string causedBy( const std::exception& e ){ return causedBy( e.what() ); } + inline string causedBy( const string& e ){ return causedBy( e.c_str() ); } + + /** in the mongodb source, use verify() instead of assert(). verify is always evaluated even in release builds. */ + inline void verify( int msgid , bool testOK ) { if ( ! testOK ) verifyFailed( msgid ); } + +#ifdef assert +#undef assert +#endif + +#define MONGO_assert(_Expression) (void)( MONGO_likely(!!(_Expression)) || (mongo::asserted(#_Expression, __FILE__, __LINE__), 0) ) +#define assert MONGO_assert + + /* "user assert". if asserts, user did something wrong, not our code */ +#define MONGO_uassert(msgid, msg, expr) (void)( MONGO_likely(!!(expr)) || (mongo::uasserted(msgid, msg), 0) ) +#define uassert MONGO_uassert + + /* warning only - keeps going */ +#define MONGO_wassert(_Expression) (void)( MONGO_likely(!!(_Expression)) || (mongo::wasserted(#_Expression, __FILE__, __LINE__), 0) ) +#define wassert MONGO_wassert + + /* display a message, no context, and throw assertionexception + + easy way to throw an exception and log something without our stack trace + display happening. + */ +#define MONGO_massert(msgid, msg, expr) (void)( MONGO_likely(!!(expr)) || (mongo::msgasserted(msgid, msg), 0) ) +#define massert MONGO_massert + + /* dassert is 'debug assert' -- might want to turn off for production as these + could be slow. + */ +#if defined(_DEBUG) +# define MONGO_dassert assert +#else +# define MONGO_dassert(x) +#endif +#define dassert MONGO_dassert + + // some special ids that we want to duplicate + + // > 10000 asserts + // < 10000 UserException + + enum { ASSERT_ID_DUPKEY = 11000 }; + + /* throws a uassertion with an appropriate msg */ + void streamNotGood( int code , string msg , std::ios& myios ) MONGO_NORETURN; + + inline void assertStreamGood(unsigned msgid, string msg, std::ios& myios) { + if( !myios.good() ) streamNotGood(msgid, msg, myios); + } + + string demangleName( const type_info& typeinfo ); + +} // namespace mongo + +#define BOOST_CHECK_EXCEPTION MONGO_BOOST_CHECK_EXCEPTION +#define MONGO_BOOST_CHECK_EXCEPTION( expression ) \ + try { \ + expression; \ + } catch ( const std::exception &e ) { \ + stringstream ss; \ + ss << "caught boost exception: " << e.what() << ' ' << __FILE__ << ' ' << __LINE__; \ + msgasserted( 13294 , ss.str() ); \ + } catch ( ... ) { \ + massert( 10437 , "unknown boost failed" , false ); \ + } + +#define MONGO_BOOST_CHECK_EXCEPTION_WITH_MSG( expression, msg ) \ + try { \ + expression; \ + } catch ( const std::exception &e ) { \ + stringstream ss; \ + ss << msg << " caught boost exception: " << e.what(); \ + msgasserted( 14043 , ss.str() ); \ + } catch ( ... ) { \ + msgasserted( 14044 , string("unknown boost failed ") + msg ); \ + } + +#define DESTRUCTOR_GUARD MONGO_DESTRUCTOR_GUARD +#define MONGO_DESTRUCTOR_GUARD( expression ) \ + try { \ + expression; \ + } catch ( const std::exception &e ) { \ + problem() << "caught exception (" << e.what() << ") in destructor (" << __FUNCTION__ << ")" << endl; \ + } catch ( ... ) { \ + problem() << "caught unknown exception in destructor (" << __FUNCTION__ << ")" << endl; \ + } + +#undef MONGO_NORETURN diff --git a/src/mongo/util/background.cpp b/src/mongo/util/background.cpp new file mode 100644 index 00000000000..ef3ee9426b9 --- /dev/null +++ b/src/mongo/util/background.cpp @@ -0,0 +1,190 @@ +// @file background.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" + +#include "concurrency/mutex.h" +#include "concurrency/spin_lock.h" + +#include "background.h" +#include "time_support.h" +#include "timer.h" + +#include "mongoutils/str.h" + +namespace mongo { + + // both the BackgroundJob and the internal thread point to JobStatus + struct BackgroundJob::JobStatus { + JobStatus( bool delFlag ) + : deleteSelf(delFlag), m("backgroundJob"), state(NotStarted) { } + + const bool deleteSelf; + + mongo::mutex m; // protects state below + boost::condition finished; // means _state == Done + State state; + }; + + BackgroundJob::BackgroundJob( bool selfDelete ) { + _status.reset( new JobStatus( selfDelete ) ); + } + + // Background object can be only be destroyed after jobBody() ran + void BackgroundJob::jobBody( boost::shared_ptr<JobStatus> status ) { + LOG(1) << "BackgroundJob starting: " << name() << endl; + { + scoped_lock l( status->m ); + massert( 13643 , mongoutils::str::stream() << "backgroundjob already started: " << name() , status->state == NotStarted ); + status->state = Running; + } + + const string threadName = name(); + if( ! threadName.empty() ) + setThreadName( threadName.c_str() ); + + try { + run(); + } + catch ( std::exception& e ) { + log( LL_ERROR ) << "backgroundjob " << name() << "error: " << e.what() << endl; + } + catch(...) { + log( LL_ERROR ) << "uncaught exception in BackgroundJob " << name() << endl; + } + + { + scoped_lock l( status->m ); + status->state = Done; + status->finished.notify_all(); + } + + if( status->deleteSelf ) + delete this; + } + + BackgroundJob& BackgroundJob::go() { + boost::thread t( boost::bind( &BackgroundJob::jobBody , this, _status ) ); + return *this; + } + + bool BackgroundJob::wait( unsigned msTimeOut ) { + assert( !_status->deleteSelf ); // you cannot call wait on a self-deleting job + scoped_lock l( _status->m ); + while ( _status->state != Done ) { + if ( msTimeOut ) { + // add msTimeOut millisecond to current time + boost::xtime xt; + boost::xtime_get( &xt, boost::TIME_UTC ); + + unsigned long long ns = msTimeOut * 1000000ULL; // milli to nano + if ( xt.nsec + ns < 1000000000 ) { + xt.nsec = (boost::xtime::xtime_nsec_t) (xt.nsec + ns); + } + else { + xt.sec += 1 + ns / 1000000000; + xt.nsec = ( ns + xt.nsec ) % 1000000000; + } + + if ( ! _status->finished.timed_wait( l.boost() , xt ) ) + return false; + + } + else { + _status->finished.wait( l.boost() ); + } + } + return true; + } + + BackgroundJob::State BackgroundJob::getState() const { + scoped_lock l( _status->m); + return _status->state; + } + + bool BackgroundJob::running() const { + scoped_lock l( _status->m); + return _status->state == Running; + } + + // ------------------------- + + PeriodicTask::PeriodicTask() { + if ( ! theRunner ) + theRunner = new Runner(); + theRunner->add( this ); + } + + PeriodicTask::~PeriodicTask() { + theRunner->remove( this ); + } + + void PeriodicTask::Runner::add( PeriodicTask* task ) { + scoped_spinlock lk( _lock ); + _tasks.push_back( task ); + } + + void PeriodicTask::Runner::remove( PeriodicTask* task ) { + scoped_spinlock lk( _lock ); + for ( size_t i=0; i<_tasks.size(); i++ ) { + if ( _tasks[i] == task ) { + _tasks[i] = 0; + break; + } + } + } + + void PeriodicTask::Runner::run() { + int sleeptime = 60; + DEV sleeptime = 5; // to catch race conditions + + while ( ! inShutdown() ) { + + sleepsecs( sleeptime ); + + scoped_spinlock lk( _lock ); + + size_t size = _tasks.size(); + + for ( size_t i=0; i<size; i++ ) { + PeriodicTask * t = _tasks[i]; + if ( ! t ) + continue; + + if ( inShutdown() ) + break; + + Timer timer; + try { + t->taskDoWork(); + } + catch ( std::exception& e ) { + error() << "task: " << t->taskName() << " failed: " << e.what() << endl; + } + catch ( ... ) { + error() << "task: " << t->taskName() << " failed with unknown error" << endl; + } + + int ms = timer.millis(); + LOG( ms <= 3 ? 1 : 0 ) << "task: " << t->taskName() << " took: " << ms << "ms" << endl; + } + } + } + + PeriodicTask::Runner* PeriodicTask::theRunner = 0; + +} // namespace mongo diff --git a/src/mongo/util/background.h b/src/mongo/util/background.h new file mode 100644 index 00000000000..496a1f44f88 --- /dev/null +++ b/src/mongo/util/background.h @@ -0,0 +1,155 @@ +// @file background.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "concurrency/spin_lock.h" + +namespace mongo { + + /** + * Background thread dispatching. + * subclass and define run() + * + * It is ok to call go(), that is, run the job, more than once -- if the + * previous invocation has finished. Thus one pattern of use is to embed + * a backgroundjob in your object and reuse it (or same thing with + * inheritance). Each go() call spawns a new thread. + * + * Thread safety: + * note when job destructs, the thread is not terminated if still running. + * generally if the thread could still be running, allocate the job dynamically + * and set deleteSelf to true. + * + * go() and wait() are not thread safe + * run() will be executed on the background thread + * BackgroundJob object must exist for as long the background thread is running + */ + + class BackgroundJob : boost::noncopyable { + protected: + /** + * sub-class must intantiate the BackgrounJob + * + * @param selfDelete if set to true, object will destruct itself after the run() finished + * @note selfDelete instantes cannot be wait()-ed upon + */ + explicit BackgroundJob(bool selfDelete = false); + + virtual string name() const = 0; + + /** + * define this to do your work. + * after this returns, state is set to done. + * after this returns, deleted if deleteSelf true. + * + * NOTE: + * if run() throws, the exception will be caught within 'this' object and will ultimately lead to the + * BackgroundJob's thread being finished, as if run() returned. + * + */ + virtual void run() = 0; + + public: + enum State { + NotStarted, + Running, + Done + }; + + virtual ~BackgroundJob() { } + + /** + * starts job. + * returns immediatelly after dispatching. + * + * @note the BackgroundJob object must live for as long the thread is still running, ie + * until getState() returns Done. + */ + BackgroundJob& go(); + + /** + * wait for completion. + * + * @param msTimeOut maximum amount of time to wait in millisecons + * @return true if did not time out. false otherwise. + * + * @note you can call wait() more than once if the first call times out. + * but you cannot call wait on a self-deleting job. + */ + bool wait( unsigned msTimeOut = 0 ); + + // accessors + State getState() const; + bool running() const; + + private: + struct JobStatus; + boost::shared_ptr<JobStatus> _status; // shared between 'this' and body() thread + + void jobBody( boost::shared_ptr<JobStatus> status ); + + }; + + /** + * these run "roughly" every minute + * instantiate statically + * class MyTask : public PeriodicTask { + * public: + * virtual string name() const { return "MyTask; " } + * virtual void doWork() { log() << "hi" << endl; } + * } myTask; + */ + class PeriodicTask { + public: + PeriodicTask(); + virtual ~PeriodicTask(); + + virtual void taskDoWork() = 0; + virtual string taskName() const = 0; + + class Runner : public BackgroundJob { + public: + virtual ~Runner(){} + + virtual string name() const { return "PeriodicTask::Runner"; } + + virtual void run(); + + void add( PeriodicTask* task ); + void remove( PeriodicTask* task ); + + private: + + SpinLock _lock; + + // these are NOT owned by Runner + // Runner will not delete these + // this never gets smaller + // only fields replaced with nulls + vector<PeriodicTask*> _tasks; + + }; + + static Runner* theRunner; + + }; + + + + +} // namespace mongo diff --git a/src/mongo/util/base64.cpp b/src/mongo/util/base64.cpp new file mode 100644 index 00000000000..aff06e26126 --- /dev/null +++ b/src/mongo/util/base64.cpp @@ -0,0 +1,109 @@ +// util/base64.cpp + + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "base64.h" + +namespace mongo { + namespace base64 { + + Alphabet alphabet; + + void encode( stringstream& ss , const char * data , int size ) { + for ( int i=0; i<size; i+=3 ) { + int left = size - i; + const unsigned char * start = (const unsigned char*)data + i; + + // byte 0 + ss << alphabet.e(start[0]>>2); + + // byte 1 + unsigned char temp = ( start[0] << 4 ); + if ( left == 1 ) { + ss << alphabet.e(temp); + break; + } + temp |= ( ( start[1] >> 4 ) & 0xF ); + ss << alphabet.e(temp); + + // byte 2 + temp = ( start[1] & 0xF ) << 2; + if ( left == 2 ) { + ss << alphabet.e(temp); + break; + } + temp |= ( ( start[2] >> 6 ) & 0x3 ); + ss << alphabet.e(temp); + + // byte 3 + ss << alphabet.e(start[2] & 0x3f); + } + + int mod = size % 3; + if ( mod == 1 ) { + ss << "=="; + } + else if ( mod == 2 ) { + ss << "="; + } + } + + + string encode( const char * data , int size ) { + stringstream ss; + encode( ss , data ,size ); + return ss.str(); + } + + string encode( const string& s ) { + return encode( s.c_str() , s.size() ); + } + + + void decode( stringstream& ss , const string& s ) { + uassert( 10270 , "invalid base64" , s.size() % 4 == 0 ); + const unsigned char * data = (const unsigned char*)s.c_str(); + int size = s.size(); + + unsigned char buf[3]; + for ( int i=0; i<size; i+=4) { + const unsigned char * start = data + i; + buf[0] = ( ( alphabet.decode[start[0]] << 2 ) & 0xFC ) | ( ( alphabet.decode[start[1]] >> 4 ) & 0x3 ); + buf[1] = ( ( alphabet.decode[start[1]] << 4 ) & 0xF0 ) | ( ( alphabet.decode[start[2]] >> 2 ) & 0xF ); + buf[2] = ( ( alphabet.decode[start[2]] << 6 ) & 0xC0 ) | ( ( alphabet.decode[start[3]] & 0x3F ) ); + + int len = 3; + if ( start[3] == '=' ) { + len = 2; + if ( start[2] == '=' ) { + len = 1; + } + } + ss.write( (const char*)buf , len ); + } + } + + string decode( const string& s ) { + stringstream ss; + decode( ss , s ); + return ss.str(); + } + + } +} + diff --git a/src/mongo/util/base64.h b/src/mongo/util/base64.h new file mode 100644 index 00000000000..505b5d78cca --- /dev/null +++ b/src/mongo/util/base64.h @@ -0,0 +1,68 @@ +// util/base64.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + namespace base64 { + + class Alphabet { + public: + Alphabet() + : encode((unsigned char*) + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/") + , decode(new unsigned char[257]) { + memset( decode.get() , 0 , 256 ); + for ( int i=0; i<64; i++ ) { + decode[ encode[i] ] = i; + } + + test(); + } + void test() { + assert( strlen( (char*)encode ) == 64 ); + for ( int i=0; i<26; i++ ) + assert( encode[i] == toupper( encode[i+26] ) ); + } + + char e( int x ) { + return encode[x&0x3f]; + } + + private: + const unsigned char * encode; + public: + boost::scoped_array<unsigned char> decode; + }; + + extern Alphabet alphabet; + + + void encode( stringstream& ss , const char * data , int size ); + string encode( const char * data , int size ); + string encode( const string& s ); + + void decode( stringstream& ss , const string& s ); + string decode( const string& s ); + + + void testAlphabet(); + } +} diff --git a/src/mongo/util/bson_util.h b/src/mongo/util/bson_util.h new file mode 100644 index 00000000000..973e31f1af1 --- /dev/null +++ b/src/mongo/util/bson_util.h @@ -0,0 +1,42 @@ +// bson_util.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../pch.h" + +namespace mongo { + +template <typename T> +void bsonArrToNumVector(BSONElement el, vector<T>& results){ + + if(el.type() == Array){ + + vector<BSONElement> elements = el.Array(); + + for(vector<BSONElement>::iterator i = elements.begin(); i != elements.end(); ++i){ + results.push_back( (T) (*i).Number() ); + } + } + else if(el.isNumber()){ + results.push_back( (T) el.Number() ); + } + +} + + +} diff --git a/src/mongo/util/bufreader.h b/src/mongo/util/bufreader.h new file mode 100644 index 00000000000..53f0ba744e2 --- /dev/null +++ b/src/mongo/util/bufreader.h @@ -0,0 +1,100 @@ +// @file bufreader.h parse a memory region into usable pieces + +/** +* Copyright (C) 2009 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + /** helper to read and parse a block of memory + methods throw the eof exception if the operation would pass the end of the + buffer with which we are working. + */ + class BufReader : boost::noncopyable { + public: + class eof : public std::exception { + public: + eof() { } + virtual const char * what() { return "BufReader eof"; } + }; + + BufReader(const void *p, unsigned len) : _start(p), _pos(p), _end(((char *)_pos)+len) { } + + bool atEof() const { return _pos == _end; } + + /** read in the object specified, and advance buffer pointer */ + template <typename T> + void read(T &t) { + T* cur = (T*) _pos; + T *next = cur + 1; + if( _end < next ) throw eof(); + t = *cur; + _pos = next; + } + + /** verify we can look at t, but do not advance */ + template <typename T> + void peek(T &t) { + T* cur = (T*) _pos; + T *next = cur + 1; + if( _end < next ) throw eof(); + t = *cur; + } + + /** return current offset into buffer */ + unsigned offset() const { return (char*)_pos - (char*)_start; } + + /** return remaining bytes */ + unsigned remaining() const { return (char*)_end -(char*)_pos; } + + /** back up by nbytes */ + void rewind(unsigned nbytes) { + _pos = ((char *) _pos) - nbytes; + assert( _pos >= _start ); + } + + /** return current position pointer, and advance by len */ + const void* skip(unsigned len) { + const char *nxt = ((char *) _pos) + len; + if( _end < nxt ) throw eof(); + const void *p = _pos; + _pos = nxt; + return p; + } + + void readStr(string& s) { + StringBuilder b; + while( 1 ) { + char ch; + read(ch); + if( ch == 0 ) + break; + b << ch; + } + s = b.str(); + } + + const void* pos() { return _pos; } + const void* start() { return _start; } + + private: + const void *_start; + const void *_pos; + const void *_end; + }; + +} diff --git a/src/mongo/util/checksum.h b/src/mongo/util/checksum.h new file mode 100644 index 00000000000..009ab56fbeb --- /dev/null +++ b/src/mongo/util/checksum.h @@ -0,0 +1,37 @@ +#pragma once
+#include "../pch.h"
+namespace mongo {
+ /** a simple, rather dumb, but very fast checksum. see perftests.cpp for unit tests. */
+ struct Checksum {
+ union {
+ unsigned char bytes[16];
+ unsigned long long words[2];
+ };
+
+ // if you change this you must bump dur::CurrentVersion
+ void gen(const void *buf, unsigned len) {
+ wassert( ((size_t)buf) % 8 == 0 ); // performance warning
+ unsigned n = len / 8 / 2;
+ const unsigned long long *p = (const unsigned long long *) buf;
+ unsigned long long a = 0;
+ for( unsigned i = 0; i < n; i++ ) {
+ a += (*p ^ i);
+ p++;
+ }
+ unsigned long long b = 0;
+ for( unsigned i = 0; i < n; i++ ) {
+ b += (*p ^ i);
+ p++;
+ }
+ unsigned long long c = 0;
+ for( unsigned i = n * 2 * 8; i < len; i++ ) { // 0-7 bytes left
+ c = (c << 8) | ((const char *)buf)[i];
+ }
+ words[0] = a ^ len;
+ words[1] = b ^ c;
+ }
+
+ bool operator==(const Checksum& rhs) const { return words[0]==rhs.words[0] && words[1]==rhs.words[1]; }
+ bool operator!=(const Checksum& rhs) const { return words[0]!=rhs.words[0] || words[1]!=rhs.words[1]; }
+ };
+}
diff --git a/src/mongo/util/compress.cpp b/src/mongo/util/compress.cpp new file mode 100644 index 00000000000..bcde488b88b --- /dev/null +++ b/src/mongo/util/compress.cpp @@ -0,0 +1,31 @@ +// @file compress.cpp + +#include "../third_party/snappy/snappy.h" +#include "compress.h" +#include <string> +#include <string.h> +#include <assert.h> + +namespace mongo { + + void rawCompress(const char* input, + size_t input_length, + char* compressed, + size_t* compressed_length) + { + snappy::RawCompress(input, input_length, compressed, compressed_length); + } + + size_t maxCompressedLength(size_t source_len) { + return snappy::MaxCompressedLength(source_len); + } + + size_t compress(const char* input, size_t input_length, std::string* output) { + return snappy::Compress(input, input_length, output); + } + + bool uncompress(const char* compressed, size_t compressed_length, std::string* uncompressed) { + return snappy::Uncompress(compressed, compressed_length, uncompressed); + } + +} diff --git a/src/mongo/util/compress.h b/src/mongo/util/compress.h new file mode 100644 index 00000000000..5bc5a3392bb --- /dev/null +++ b/src/mongo/util/compress.h @@ -0,0 +1,21 @@ +// @file compress.h + +#pragma once + +#include <string> + +namespace mongo { + + size_t compress(const char* input, size_t input_length, std::string* output); + + bool uncompress(const char* compressed, size_t compressed_length, std::string* uncompressed); + + size_t maxCompressedLength(size_t source_len); + void rawCompress(const char* input, + size_t input_length, + char* compressed, + size_t* compressed_length); + +} + + diff --git a/src/mongo/util/concurrency/README b/src/mongo/util/concurrency/README new file mode 100644 index 00000000000..1a19264f4b6 --- /dev/null +++ b/src/mongo/util/concurrency/README @@ -0,0 +1,39 @@ +util/concurrency/ files + +msg.h - message passing between threads + +mutex.h - small enhancements that wrap boost::mutex + also SimpleMutex + +mvar.h + This is based on haskell's MVar synchronization primitive: + http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent-MVar.html + It is a thread-safe queue that can hold at most one object. + You can also think of it as a box that can be either full or empty. + +race.h + RACECHECK + +rwlock.h - read/write locks (RWLock) + RWLock + RWLockRecursive + RWLockRecursiveNongreedy + +spin_lock.h + +synchronization.h + Notification, NotifyAll + +threadlocal.h + +thread_pool.h + +value.h + Guarded + DiagStr + mapsf + +goofy things that need reworking: + list.h + task.h + diff --git a/src/mongo/util/concurrency/list.h b/src/mongo/util/concurrency/list.h new file mode 100644 index 00000000000..61bdd55f46f --- /dev/null +++ b/src/mongo/util/concurrency/list.h @@ -0,0 +1,99 @@ +// list.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + /* DONT USE THIS. it was a dumb idea. + + this class uses a mutex for writes, but not for reads. + we can get fancier later... + + struct Member : public List1<Member>::Base { + const char *host; + int port; + }; + List1<Member> _members; + _members.head()->next(); + + */ + template<typename T> + class List1 : boost::noncopyable { + public: + /* next() and head() return 0 at end of list */ + + List1() : _head(0), _m("List1"), _orphans(0) { } + + class Base { + friend class List1; + T *_next; + public: + Base() : _next(0){} + ~Base() { wassert(false); } // we never want this to happen + T* next() const { return _next; } + }; + + /** note this is safe: + + T* p = mylist.head(); + if( p ) + use(p); + + and this is not: + + if( mylist.head() ) + use( mylist.head() ); // could become 0 + */ + T* head() const { return (T*) _head; } + + void push(T* t) { + assert( t->_next == 0 ); + scoped_lock lk(_m); + t->_next = (T*) _head; + _head = t; + } + + // intentionally leaks. + void orphanAll() { + scoped_lock lk(_m); + _head = 0; + } + + /* t is not deleted, but is removed from the list. (orphaned) */ + void orphan(T* t) { + scoped_lock lk(_m); + T *&prev = (T*&) _head; + T *n = prev; + while( n != t ) { + uassert( 14050 , "List1: item to orphan not in list", n ); + prev = n->_next; + n = prev; + } + prev = t->_next; + if( ++_orphans > 500 ) + log() << "warning List1 orphans=" << _orphans << '\n'; + } + + private: + volatile T *_head; + mongo::mutex _m; + int _orphans; + }; + +}; diff --git a/src/mongo/util/concurrency/msg.h b/src/mongo/util/concurrency/msg.h new file mode 100644 index 00000000000..0b9a7c5048c --- /dev/null +++ b/src/mongo/util/concurrency/msg.h @@ -0,0 +1,61 @@ +// @file msg.h - interthread message passing + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful,b +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <deque> +#include "task.h" + +namespace mongo { + + namespace task { + + typedef boost::function<void()> lam; + + /** typical usage is: task::fork( new Server("threadname") ); */ + class Server : public Task { + public: + /** send a message to the port */ + void send(lam); + + Server(string name) : m("server"), _name(name), rq(false) { } + virtual ~Server() { } + + /** send message but block until function completes */ + void call(const lam&); + + void requeue() { rq = true; } + + protected: + /* REMINDER : for use in mongod, you will want to have this call Client::initThread(). */ + virtual void starting() { } + + private: + virtual bool initClient() { return true; } + virtual string name() const { return _name; } + void doWork(); + deque<lam> d; + mongo::mutex m; + boost::condition c; + string _name; + bool rq; + }; + + } + +} diff --git a/src/mongo/util/concurrency/mutex.h b/src/mongo/util/concurrency/mutex.h new file mode 100644 index 00000000000..429f280b1cb --- /dev/null +++ b/src/mongo/util/concurrency/mutex.h @@ -0,0 +1,228 @@ +// @file mutex.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../heapcheck.h" +#include "threadlocal.h" +#if defined(_DEBUG) +#include "mutexdebugger.h" +#endif + +namespace mongo { + + void printStackTrace( ostream &o ); + + inline boost::xtime incxtimemillis( long long s ) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += (int)( s / 1000 ); + xt.nsec += (int)(( s % 1000 ) * 1000000); + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + return xt; + } + + // If you create a local static instance of this class, that instance will be destroyed + // before all global static objects are destroyed, so _destroyingStatics will be set + // to true before the global static variables are destroyed. + class StaticObserver : boost::noncopyable { + public: + static bool _destroyingStatics; + ~StaticObserver() { _destroyingStatics = true; } + }; + + /** On pthread systems, it is an error to destroy a mutex while held (boost mutex + * may use pthread). Static global mutexes may be held upon shutdown in our + * implementation, and this way we avoid destroying them. + * NOT recursive. + */ + class mutex : boost::noncopyable { + public: + const char * const _name; + mutex(const char *name) : _name(name) + { + _m = new boost::timed_mutex(); + IGNORE_OBJECT( _m ); // Turn-off heap checking on _m + } + ~mutex() { + if( !StaticObserver::_destroyingStatics ) { + UNIGNORE_OBJECT( _m ); + delete _m; + } + } + + class try_lock : boost::noncopyable { + public: + try_lock( mongo::mutex &m , int millis = 0 ) + : _l( m.boost() , incxtimemillis( millis ) ) , +#if BOOST_VERSION >= 103500 + ok( _l.owns_lock() ) +#else + ok( _l.locked() ) +#endif + { } + private: + boost::timed_mutex::scoped_timed_lock _l; + public: + const bool ok; + }; + + class scoped_lock : boost::noncopyable { + public: +#if defined(_DEBUG) + struct PostStaticCheck { + PostStaticCheck() { + if ( StaticObserver::_destroyingStatics ) { + cout << "_DEBUG warning trying to lock a mongo::mutex during static shutdown" << endl; + printStackTrace( cout ); + } + } + } _check; + mongo::mutex * const _mut; +#endif + scoped_lock( mongo::mutex &m ) : +#if defined(_DEBUG) + _mut(&m), +#endif + _l( m.boost() ) { +#if defined(_DEBUG) + mutexDebugger.entering(_mut->_name); +#endif + } + ~scoped_lock() { +#if defined(_DEBUG) + mutexDebugger.leaving(_mut->_name); +#endif + } + boost::timed_mutex::scoped_lock &boost() { return _l; } + private: + boost::timed_mutex::scoped_lock _l; + }; + private: + boost::timed_mutex &boost() { return *_m; } + boost::timed_mutex *_m; + }; + + typedef mutex::scoped_lock scoped_lock; + typedef boost::recursive_mutex::scoped_lock recursive_scoped_lock; + + /** The concept with SimpleMutex is that it is a basic lock/unlock with no + special functionality (such as try and try timeout). Thus it can be + implemented using OS-specific facilities in all environments (if desired). + On Windows, the implementation below is faster than boost mutex. + */ +#if defined(_WIN32) + class SimpleMutex : boost::noncopyable { + CRITICAL_SECTION _cs; + public: + SimpleMutex(const char *name) { InitializeCriticalSection(&_cs); } + ~SimpleMutex() { DeleteCriticalSection(&_cs); } + +#if defined(_DEBUG) + ThreadLocalValue<int> _nlocksByMe; + void lock() { + assert( _nlocksByMe.get() == 0 ); // indicates you rae trying to lock recursively + _nlocksByMe.set(1); + EnterCriticalSection(&_cs); + } + void dassertLocked() const { + assert( _nlocksByMe.get() == 1 ); + } + void unlock() { + dassertLocked(); + _nlocksByMe.set(0); + LeaveCriticalSection(&_cs); + } +#else + void dassertLocked() const { } + void lock() { + EnterCriticalSection(&_cs); + } + void unlock() { + LeaveCriticalSection(&_cs); + } +#endif + + class scoped_lock : boost::noncopyable { + SimpleMutex& _m; + public: + scoped_lock( SimpleMutex &m ) : _m(m) { _m.lock(); } + ~scoped_lock() { _m.unlock(); } +# if defined(_DEBUG) + const SimpleMutex& m() const { return _m; } +# endif + }; + }; +#else + class SimpleMutex : boost::noncopyable { + public: + void dassertLocked() const { } + SimpleMutex(const char* name) { assert( pthread_mutex_init(&_lock,0) == 0 ); } + ~SimpleMutex(){ + if ( ! StaticObserver::_destroyingStatics ) { + assert( pthread_mutex_destroy(&_lock) == 0 ); + } + } + + void lock() { assert( pthread_mutex_lock(&_lock) == 0 ); } + void unlock() { assert( pthread_mutex_unlock(&_lock) == 0 ); } + public: + class scoped_lock : boost::noncopyable { + SimpleMutex& _m; + public: + scoped_lock( SimpleMutex &m ) : _m(m) { _m.lock(); } + ~scoped_lock() { _m.unlock(); } + const SimpleMutex& m() const { return _m; } + }; + + private: + pthread_mutex_t _lock; + }; + +#endif + + /** This can be used instead of boost recursive mutex. The advantage is the _DEBUG checks + * and ability to assertLocked(). This has not yet been tested for speed vs. the boost one. + */ + class RecursiveMutex : boost::noncopyable { + public: + RecursiveMutex(const char* name) : m(name) { } + bool isLocked() const { return n.get() > 0; } + class scoped_lock : boost::noncopyable { + RecursiveMutex& rm; + int& nLocksByMe; + public: + scoped_lock( RecursiveMutex &m ) : rm(m), nLocksByMe(rm.n.getRef()) { + if( nLocksByMe++ == 0 ) + rm.m.lock(); + } + ~scoped_lock() { + assert( nLocksByMe > 0 ); + if( --nLocksByMe == 0 ) { + rm.m.unlock(); + } + } + }; + private: + SimpleMutex m; + ThreadLocalValue<int> n; + }; + +} diff --git a/src/mongo/util/concurrency/mutexdebugger.h b/src/mongo/util/concurrency/mutexdebugger.h new file mode 100644 index 00000000000..7dc57f29e98 --- /dev/null +++ b/src/mongo/util/concurrency/mutexdebugger.h @@ -0,0 +1,117 @@ +#pragma once + +namespace mongo { + + /** only used on _DEBUG builds. + MutexDebugger checks that we always acquire locks for multiple mutexes in a consistant (acyclic) order. + If we were inconsistent we could deadlock. + */ + class MutexDebugger { + typedef const char * mid; // mid = mutex ID + typedef map<mid,int> Preceeding; + map< mid, int > maxNest; + boost::thread_specific_ptr< Preceeding > us; + map< mid, set<mid> > followers; + boost::mutex &x; + unsigned magic; + void aBreakPoint() { } // for debugging + public: + // set these to create an assert that + // b must never be locked before a + // so + // a.lock(); b.lock(); is fine + // b.lock(); alone is fine too + // only checked on _DEBUG builds. + string a,b; + + /** outputs some diagnostic info on mutexes (on _DEBUG builds) */ + void programEnding(); + + MutexDebugger(); + + string currentlyLocked() const { + Preceeding *_preceeding = us.get(); + if( _preceeding == 0 ) + return ""; + Preceeding &preceeding = *_preceeding; + stringstream q; + for( Preceeding::const_iterator i = preceeding.begin(); i != preceeding.end(); i++ ) { + if( i->second > 0 ) + q << " " << i->first << ' ' << i->second << '\n'; + } + return q.str(); + } + + void entering(mid m) { + if( this == 0 || m == 0 ) return; + assert( magic == 0x12345678 ); + + Preceeding *_preceeding = us.get(); + if( _preceeding == 0 ) + us.reset( _preceeding = new Preceeding() ); + Preceeding &preceeding = *_preceeding; + + if( a == m ) { + aBreakPoint(); + if( preceeding[b.c_str()] ) { + cout << "****** MutexDebugger error! warning " << b << " was locked before " << a << endl; + assert(false); + } + } + + preceeding[m]++; + if( preceeding[m] > 1 ) { + // recursive re-locking. + if( preceeding[m] > maxNest[m] ) + maxNest[m] = preceeding[m]; + return; + } + + bool failed = false; + string err; + { + boost::mutex::scoped_lock lk(x); + followers[m]; + for( Preceeding::iterator i = preceeding.begin(); i != preceeding.end(); i++ ) { + if( m != i->first && i->second > 0 ) { + followers[i->first].insert(m); + if( followers[m].count(i->first) != 0 ) { + failed = true; + stringstream ss; + mid bad = i->first; + ss << "mutex problem" << + "\n when locking " << m << + "\n " << bad << " was already locked and should not be." + "\n set a and b above to debug.\n"; + stringstream q; + for( Preceeding::iterator i = preceeding.begin(); i != preceeding.end(); i++ ) { + if( i->first != m && i->first != bad && i->second > 0 ) + q << " " << i->first << '\n'; + } + string also = q.str(); + if( !also.empty() ) + ss << "also locked before " << m << " in this thread (no particular order):\n" << also; + err = ss.str(); + break; + } + } + } + } + if( failed ) { + cout << err << endl; + assert( 0 ); + } + } + void leaving(mid m) { + if( this == 0 || m == 0 ) return; // still in startup pre-main() + Preceeding& preceeding = *us.get(); + preceeding[m]--; + if( preceeding[m] < 0 ) { + cout << "ERROR: lock count for " << m << " is " << preceeding[m] << endl; + assert( preceeding[m] >= 0 ); + } + } + }; + extern MutexDebugger &mutexDebugger; + +} diff --git a/src/mongo/util/concurrency/mvar.h b/src/mongo/util/concurrency/mvar.h new file mode 100644 index 00000000000..bc1855a85cc --- /dev/null +++ b/src/mongo/util/concurrency/mvar.h @@ -0,0 +1,118 @@ +// mvar.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + + /* This is based on haskell's MVar synchronization primitive: + * http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent-MVar.html + * + * It is a thread-safe queue that can hold at most one object. + * You can also think of it as a box that can be either full or empty. + */ + + template <typename T> + class MVar { + public: + enum State {EMPTY=0, FULL}; + + // create an empty MVar + MVar() + : _state(EMPTY) + {} + + // creates a full MVar + MVar(const T& val) + : _state(FULL) + , _value(val) + {} + + // puts val into the MVar and returns true or returns false if full + // never blocks + bool tryPut(const T& val) { + // intentionally repeat test before and after lock + if (_state == FULL) return false; + Mutex::scoped_lock lock(_mutex); + if (_state == FULL) return false; + + _state = FULL; + _value = val; + + // unblock threads waiting to 'take' + _condition.notify_all(); + + return true; + } + + // puts val into the MVar + // will block if the MVar is already full + void put(const T& val) { + Mutex::scoped_lock lock(_mutex); + while (!tryPut(val)) { + // unlocks lock while waiting and relocks before returning + _condition.wait(lock); + } + } + + // takes val out of the MVar and returns true or returns false if empty + // never blocks + bool tryTake(T& out) { + // intentionally repeat test before and after lock + if (_state == EMPTY) return false; + Mutex::scoped_lock lock(_mutex); + if (_state == EMPTY) return false; + + _state = EMPTY; + out = _value; + + // unblock threads waiting to 'put' + _condition.notify_all(); + + return true; + } + + // takes val out of the MVar + // will block if the MVar is empty + T take() { + T ret = T(); + + Mutex::scoped_lock lock(_mutex); + while (!tryTake(ret)) { + // unlocks lock while waiting and relocks before returning + _condition.wait(lock); + } + + return ret; + } + + + // Note: this is fast because there is no locking, but state could + // change before you get a chance to act on it. + // Mainly useful for sanity checks / asserts. + State getState() { return _state; } + + + private: + State _state; + T _value; + typedef boost::recursive_mutex Mutex; + Mutex _mutex; + boost::condition _condition; + }; + +} diff --git a/src/mongo/util/concurrency/race.h b/src/mongo/util/concurrency/race.h new file mode 100644 index 00000000000..837ae23ac13 --- /dev/null +++ b/src/mongo/util/concurrency/race.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../goodies.h" // printStackTrace +#include "mutexdebugger.h" + +namespace mongo { + + namespace race { + +#ifdef _WIN32 + typedef unsigned threadId_t; +#else + typedef pthread_t threadId_t; +#endif + +#if defined(_DEBUG) + + class Block { + volatile int n; + unsigned ncalls; + const string file; + const unsigned line; + void fail() { + log() << "\n\n\nrace: synchronization (race condition) failure\ncurrent locks this thread (" << getThreadName() << "):\n" + << mutexDebugger.currentlyLocked() << endl; + printStackTrace(); + ::abort(); + } + void enter() { + if( ++n != 1 ) fail(); + ncalls++; + if( ncalls < 100 ) { + sleepmillis(0); + } + else { + RARELY { + sleepmillis(0); + if( ncalls < 128 * 20 ) { + OCCASIONALLY { + sleepmillis(3); + } + } + } + } + } + void leave() { + if( --n != 0 ) fail(); + } + public: + Block(string f, unsigned l) : n(0), ncalls(0), file(f), line(l) { } + ~Block() { + if( ncalls > 1000000 ) { + // just so we know if we are slowing things down + log() << "race::Block lots of calls " << file << ' ' << line << " n:" << ncalls << endl; + } + } + class Within { + Block& _s; + public: + Within(Block& s) : _s(s) { _s.enter(); } + ~Within() { _s.leave(); } + }; + }; + + /* in a rwlock situation this will fail, so not appropriate for things like that. */ +# define RACECHECK \ + static race::Block __cp(__FILE__, __LINE__); \ + race::Block::Within __ck(__cp); + +#else + /* !_DEBUG */ +# define RACECHECK + +#endif + + } +} diff --git a/src/mongo/util/concurrency/rwlock.h b/src/mongo/util/concurrency/rwlock.h new file mode 100644 index 00000000000..3dbfc35ed6e --- /dev/null +++ b/src/mongo/util/concurrency/rwlock.h @@ -0,0 +1,271 @@ +// @file rwlock.h generic reader-writer lock (cross platform support) + +/* + * Copyright (C) 2010 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "mutex.h" +#include "../time_support.h" +#include "rwlockimpl.h" + +#if defined(_DEBUG) +#include "mutexdebugger.h" +#endif + +namespace mongo { + + /** separated out as later the implementation of this may be different than RWLock, + depending on OS, as there is no upgrade etc. facility herein. + */ + class SimpleRWLock : public RWLockBase { + public: + void lock() { RWLockBase::lock(); } + void unlock() { RWLockBase::unlock(); } + void lock_shared() { RWLockBase::lock_shared(); } + void unlock_shared() { RWLockBase::unlock_shared(); } + class Shared : boost::noncopyable { + SimpleRWLock& _r; + public: + Shared(SimpleRWLock& rwlock) : _r(rwlock) {_r.lock_shared(); } + ~Shared() { _r.unlock_shared(); } + }; + class Exclusive : boost::noncopyable { + SimpleRWLock& _r; + public: + Exclusive(SimpleRWLock& rwlock) : _r(rwlock) {_r.lock(); } + ~Exclusive() { _r.unlock(); } + }; + }; + + class RWLock : public RWLockBase { + enum { NilState, UpgradableState, Exclusive } x; // only bother to set when doing upgradable related things + public: + const char * const _name; + RWLock(const char *name) : _name(name) { + x = NilState; + } + void lock() { + RWLockBase::lock(); +#if defined(_DEBUG) + mutexDebugger.entering(_name); +#endif + } + void unlock() { +#if defined(_DEBUG) + mutexDebugger.leaving(_name); +#endif + RWLockBase::unlock(); + } + + void lock_shared() { RWLockBase::lock_shared(); } + void unlock_shared() { RWLockBase::unlock_shared(); } + private: + void lockAsUpgradable() { RWLockBase::lockAsUpgradable(); } + void unlockFromUpgradable() { // upgradable -> unlocked + RWLockBase::unlockFromUpgradable(); + } + public: + void upgrade() { // upgradable -> exclusive lock + assert( x == UpgradableState ); + RWLockBase::upgrade(); + x = Exclusive; + } + + bool lock_shared_try( int millis ) { return RWLockBase::lock_shared_try(millis); } + + bool lock_try( int millis = 0 ) { + if( RWLockBase::lock_try(millis) ) { +#if defined(_DEBUG) + mutexDebugger.entering(_name); +#endif + return true; + } + return false; + } + + /** acquire upgradable state. You must be unlocked before creating. + unlocks on destruction, whether in upgradable state or upgraded to exclusive + in the interim. + */ + class Upgradable : boost::noncopyable { + RWLock& _r; + public: + Upgradable(RWLock& r) : _r(r) { + r.lockAsUpgradable(); + assert( _r.x == NilState ); + _r.x = RWLock::UpgradableState; + } + ~Upgradable() { + if( _r.x == RWLock::UpgradableState ) { + _r.x = NilState; + _r.unlockFromUpgradable(); + } + else { + //TEMP assert( _r.x == Exclusive ); // has been upgraded + _r.x = NilState; + _r.unlock(); + } + } + }; + }; + + /** throws on failure to acquire in the specified time period. */ + class rwlock_try_write : boost::noncopyable { + public: + struct exception { }; + rwlock_try_write(RWLock& l, int millis = 0) : _l(l) { + if( !l.lock_try(millis) ) + throw exception(); + } + ~rwlock_try_write() { _l.unlock(); } + private: + RWLock& _l; + }; + + class rwlock_shared : boost::noncopyable { + public: + rwlock_shared(RWLock& rwlock) : _r(rwlock) {_r.lock_shared(); } + ~rwlock_shared() { _r.unlock_shared(); } + private: + RWLock& _r; + }; + + /* scoped lock for RWLock */ + class rwlock : boost::noncopyable { + public: + /** + * @param write acquire write lock if true sharable if false + * @param lowPriority if > 0, will try to get the lock non-greedily for that many ms + */ + rwlock( const RWLock& lock , bool write, /* bool alreadyHaveLock = false , */int lowPriorityWaitMS = 0 ) + : _lock( (RWLock&)lock ) , _write( write ) { + { + if ( _write ) { + _lock.lock(); + } + else { + _lock.lock_shared(); + } + } + } + ~rwlock() { + if ( _write ) + _lock.unlock(); + else + _lock.unlock_shared(); + } + private: + RWLock& _lock; + const bool _write; + }; + + // ---------------------------------------------------------------------------------------- + + /** recursive on shared locks is ok for this implementation */ + class RWLockRecursive : protected RWLockBase { + protected: + ThreadLocalValue<int> _state; + void lock(); // not implemented - Lock() should be used; didn't overload this name to avoid mistakes + virtual void Lock() { RWLockBase::lock(); } + public: + virtual ~RWLockRecursive() { } + const char * const _name; + RWLockRecursive(const char *name) : _name(name) { } + + void assertExclusivelyLocked() { + assert( _state.get() < 0 ); + } + + class Exclusive : boost::noncopyable { + RWLockRecursive& _r; + public: + Exclusive(RWLockRecursive& r) : _r(r) { + int s = _r._state.get(); + dassert( s <= 0 ); + if( s == 0 ) + _r.Lock(); + _r._state.set(s-1); + } + ~Exclusive() { + int s = _r._state.get(); + DEV wassert( s < 0 ); // wassert: don't throw from destructors + ++s; + _r._state.set(s); + if ( s == 0 ) + _r.unlock(); + } + }; + + class Shared : boost::noncopyable { + RWLockRecursive& _r; + bool _alreadyLockedExclusiveByUs; + public: + Shared(RWLockRecursive& r) : _r(r) { + int s = _r._state.get(); + _alreadyLockedExclusiveByUs = s < 0; + if( !_alreadyLockedExclusiveByUs ) { + dassert( s >= 0 ); // -1 would mean exclusive + if( s == 0 ) + _r.lock_shared(); + _r._state.set(s+1); + } + } + ~Shared() { + if( _alreadyLockedExclusiveByUs ) { + DEV wassert( _r._state.get() < 0 ); + } + else { + int s = _r._state.get() - 1; + DEV wassert( s >= 0 ); + _r._state.set(s); + if( s == 0 ) + _r.unlock_shared(); + } + } + }; + }; + + class RWLockRecursiveNongreedy : public RWLockRecursive { + virtual void Lock() { + bool got = false; + for ( int i=0; i<lowPriorityWaitMS; i++ ) { + if ( lock_try(0) ) { + got = true; + break; + } + int sleep = 1; + if ( i > ( lowPriorityWaitMS / 20 ) ) + sleep = 10; + sleepmillis(sleep); + i += ( sleep - 1 ); + } + if ( ! got ) { + log() << "couldn't lazily get rwlock" << endl; + RWLockBase::lock(); + } + } + + public: + const int lowPriorityWaitMS; + RWLockRecursiveNongreedy(const char *nm, int lpwaitms) : RWLockRecursive(nm), lowPriorityWaitMS(lpwaitms) { } + const char * implType() const { return RWLockRecursive::implType(); } + + //just for testing: + bool __lock_try( int millis ) { return RWLockRecursive::lock_try(millis); } + }; + +} diff --git a/src/mongo/util/concurrency/rwlockimpl.h b/src/mongo/util/concurrency/rwlockimpl.h new file mode 100644 index 00000000000..4e07231447b --- /dev/null +++ b/src/mongo/util/concurrency/rwlockimpl.h @@ -0,0 +1,170 @@ +// @file rwlockimpl.h + +#pragma once + +#if defined(MONGO_USE_SRW_ON_WINDOWS) && defined(_WIN32) + +// windows slimreaderwriter version. newer windows versions only + +namespace mongo { + class RWLockBase : boost::noncopyable { + SRWLOCK _lock; + protected: + RWLockBase() { InitializeSRWLock(&_lock); } + ~RWLockBase() { + // no special action needed to destroy a SRWLOCK + } + void lock() { AcquireSRWLockExclusive(&_lock); } + void unlock() { ReleaseSRWLockExclusive(&_lock); } + void lock_shared() { AcquireSRWLockShared(&_lock); } + void unlock_shared() { ReleaseSRWLockShared(&_lock); } + bool lock_shared_try( int millis ) { + if( TryAcquireSRWLockShared(&_lock) ) + return true; + if( millis == 0 ) + return false; + unsigned long long end = curTimeMicros64() + millis*1000; + while( 1 ) { + Sleep(1); + if( TryAcquireSRWLockShared(&_lock) ) + return true; + if( curTimeMicros64() >= end ) + break; + } + return false; + } + bool lock_try( int millis = 0 ) { + if( TryAcquireSRWLockExclusive(&_lock) ) // quick check to optimistically avoid calling curTimeMicros64 + return true; + if( millis == 0 ) + return false; + unsigned long long end = curTimeMicros64() + millis*1000; + do { + Sleep(1); + if( TryAcquireSRWLockExclusive(&_lock) ) + return true; + } while( curTimeMicros64() < end ); + return false; + } + // no upgradable for this impl + void lockAsUpgradable() { lock(); } + void unlockFromUpgradable() { unlock(); } + void upgrade() { } + public: + const char * implType() const { return "WINSRW"; } + }; +} + +#elif( BOOST_VERSION < 103500 ) + +# if defined(_WIN32) +# error need boost >= 1.35 for windows +# endif + +// pthreads version + +# include <pthread.h> + +namespace mongo { + class RWLockBase : boost::noncopyable { + pthread_rwlock_t _lock; + static void check( int x ) { + if( x == 0 ) return; + log() << "pthread rwlock failed: " << x << endl; + assert( x == 0 ); + } + + ~RWLockBase() { + if ( ! StaticObserver::_destroyingStatics ) { + wassert( pthread_rwlock_destroy( &_lock ) == 0 ); // wassert as don't want to throw from a destructor + } + } + + protected: + RWLockBase() { + check( pthread_rwlock_init( &_lock , 0 ) ); + } + + void lock() { check( pthread_rwlock_wrlock( &_lock ) ); } + void unlock() { check( pthread_rwlock_unlock( &_lock ) ); } + void lock_shared() { check( pthread_rwlock_rdlock( &_lock ) ); } + void unlock_shared() { check( pthread_rwlock_unlock( &_lock ) ); } + bool lock_shared_try( int millis ) { return _try( millis , false ); } + bool lock_try( int millis = 0 ) { return _try( millis , true ); } + bool _try( int millis , bool write ) { + while ( true ) { + int x = write ? + pthread_rwlock_trywrlock( &_lock ) : + pthread_rwlock_tryrdlock( &_lock ); + if ( x <= 0 ) + return true; + if ( millis-- <= 0 ) + return false; + if ( x == EBUSY ) { + sleepmillis(1); + continue; + } + check(x); + } + return false; + } + // no upgradable for this impl + void lockAsUpgradable() { lock(); } + void unlockFromUpgradable() { unlock(); } + void upgrade() { } + public: + const char * implType() const { return "posix"; } + }; +} + +#else + +// Boost version + +# if defined(_WIN32) +# include "shared_mutex_win.hpp" +namespace mongo { typedef boost::modified_shared_mutex shared_mutex; } +# else +# include <boost/thread/shared_mutex.hpp> +namespace mongo { using boost::shared_mutex; } +# endif +# undef assert +# define assert MONGO_assert + +namespace mongo { + class RWLockBase : boost::noncopyable { + shared_mutex _m; + protected: + void lock() { + _m.lock(); + } + void unlock() { + _m.unlock(); + } + void lockAsUpgradable() { + _m.lock_upgrade(); + } + void unlockFromUpgradable() { // upgradable -> unlocked + _m.unlock_upgrade(); + } + void upgrade() { // upgradable -> exclusive lock + _m.unlock_upgrade_and_lock(); + } + void lock_shared() { + _m.lock_shared(); + } + void unlock_shared() { + _m.unlock_shared(); + } + bool lock_shared_try( int millis ) { + return _m.timed_lock_shared( boost::posix_time::milliseconds(millis) ); + } + bool lock_try( int millis = 0 ) { + return _m.timed_lock( boost::posix_time::milliseconds(millis) ); + } + public: + const char * implType() const { return "boost"; } + }; +} + +#endif diff --git a/src/mongo/util/concurrency/shared_mutex_win.hpp b/src/mongo/util/concurrency/shared_mutex_win.hpp new file mode 100644 index 00000000000..e850fc6bab4 --- /dev/null +++ b/src/mongo/util/concurrency/shared_mutex_win.hpp @@ -0,0 +1,594 @@ +#ifndef BOOST_THREAD_WIN32_SHARED_MUTEX_HPP_MODIFIED
+#define BOOST_THREAD_WIN32_SHARED_MUTEX_HPP_MODIFIED
+
+// (C) Copyright 2006-8 Anthony Williams
+//
+// Distributed under the Boost Software License, Version 1.0. (See
+// accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+/* MongoDB :
+ Slightly modified boost file to not die above 127 pending writes
+ Here is what changed (from boost 1.42.0 shared_mutex.hpp):
+ 1,2c1,2
+ < #ifndef BOOST_THREAD_WIN32_SHARED_MUTEX_HPP
+ < #define BOOST_THREAD_WIN32_SHARED_MUTEX_HPP
+ ---
+ > #ifndef BOOST_THREAD_WIN32_SHARED_MUTEX_HPP_MODIFIED
+ > #define BOOST_THREAD_WIN32_SHARED_MUTEX_HPP_MODIFIED
+ 22c27
+ < class shared_mutex:
+ ---
+ > class modified_shared_mutex:
+ 73c78
+ < shared_mutex():
+ ---
+ > modified_shared_mutex():
+ 84c89
+ < ~shared_mutex()
+ ---
+ > ~modified_shared_mutex()
+ 283a289,290
+ > if( new_state.exclusive_waiting == 127 ) // the maximum already!
+ > break;
+*/
+
+#include <boost/assert.hpp>
+#include <boost/detail/interlocked.hpp>
+#include <boost/thread/win32/thread_primitives.hpp>
+#include <boost/static_assert.hpp>
+#include <limits.h>
+#include <boost/utility.hpp>
+#include <boost/thread/thread_time.hpp>
+
+#include <boost/config/abi_prefix.hpp>
+
+namespace boost
+{
+ class modified_shared_mutex:
+ private boost::noncopyable
+ {
+ private:
+ struct state_data
+ {
+ unsigned shared_count:11,
+ shared_waiting:11,
+ exclusive:1,
+ upgrade:1,
+ exclusive_waiting:7,
+ exclusive_waiting_blocked:1;
+
+ friend bool operator==(state_data const& lhs,state_data const& rhs)
+ {
+ return *reinterpret_cast<unsigned const*>(&lhs)==*reinterpret_cast<unsigned const*>(&rhs);
+ }
+ };
+
+
+ template<typename T>
+ T interlocked_compare_exchange(T* target,T new_value,T comparand)
+ {
+ BOOST_STATIC_ASSERT(sizeof(T)==sizeof(long));
+ long const res=BOOST_INTERLOCKED_COMPARE_EXCHANGE(reinterpret_cast<long*>(target),
+ *reinterpret_cast<long*>(&new_value),
+ *reinterpret_cast<long*>(&comparand));
+ return *reinterpret_cast<T const*>(&res);
+ }
+
+ state_data state;
+ detail::win32::handle semaphores[2];
+ detail::win32::handle &unlock_sem;
+ detail::win32::handle &exclusive_sem;
+ detail::win32::handle upgrade_sem;
+
+ void release_waiters(state_data old_state)
+ {
+ if(old_state.exclusive_waiting)
+ {
+ BOOST_VERIFY(detail::win32::ReleaseSemaphore(exclusive_sem,1,0)!=0);
+ }
+
+ if(old_state.shared_waiting || old_state.exclusive_waiting)
+ {
+ BOOST_VERIFY(detail::win32::ReleaseSemaphore(unlock_sem,old_state.shared_waiting + (old_state.exclusive_waiting?1:0),0)!=0);
+ }
+ }
+
+
+ public:
+ modified_shared_mutex():
+ unlock_sem(semaphores[0]),
+ exclusive_sem(semaphores[1])
+ {
+ unlock_sem=detail::win32::create_anonymous_semaphore(0,LONG_MAX);
+ exclusive_sem=detail::win32::create_anonymous_semaphore(0,LONG_MAX);
+ upgrade_sem=detail::win32::create_anonymous_semaphore(0,LONG_MAX);
+ state_data state_={0};
+ state=state_;
+ }
+
+ ~modified_shared_mutex()
+ {
+ detail::win32::CloseHandle(upgrade_sem);
+ detail::win32::CloseHandle(unlock_sem);
+ detail::win32::CloseHandle(exclusive_sem);
+ }
+
+ bool try_lock_shared()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(!new_state.exclusive && !new_state.exclusive_waiting_blocked)
+ {
+ ++new_state.shared_count;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ return !(old_state.exclusive| old_state.exclusive_waiting_blocked);
+ }
+
+ void lock_shared()
+ {
+ BOOST_VERIFY(timed_lock_shared(::boost::detail::get_system_time_sentinel()));
+ }
+
+ template<typename TimeDuration>
+ bool timed_lock_shared(TimeDuration const & relative_time)
+ {
+ return timed_lock_shared(get_system_time()+relative_time);
+ }
+
+ bool timed_lock_shared(boost::system_time const& wait_until)
+ {
+ for(;;)
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.exclusive || new_state.exclusive_waiting_blocked)
+ {
+ ++new_state.shared_waiting;
+ }
+ else
+ {
+ ++new_state.shared_count;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+
+ if(!(old_state.exclusive| old_state.exclusive_waiting_blocked))
+ {
+ return true;
+ }
+
+ unsigned long const res=detail::win32::WaitForSingleObject(unlock_sem,::boost::detail::get_milliseconds_until(wait_until));
+ if(res==detail::win32::timeout)
+ {
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.exclusive || new_state.exclusive_waiting_blocked)
+ {
+ if(new_state.shared_waiting)
+ {
+ --new_state.shared_waiting;
+ }
+ }
+ else
+ {
+ ++new_state.shared_count;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+
+ if(!(old_state.exclusive| old_state.exclusive_waiting_blocked))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ BOOST_ASSERT(res==0);
+ }
+ }
+
+ void unlock_shared()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ bool const last_reader=!--new_state.shared_count;
+
+ if(last_reader)
+ {
+ if(new_state.upgrade)
+ {
+ new_state.upgrade=false;
+ new_state.exclusive=true;
+ }
+ else
+ {
+ if(new_state.exclusive_waiting)
+ {
+ --new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=false;
+ }
+ new_state.shared_waiting=0;
+ }
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ if(last_reader)
+ {
+ if(old_state.upgrade)
+ {
+ BOOST_VERIFY(detail::win32::ReleaseSemaphore(upgrade_sem,1,0)!=0);
+ }
+ else
+ {
+ release_waiters(old_state);
+ }
+ }
+ break;
+ }
+ old_state=current_state;
+ }
+ }
+
+ void lock()
+ {
+ BOOST_VERIFY(timed_lock(::boost::detail::get_system_time_sentinel()));
+ }
+
+ template<typename TimeDuration>
+ bool timed_lock(TimeDuration const & relative_time)
+ {
+ return timed_lock(get_system_time()+relative_time);
+ }
+
+ bool try_lock()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.shared_count || new_state.exclusive)
+ {
+ return false;
+ }
+ else
+ {
+ new_state.exclusive=true;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ return true;
+ }
+
+
+ bool timed_lock(boost::system_time const& wait_until)
+ {
+ for(;;)
+ {
+ state_data old_state=state;
+
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.shared_count || new_state.exclusive)
+ {
+ if( new_state.exclusive_waiting == 127 ) // the maximum already!
+ break;
+ ++new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=true;
+ }
+ else
+ {
+ new_state.exclusive=true;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+
+ if(!old_state.shared_count && !old_state.exclusive)
+ {
+ return true;
+ }
+ unsigned long const wait_res=detail::win32::WaitForMultipleObjects(2,semaphores,true,::boost::detail::get_milliseconds_until(wait_until));
+ if(wait_res==detail::win32::timeout)
+ {
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.shared_count || new_state.exclusive)
+ {
+ if(new_state.exclusive_waiting)
+ {
+ if(!--new_state.exclusive_waiting)
+ {
+ new_state.exclusive_waiting_blocked=false;
+ }
+ }
+ }
+ else
+ {
+ new_state.exclusive=true;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ if(!old_state.shared_count && !old_state.exclusive)
+ {
+ return true;
+ }
+ return false;
+ }
+ BOOST_ASSERT(wait_res<2);
+ }
+ }
+
+ void unlock()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ new_state.exclusive=false;
+ if(new_state.exclusive_waiting)
+ {
+ --new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=false;
+ }
+ new_state.shared_waiting=0;
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ release_waiters(old_state);
+ }
+
+ void lock_upgrade()
+ {
+ for(;;)
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.exclusive || new_state.exclusive_waiting_blocked || new_state.upgrade)
+ {
+ ++new_state.shared_waiting;
+ }
+ else
+ {
+ ++new_state.shared_count;
+ new_state.upgrade=true;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+
+ if(!(old_state.exclusive|| old_state.exclusive_waiting_blocked|| old_state.upgrade))
+ {
+ return;
+ }
+
+ BOOST_VERIFY(!detail::win32::WaitForSingleObject(unlock_sem,detail::win32::infinite));
+ }
+ }
+
+ bool try_lock_upgrade()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ if(new_state.exclusive || new_state.exclusive_waiting_blocked || new_state.upgrade)
+ {
+ return false;
+ }
+ else
+ {
+ ++new_state.shared_count;
+ new_state.upgrade=true;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ return true;
+ }
+
+ void unlock_upgrade()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ new_state.upgrade=false;
+ bool const last_reader=!--new_state.shared_count;
+
+ if(last_reader)
+ {
+ if(new_state.exclusive_waiting)
+ {
+ --new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=false;
+ }
+ new_state.shared_waiting=0;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ if(last_reader)
+ {
+ release_waiters(old_state);
+ }
+ break;
+ }
+ old_state=current_state;
+ }
+ }
+
+ void unlock_upgrade_and_lock()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ bool const last_reader=!--new_state.shared_count;
+
+ if(last_reader)
+ {
+ new_state.upgrade=false;
+ new_state.exclusive=true;
+ }
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ if(!last_reader)
+ {
+ BOOST_VERIFY(!detail::win32::WaitForSingleObject(upgrade_sem,detail::win32::infinite));
+ }
+ break;
+ }
+ old_state=current_state;
+ }
+ }
+
+ void unlock_and_lock_upgrade()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ new_state.exclusive=false;
+ new_state.upgrade=true;
+ ++new_state.shared_count;
+ if(new_state.exclusive_waiting)
+ {
+ --new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=false;
+ }
+ new_state.shared_waiting=0;
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ release_waiters(old_state);
+ }
+
+ void unlock_and_lock_shared()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ new_state.exclusive=false;
+ ++new_state.shared_count;
+ if(new_state.exclusive_waiting)
+ {
+ --new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=false;
+ }
+ new_state.shared_waiting=0;
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ release_waiters(old_state);
+ }
+
+ void unlock_upgrade_and_lock_shared()
+ {
+ state_data old_state=state;
+ for(;;)
+ {
+ state_data new_state=old_state;
+ new_state.upgrade=false;
+ if(new_state.exclusive_waiting)
+ {
+ --new_state.exclusive_waiting;
+ new_state.exclusive_waiting_blocked=false;
+ }
+ new_state.shared_waiting=0;
+
+ state_data const current_state=interlocked_compare_exchange(&state,new_state,old_state);
+ if(current_state==old_state)
+ {
+ break;
+ }
+ old_state=current_state;
+ }
+ release_waiters(old_state);
+ }
+
+ };
+}
+
+#include <boost/config/abi_suffix.hpp>
+
+#endif
diff --git a/src/mongo/util/concurrency/spin_lock.cpp b/src/mongo/util/concurrency/spin_lock.cpp new file mode 100644 index 00000000000..cbf517b2746 --- /dev/null +++ b/src/mongo/util/concurrency/spin_lock.cpp @@ -0,0 +1,107 @@ +// spin_lock.cpp + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" // todo eliminate this include +#include <time.h> +#include "spin_lock.h" + +namespace mongo { + + SpinLock::~SpinLock() { +#if defined(_WIN32) + DeleteCriticalSection(&_cs); +#elif defined(__USE_XOPEN2K) + pthread_spin_destroy(&_lock); +#endif + } + + SpinLock::SpinLock() +#if defined(_WIN32) + { InitializeCriticalSectionAndSpinCount(&_cs, 4000); } +#elif defined(__USE_XOPEN2K) + { pthread_spin_init( &_lock , 0 ); } +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + : _locked( false ) { } +#else + : _mutex( "SpinLock" ) { } +#endif + +#if defined(__USE_XOPEN2K) + NOINLINE_DECL void SpinLock::_lk() { + /** + * this is designed to perform close to the default spin lock + * the reason for the mild insanity is to prevent horrible performance + * when contention spikes + * it allows spinlocks to be used in many more places + * which is good because even with this change they are about 8x faster on linux + */ + + for ( int i=0; i<1000; i++ ) { + if ( pthread_spin_trylock( &_lock ) == 0 ) + return; + asm volatile ( "pause" ) ; // maybe trylock does this; just in case. + } + + for ( int i=0; i<1000; i++ ) { + if ( pthread_spin_trylock( &_lock ) == 0 ) + return; + pthread_yield(); + } + + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = 5000000; + + while ( pthread_spin_trylock( &_lock ) != 0 ) { + nanosleep(&t, NULL); + } + } +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + void SpinLock::lock() { + + // fast path + if (!_locked && !__sync_lock_test_and_set(&_locked, true)) { + return; + } + + // wait for lock + int wait = 1000; + while ((wait-- > 0) && (_locked)) { + asm volatile ( "pause" ) ; + } + + // if failed to grab lock, sleep + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = 5000000; + while (__sync_lock_test_and_set(&_locked, true)) { + nanosleep(&t, NULL); + } + } +#endif + + bool SpinLock::isfast() { +#if defined(_WIN32) || defined(__USE_XOPEN2K) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + return true; +#else + return false; +#endif + } + + +} // namespace mongo diff --git a/src/mongo/util/concurrency/spin_lock.h b/src/mongo/util/concurrency/spin_lock.h new file mode 100644 index 00000000000..d90de51afac --- /dev/null +++ b/src/mongo/util/concurrency/spin_lock.h @@ -0,0 +1,77 @@ +// spin_lock.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "mutex.h" + +namespace mongo { + + /** + * The spinlock currently requires late GCC support routines to be efficient. + * Other platforms default to a mutex implemenation. + */ + class SpinLock : boost::noncopyable { + public: + SpinLock(); + ~SpinLock(); + + static bool isfast(); // true if a real spinlock on this platform + + private: +#if defined(_WIN32) + CRITICAL_SECTION _cs; + public: + void lock() {EnterCriticalSection(&_cs); } + void unlock() { LeaveCriticalSection(&_cs); } +#elif defined(__USE_XOPEN2K) + pthread_spinlock_t _lock; + void _lk(); + public: + void unlock() { pthread_spin_unlock(&_lock); } + void lock() { + if ( pthread_spin_trylock( &_lock ) == 0 ) + return; + _lk(); + } +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + volatile bool _locked; + public: + void unlock() {__sync_lock_release(&_locked); } + void lock(); +#else + // default to a mutex if not implemented + SimpleMutex _mutex; + public: + void unlock() { _mutex.unlock(); } + void lock() { _mutex.lock(); } +#endif + }; + + class scoped_spinlock : boost::noncopyable { + public: + scoped_spinlock( SpinLock& l ) : _l(l) { + _l.lock(); + } + ~scoped_spinlock() { + _l.unlock();} + private: + SpinLock& _l; + }; + +} // namespace mongo diff --git a/src/mongo/util/concurrency/synchronization.cpp b/src/mongo/util/concurrency/synchronization.cpp new file mode 100644 index 00000000000..4186745dc16 --- /dev/null +++ b/src/mongo/util/concurrency/synchronization.cpp @@ -0,0 +1,81 @@ +// synchronization.cpp + +/* Copyright 2010 10gen 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. + */ + +#include "pch.h" +#include "synchronization.h" + +namespace mongo { + + Notification::Notification() : _mutex ( "Notification" ){ + lookFor = 1; + cur = 0; + } + + Notification::~Notification() { } + + void Notification::waitToBeNotified() { + scoped_lock lock( _mutex ); + while ( lookFor != cur ) + _condition.wait( lock.boost() ); + lookFor++; + } + + void Notification::notifyOne() { + scoped_lock lock( _mutex ); + assert( cur != lookFor ); + cur++; + _condition.notify_one(); + } + + /* --- NotifyAll --- */ + + NotifyAll::NotifyAll() : _mutex("NotifyAll") { + _lastDone = 0; + _lastReturned = 0; + _nWaiting = 0; + } + + NotifyAll::When NotifyAll::now() { + scoped_lock lock( _mutex ); + return ++_lastReturned; + } + + void NotifyAll::waitFor(When e) { + scoped_lock lock( _mutex ); + ++_nWaiting; + while( _lastDone < e ) { + _condition.wait( lock.boost() ); + } + } + + void NotifyAll::awaitBeyondNow() { + scoped_lock lock( _mutex ); + ++_nWaiting; + When e = ++_lastReturned; + while( _lastDone <= e ) { + _condition.wait( lock.boost() ); + } + } + + void NotifyAll::notifyAll(When e) { + scoped_lock lock( _mutex ); + _lastDone = e; + _nWaiting = 0; + _condition.notify_all(); + } + +} // namespace mongo diff --git a/src/mongo/util/concurrency/synchronization.h b/src/mongo/util/concurrency/synchronization.h new file mode 100644 index 00000000000..f9a40cc3ab9 --- /dev/null +++ b/src/mongo/util/concurrency/synchronization.h @@ -0,0 +1,86 @@ +// synchronization.h + +/* Copyright 2010 10gen 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. + */ + +#pragma once + +#include <boost/thread/condition.hpp> +#include "mutex.h" + +namespace mongo { + + /* + * A class to establish a synchronization point between two threads. One thread is the waiter and one is + * the notifier. After the notification event, both proceed normally. + * + * This class is thread-safe. + */ + class Notification { + public: + Notification(); + ~Notification(); + + /* + * Blocks until the method 'notifyOne()' is called. + */ + void waitToBeNotified(); + + /* + * Notifies the waiter of '*this' that it can proceed. Can only be called once. + */ + void notifyOne(); + + private: + mongo::mutex _mutex; // protects state below + unsigned long long lookFor; + unsigned long long cur; + boost::condition _condition; // cond over _notified being true + }; + + /** establishes a synchronization point between threads. N threads are waits and one is notifier. + threadsafe. + */ + class NotifyAll : boost::noncopyable { + public: + NotifyAll(); + + typedef unsigned long long When; + + When now(); + + /** awaits the next notifyAll() call by another thread. notifications that precede this + call are ignored -- we are looking for a fresh event. + */ + void waitFor(When); + + /** a bit faster than waitFor( now() ) */ + void awaitBeyondNow(); + + /** may be called multiple times. notifies all waiters */ + void notifyAll(When); + + /** indicates how many threads are waiting for a notify. */ + unsigned nWaiting() const { return _nWaiting; } + + private: + mongo::mutex _mutex; + boost::condition _condition; + When _lastDone; + When _lastReturned; + unsigned _nWaiting; + }; + +} // namespace mongo diff --git a/src/mongo/util/concurrency/task.cpp b/src/mongo/util/concurrency/task.cpp new file mode 100644 index 00000000000..0b6ab166f19 --- /dev/null +++ b/src/mongo/util/concurrency/task.cpp @@ -0,0 +1,181 @@ +// @file task.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful,b +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" + +#include <boost/thread/condition.hpp> + +#include "task.h" +#include "../goodies.h" +#include "../unittest.h" +#include "../time_support.h" + +namespace mongo { + + namespace task { + + /*void foo() { + boost::mutex m; + boost::mutex::scoped_lock lk(m); + boost::condition cond; + cond.wait(lk); + cond.notify_one(); + }*/ + + Task::Task() + : BackgroundJob( true /* deleteSelf */ ) { + n = 0; + repeat = 0; + } + + void Task::halt() { repeat = 0; } + + void Task::run() { + assert( n == 0 ); + while( 1 ) { + n++; + try { + doWork(); + } + catch(...) { } + if( repeat == 0 ) + break; + sleepmillis(repeat); + if( inShutdown() ) + break; + } + } + + void Task::begin() { + go(); + } + + void fork(Task *t) { + t->begin(); + } + + void repeat(Task *t, unsigned millis) { + t->repeat = millis; + t->begin(); + } + + } +} + +#include "msg.h" + +/* task::Server */ + +namespace mongo { + namespace task { + + /* to get back a return value */ + struct Ret { + Ret() : done(false),m("Ret") { } + bool done; + mongo::mutex m; + boost::condition c; + const lam *msg; + void f() { + (*msg)(); + done = true; + c.notify_one(); + } + }; + + void Server::call( const lam& msg ) { + Ret r; + r.msg = &msg; + lam f = boost::bind(&Ret::f, &r); + send(f); + { + scoped_lock lk(r.m); + while( !r.done ) + r.c.wait(lk.boost()); + } + } + + void Server::send( lam msg ) { + { + scoped_lock lk(m); + d.push_back(msg); + wassert( d.size() < 1024 ); + } + c.notify_one(); + } + + void Server::doWork() { + starting(); + while( 1 ) { + lam f; + try { + scoped_lock lk(m); + while( d.empty() ) + c.wait(lk.boost()); + f = d.front(); + d.pop_front(); + } + catch(...) { + log() << "ERROR exception in Server:doWork?" << endl; + } + try { + f(); + if( rq ) { + rq = false; + { + scoped_lock lk(m); + d.push_back(f); + } + } + } + catch(std::exception& e) { + log() << "Server::doWork task:" << name() << " exception:" << e.what() << endl; + } + catch(const char *p) { + log() << "Server::doWork task:" << name() << " unknown c exception:" << + ((p&&strlen(p)<800)?p:"?") << endl; + } + catch(...) { + log() << "Server::doWork unknown exception task:" << name() << endl; + } + } + } + + static Server *s; + static void abc(int i) { + cout << "Hello " << i << endl; + s->requeue(); + } + class TaskUnitTest : public mongo::UnitTest { + public: + virtual void run() { + lam f = boost::bind(abc, 3); + //f(); + + s = new Server("unittest"); + fork(s); + s->send(f); + + sleepsecs(30); + cout <<" done" << endl; + + } + }; // not running. taskunittest; + + } +} diff --git a/src/mongo/util/concurrency/task.h b/src/mongo/util/concurrency/task.h new file mode 100644 index 00000000000..d7b45eeef24 --- /dev/null +++ b/src/mongo/util/concurrency/task.h @@ -0,0 +1,72 @@ +// @file task.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful,b +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../background.h" + +namespace mongo { + + namespace task { + + /** abstraction around threads. simpler than BackgroundJob which is used behind the scenes. + allocate the Task dynamically. when the thread terminates, the Task object will delete itself. + */ + class Task : private BackgroundJob { + protected: + virtual void doWork() = 0; // implement the task here. + virtual string name() const = 0; // name the threada + public: + Task(); + + /** for a repeating task, stop after current invocation ends. can be called by other threads + as long as the Task is still in scope. + */ + void halt(); + private: + unsigned n, repeat; + friend void fork(Task* t); + friend void repeat(Task* t, unsigned millis); + virtual void run(); + //virtual void ending() { } + void begin(); + }; + + /** run once */ + void fork(Task *t); + + /** run doWork() over and over, with a pause between runs of millis */ + void repeat(Task *t, unsigned millis); + + /*** Example *** + inline void sample() { + class Sample : public Task { + public: + int result; + virtual void doWork() { result = 1234; } + Sample() : result(0) { } + }; + shared_ptr<Sample> q( new Sample() ); + fork(q); + cout << q->result << endl; // could print 1234 or 0. + } + */ + + } + +} diff --git a/src/mongo/util/concurrency/thread_pool.cpp b/src/mongo/util/concurrency/thread_pool.cpp new file mode 100644 index 00000000000..1c258847cb5 --- /dev/null +++ b/src/mongo/util/concurrency/thread_pool.cpp @@ -0,0 +1,141 @@ +/* threadpool.cpp +*/ + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "thread_pool.h" +#include "mvar.h" + +namespace mongo { + namespace threadpool { + + // Worker thread + class Worker : boost::noncopyable { + public: + explicit Worker(ThreadPool& owner) + : _owner(owner) + , _is_done(true) + , _thread(boost::bind(&Worker::loop, this)) + {} + + // destructor will block until current operation is completed + // Acts as a "join" on this thread + ~Worker() { + _task.put(Task()); + _thread.join(); + } + + void set_task(Task& func) { + assert(!func.empty()); + assert(_is_done); + _is_done = false; + + _task.put(func); + } + + private: + ThreadPool& _owner; + MVar<Task> _task; + bool _is_done; // only used for error detection + boost::thread _thread; + + void loop() { + while (true) { + Task task = _task.take(); + if (task.empty()) + break; // ends the thread + + try { + task(); + } + catch (std::exception e) { + log() << "Unhandled exception in worker thread: " << e.what() << endl;; + } + catch (...) { + log() << "Unhandled non-exception in worker thread" << endl; + } + _is_done = true; + _owner.task_done(this); + } + } + }; + + ThreadPool::ThreadPool(int nThreads) + : _mutex("ThreadPool"), _tasksRemaining(0) + , _nThreads(nThreads) { + scoped_lock lock(_mutex); + while (nThreads-- > 0) { + Worker* worker = new Worker(*this); + _freeWorkers.push_front(worker); + } + } + + ThreadPool::~ThreadPool() { + join(); + + assert(_tasks.empty()); + + // O(n) but n should be small + assert(_freeWorkers.size() == (unsigned)_nThreads); + + while(!_freeWorkers.empty()) { + delete _freeWorkers.front(); + _freeWorkers.pop_front(); + } + } + + void ThreadPool::join() { + scoped_lock lock(_mutex); + while(_tasksRemaining) { + _condition.wait(lock.boost()); + } + } + + void ThreadPool::schedule(Task task) { + scoped_lock lock(_mutex); + + _tasksRemaining++; + + if (!_freeWorkers.empty()) { + _freeWorkers.front()->set_task(task); + _freeWorkers.pop_front(); + } + else { + _tasks.push_back(task); + } + } + + // should only be called by a worker from the worker thread + void ThreadPool::task_done(Worker* worker) { + scoped_lock lock(_mutex); + + if (!_tasks.empty()) { + worker->set_task(_tasks.front()); + _tasks.pop_front(); + } + else { + _freeWorkers.push_front(worker); + } + + _tasksRemaining--; + + if(_tasksRemaining == 0) + _condition.notify_all(); + } + + } //namespace threadpool +} //namespace mongo diff --git a/src/mongo/util/concurrency/thread_pool.h b/src/mongo/util/concurrency/thread_pool.h new file mode 100644 index 00000000000..b348ed1d01b --- /dev/null +++ b/src/mongo/util/concurrency/thread_pool.h @@ -0,0 +1,82 @@ +// thread_pool.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include <boost/function.hpp> +#include <boost/bind.hpp> +#undef assert +#define assert MONGO_assert + +namespace mongo { + + namespace threadpool { + class Worker; + + typedef boost::function<void(void)> Task; //nullary function or functor + + // exported to the mongo namespace + class ThreadPool : boost::noncopyable { + public: + explicit ThreadPool(int nThreads=8); + + // blocks until all tasks are complete (tasks_remaining() == 0) + // You should not call schedule while in the destructor + ~ThreadPool(); + + // blocks until all tasks are complete (tasks_remaining() == 0) + // does not prevent new tasks from being scheduled so could wait forever. + // Also, new tasks could be scheduled after this returns. + void join(); + + // task will be copied a few times so make sure it's relatively cheap + void schedule(Task task); + + // Helpers that wrap schedule and boost::bind. + // Functor and args will be copied a few times so make sure it's relatively cheap + template<typename F, typename A> + void schedule(F f, A a) { schedule(boost::bind(f,a)); } + template<typename F, typename A, typename B> + void schedule(F f, A a, B b) { schedule(boost::bind(f,a,b)); } + template<typename F, typename A, typename B, typename C> + void schedule(F f, A a, B b, C c) { schedule(boost::bind(f,a,b,c)); } + template<typename F, typename A, typename B, typename C, typename D> + void schedule(F f, A a, B b, C c, D d) { schedule(boost::bind(f,a,b,c,d)); } + template<typename F, typename A, typename B, typename C, typename D, typename E> + void schedule(F f, A a, B b, C c, D d, E e) { schedule(boost::bind(f,a,b,c,d,e)); } + + int tasks_remaining() { return _tasksRemaining; } + + private: + mongo::mutex _mutex; + boost::condition _condition; + + list<Worker*> _freeWorkers; //used as LIFO stack (always front) + list<Task> _tasks; //used as FIFO queue (push_back, pop_front) + int _tasksRemaining; // in queue + currently processing + int _nThreads; // only used for sanity checking. could be removed in the future. + + // should only be called by a worker from the worker's thread + void task_done(Worker* worker); + friend class Worker; + }; + + } //namespace threadpool + + using threadpool::ThreadPool; + +} //namespace mongo diff --git a/src/mongo/util/concurrency/threadlocal.h b/src/mongo/util/concurrency/threadlocal.h new file mode 100644 index 00000000000..57a4f799dfa --- /dev/null +++ b/src/mongo/util/concurrency/threadlocal.h @@ -0,0 +1,126 @@ +#pragma once + +/** +* Copyright (C) 2011 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <boost/thread/tss.hpp> + +namespace mongo { + + using boost::thread_specific_ptr; + + /* thread local "value" rather than a pointer + good for things which have copy constructors (and the copy constructor is fast enough) + e.g. + ThreadLocalValue<int> myint; + */ + template<class T> + class ThreadLocalValue { + public: + ThreadLocalValue( T def = 0 ) : _default( def ) { } + + T get() const { + T * val = _val.get(); + if ( val ) + return *val; + return _default; + } + + void set( const T& i ) { + T *v = _val.get(); + if( v ) { + *v = i; + return; + } + v = new T(i); + _val.reset( v ); + } + + T& getRef() { + T *v = _val.get(); + if( v ) { + return *v; + } + v = new T(_default); + _val.reset( v ); + return *v; + } + + private: + boost::thread_specific_ptr<T> _val; + const T _default; + }; + + /* TSP + These macros use intrinsics which are faster than boost::thread_specific_ptr. + However the intrinsics don't free up objects on thread closure. Thus we use + a combination here, with the assumption that reset's are infrequent, so that + get's are fast. + */ +#if defined(_WIN32) || (defined(__GNUC__) && defined(__linux__)) + + template< class T > + struct TSP { + boost::thread_specific_ptr<T> tsp; + public: + T* get() const; + void reset(T* v); + }; + +# if defined(_WIN32) + +# define TSP_DECLARE(T,p) extern TSP<T> p; + +# define TSP_DEFINE(T,p) __declspec( thread ) T* _ ## p; \ + TSP<T> p; \ + template<> T* TSP<T>::get() const { return _ ## p; } \ + void TSP<T>::reset(T* v) { \ + tsp.reset(v); \ + _ ## p = v; \ + } +# else + +# define TSP_DECLARE(T,p) \ + extern __thread T* _ ## p; \ + template<> inline T* TSP<T>::get() const { return _ ## p; } \ + extern TSP<T> p; + +# define TSP_DEFINE(T,p) \ + __thread T* _ ## p; \ + template<> void TSP<T>::reset(T* v) { \ + tsp.reset(v); \ + _ ## p = v; \ + } \ + TSP<T> p; +# endif + +#else + + template< class T > + struct TSP { + thread_specific_ptr<T> tsp; + public: + T* get() const { return tsp.get(); } + void reset(T* v) { tsp.reset(v); } + }; + +# define TSP_DECLARE(T,p) extern TSP<T> p; + +# define TSP_DEFINE(T,p) TSP<T> p; + +#endif + +} diff --git a/src/mongo/util/concurrency/value.h b/src/mongo/util/concurrency/value.h new file mode 100644 index 00000000000..fdd0d9bbb42 --- /dev/null +++ b/src/mongo/util/concurrency/value.h @@ -0,0 +1,139 @@ +/* @file value.h + concurrency helpers DiagStr, Guarded +*/ + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful,b +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "spin_lock.h" + +namespace mongo { + + /** declare that a variable that is "guarded" by a mutex. + + The decl documents the rule. For example "counta and countb are guarded by xyzMutex": + + Guarded<int, xyzMutex> counta; + Guarded<int, xyzMutex> countb; + + Upon use, specify the scoped_lock object. This makes it hard for someone + later to forget to be in the lock. Check is made that it is the right lock in _DEBUG + builds at runtime. + */ + template <typename T, SimpleMutex& BY> + class Guarded { + T _val; + public: + T& ref(const SimpleMutex::scoped_lock& lk) { + dassert( &lk.m() == &BY ); + return _val; + } + }; + + // todo: rename this to ThreadSafeString or something + /** there is now one mutex per DiagStr. If you have hundreds or millions of + DiagStrs you'll need to do something different. + */ + class DiagStr { + mutable SpinLock m; + string _s; + public: + DiagStr(const DiagStr& r) : _s(r.get()) { } + DiagStr(const string& r) : _s(r) { } + DiagStr() { } + bool empty() const { + scoped_spinlock lk(m); + return _s.empty(); + } + string get() const { + scoped_spinlock lk(m); + return _s; + } + void set(const char *s) { + scoped_spinlock lk(m); + _s = s; + } + void set(const string& s) { + scoped_spinlock lk(m); + _s = s; + } + operator string() const { return get(); } + void operator=(const string& s) { set(s); } + void operator=(const DiagStr& rhs) { + set( rhs.get() ); + } + + // == is not defined. use get() == ... instead. done this way so one thinks about if composing multiple operations + bool operator==(const string& s) const; + }; + + /** Thread safe map. + Be careful not to use this too much or it could make things slow; + if not a hot code path no problem. + + Examples: + + mapsf<int,int> mp; + + int x = mp.get(); + + map<int,int> two; + mp.swap(two); + + { + mapsf<int,int>::ref r(mp); + r[9] = 1; + map<int,int>::iterator i = r.r.begin(); + } + + */ + template< class K, class V > + struct mapsf : boost::noncopyable { + SimpleMutex m; + map<K,V> val; + friend struct ref; + public: + mapsf() : m("mapsf") { } + void swap(map<K,V>& rhs) { + SimpleMutex::scoped_lock lk(m); + val.swap(rhs); + } + bool empty() { + SimpleMutex::scoped_lock lk(m); + return val.empty(); + } + // safe as we pass by value: + V get(K k) { + SimpleMutex::scoped_lock lk(m); + typename map<K,V>::iterator i = val.find(k); + if( i == val.end() ) + return V(); + return i->second; + } + // think about deadlocks when using ref. the other methods + // above will always be safe as they are "leaf" operations. + struct ref { + SimpleMutex::scoped_lock lk; + public: + map<K,V> &r; + ref(mapsf<K,V> &m) : lk(m.m), r(m.val) { } + V& operator[](const K& k) { return r[k]; } + }; + }; + +} diff --git a/src/mongo/util/concurrency/vars.cpp b/src/mongo/util/concurrency/vars.cpp new file mode 100644 index 00000000000..0b2fc960c04 --- /dev/null +++ b/src/mongo/util/concurrency/vars.cpp @@ -0,0 +1,56 @@ +// vars.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful,b +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" +#include "mutex.h" +#include "value.h" + +namespace mongo { + +#if defined(_DEBUG) + + // intentional leak. otherwise destructor orders can be problematic at termination. + MutexDebugger &mutexDebugger = *(new MutexDebugger()); + + MutexDebugger::MutexDebugger() : + x( *(new boost::mutex()) ), magic(0x12345678) { + // optional way to debug lock order + /* + a = "a_lock"; + b = "b_lock"; + */ + } + + void MutexDebugger::programEnding() { + if( logLevel>=1 && followers.size() ) { + std::cout << followers.size() << " mutexes in program" << endl; + for( map< mid, set<mid> >::iterator i = followers.begin(); i != followers.end(); i++ ) { + cout << i->first; + if( maxNest[i->first] > 1 ) + cout << " maxNest:" << maxNest[i->first]; + cout << '\n'; + for( set<mid>::iterator j = i->second.begin(); j != i->second.end(); j++ ) + cout << " " << *j << '\n'; + } + cout.flush(); + } + } + +#endif + +} diff --git a/src/mongo/util/debug_util.cpp b/src/mongo/util/debug_util.cpp new file mode 100644 index 00000000000..8ba6534ef7c --- /dev/null +++ b/src/mongo/util/debug_util.cpp @@ -0,0 +1,60 @@ +// debug_util.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "../db/cmdline.h" +#include "../db/jsobj.h" + +namespace mongo { + +#if defined(USE_GDBSERVER) + /* Magic gdb trampoline + * Do not call directly! call setupSIGTRAPforGDB() + * Assumptions: + * 1) gdbserver is on your path + * 2) You have run "handle SIGSTOP noprint" in gdb + * 3) cmdLine.port + 2000 is free + */ + void launchGDB(int) { + // Don't come back here + signal(SIGTRAP, SIG_IGN); + + int newPort = cmdLine.port + 2000; + string newPortStr = "localhost:" + BSONObjBuilder::numStr(newPort); + string pidToDebug = BSONObjBuilder::numStr(getpid()); + + cout << "\n\n\t**** Launching gdbserver on " << newPortStr << " ****" << endl << endl; + if (fork() == 0) { + //child + execlp("gdbserver", "gdbserver", "--attach", newPortStr.c_str(), pidToDebug.c_str(), NULL); + perror(NULL); + } + else { + //parent + raise(SIGSTOP); // pause all threads until gdb connects and continues + raise(SIGTRAP); // break inside gdbserver + } + } + + void setupSIGTRAPforGDB() { + assert( signal(SIGTRAP , launchGDB ) != SIG_ERR ); + } +#else + void setupSIGTRAPforGDB() { + } +#endif +} diff --git a/src/mongo/util/debug_util.h b/src/mongo/util/debug_util.h new file mode 100644 index 00000000000..abed8d94924 --- /dev/null +++ b/src/mongo/util/debug_util.h @@ -0,0 +1,106 @@ +// debug_util.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#ifndef _WIN32 +#include <signal.h> +#endif + +namespace mongo { + +// for debugging + typedef struct _Ints { + int i[100]; + } *Ints; + typedef struct _Chars { + char c[200]; + } *Chars; + + typedef char CHARS[400]; + + typedef struct _OWS { + int size; + char type; + char string[400]; + } *OWS; + +#if defined(_DEBUG) + enum {DEBUG_BUILD = 1}; +#else + enum {DEBUG_BUILD = 0}; +#endif + +#define MONGO_DEV if( DEBUG_BUILD ) +#define DEV MONGO_DEV + +#define MONGO_DEBUGGING if( 0 ) +#define DEBUGGING MONGO_DEBUGGING + +// The following declare one unique counter per enclosing function. +// NOTE The implementation double-increments on a match, but we don't really care. +#define MONGO_SOMETIMES( occasion, howOften ) for( static unsigned occasion = 0; ++occasion % howOften == 0; ) +#define SOMETIMES MONGO_SOMETIMES + +#define MONGO_OCCASIONALLY SOMETIMES( occasionally, 16 ) +#define OCCASIONALLY MONGO_OCCASIONALLY + +#define MONGO_RARELY SOMETIMES( rarely, 128 ) +#define RARELY MONGO_RARELY + +#define MONGO_ONCE for( static bool undone = true; undone; undone = false ) +#define ONCE MONGO_ONCE + +#if defined(_WIN32) + inline int strcasecmp(const char* s1, const char* s2) {return _stricmp(s1, s2);} +#endif + + // Sets SIGTRAP handler to launch GDB + // Noop unless on *NIX and compiled with _DEBUG + void setupSIGTRAPforGDB(); + + extern int tlogLevel; + + inline void breakpoint() { + if ( tlogLevel < 0 ) + return; +#ifdef _WIN32 + //DEV DebugBreak(); +#endif +#ifndef _WIN32 + // code to raise a breakpoint in GDB + ONCE { + //prevent SIGTRAP from crashing the program if default action is specified and we are not in gdb + struct sigaction current; + sigaction(SIGTRAP, NULL, ¤t); + if (current.sa_handler == SIG_DFL) { + signal(SIGTRAP, SIG_IGN); + } + } + + raise(SIGTRAP); +#endif + } + + + // conditional breakpoint + inline void breakif(bool test) { + if (test) + breakpoint(); + } + +} // namespace mongo diff --git a/src/mongo/util/embedded_builder.h b/src/mongo/util/embedded_builder.h new file mode 100644 index 00000000000..abf518e2583 --- /dev/null +++ b/src/mongo/util/embedded_builder.h @@ -0,0 +1,92 @@ +// embedded_builder.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + + // utility class for assembling hierarchical objects + class EmbeddedBuilder { + public: + EmbeddedBuilder( BSONObjBuilder *b ) { + _builders.push_back( make_pair( "", b ) ); + } + // It is assumed that the calls to prepareContext will be made with the 'name' + // parameter in lex ascending order. + void prepareContext( string &name ) { + int i = 1, n = _builders.size(); + while( i < n && + name.substr( 0, _builders[ i ].first.length() ) == _builders[ i ].first && + ( name[ _builders[i].first.length() ] == '.' || name[ _builders[i].first.length() ] == 0 ) + ) { + name = name.substr( _builders[ i ].first.length() + 1 ); + ++i; + } + for( int j = n - 1; j >= i; --j ) { + popBuilder(); + } + for( string next = splitDot( name ); !next.empty(); next = splitDot( name ) ) { + addBuilder( next ); + } + } + void appendAs( const BSONElement &e, string name ) { + if ( e.type() == Object && e.valuesize() == 5 ) { // empty object -- this way we can add to it later + string dummyName = name + ".foo"; + prepareContext( dummyName ); + return; + } + prepareContext( name ); + back()->appendAs( e, name ); + } + BufBuilder &subarrayStartAs( string name ) { + prepareContext( name ); + return back()->subarrayStart( name ); + } + void done() { + while( ! _builderStorage.empty() ) + popBuilder(); + } + + static string splitDot( string & str ) { + size_t pos = str.find( '.' ); + if ( pos == string::npos ) + return ""; + string ret = str.substr( 0, pos ); + str = str.substr( pos + 1 ); + return ret; + } + + private: + void addBuilder( const string &name ) { + shared_ptr< BSONObjBuilder > newBuilder( new BSONObjBuilder( back()->subobjStart( name ) ) ); + _builders.push_back( make_pair( name, newBuilder.get() ) ); + _builderStorage.push_back( newBuilder ); + } + void popBuilder() { + back()->done(); + _builders.pop_back(); + _builderStorage.pop_back(); + } + + BSONObjBuilder *back() { return _builders.back().second; } + + vector< pair< string, BSONObjBuilder * > > _builders; + vector< shared_ptr< BSONObjBuilder > > _builderStorage; + + }; + +} //namespace mongo diff --git a/src/mongo/util/file.h b/src/mongo/util/file.h new file mode 100644 index 00000000000..368e6927b43 --- /dev/null +++ b/src/mongo/util/file.h @@ -0,0 +1,230 @@ +// file.h cross platform basic file class. supports 64 bit offsets and such. + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#if !defined(_WIN32) +#include "errno.h" +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/statvfs.h> +#endif +#include "text.h" + +namespace mongo { + +#ifndef __sunos__ + typedef uint64_t fileofs; +#else + typedef boost::uint64_t fileofs; +#endif + + /* NOTE: not thread-safe. (at least the windows implementation isn't. */ + + class FileInterface { + public: + void open(const char *fn) {} + void write(fileofs o, const char *data, unsigned len) {} + void read(fileofs o, char *data, unsigned len) {} + bool bad() {return false;} + bool is_open() {return false;} + fileofs len() { return 0; } + void fsync() { assert(false); } + + // shrink file to size bytes. No-op if file already smaller. + void truncate(fileofs size); + + /** @return -1 if error or unavailable */ + static boost::intmax_t freeSpace(const string &path) { assert(false); return -1; } + }; + +#if defined(_WIN32) +#include <io.h> + + class File : public FileInterface { + HANDLE fd; + bool _bad; + string _name; + void err(BOOL b=false) { /* false = error happened */ + if( !b && !_bad ) { + _bad = true; + log() << "File " << _name << "I/O error " << GetLastError() << '\n'; + } + } + public: + File() { + fd = INVALID_HANDLE_VALUE; + _bad = true; + } + ~File() { + if( is_open() ) CloseHandle(fd); + fd = INVALID_HANDLE_VALUE; + } + void open(const char *filename, bool readOnly=false , bool direct=false) { + _name = filename; + fd = CreateFile( + toNativeString(filename).c_str(), + ( readOnly ? 0 : GENERIC_WRITE ) | GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, + NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if( !is_open() ) { + DWORD e = GetLastError(); + log() << "Create/Open File failed " << filename << ' ' << errnoWithDescription(e) << endl; + } + else + _bad = false; + } + static boost::intmax_t freeSpace(const string &path) { + ULARGE_INTEGER avail; + if( GetDiskFreeSpaceEx(toNativeString(path.c_str()).c_str(), &avail, NULL, NULL) ) { + return avail.QuadPart; + } + DWORD e = GetLastError(); + log() << "GetDiskFreeSpaceEx fails errno: " << e << endl; + return -1; + } + void write(fileofs o, const char *data, unsigned len) { + LARGE_INTEGER li; + li.QuadPart = o; + SetFilePointerEx(fd, li, NULL, FILE_BEGIN); + DWORD written; + err( WriteFile(fd, data, len, &written, NULL) ); + } + void read(fileofs o, char *data, unsigned len) { + DWORD read; + LARGE_INTEGER li; + li.QuadPart = o; + SetFilePointerEx(fd, li, NULL, FILE_BEGIN); + int ok = ReadFile(fd, data, len, &read, 0); + if( !ok ) + err(ok); + else + massert( 10438 , "ReadFile error - truncated file?", read == len); + } + bool bad() { return _bad; } + bool is_open() { return fd != INVALID_HANDLE_VALUE; } + fileofs len() { + LARGE_INTEGER li; + li.LowPart = GetFileSize(fd, (DWORD *) &li.HighPart); + if( li.HighPart == 0 && li.LowPart == INVALID_FILE_SIZE ) { + err( false ); + return 0; + } + return li.QuadPart; + } + void fsync() { FlushFileBuffers(fd); } + + void truncate(fileofs size) { + if (len() <= size) + return; + + LARGE_INTEGER li; + li.QuadPart = size; + if (SetFilePointerEx(fd, li, NULL, FILE_BEGIN) == 0){ + err(false); + return; //couldn't seek + } + + err(SetEndOfFile(fd)); + } + }; + +#else + + class File : public FileInterface { + public: + int fd; + private: + bool _bad; + void err(bool ok) { + if( !ok && !_bad ) { + _bad = true; + log() << "File I/O " << errnoWithDescription() << '\n'; + } + } + public: + File() { + fd = -1; + _bad = true; + } + ~File() { + if( is_open() ) ::close(fd); + fd = -1; + } + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + + void open(const char *filename, bool readOnly=false , bool direct=false) { + fd = ::open(filename, + O_CREAT | ( readOnly ? 0 : ( O_RDWR | O_NOATIME ) ) +#if defined(O_DIRECT) + | ( direct ? O_DIRECT : 0 ) +#endif + , + S_IRUSR | S_IWUSR); + if ( fd <= 0 ) { + out() << "couldn't open " << filename << ' ' << errnoWithDescription() << endl; + return; + } + _bad = false; + } + void write(fileofs o, const char *data, unsigned len) { + err( ::pwrite(fd, data, len, o) == (int) len ); + } + void read(fileofs o, char *data, unsigned len) { + ssize_t s = ::pread(fd, data, len, o); + if( s == -1 ) { + err(false); + } + else if( s != (int) len ) { + _bad = true; + log() << "File error read:" << s << " bytes, wanted:" << len << " ofs:" << o << endl; + } + } + bool bad() { return _bad; } + bool is_open() { return fd > 0; } + fileofs len() { + off_t o = lseek(fd, 0, SEEK_END); + if( o != (off_t) -1 ) + return o; + err(false); + return 0; + } + void fsync() { ::fsync(fd); } + static boost::intmax_t freeSpace ( const string &path ) { + struct statvfs info; + assert( !statvfs( path.c_str() , &info ) ); + return boost::intmax_t( info.f_bavail ) * info.f_frsize; + } + + void truncate(fileofs size) { + if (len() <= size) + return; + + err(ftruncate(fd, size) == 0); + } + }; + + +#endif + + +} + diff --git a/src/mongo/util/file_allocator.cpp b/src/mongo/util/file_allocator.cpp new file mode 100644 index 00000000000..b0572f971bd --- /dev/null +++ b/src/mongo/util/file_allocator.cpp @@ -0,0 +1,329 @@ +// @file file_allocator.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include <fcntl.h> +#include <errno.h> + +#if defined(__freebsd__) || defined(__openbsd__) +#include <sys/stat.h> +#endif + +#include "timer.h" +#include "mongoutils/str.h" +using namespace mongoutils; + +#ifndef O_NOATIME +#define O_NOATIME (0) +#endif + +#include "file_allocator.h" +#include "paths.h" + +namespace mongo { + + boost::filesystem::path ensureParentDirCreated(const boost::filesystem::path& p){ + const boost::filesystem::path parent = p.branch_path(); + + if (! boost::filesystem::exists(parent)){ + ensureParentDirCreated(parent); + log() << "creating directory " << parent.string() << endl; + boost::filesystem::create_directory(parent); + flushMyDirectory(parent); // flushes grandparent to ensure parent exists after crash + } + + assert(boost::filesystem::is_directory(parent)); + return parent; + } + +#if defined(_WIN32) + FileAllocator::FileAllocator() { + } + + void FileAllocator::start() { + } + + void FileAllocator::requestAllocation( const string &name, long &size ) { + /* Some of the system calls in the file allocator don't work in win, + so no win support - 32 or 64 bit. Plus we don't seem to need preallocation + on windows anyway as we don't have to pre-zero the file there. + */ + } + + void FileAllocator::allocateAsap( const string &name, unsigned long long &size ) { + // no-op + } + + void FileAllocator::waitUntilFinished() const { + // no-op + } + + void FileAllocator::ensureLength(int fd , long size) { + // we don't zero on windows + // TODO : we should to avoid fragmentation + } + + bool FileAllocator::hasFailed() const { + return false; + } + +#else + + FileAllocator::FileAllocator() + : _pendingMutex("FileAllocator"), _failed() { + } + + + void FileAllocator::start() { + boost::thread t( boost::bind( &FileAllocator::run , this ) ); + } + + void FileAllocator::requestAllocation( const string &name, long &size ) { + scoped_lock lk( _pendingMutex ); + if ( _failed ) + return; + long oldSize = prevSize( name ); + if ( oldSize != -1 ) { + size = oldSize; + return; + } + _pending.push_back( name ); + _pendingSize[ name ] = size; + _pendingUpdated.notify_all(); + } + + void FileAllocator::allocateAsap( const string &name, unsigned long long &size ) { + scoped_lock lk( _pendingMutex ); + long oldSize = prevSize( name ); + if ( oldSize != -1 ) { + size = oldSize; + if ( !inProgress( name ) ) + return; + } + checkFailure(); + _pendingSize[ name ] = size; + if ( _pending.size() == 0 ) + _pending.push_back( name ); + else if ( _pending.front() != name ) { + _pending.remove( name ); + list< string >::iterator i = _pending.begin(); + ++i; + _pending.insert( i, name ); + } + _pendingUpdated.notify_all(); + while( inProgress( name ) ) { + checkFailure(); + _pendingUpdated.wait( lk.boost() ); + } + + } + + void FileAllocator::waitUntilFinished() const { + if ( _failed ) + return; + scoped_lock lk( _pendingMutex ); + while( _pending.size() != 0 ) + _pendingUpdated.wait( lk.boost() ); + } + + void FileAllocator::ensureLength(int fd , long size) { +#if defined(__linux__) + int ret = posix_fallocate(fd,0,size); + if ( ret == 0 ) + return; + + log() << "FileAllocator: posix_fallocate failed: " << errnoWithDescription( ret ) << " falling back" << endl; +#endif + + off_t filelen = lseek(fd, 0, SEEK_END); + if ( filelen < size ) { + if (filelen != 0) { + stringstream ss; + ss << "failure creating new datafile; lseek failed for fd " << fd << " with errno: " << errnoWithDescription(); + uassert( 10440 , ss.str(), filelen == 0 ); + } + // Check for end of disk. + + uassert( 10441 , str::stream() << "Unable to allocate new file of size " << size << ' ' << errnoWithDescription(), + size - 1 == lseek(fd, size - 1, SEEK_SET) ); + uassert( 10442 , str::stream() << "Unable to allocate new file of size " << size << ' ' << errnoWithDescription(), + 1 == write(fd, "", 1) ); + lseek(fd, 0, SEEK_SET); + + const long z = 256 * 1024; + const boost::scoped_array<char> buf_holder (new char[z]); + char* buf = buf_holder.get(); + memset(buf, 0, z); + long left = size; + while ( left > 0 ) { + long towrite = left; + if ( towrite > z ) + towrite = z; + + int written = write( fd , buf , towrite ); + uassert( 10443 , errnoWithPrefix("FileAllocator: file write failed" ), written > 0 ); + left -= written; + } + } + } + + bool FileAllocator::hasFailed() const { + return _failed; + } + + void FileAllocator::checkFailure() { + if (_failed) { + // we want to log the problem (diskfull.js expects it) but we do not want to dump a stack tracke + msgassertedNoTrace( 12520, "new file allocation failure" ); + } + } + + long FileAllocator::prevSize( const string &name ) const { + if ( _pendingSize.count( name ) > 0 ) + return _pendingSize[ name ]; + if ( boost::filesystem::exists( name ) ) + return boost::filesystem::file_size( name ); + return -1; + } + + // caller must hold _pendingMutex lock. + bool FileAllocator::inProgress( const string &name ) const { + for( list< string >::const_iterator i = _pending.begin(); i != _pending.end(); ++i ) + if ( *i == name ) + return true; + return false; + } + + string makeTempFileName( path root ) { + while( 1 ) { + path p = root / "_tmp"; + stringstream ss; + ss << (unsigned) rand(); + p /= ss.str(); + string fn = p.string(); + if( !boost::filesystem::exists(p) ) + return fn; + } + return ""; + } + + void FileAllocator::run( FileAllocator * fa ) { + setThreadName( "FileAllocator" ); + while( 1 ) { + { + scoped_lock lk( fa->_pendingMutex ); + if ( fa->_pending.size() == 0 ) + fa->_pendingUpdated.wait( lk.boost() ); + } + while( 1 ) { + string name; + long size; + { + scoped_lock lk( fa->_pendingMutex ); + if ( fa->_pending.size() == 0 ) + break; + name = fa->_pending.front(); + size = fa->_pendingSize[ name ]; + } + + string tmp; + long fd = 0; + try { + log() << "allocating new datafile " << name << ", filling with zeroes..." << endl; + + boost::filesystem::path parent = ensureParentDirCreated(name); + tmp = makeTempFileName( parent ); + ensureParentDirCreated(tmp); + + fd = open(tmp.c_str(), O_CREAT | O_RDWR | O_NOATIME, S_IRUSR | S_IWUSR); + if ( fd <= 0 ) { + log() << "FileAllocator: couldn't create " << name << " (" << tmp << ") " << errnoWithDescription() << endl; + uasserted(10439, ""); + } + +#if defined(POSIX_FADV_DONTNEED) + if( posix_fadvise(fd, 0, size, POSIX_FADV_DONTNEED) ) { + log() << "warning: posix_fadvise fails " << name << " (" << tmp << ") " << errnoWithDescription() << endl; + } +#endif + + Timer t; + + /* make sure the file is the full desired length */ + ensureLength( fd , size ); + + close( fd ); + fd = 0; + + if( rename(tmp.c_str(), name.c_str()) ) { + log() << "error: couldn't rename " << tmp << " to " << name << ' ' << errnoWithDescription() << endl; + uasserted(13653, ""); + } + flushMyDirectory(name); + + log() << "done allocating datafile " << name << ", " + << "size: " << size/1024/1024 << "MB, " + << " took " << ((double)t.millis())/1000.0 << " secs" + << endl; + + // no longer in a failed state. allow new writers. + fa->_failed = false; + } + catch ( ... ) { + if ( fd > 0 ) + close( fd ); + log() << "error failed to allocate new file: " << name + << " size: " << size << ' ' << errnoWithDescription() << warnings; + log() << " will try again in 10 seconds" << endl; // not going to warning logs + try { + if ( tmp.size() ) + BOOST_CHECK_EXCEPTION( boost::filesystem::remove( tmp ) ); + BOOST_CHECK_EXCEPTION( boost::filesystem::remove( name ) ); + } + catch ( ... ) { + } + scoped_lock lk( fa->_pendingMutex ); + fa->_failed = true; + // not erasing from pending + fa->_pendingUpdated.notify_all(); + + + sleepsecs(10); + continue; + } + + { + scoped_lock lk( fa->_pendingMutex ); + fa->_pendingSize.erase( name ); + fa->_pending.pop_front(); + fa->_pendingUpdated.notify_all(); + } + } + } + } + +#endif + + FileAllocator* FileAllocator::_instance = 0; + + FileAllocator* FileAllocator::get(){ + if ( ! _instance ) + _instance = new FileAllocator(); + return _instance; + } + +} // namespace mongo diff --git a/src/mongo/util/file_allocator.h b/src/mongo/util/file_allocator.h new file mode 100644 index 00000000000..7c3cacb2888 --- /dev/null +++ b/src/mongo/util/file_allocator.h @@ -0,0 +1,91 @@ +// @file file_allocator.h + +/* Copyright 2009 10gen 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. + */ + +#include "../pch.h" + +namespace mongo { + + /* + * Handles allocation of contiguous files on disk. Allocation may be + * requested asynchronously or synchronously. + * singleton + */ + class FileAllocator : boost::noncopyable { + /* + * The public functions may not be called concurrently. The allocation + * functions may be called multiple times per file, but only the first + * size specified per file will be used. + */ + public: + void start(); + + /** + * May be called if file exists. If file exists, or its allocation has + * been requested, size is updated to match existing file size. + */ + void requestAllocation( const string &name, long &size ); + + + /** + * Returns when file has been allocated. If file exists, size is + * updated to match existing file size. + */ + void allocateAsap( const string &name, unsigned long long &size ); + + void waitUntilFinished() const; + + bool hasFailed() const; + + static void ensureLength(int fd , long size); + + /** @return the singletone */ + static FileAllocator * get(); + + private: + + FileAllocator(); + +#if !defined(_WIN32) + void checkFailure(); + + // caller must hold pendingMutex_ lock. Returns size if allocated or + // allocation requested, -1 otherwise. + long prevSize( const string &name ) const; + + // caller must hold pendingMutex_ lock. + bool inProgress( const string &name ) const; + + /** called from the worked thread */ + static void run( FileAllocator * fa ); + + mutable mongo::mutex _pendingMutex; + mutable boost::condition _pendingUpdated; + + list< string > _pending; + mutable map< string, long > _pendingSize; + + bool _failed; +#endif + + static FileAllocator* _instance; + + }; + + /** like "mkdir -p" but on parent dir of p rather than p itself */ + boost::filesystem::path ensureParentDirCreated(const boost::filesystem::path& p); + +} // namespace mongo diff --git a/src/mongo/util/goodies.h b/src/mongo/util/goodies.h new file mode 100644 index 00000000000..9398b5c3f1d --- /dev/null +++ b/src/mongo/util/goodies.h @@ -0,0 +1,475 @@ +// @file goodies.h +// miscellaneous + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../bson/util/misc.h" +#include "concurrency/mutex.h" + +namespace mongo { + + /* @return a dump of the buffer as hex byte ascii output */ + string hexdump(const char *data, unsigned len); + + /** + * @return if this name has an increasing counter associated, return the value + * otherwise 0 + */ + unsigned setThreadName(const char * name); + string getThreadName(); + + template<class T> + inline string ToString(const T& t) { + stringstream s; + s << t; + return s.str(); + } + +#if !defined(_WIN32) && !defined(NOEXECINFO) && !defined(__freebsd__) && !defined(__openbsd__) && !defined(__sun__) + +} // namespace mongo + +#include <pthread.h> +#include <execinfo.h> + +namespace mongo { + + inline pthread_t GetCurrentThreadId() { + return pthread_self(); + } + + /* use "addr2line -CFe <exe>" to parse. */ + inline void printStackTrace( ostream &o = cout ) { + void *b[20]; + + int size = backtrace(b, 20); + for (int i = 0; i < size; i++) + o << hex << b[i] << dec << ' '; + o << endl; + + char **strings; + + strings = backtrace_symbols(b, size); + for (int i = 0; i < size; i++) + o << ' ' << strings[i] << '\n'; + o.flush(); + free (strings); + } +#else + inline void printStackTrace( ostream &o = cout ) { } +#endif + + bool isPrime(int n); + int nextPrime(int n); + + inline void dumpmemory(const char *data, int len) { + if ( len > 1024 ) + len = 1024; + try { + const char *q = data; + const char *p = q; + while ( len > 0 ) { + for ( int i = 0; i < 16; i++ ) { + if ( *p >= 32 && *p <= 126 ) + cout << *p; + else + cout << '.'; + p++; + } + cout << " "; + p -= 16; + for ( int i = 0; i < 16; i++ ) + cout << (unsigned) ((unsigned char)*p++) << ' '; + cout << endl; + len -= 16; + } + } + catch (...) { + } + } + +// PRINT(2+2); prints "2+2: 4" +#define MONGO_PRINT(x) cout << #x ": " << (x) << endl +#define PRINT MONGO_PRINT +// PRINTFL; prints file:line +#define MONGO_PRINTFL cout << __FILE__ ":" << __LINE__ << endl +#define PRINTFL MONGO_PRINTFL +#define MONGO_FLOG log() << __FILE__ ":" << __LINE__ << endl +#define FLOG MONGO_FLOG + +#undef assert +#define assert MONGO_assert + + inline bool startsWith(const char *str, const char *prefix) { + size_t l = strlen(prefix); + if ( strlen(str) < l ) return false; + return strncmp(str, prefix, l) == 0; + } + inline bool startsWith(string s, string p) { return startsWith(s.c_str(), p.c_str()); } + + inline bool endsWith(const char *p, const char *suffix) { + size_t a = strlen(p); + size_t b = strlen(suffix); + if ( b > a ) return false; + return strcmp(p + a - b, suffix) == 0; + } + + inline unsigned long swapEndian(unsigned long x) { + return + ((x & 0xff) << 24) | + ((x & 0xff00) << 8) | + ((x & 0xff0000) >> 8) | + ((x & 0xff000000) >> 24); + } + +#if defined(BOOST_LITTLE_ENDIAN) + inline unsigned long fixEndian(unsigned long x) { + return x; + } +#else + inline unsigned long fixEndian(unsigned long x) { + return swapEndian(x); + } +#endif + +#if !defined(_WIN32) + typedef int HANDLE; + inline void strcpy_s(char *dst, unsigned len, const char *src) { + assert( strlen(src) < len ); + strcpy(dst, src); + } +#else + typedef void *HANDLE; +#endif + + class ProgressMeter : boost::noncopyable { + public: + ProgressMeter( unsigned long long total , int secondsBetween = 3 , int checkInterval = 100 , string units = "" ) : _units(units) { + reset( total , secondsBetween , checkInterval ); + } + + ProgressMeter() { + _active = 0; + _units = ""; + } + + // typically you do ProgressMeterHolder + void reset( unsigned long long total , int secondsBetween = 3 , int checkInterval = 100 ) { + _total = total; + _secondsBetween = secondsBetween; + _checkInterval = checkInterval; + + _done = 0; + _hits = 0; + _lastTime = (int)time(0); + + _active = 1; + } + + void finished() { + _active = 0; + } + + bool isActive() { + return _active; + } + + /** + * @param n how far along we are relative to the total # we set in CurOp::setMessage + * @return if row was printed + */ + bool hit( int n = 1 ) { + if ( ! _active ) { + cout << "warning: hit an inactive ProgressMeter" << endl; + return false; + } + + _done += n; + _hits++; + if ( _hits % _checkInterval ) + return false; + + int t = (int) time(0); + if ( t - _lastTime < _secondsBetween ) + return false; + + if ( _total > 0 ) { + int per = (int)( ( (double)_done * 100.0 ) / (double)_total ); + cout << "\t\t" << _done << "/" << _total << "\t" << per << "%"; + + if ( ! _units.empty() ) { + cout << "\t(" << _units << ")" << endl; + } + else { + cout << endl; + } + } + _lastTime = t; + return true; + } + + void setUnits( string units ) { + _units = units; + } + + void setTotalWhileRunning( unsigned long long total ) { + _total = total; + } + + unsigned long long done() const { return _done; } + + unsigned long long hits() const { return _hits; } + + unsigned long long total() const { return _total; } + + string toString() const { + if ( ! _active ) + return ""; + stringstream buf; + buf << _done << "/" << _total << " " << (_done*100)/_total << "%"; + + if ( ! _units.empty() ) { + buf << "\t(" << _units << ")" << endl; + } + + return buf.str(); + } + + bool operator==( const ProgressMeter& other ) const { + return this == &other; + } + private: + + bool _active; + + unsigned long long _total; + int _secondsBetween; + int _checkInterval; + + unsigned long long _done; + unsigned long long _hits; + int _lastTime; + + string _units; + }; + + // e.g.: + // CurOp * op = cc().curop(); + // ProgressMeterHolder pm( op->setMessage( "index: (1/3) external sort" , d->stats.nrecords , 10 ) ); + // loop { pm.hit(); } + class ProgressMeterHolder : boost::noncopyable { + public: + ProgressMeterHolder( ProgressMeter& pm ) + : _pm( pm ) { + } + + ~ProgressMeterHolder() { + _pm.finished(); + } + + ProgressMeter* operator->() { + return &_pm; + } + + bool hit( int n = 1 ) { + return _pm.hit( n ); + } + + void finished() { + _pm.finished(); + } + + bool operator==( const ProgressMeter& other ) { + return _pm == other; + } + + private: + ProgressMeter& _pm; + }; + + class TicketHolder { + public: + TicketHolder( int num ) : _mutex("TicketHolder") { + _outof = num; + _num = num; + } + + bool tryAcquire() { + scoped_lock lk( _mutex ); + if ( _num <= 0 ) { + if ( _num < 0 ) { + cerr << "DISASTER! in TicketHolder" << endl; + } + return false; + } + _num--; + return true; + } + + void release() { + scoped_lock lk( _mutex ); + _num++; + } + + void resize( int newSize ) { + scoped_lock lk( _mutex ); + int used = _outof - _num; + if ( used > newSize ) { + cout << "ERROR: can't resize since we're using (" << used << ") more than newSize(" << newSize << ")" << endl; + return; + } + + _outof = newSize; + _num = _outof - used; + } + + int available() const { + return _num; + } + + int used() const { + return _outof - _num; + } + + int outof() const { return _outof; } + + private: + int _outof; + int _num; + mongo::mutex _mutex; + }; + + class TicketHolderReleaser { + public: + TicketHolderReleaser( TicketHolder * holder ) { + _holder = holder; + } + + ~TicketHolderReleaser() { + _holder->release(); + } + private: + TicketHolder * _holder; + }; + + + /** + * this is a thread safe string + * you will never get a bad pointer, though data may be mungedd + */ + class ThreadSafeString : boost::noncopyable { + public: + ThreadSafeString( size_t size=256 ) + : _size( size ) , _buf( new char[size] ) { + memset( _buf , 0 , _size ); + } + + ThreadSafeString( const ThreadSafeString& other ) + : _size( other._size ) , _buf( new char[_size] ) { + strncpy( _buf , other._buf , _size ); + } + + ~ThreadSafeString() { + delete[] _buf; + _buf = 0; + } + + string toString() const { + string s = _buf; + return s; + } + + ThreadSafeString& operator=( const char * str ) { + size_t s = strlen(str); + if ( s >= _size - 2 ) + s = _size - 2; + strncpy( _buf , str , s ); + _buf[s] = 0; + return *this; + } + + bool operator==( const ThreadSafeString& other ) const { + return strcmp( _buf , other._buf ) == 0; + } + + bool operator==( const char * str ) const { + return strcmp( _buf , str ) == 0; + } + + bool operator!=( const char * str ) const { + return strcmp( _buf , str ) != 0; + } + + bool empty() const { + return _buf == 0 || _buf[0] == 0; + } + + private: + size_t _size; + char * _buf; + }; + + ostream& operator<<( ostream &s, const ThreadSafeString &o ); + + /** A generic pointer type for function arguments. + * It will convert from any pointer type except auto_ptr. + * Semantics are the same as passing the pointer returned from get() + * const ptr<T> => T * const + * ptr<const T> => T const * or const T* + */ + template <typename T> + struct ptr { + + ptr() : _p(NULL) {} + + // convert to ptr<T> + ptr(T* p) : _p(p) {} // needed for NULL + template<typename U> ptr(U* p) : _p(p) {} + template<typename U> ptr(const ptr<U>& p) : _p(p) {} + template<typename U> ptr(const boost::shared_ptr<U>& p) : _p(p.get()) {} + template<typename U> ptr(const boost::scoped_ptr<U>& p) : _p(p.get()) {} + //template<typename U> ptr(const auto_ptr<U>& p) : _p(p.get()) {} + + // assign to ptr<T> + ptr& operator= (T* p) { _p = p; return *this; } // needed for NULL + template<typename U> ptr& operator= (U* p) { _p = p; return *this; } + template<typename U> ptr& operator= (const ptr<U>& p) { _p = p; return *this; } + template<typename U> ptr& operator= (const boost::shared_ptr<U>& p) { _p = p.get(); return *this; } + template<typename U> ptr& operator= (const boost::scoped_ptr<U>& p) { _p = p.get(); return *this; } + //template<typename U> ptr& operator= (const auto_ptr<U>& p) { _p = p.get(); return *this; } + + // use + T* operator->() const { return _p; } + T& operator*() const { return *_p; } + + // convert from ptr<T> + operator T* () const { return _p; } + + private: + T* _p; + }; + + + + using boost::shared_ptr; + using boost::scoped_ptr; + using boost::scoped_array; + using boost::intrusive_ptr; + using boost::bad_lexical_cast; + using boost::dynamic_pointer_cast; +} // namespace mongo diff --git a/src/mongo/util/hashtab.h b/src/mongo/util/hashtab.h new file mode 100644 index 00000000000..f1a33068e07 --- /dev/null +++ b/src/mongo/util/hashtab.h @@ -0,0 +1,179 @@ +/* hashtab.h + + Simple, fixed size hash table. Darn simple. + + Uses a contiguous block of memory, so you can put it in a memory mapped file very easily. +*/ + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../pch.h" +#include <map> +#include "../db/dur.h" + +namespace mongo { + +#pragma pack(1) + + /* you should define: + + int Key::hash() return > 0 always. + */ + + template < + class Key, + class Type + > + class HashTable : boost::noncopyable { + public: + const char *name; + struct Node { + int hash; + Key k; + Type value; + bool inUse() { + return hash != 0; + } + void setUnused() { + hash = 0; + } + }; + void* _buf; + int n; // number of hashtable buckets + int maxChain; + + Node& nodes(int i) { + Node *nodes = (Node *) _buf; + return nodes[i]; + } + + int _find(const Key& k, bool& found) { + found = false; + int h = k.hash(); + int i = h % n; + int start = i; + int chain = 0; + int firstNonUsed = -1; + while ( 1 ) { + if ( !nodes(i).inUse() ) { + if ( firstNonUsed < 0 ) + firstNonUsed = i; + } + + if ( nodes(i).hash == h && nodes(i).k == k ) { + if ( chain >= 200 ) + out() << "warning: hashtable " << name << " long chain " << endl; + found = true; + return i; + } + chain++; + i = (i+1) % n; + if ( i == start ) { + // shouldn't get here / defensive for infinite loops + out() << "error: hashtable " << name << " is full n:" << n << endl; + return -1; + } + if( chain >= maxChain ) { + if ( firstNonUsed >= 0 ) + return firstNonUsed; + out() << "error: hashtable " << name << " max chain reached:" << maxChain << endl; + return -1; + } + } + } + + public: + /* buf must be all zeroes on initialization. */ + HashTable(void* buf, int buflen, const char *_name) : name(_name) { + int m = sizeof(Node); + // out() << "hashtab init, buflen:" << buflen << " m:" << m << endl; + n = buflen / m; + if ( (n & 1) == 0 ) + n--; + maxChain = (int) (n * 0.05); + _buf = buf; + //nodes = (Node *) buf; + + if ( sizeof(Node) != 628 ) { + out() << "HashTable() " << _name << " sizeof(node):" << sizeof(Node) << " n:" << n << " sizeof(Key): " << sizeof(Key) << " sizeof(Type):" << sizeof(Type) << endl; + assert( sizeof(Node) == 628 ); + } + + } + + Type* get(const Key& k) { + bool found; + int i = _find(k, found); + if ( found ) + return &nodes(i).value; + return 0; + } + + void kill(const Key& k) { + bool found; + int i = _find(k, found); + if ( i >= 0 && found ) { + Node* n = &nodes(i); + n = getDur().writing(n); + n->k.kill(); + n->setUnused(); + } + } + + /** returns false if too full */ + bool put(const Key& k, const Type& value) { + bool found; + int i = _find(k, found); + if ( i < 0 ) + return false; + Node* n = getDur().writing( &nodes(i) ); + if ( !found ) { + n->k = k; + n->hash = k.hash(); + } + else { + assert( n->hash == k.hash() ); + } + n->value = value; + return true; + } + + typedef void (*IteratorCallback)( const Key& k , Type& v ); + void iterAll( IteratorCallback callback ) { + for ( int i=0; i<n; i++ ) { + if ( nodes(i).inUse() ) { + callback( nodes(i).k , nodes(i).value ); + } + } + } + + // TODO: should probably use boost::bind for this, but didn't want to look at it + typedef void (*IteratorCallback2)( const Key& k , Type& v , void * extra ); + void iterAll( IteratorCallback2 callback , void * extra ) { + for ( int i=0; i<n; i++ ) { + if ( nodes(i).inUse() ) { + callback( nodes(i).k , nodes(i).value , extra ); + } + } + } + + }; + +#pragma pack() + +} // namespace mongo diff --git a/src/mongo/util/heapcheck.h b/src/mongo/util/heapcheck.h new file mode 100644 index 00000000000..95da9538db5 --- /dev/null +++ b/src/mongo/util/heapcheck.h @@ -0,0 +1,33 @@ +// @file heapcheck.h + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#if defined(HEAP_CHECKING) + +#include <google/heap-checker.h> + +#define IGNORE_OBJECT( a ) HeapLeakChecker::IgnoreObject( a ) +#define UNIGNORE_OBJECT( a ) HeapLeakChecker::UnIgnoreObject( a ) + +#else + +#define IGNORE_OBJECT( a ) +#define UNIGNORE_OBJECT( a ) + +#endif diff --git a/src/mongo/util/hex.h b/src/mongo/util/hex.h new file mode 100644 index 00000000000..8cf30f2d9d3 --- /dev/null +++ b/src/mongo/util/hex.h @@ -0,0 +1,67 @@ +// util/hex.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + //can't use hex namespace because it conflicts with hex iostream function + inline int fromHex( char c ) { + if ( '0' <= c && c <= '9' ) + return c - '0'; + if ( 'a' <= c && c <= 'f' ) + return c - 'a' + 10; + if ( 'A' <= c && c <= 'F' ) + return c - 'A' + 10; + assert( false ); + return 0xff; + } + inline char fromHex( const char *c ) { + return (char)(( fromHex( c[ 0 ] ) << 4 ) | fromHex( c[ 1 ] )); + } + + inline string toHex(const void* inRaw, int len) { + static const char hexchars[] = "0123456789ABCDEF"; + + StringBuilder out; + const char* in = reinterpret_cast<const char*>(inRaw); + for (int i=0; i<len; ++i) { + char c = in[i]; + char hi = hexchars[(c & 0xF0) >> 4]; + char lo = hexchars[(c & 0x0F)]; + + out << hi << lo; + } + + return out.str(); + } + + inline string toHexLower(const void* inRaw, int len) { + static const char hexchars[] = "0123456789abcdef"; + + StringBuilder out; + const char* in = reinterpret_cast<const char*>(inRaw); + for (int i=0; i<len; ++i) { + char c = in[i]; + char hi = hexchars[(c & 0xF0) >> 4]; + char lo = hexchars[(c & 0x0F)]; + + out << hi << lo; + } + + return out.str(); + } +} diff --git a/src/mongo/util/histogram.cpp b/src/mongo/util/histogram.cpp new file mode 100644 index 00000000000..17a85059d58 --- /dev/null +++ b/src/mongo/util/histogram.cpp @@ -0,0 +1,131 @@ +// histogram.cc + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <iomanip> +#include <limits> +#include <sstream> + +#include "histogram.h" + +namespace mongo { + + using std::ostringstream; + using std::setfill; + using std::setw; + + Histogram::Histogram( const Options& opts ) + : _initialValue( opts.initialValue ) + , _numBuckets( opts.numBuckets ) + , _boundaries( new uint32_t[_numBuckets] ) + , _buckets( new uint64_t[_numBuckets] ) { + + // TODO more sanity checks + // + not too few buckets + // + initialBucket and bucketSize fit within 32 bit ints + + // _boundaries store the maximum value falling in that bucket. + if ( opts.exponential ) { + uint32_t twoPow = 1; // 2^0 + for ( uint32_t i = 0; i < _numBuckets - 1; i++) { + _boundaries[i] = _initialValue + opts.bucketSize * twoPow; + twoPow *= 2; // 2^i+1 + } + } + else { + _boundaries[0] = _initialValue + opts.bucketSize; + for ( uint32_t i = 1; i < _numBuckets - 1; i++ ) { + _boundaries[i] = _boundaries[ i-1 ] + opts.bucketSize; + } + } + _boundaries[ _numBuckets-1 ] = std::numeric_limits<uint32_t>::max(); + + for ( uint32_t i = 0; i < _numBuckets; i++ ) { + _buckets[i] = 0; + } + } + + Histogram::~Histogram() { + delete [] _boundaries; + delete [] _buckets; + } + + void Histogram::insert( uint32_t element ) { + if ( element < _initialValue) return; + + _buckets[ _findBucket(element) ] += 1; + } + + string Histogram::toHTML() const { + uint64_t max = 0; + for ( uint32_t i = 0; i < _numBuckets; i++ ) { + if ( _buckets[i] > max ) { + max = _buckets[i]; + } + } + if ( max == 0 ) { + return "histogram is empty\n"; + } + + // normalize buckets to max + const int maxBar = 20; + ostringstream ss; + for ( uint32_t i = 0; i < _numBuckets; i++ ) { + int barSize = _buckets[i] * maxBar / max; + ss << string( barSize,'*' ) + << setfill(' ') << setw( maxBar-barSize + 12 ) + << _boundaries[i] << '\n'; + } + + return ss.str(); + } + + uint64_t Histogram::getCount( uint32_t bucket ) const { + if ( bucket >= _numBuckets ) return 0; + + return _buckets[ bucket ]; + } + + uint32_t Histogram::getBoundary( uint32_t bucket ) const { + if ( bucket >= _numBuckets ) return 0; + + return _boundaries[ bucket ]; + } + + uint32_t Histogram::getBucketsNum() const { + return _numBuckets; + } + + uint32_t Histogram::_findBucket( uint32_t element ) const { + // TODO assert not too small a value? + + uint32_t low = 0; + uint32_t high = _numBuckets - 1; + while ( low < high ) { + // low + ( (high - low) / 2 ); + uint32_t mid = ( low + high ) >> 1; + if ( element > _boundaries[ mid ] ) { + low = mid + 1; + } + else { + high = mid; + } + } + return low; + } + +} // namespace mongo diff --git a/src/mongo/util/histogram.h b/src/mongo/util/histogram.h new file mode 100644 index 00000000000..40ec5628dda --- /dev/null +++ b/src/mongo/util/histogram.h @@ -0,0 +1,128 @@ +// histogram.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef UTIL_HISTOGRAM_HEADER +#define UTIL_HISTOGRAM_HEADER + +#include "../pch.h" + +#include <string> + +namespace mongo { + + using std::string; + + /** + * A histogram for a 32-bit integer range. + */ + class Histogram { + public: + /** + * Construct a histogram with 'numBuckets' buckets, optionally + * having the first bucket start at 'initialValue' rather than + * 0. By default, the histogram buckets will be 'bucketSize' wide. + * + * Usage example: + * Histogram::Options opts; + * opts.numBuckets = 3; + * opts.bucketSize = 10; + * Histogram h( opts ); + * + * Generates the bucket ranges [0..10],[11..20],[21..max_int] + * + * Alternatively, the flag 'exponential' could be turned on, in + * which case a bucket's maximum value will be + * initialValue + bucketSize * 2 ^ [0..numBuckets-1] + * + * Usage example: + * Histogram::Options opts; + * opts.numBuckets = 4; + * opts.bucketSize = 125; + * opts.exponential = true; + * Histogram h( opts ); + * + * Generates the bucket ranges [0..125],[126..250],[251..500],[501..max_int] + */ + struct Options { + boost::uint32_t numBuckets; + boost::uint32_t bucketSize; + boost::uint32_t initialValue; + + // use exponential buckets? + bool exponential; + + Options() + : numBuckets(0) + , bucketSize(0) + , initialValue(0) + , exponential(false) {} + }; + explicit Histogram( const Options& opts ); + ~Histogram(); + + /** + * Find the bucket that 'element' falls into and increment its count. + */ + void insert( boost::uint32_t element ); + + /** + * Render the histogram as string that can be used inside an + * HTML doc. + */ + string toHTML() const; + + // testing interface below -- consider it private + + /** + * Return the count for the 'bucket'-th bucket. + */ + boost::uint64_t getCount( boost::uint32_t bucket ) const; + + /** + * Return the maximum element that would fall in the + * 'bucket'-th bucket. + */ + boost::uint32_t getBoundary( boost::uint32_t bucket ) const; + + /** + * Return the number of buckets in this histogram. + */ + boost::uint32_t getBucketsNum() const; + + private: + /** + * Returns the bucket where 'element' should fall + * into. Currently assumes that 'element' is greater than the + * minimum 'inialValue'. + */ + boost::uint32_t _findBucket( boost::uint32_t element ) const; + + boost::uint32_t _initialValue; // no value lower than it is recorded + boost::uint32_t _numBuckets; // total buckets in the histogram + + // all below owned here + boost::uint32_t* _boundaries; // maximum element of each bucket + boost::uint64_t* _buckets; // current count of each bucket + + Histogram( const Histogram& ); + Histogram& operator=( const Histogram& ); + }; + +} // namespace mongo + +#endif // UTIL_HISTOGRAM_HEADER diff --git a/src/mongo/util/intrusive_counter.cpp b/src/mongo/util/intrusive_counter.cpp new file mode 100755 index 00000000000..fc01f40b41a --- /dev/null +++ b/src/mongo/util/intrusive_counter.cpp @@ -0,0 +1,30 @@ +/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "util/intrusive_counter.h"
+
+namespace mongo {
+
+ void IntrusiveCounterUnsigned::addRef() const {
+ ++counter;
+ }
+
+ void IntrusiveCounterUnsigned::release() const {
+ if (!--counter)
+ delete this;
+ }
+
+}
diff --git a/src/mongo/util/intrusive_counter.h b/src/mongo/util/intrusive_counter.h new file mode 100755 index 00000000000..bcebb6288cf --- /dev/null +++ b/src/mongo/util/intrusive_counter.h @@ -0,0 +1,79 @@ +/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace mongo {
+
+/*
+ IntrusiveCounter is a sharable implementation of a reference counter that
+ objects can use to be compatible with boost::intrusive_ptr<>.
+
+ Some objects that use IntrusiveCounter are immutable, and only have
+ const methods. This may require their pointers to be declared as
+ intrusive_ptr<const ClassName> . In order to be able to share pointers to
+ these immutables, the methods associated with IntrusiveCounter are declared
+ as const, and the counter itself is marked as mutable.
+
+ IntrusiveCounter itself is abstract, allowing for multiple implementations.
+ For example, IntrusiveCounterUnsigned uses ordinary unsigned integers for
+ the reference count, and is good for situations where thread safety is not
+ required. For others, other implementations using atomic integers should
+ be used. For static objects, the implementations of addRef() and release()
+ can be overridden to do nothing.
+ */
+ class IntrusiveCounter :
+ boost::noncopyable {
+ public:
+ virtual ~IntrusiveCounter() {};
+
+ // these are here for the boost intrusive_ptr<> class
+ friend inline void intrusive_ptr_add_ref(const IntrusiveCounter *pIC) {
+ pIC->addRef(); };
+ friend inline void intrusive_ptr_release(const IntrusiveCounter *pIC) {
+ pIC->release(); };
+
+ virtual void addRef() const = 0;
+ virtual void release() const = 0;
+ };
+
+ class IntrusiveCounterUnsigned :
+ public IntrusiveCounter {
+ public:
+ // virtuals from IntrusiveCounter
+ virtual void addRef() const;
+ virtual void release() const;
+
+ IntrusiveCounterUnsigned();
+
+ private:
+ mutable unsigned counter;
+ };
+
+};
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline IntrusiveCounterUnsigned::IntrusiveCounterUnsigned():
+ counter(0) {
+ }
+
+};
diff --git a/src/mongo/util/log.cpp b/src/mongo/util/log.cpp new file mode 100644 index 00000000000..aa249597b57 --- /dev/null +++ b/src/mongo/util/log.cpp @@ -0,0 +1,197 @@ +/** @file log.cpp + */ + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "assert_util.h" +#include "assert.h" +#include <cmath> +#include "time_support.h" +using namespace std; + +#ifdef _WIN32 +# include <io.h> +# include <fcntl.h> +#else +# include <cxxabi.h> +# include <sys/file.h> +#endif + +#ifdef _WIN32 +# define dup2 _dup2 // Microsoft headers use ISO C names +# define fileno _fileno +#endif + +namespace mongo { + + Nullstream nullstream; + vector<Tee*>* Logstream::globalTees = 0; + + thread_specific_ptr<Logstream> Logstream::tsp; + + class LoggingManager { + public: + LoggingManager() + : _enabled(0) , _file(0) { + } + + void start( const string& lp , bool append ) { + uassert( 10268 , "LoggingManager already started" , ! _enabled ); + _append = append; + + bool exists = boost::filesystem::exists(lp); + bool isdir = boost::filesystem::is_directory(lp); + bool isreg = boost::filesystem::is_regular_file(lp); + + if ( exists ) { + if ( isdir ) { + cout << "logpath [" << lp << "] should be a filename, not a directory" << endl; + + dbexit( EXIT_BADOPTIONS ); + assert( 0 ); + } + + if ( ! append ) { + // only attempt rename if log is regular file + if ( isreg ) { + stringstream ss; + ss << lp << "." << terseCurrentTime( false ); + string s = ss.str(); + + if ( ! rename( lp.c_str() , s.c_str() ) ) { + cout << "log file [" << lp << "] exists; copied to temporary file [" << s << "]" << endl; + } else { + cout << "log file [" << lp << "] exists and couldn't make backup; run with --logappend or manually remove file (" << strerror(errno) << ")" << endl; + + dbexit( EXIT_BADOPTIONS ); + assert( 0 ); + } + } + } + } + // test path + FILE * test = fopen( lp.c_str() , _append ? "a" : "w" ); + if ( ! test ) { + cout << "can't open [" << lp << "] for log file: " << errnoWithDescription() << endl; + dbexit( EXIT_BADOPTIONS ); + assert( 0 ); + } + + if (append && exists){ + // two blank lines before and after + const string msg = "\n\n***** SERVER RESTARTED *****\n\n\n"; + massert(14036, errnoWithPrefix("couldn't write to log file"), + fwrite(msg.data(), 1, msg.size(), test) == msg.size()); + } + + fclose( test ); + + _path = lp; + _enabled = 1; + rotate(); + } + + void rotate() { + if ( ! _enabled ) { + cout << "LoggingManager not enabled" << endl; + return; + } + + if ( _file ) { + +#ifdef POSIX_FADV_DONTNEED + posix_fadvise(fileno(_file), 0, 0, POSIX_FADV_DONTNEED); +#endif + + // Rename the (open) existing log file to a timestamped name + stringstream ss; + ss << _path << "." << terseCurrentTime( false ); + string s = ss.str(); + rename( _path.c_str() , s.c_str() ); + } + + FILE* tmp = 0; // The new file using the original logpath name + +#if _WIN32 + // We rename an open log file (above, on next rotation) and the trick to getting Windows to do that is + // to open the file with FILE_SHARE_DELETE. So, we can't use the freopen() call that non-Windows + // versions use because it would open the file without the FILE_SHARE_DELETE flag we need. + // + HANDLE newFileHandle = CreateFileA( + _path.c_str(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + if ( INVALID_HANDLE_VALUE != newFileHandle ) { + int newFileDescriptor = _open_osfhandle( reinterpret_cast<intptr_t>(newFileHandle), _O_APPEND ); + tmp = _fdopen( newFileDescriptor, _append ? "a" : "w" ); + } +#else + tmp = freopen(_path.c_str(), _append ? "a" : "w", stdout); +#endif + if ( !tmp ) { + cerr << "can't open: " << _path.c_str() << " for log file" << endl; + dbexit( EXIT_BADOPTIONS ); + assert( 0 ); + } + + // redirect stdout and stderr to log file + dup2( fileno( tmp ), 1 ); // stdout + dup2( fileno( tmp ), 2 ); // stderr + + Logstream::setLogFile(tmp); // after this point no thread will be using old file + +#if _WIN32 + if ( _file ) + fclose( _file ); // In Windows, we still have the old file open, close it now +#endif + +#if 0 // enable to test redirection + cout << "written to cout" << endl; + cerr << "written to cerr" << endl; + log() << "written to log()" << endl; +#endif + + _file = tmp; // Save new file for next rotation + } + + private: + bool _enabled; + string _path; + bool _append; + FILE * _file; + + } loggingManager; + + void initLogging( const string& lp , bool append ) { + cout << "all output going to: " << lp << endl; + loggingManager.start( lp , append ); + } + + void rotateLogs( int signal ) { + loggingManager.rotate(); + } + + // done *before* static initialization + FILE* Logstream::logfile = stdout; + bool Logstream::isSyslog = false; + +} diff --git a/src/mongo/util/log.h b/src/mongo/util/log.h new file mode 100644 index 00000000000..a393d4d29a5 --- /dev/null +++ b/src/mongo/util/log.h @@ -0,0 +1,581 @@ +// @file log.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include <string.h> +#include <errno.h> +#include "../bson/util/builder.h" + +#ifndef _WIN32 +#include <syslog.h> +#endif + +namespace mongo { + + enum LogLevel { LL_DEBUG , LL_INFO , LL_NOTICE , LL_WARNING , LL_ERROR , LL_SEVERE }; + + inline const char * logLevelToString( LogLevel l ) { + switch ( l ) { + case LL_DEBUG: + case LL_INFO: + case LL_NOTICE: + return ""; + case LL_WARNING: + return "warning" ; + case LL_ERROR: + return "ERROR"; + case LL_SEVERE: + return "SEVERE"; + default: + return "UNKNOWN"; + } + } + +#ifndef _WIN32 + inline const int logLevelToSysLogLevel( LogLevel l) { + switch ( l ) { + case LL_DEBUG: + return LOG_DEBUG; + case LL_INFO: + return LOG_INFO; + case LL_NOTICE: + return LOG_NOTICE; + case LL_WARNING: + return LOG_WARNING; + case LL_ERROR: + return LOG_ERR; + case LL_SEVERE: + return LOG_CRIT; + default: + return LL_INFO; + } + } +#endif + + class LabeledLevel { + public: + + LabeledLevel( int level ) : _level( level ) {} + LabeledLevel( const char* label, int level ) : _label( label ), _level( level ) {} + LabeledLevel( const string& label, int level ) : _label( label ), _level( level ) {} + + LabeledLevel operator+( int i ) const { + return LabeledLevel( _label, _level + i ); + } + + LabeledLevel operator+( const char* label ) const { + if( _label == "" ) + return LabeledLevel( label, _level ); + return LabeledLevel( _label + string("::") + label, _level ); + } + + LabeledLevel operator+( string& label ) const { + return LabeledLevel( _label + string("::") + label, _level ); + } + + LabeledLevel operator-( int i ) const { + return LabeledLevel( _label, _level - i ); + } + + const string& getLabel() const { return _label; } + int getLevel() const { return _level; } + + private: + string _label; + int _level; + }; + + class LazyString { + public: + virtual ~LazyString() {} + virtual string val() const = 0; + }; + + // Utility class for stringifying object only when val() called. + template< class T > + class LazyStringImpl : public LazyString { + public: + LazyStringImpl( const T &t ) : t_( t ) {} + virtual string val() const { return t_.toString(); } + private: + const T& t_; + }; + + class Tee { + public: + virtual ~Tee() {} + virtual void write(LogLevel level , const string& str) = 0; + }; + + class Nullstream { + public: + virtual Nullstream& operator<< (Tee* tee) { + return *this; + } + virtual ~Nullstream() {} + virtual Nullstream& operator<<(const char *) { + return *this; + } + virtual Nullstream& operator<<(const string& ) { + return *this; + } + virtual Nullstream& operator<<(const StringData& ) { + return *this; + } + virtual Nullstream& operator<<(char *) { + return *this; + } + virtual Nullstream& operator<<(char) { + return *this; + } + virtual Nullstream& operator<<(int) { + return *this; + } + virtual Nullstream& operator<<(ExitCode) { + return *this; + } + virtual Nullstream& operator<<(unsigned long) { + return *this; + } + virtual Nullstream& operator<<(long) { + return *this; + } + virtual Nullstream& operator<<(unsigned) { + return *this; + } + virtual Nullstream& operator<<(unsigned short) { + return *this; + } + virtual Nullstream& operator<<(double) { + return *this; + } + virtual Nullstream& operator<<(void *) { + return *this; + } + virtual Nullstream& operator<<(const void *) { + return *this; + } + virtual Nullstream& operator<<(long long) { + return *this; + } + virtual Nullstream& operator<<(unsigned long long) { + return *this; + } + virtual Nullstream& operator<<(bool) { + return *this; + } + virtual Nullstream& operator<<(const LazyString&) { + return *this; + } + template< class T > + Nullstream& operator<<(T *t) { + return operator<<( static_cast<void*>( t ) ); + } + template< class T > + Nullstream& operator<<(const T *t) { + return operator<<( static_cast<const void*>( t ) ); + } + template< class T > + Nullstream& operator<<(const shared_ptr<T> p ) { + T * t = p.get(); + if ( ! t ) + *this << "null"; + else + *this << *t; + return *this; + } + template< class T > + Nullstream& operator<<(const T &t) { + return operator<<( static_cast<const LazyString&>( LazyStringImpl< T >( t ) ) ); + } + + virtual Nullstream& operator<< (ostream& ( *endl )(ostream&)) { + return *this; + } + virtual Nullstream& operator<< (ios_base& (*hex)(ios_base&)) { + return *this; + } + + virtual void flush(Tee *t = 0) {} + }; + extern Nullstream nullstream; + + class Logstream : public Nullstream { + static mongo::mutex mutex; + static int doneSetup; + stringstream ss; + int indent; + LogLevel logLevel; + static FILE* logfile; + static boost::scoped_ptr<ostream> stream; + static vector<Tee*> * globalTees; + static bool isSyslog; + public: + inline static void logLockless( const StringData& s ); + + static void setLogFile(FILE* f) { + scoped_lock lk(mutex); + logfile = f; + } +#ifndef _WIN32 + static void useSyslog(const char * name) { + cout << "using syslog ident: " << name << endl; + + // openlog requires heap allocated non changing pointer + // this should only be called once per pragram execution + + char * newName = (char *) malloc( strlen(name) + 1 ); + strcpy( newName , name); + openlog( newName , LOG_ODELAY , LOG_USER ); + isSyslog = true; + } +#endif + + static int magicNumber() { + return 1717; + } + + static int getLogDesc() { + int fd = -1; + if (logfile != NULL) +#if defined(_WIN32) + // the ISO C++ conformant name is _fileno + fd = _fileno( logfile ); +#else + fd = fileno( logfile ); +#endif + return fd; + } + + inline void flush(Tee *t = 0); + + inline Nullstream& setLogLevel(LogLevel l) { + logLevel = l; + return *this; + } + + /** note these are virtual */ + Logstream& operator<<(const char *x) { ss << x; return *this; } + Logstream& operator<<(const string& x) { ss << x; return *this; } + Logstream& operator<<(const StringData& x) { ss << x.data(); return *this; } + Logstream& operator<<(char *x) { ss << x; return *this; } + Logstream& operator<<(char x) { ss << x; return *this; } + Logstream& operator<<(int x) { ss << x; return *this; } + Logstream& operator<<(ExitCode x) { ss << x; return *this; } + Logstream& operator<<(long x) { ss << x; return *this; } + Logstream& operator<<(unsigned long x) { ss << x; return *this; } + Logstream& operator<<(unsigned x) { ss << x; return *this; } + Logstream& operator<<(unsigned short x){ ss << x; return *this; } + Logstream& operator<<(double x) { ss << x; return *this; } + Logstream& operator<<(void *x) { ss << x; return *this; } + Logstream& operator<<(const void *x) { ss << x; return *this; } + Logstream& operator<<(long long x) { ss << x; return *this; } + Logstream& operator<<(unsigned long long x) { ss << x; return *this; } + Logstream& operator<<(bool x) { ss << x; return *this; } + + Logstream& operator<<(const LazyString& x) { + ss << x.val(); + return *this; + } + Nullstream& operator<< (Tee* tee) { + ss << '\n'; + flush(tee); + return *this; + } + Logstream& operator<< (ostream& ( *_endl )(ostream&)) { + ss << '\n'; + flush(0); + return *this; + } + Logstream& operator<< (ios_base& (*_hex)(ios_base&)) { + ss << _hex; + return *this; + } + + Logstream& prolog() { + return *this; + } + + void addGlobalTee( Tee * t ) { + if ( ! globalTees ) + globalTees = new vector<Tee*>(); + globalTees->push_back( t ); + } + + void indentInc(){ indent++; } + void indentDec(){ indent--; } + int getIndent() const { return indent; } + + private: + static thread_specific_ptr<Logstream> tsp; + Logstream() { + indent = 0; + _init(); + } + void _init() { + ss.str(""); + logLevel = LL_INFO; + } + public: + static Logstream& get() { + if ( StaticObserver::_destroyingStatics ) { + cout << "Logstream::get called in uninitialized state" << endl; + } + Logstream *p = tsp.get(); + if( p == 0 ) + tsp.reset( p = new Logstream() ); + return *p; + } + }; + + extern int logLevel; + extern int tlogLevel; + + inline Nullstream& out( int level = 0 ) { + if ( level > logLevel ) + return nullstream; + return Logstream::get(); + } + + /* flush the log stream if the log level is + at the specified level or higher. */ + inline void logflush(int level = 0) { + if( level > logLevel ) + Logstream::get().flush(0); + } + + /* without prolog */ + inline Nullstream& _log( int level = 0 ) { + if ( level > logLevel ) + return nullstream; + return Logstream::get(); + } + + /** logging which we may not want during unit tests (dbtests) runs. + set tlogLevel to -1 to suppress tlog() output in a test program. */ + inline Nullstream& tlog( int level = 0 ) { + if ( level > tlogLevel || level > logLevel ) + return nullstream; + return Logstream::get().prolog(); + } + + // log if debug build or if at a certain level + inline Nullstream& dlog( int level ) { + if ( level <= logLevel || DEBUG_BUILD ) + return Logstream::get().prolog(); + return nullstream; + } + + inline Nullstream& log( int level ) { + if ( level > logLevel ) + return nullstream; + return Logstream::get().prolog(); + } + +#define MONGO_LOG(level) if ( MONGO_likely(logLevel < (level)) ) { } else log( level ) +#define LOG MONGO_LOG + + inline Nullstream& log( LogLevel l ) { + return Logstream::get().prolog().setLogLevel( l ); + } + + inline Nullstream& log( const LabeledLevel& ll ) { + Nullstream& stream = log( ll.getLevel() ); + if( ll.getLabel() != "" ) + stream << "[" << ll.getLabel() << "] "; + return stream; + } + + inline Nullstream& log() { + return Logstream::get().prolog(); + } + + inline Nullstream& error() { + return log( LL_ERROR ); + } + + inline Nullstream& warning() { + return log( LL_WARNING ); + } + + /* default impl returns "" -- mongod overrides */ + extern const char * (*getcurns)(); + + inline Nullstream& problem( int level = 0 ) { + if ( level > logLevel ) + return nullstream; + Logstream& l = Logstream::get().prolog(); + l << ' ' << getcurns() << ' '; + return l; + } + + /** + log to a file rather than stdout + defined in assert_util.cpp + */ + void initLogging( const string& logpath , bool append ); + void rotateLogs( int signal = 0 ); + + std::string toUtf8String(const std::wstring& wide); + +#if defined(_WIN32) + inline string errnoWithDescription(DWORD x = GetLastError()) { +#else + inline string errnoWithDescription(int x = errno) { +#endif + stringstream s; + s << "errno:" << x << ' '; + +#if defined(_WIN32) + LPTSTR errorText = NULL; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM + |FORMAT_MESSAGE_ALLOCATE_BUFFER + |FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + x, 0, + (LPTSTR) &errorText, // output + 0, // minimum size for output buffer + NULL); + if( errorText ) { + string x = toUtf8String(errorText); + for( string::iterator i = x.begin(); i != x.end(); i++ ) { + if( *i == '\n' || *i == '\r' ) + break; + s << *i; + } + LocalFree(errorText); + } + else + s << strerror(x); + /* + DWORD n = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, x, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, 0, NULL); + */ +#else + s << strerror(x); +#endif + return s.str(); + } + + /** output the error # and error message with prefix. + handy for use as parm in uassert/massert. + */ + string errnoWithPrefix( const char * prefix ); + + void Logstream::logLockless( const StringData& s ) { + if ( s.size() == 0 ) + return; + + if ( doneSetup == 1717 ) { +#ifndef _WIN32 + if ( isSyslog ) { + syslog( LOG_INFO , "%s" , s.data() ); + } else +#endif + if (fwrite(s.data(), s.size(), 1, logfile)) { + fflush(logfile); + } + else { + int x = errno; + cout << "Failed to write to logfile: " << errnoWithDescription(x) << endl; + } + } + else { + cout << s.data(); + cout.flush(); + } + } + + void Logstream::flush(Tee *t) { + // this ensures things are sane + if ( doneSetup == 1717 ) { + string msg = ss.str(); + string threadName = getThreadName(); + const char * type = logLevelToString(logLevel); + + int spaceNeeded = (int)(msg.size() + 64 + threadName.size()); + int bufSize = 128; + while ( bufSize < spaceNeeded ) + bufSize += 128; + + BufBuilder b(bufSize); + time_t_to_String( time(0) , b.grow(20) ); + if (!threadName.empty()) { + b.appendChar( '[' ); + b.appendStr( threadName , false ); + b.appendChar( ']' ); + b.appendChar( ' ' ); + } + + for ( int i=0; i<indent; i++ ) + b.appendChar( '\t' ); + + if ( type[0] ) { + b.appendStr( type , false ); + b.appendStr( ": " , false ); + } + + b.appendStr( msg ); + + string out( b.buf() , b.len() - 1); + + scoped_lock lk(mutex); + + if( t ) t->write(logLevel,out); + if ( globalTees ) { + for ( unsigned i=0; i<globalTees->size(); i++ ) + (*globalTees)[i]->write(logLevel,out); + } +#ifndef _WIN32 + if ( isSyslog ) { + syslog( logLevelToSysLogLevel(logLevel) , "%s" , out.data() ); + } else +#endif + if(fwrite(out.data(), out.size(), 1, logfile)) { + fflush(logfile); + } + else { + int x = errno; + cout << "Failed to write to logfile: " << errnoWithDescription(x) << ": " << out << endl; + } +#ifdef POSIX_FADV_DONTNEED + // This only applies to pages that have already been flushed + RARELY posix_fadvise(fileno(logfile), 0, 0, POSIX_FADV_DONTNEED); +#endif + } + _init(); + } + + struct LogIndentLevel { + LogIndentLevel(){ + Logstream::get().indentInc(); + } + ~LogIndentLevel(){ + Logstream::get().indentDec(); + } + }; + + extern Tee* const warnings; // Things put here go in serverStatus + +} // namespace mongo diff --git a/src/mongo/util/logfile.cpp b/src/mongo/util/logfile.cpp new file mode 100644 index 00000000000..7c362be08d1 --- /dev/null +++ b/src/mongo/util/logfile.cpp @@ -0,0 +1,253 @@ +// @file logfile.cpp simple file log writing / journaling + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" +#include "logfile.h" +#include "text.h" +#include "mongoutils/str.h" +#include "unittest.h" + +using namespace mongoutils; + +namespace mongo { + struct LogfileTest : public UnitTest { + LogfileTest() { } + void run() { + if( 0 && debug ) { + try { + LogFile f("logfile_test"); + void *p = malloc(16384); + char *buf = (char*) p; + buf += 4095; + buf = (char*) (((size_t)buf)&(~0xfff)); + memset(buf, 'z', 8192); + buf[8190] = '\n'; + buf[8191] = 'B'; + buf[0] = 'A'; + f.synchronousAppend(buf, 8192); + f.synchronousAppend(buf, 8192); + free(p); + } + catch(DBException& e ) { + log() << "logfile.cpp test failed : " << e.what() << endl; + throw; + } + } + } + } __test; +} + +#if defined(_WIN32) + +namespace mongo { + + LogFile::LogFile(string name, bool readwrite) : _name(name) { + _fd = CreateFile( + toNativeString(name.c_str()).c_str(), + (readwrite?GENERIC_READ:0)|GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, + NULL); + if( _fd == INVALID_HANDLE_VALUE ) { + DWORD e = GetLastError(); + uasserted(13518, str::stream() << "couldn't open file " << name << " for writing " << errnoWithDescription(e)); + } + SetFilePointer(_fd, 0, 0, FILE_BEGIN); + } + + LogFile::~LogFile() { + if( _fd != INVALID_HANDLE_VALUE ) + CloseHandle(_fd); + } + + void LogFile::truncate() { + verify(15870, _fd != INVALID_HANDLE_VALUE); + + if (!SetEndOfFile(_fd)){ + msgasserted(15871, "Couldn't truncate file: " + errnoWithDescription()); + } + } + + void LogFile::writeAt(unsigned long long offset, const void *_buf, size_t _len) { +// TODO 64 bit offsets + OVERLAPPED o; + memset(&o,0,sizeof(o)); + (unsigned long long&) o.Offset = offset; + BOOL ok= WriteFile(_fd, _buf, _len, 0, &o); + assert(ok); + } + + void LogFile::readAt(unsigned long long offset, void *_buf, size_t _len) { +// TODO 64 bit offsets + OVERLAPPED o; + memset(&o,0,sizeof(o)); + (unsigned long long&) o.Offset = offset; + DWORD nr; + BOOL ok = ReadFile(_fd, _buf, _len, &nr, &o); + if( !ok ) { + string e = errnoWithDescription(); + //DWORD e = GetLastError(); + log() << "LogFile readAt(" << offset << ") len:" << _len << "errno:" << e << endl; + assert(false); + } + } + + void LogFile::synchronousAppend(const void *_buf, size_t _len) { + const size_t BlockSize = 8 * 1024 * 1024; + assert(_fd); + assert(_len % 4096 == 0); + const char *buf = (const char *) _buf; + size_t left = _len; + while( left ) { + size_t toWrite = min(left, BlockSize); + DWORD written; + if( !WriteFile(_fd, buf, toWrite, &written, NULL) ) { + DWORD e = GetLastError(); + if( e == 87 ) + msgasserted(13519, "error 87 appending to file - invalid parameter"); + else + uasserted(13517, str::stream() << "error appending to file " << _name << ' ' << _len << ' ' << toWrite << ' ' << errnoWithDescription(e)); + } + else { + dassert( written == toWrite ); + } + left -= written; + buf += written; + } + } + +} + +#else + +/// posix + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "paths.h" + +namespace mongo { + + LogFile::LogFile(string name, bool readwrite) : _name(name) { + int options = O_CREAT + | (readwrite?O_RDWR:O_WRONLY) +#if defined(O_DIRECT) + | O_DIRECT +#endif +#if defined(O_NOATIME) + | O_NOATIME +#endif + ; + + _fd = open(name.c_str(), options, S_IRUSR | S_IWUSR); + +#if defined(O_DIRECT) + _direct = true; + if( _fd < 0 ) { + _direct = false; + options &= ~O_DIRECT; + _fd = open(name.c_str(), options, S_IRUSR | S_IWUSR); + } +#else + _direct = false; +#endif + + if( _fd < 0 ) { + uasserted(13516, str::stream() << "couldn't open file " << name << " for writing " << errnoWithDescription()); + } + + flushMyDirectory(name); + } + + LogFile::~LogFile() { + if( _fd >= 0 ) + close(_fd); + _fd = -1; + } + + void LogFile::truncate() { + verify(15872, _fd >= 0); + + BOOST_STATIC_ASSERT(sizeof(off_t) == 8); // we don't want overflow here + const off_t pos = lseek(_fd, 0, SEEK_CUR); // doesn't actually seek + if (ftruncate(_fd, pos) != 0){ + msgasserted(15873, "Couldn't truncate file: " + errnoWithDescription()); + } + + fsync(_fd); + } + + void LogFile::writeAt(unsigned long long offset, const void *buf, size_t len) { + assert(((size_t)buf)%4096==0); // aligned + ssize_t written = pwrite(_fd, buf, len, offset); + if( written != (ssize_t) len ) { + log() << "writeAt fails " << errnoWithDescription() << endl; + } +#if defined(__linux__) + fdatasync(_fd); +#else + fsync(_fd); +#endif + } + + void LogFile::readAt(unsigned long long offset, void *_buf, size_t _len) { + assert(((size_t)_buf)%4096==0); // aligned + ssize_t rd = pread(_fd, _buf, _len, offset); + assert( rd != -1 ); + } + + void LogFile::synchronousAppend(const void *b, size_t len) { +#ifdef POSIX_FADV_DONTNEED + const off_t pos = lseek(_fd, 0, SEEK_CUR); // doesn't actually seek, just get current position +#endif + + const char *buf = (char *) b; + assert(_fd); + assert(((size_t)buf)%4096==0); // aligned + if( len % 4096 != 0 ) { + log() << len << ' ' << len % 4096 << endl; + assert(false); + } + ssize_t written = write(_fd, buf, len); + if( written != (ssize_t) len ) { + log() << "write fails written:" << written << " len:" << len << " buf:" << buf << ' ' << errnoWithDescription() << endl; + uasserted(13515, str::stream() << "error appending to file " << _fd << ' ' << errnoWithDescription()); + } + + if( +#if defined(__linux__) + fdatasync(_fd) < 0 +#else + fsync(_fd) +#endif + ) { + uasserted(13514, str::stream() << "error appending to file on fsync " << ' ' << errnoWithDescription()); + } + +#ifdef POSIX_FADV_DONTNEED + if (!_direct) + posix_fadvise(_fd, pos, len, POSIX_FADV_DONTNEED); +#endif + } + +} + +#endif diff --git a/src/mongo/util/logfile.h b/src/mongo/util/logfile.h new file mode 100644 index 00000000000..e41ecc2f6ec --- /dev/null +++ b/src/mongo/util/logfile.h @@ -0,0 +1,58 @@ +// @file logfile.h simple file log writing / journaling + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace mongo { + + class LogFile { + public: + /** create the file and open. must not already exist. + throws UserAssertion on i/o error + */ + LogFile(string name, bool readwrite = false); + + /** closes */ + ~LogFile(); + + /** append to file. does not return until sync'd. uses direct i/o when possible. + throws UserAssertion on an i/o error + note direct i/o may have alignment requirements + */ + void synchronousAppend(const void *buf, size_t len); + + /** write at specified offset. must be aligned. noreturn until physically written. thread safe */ + void writeAt(unsigned long long offset, const void *_bug, size_t _len); + + void readAt(unsigned long long offset, void *_buf, size_t _len); + + const string _name; + + void truncate(); // Removes extra data after current position + + private: +#if defined(_WIN32) + typedef HANDLE fd_type; +#else + typedef int fd_type; +#endif + fd_type _fd; + bool _direct; // are we using direct I/O + }; + +} diff --git a/src/mongo/util/lruishmap.h b/src/mongo/util/lruishmap.h new file mode 100644 index 00000000000..ba91bf6f0f6 --- /dev/null +++ b/src/mongo/util/lruishmap.h @@ -0,0 +1,78 @@ +// lru-ish map.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../pch.h" +#include "../util/goodies.h" + +namespace mongo { + + /* Your K object must define: + int hash() - must always return > 0. + operator== + */ + + template <class K, class V, int MaxChain> + class LRUishMap { + public: + LRUishMap(int _n) { + n = nextPrime(_n); + keys = new K[n]; + hashes = new int[n]; + for ( int i = 0; i < n; i++ ) hashes[i] = 0; + } + ~LRUishMap() { + delete[] keys; + delete[] hashes; + } + + int _find(const K& k, bool& found) { + int h = k.hash(); + assert( h > 0 ); + int j = h % n; + int first = j; + for ( int i = 0; i < MaxChain; i++ ) { + if ( hashes[j] == h ) { + if ( keys[j] == k ) { + found = true; + return j; + } + } + else if ( hashes[j] == 0 ) { + found = false; + return j; + } + } + found = false; + return first; + } + + V* find(const K& k) { + bool found; + int j = _find(k, found); + return found ? &values[j] : 0; + } + + private: + int n; + K *keys; + int *hashes; + V *values; + }; + +} // namespace mongo diff --git a/src/mongo/util/md5.c b/src/mongo/util/md5.c new file mode 100644 index 00000000000..c35d96c5ef5 --- /dev/null +++ b/src/mongo/util/md5.c @@ -0,0 +1,381 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include <string.h> + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include <stdio.h> in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include <string.h> + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/src/mongo/util/md5.h b/src/mongo/util/md5.h new file mode 100644 index 00000000000..a3f3b6db0e2 --- /dev/null +++ b/src/mongo/util/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke <purschke@bnl.gov>. + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + + /* Initialize the algorithm. */ + void md5_init(md5_state_t *pms); + + /* Append a string to the message. */ + void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + + /* Finish the message and return the digest. */ + void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/src/mongo/util/md5.hpp b/src/mongo/util/md5.hpp new file mode 100644 index 00000000000..dc061719747 --- /dev/null +++ b/src/mongo/util/md5.hpp @@ -0,0 +1,58 @@ +// md5.hpp + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "md5.h" + +namespace mongo { + + typedef unsigned char md5digest[16]; + + inline void md5(const void *buf, int nbytes, md5digest digest) { + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t *) buf, nbytes); + md5_finish(&st, digest); + } + + inline void md5(const char *str, md5digest digest) { + md5(str, strlen(str), digest); + } + + inline std::string digestToString( md5digest digest ){ + static const char * letters = "0123456789abcdef"; + stringstream ss; + for ( int i=0; i<16; i++){ + unsigned char c = digest[i]; + ss << letters[ ( c >> 4 ) & 0xf ] << letters[ c & 0xf ]; + } + return ss.str(); + } + + inline std::string md5simpledigest( const void* buf, int nbytes){ + md5digest d; + md5( buf, nbytes , d ); + return digestToString( d ); + } + + inline std::string md5simpledigest( string s ){ + return md5simpledigest(s.data(), s.size()); + } + + +} // namespace mongo diff --git a/src/mongo/util/md5main.cpp b/src/mongo/util/md5main.cpp new file mode 100644 index 00000000000..9995fee8fa7 --- /dev/null +++ b/src/mongo/util/md5main.cpp @@ -0,0 +1,142 @@ +/* + Copyright (C) 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5main.c,v 1.1 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Splits off main program into a separate file, md5main.c. + */ + +#include "pch.h" +#include "md5.h" +#include <math.h> +#include <stdio.h> +#include <string.h> + +/* + * This file builds an executable that performs various functions related + * to the MD5 library. Typical compilation: + * gcc -o md5main -lm md5main.c md5.c + */ +static const char *const usage = "\ +Usage:\n\ + md5main --test # run the self-test (A.5 of RFC 1321)\n\ + md5main --t-values # print the T values for the library\n\ + md5main --version # print the version of the package\n\ +"; +static const char *const version = "2002-04-13"; + +/* modified: not static, renamed */ +/* Run the self-test. */ +/*static*/ int +//do_test(void) +do_md5_test(void) { + static const char *const test[7*2] = { + "", "d41d8cd98f00b204e9800998ecf8427e", + "a", "0cc175b9c0f1b6a831c399e269772661", + "abc", "900150983cd24fb0d6963f7d28e17f72", + "message digest", "f96b697d7cb7938d525a2f31aaf161d0", + "abcdefghijklmnopqrstuvwxyz", "c3fcd3d76192e4007dfb496cca67e13b", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "d174ab98d277d9f5a5611c2c9f419d9f", + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", "57edf4a22be3c955ac49da2e2107b67a" + }; + int i; + int status = 0; + + for (i = 0; i < 7*2; i += 2) { + md5_state_t state; + md5_byte_t digest[16]; + char hex_output[16*2 + 1]; + int di; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)test[i], strlen(test[i])); + md5_finish(&state, digest); + for (di = 0; di < 16; ++di) + sprintf(hex_output + di * 2, "%02x", digest[di]); + if (strcmp(hex_output, test[i + 1])) { + printf("MD5 (\"%s\") = ", test[i]); + puts(hex_output); + printf("**** ERROR, should be: %s\n", test[i + 1]); + status = 1; + } + } +// if (status == 0) + /*modified commented out: puts("md5 self-test completed successfully."); */ + return status; +} + +/* Print the T values. */ +static int +do_t_values(void) { + int i; + for (i = 1; i <= 64; ++i) { + unsigned long v = (unsigned long)(4294967296.0 * fabs(sin((double)i))); + + /* + * The following nonsense is only to avoid compiler warnings about + * "integer constant is unsigned in ANSI C, signed with -traditional". + */ + if (v >> 31) { + printf("#define T%d /* 0x%08lx */ (T_MASK ^ 0x%08lx)\n", i, + v, (unsigned long)(unsigned int)(~v)); + } + else { + printf("#define T%d 0x%08lx\n", i, v); + } + } + return 0; +} + +/* modified from original code changed function name main->md5main */ +/* Main program */ +int +md5main(int argc, char *argv[]) { + if (argc == 2) { + if (!strcmp(argv[1], "--test")) + return do_md5_test(); + if (!strcmp(argv[1], "--t-values")) + return do_t_values(); + if (!strcmp(argv[1], "--version")) { + puts(version); + return 0; + } + } + puts(usage); + return 0; +} + diff --git a/src/mongo/util/mmap.cpp b/src/mongo/util/mmap.cpp new file mode 100755 index 00000000000..1eb0242e657 --- /dev/null +++ b/src/mongo/util/mmap.cpp @@ -0,0 +1,211 @@ +// mmap.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "mmap.h" +#include "processinfo.h" +#include "concurrency/rwlock.h" +#include "../db/namespace.h" +#include "../db/cmdline.h" + +namespace mongo { + + set<MongoFile*> MongoFile::mmfiles; + map<string,MongoFile*> MongoFile::pathToFile; + + /* Create. Must not exist. + @param zero fill file with zeros when true + */ + void* MemoryMappedFile::create(string filename, unsigned long long len, bool zero) { + uassert( 13468, string("can't create file already exists ") + filename, !exists(filename) ); + void *p = map(filename.c_str(), len); + if( p && zero ) { + size_t sz = (size_t) len; + assert( len == sz ); + memset(p, 0, sz); + } + return p; + } + + /*static*/ void MemoryMappedFile::updateLength( const char *filename, unsigned long long &length ) { + if ( !boost::filesystem::exists( filename ) ) + return; + // make sure we map full length if preexisting file. + boost::uintmax_t l = boost::filesystem::file_size( filename ); + length = l; + } + + void* MemoryMappedFile::map(const char *filename) { + unsigned long long l; + try { + l = boost::filesystem::file_size( filename ); + } + catch(boost::filesystem::filesystem_error& e) { + uasserted(15922, str::stream() << "couldn't get file length when opening mapping " << filename << ' ' << e.what() ); + } + return map( filename , l ); + } + void* MemoryMappedFile::mapWithOptions(const char *filename, int options) { + unsigned long long l; + try { + l = boost::filesystem::file_size( filename ); + } + catch(boost::filesystem::filesystem_error& e) { + uasserted(15923, str::stream() << "couldn't get file length when opening mapping " << filename << ' ' << e.what() ); + } + return map( filename , l, options ); + } + + /* --- MongoFile ------------------------------------------------- + this is the administrative stuff + */ + + RWLockRecursiveNongreedy LockMongoFilesShared::mmmutex("mmmutex",10*60*1000 /* 10 minutes */); + unsigned LockMongoFilesShared::era = 99; // note this rolls over + + /* subclass must call in destructor (or at close). + removes this from pathToFile and other maps + safe to call more than once, albeit might be wasted work + ideal to call close to the close, if the close is well before object destruction + */ + void MongoFile::destroyed() { + LockMongoFilesShared::assertExclusivelyLocked(); + mmfiles.erase(this); + pathToFile.erase( filename() ); + } + + /*static*/ + void MongoFile::closeAllFiles( stringstream &message ) { + static int closingAllFiles = 0; + if ( closingAllFiles ) { + message << "warning closingAllFiles=" << closingAllFiles << endl; + return; + } + ++closingAllFiles; + + LockMongoFilesExclusive lk; + + ProgressMeter pm( mmfiles.size() , 2 , 1 ); + set<MongoFile*> temp = mmfiles; + for ( set<MongoFile*>::iterator i = temp.begin(); i != temp.end(); i++ ) { + (*i)->close(); // close() now removes from mmfiles + pm.hit(); + } + message << "closeAllFiles() finished"; + --closingAllFiles; + } + + /*static*/ long long MongoFile::totalMappedLength() { + unsigned long long total = 0; + + LockMongoFilesShared lk; + + for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) + total += (*i)->length(); + + return total; + } + + void nullFunc() { } + + // callback notifications + void (*MongoFile::notifyPreFlush)() = nullFunc; + void (*MongoFile::notifyPostFlush)() = nullFunc; + + /*static*/ int MongoFile::flushAll( bool sync ) { + notifyPreFlush(); + int x = _flushAll(sync); + notifyPostFlush(); + return x; + } + + /*static*/ int MongoFile::_flushAll( bool sync ) { + if ( ! sync ) { + int num = 0; + LockMongoFilesShared lk; + for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) { + num++; + MongoFile * mmf = *i; + if ( ! mmf ) + continue; + + mmf->flush( sync ); + } + return num; + } + + // want to do it sync + set<MongoFile*> seen; + while ( true ) { + auto_ptr<Flushable> f; + { + LockMongoFilesShared lk; + for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) { + MongoFile * mmf = *i; + if ( ! mmf ) + continue; + if ( seen.count( mmf ) ) + continue; + f.reset( mmf->prepareFlush() ); + seen.insert( mmf ); + break; + } + } + if ( ! f.get() ) + break; + + f->flush(); + } + return seen.size(); + } + + void MongoFile::created() { + LockMongoFilesExclusive lk; + mmfiles.insert(this); + } + + void MongoFile::setFilename(string fn) { + LockMongoFilesExclusive lk; + assert( _filename.empty() ); + _filename = fn; + MongoFile *&ptf = pathToFile[fn]; + massert(13617, "MongoFile : multiple opens of same filename", ptf == 0); + ptf = this; + } + +#if defined(_DEBUG) + void MongoFile::markAllWritable() { + if( cmdLine.dur ) + return; + LockMongoFilesShared lk; + for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) { + MongoFile * mmf = *i; + if (mmf) mmf->_lock(); + } + } + + void MongoFile::unmarkAllWritable() { + if( cmdLine.dur ) + return; + LockMongoFilesShared lk; + for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) { + MongoFile * mmf = *i; + if (mmf) mmf->_unlock(); + } + } +#endif +} // namespace mongo diff --git a/src/mongo/util/mmap.h b/src/mongo/util/mmap.h new file mode 100644 index 00000000000..2d4454bbc7f --- /dev/null +++ b/src/mongo/util/mmap.h @@ -0,0 +1,305 @@ +// mmap.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once +#include <boost/thread/xtime.hpp> +#include "concurrency/rwlock.h" + +namespace mongo { + + class MAdvise { + void *_p; + unsigned _len; + public: + enum Advice { Sequential=1 }; + MAdvise(void *p, unsigned len, Advice a); + ~MAdvise(); // destructor resets the range to MADV_NORMAL + }; + + // lock order: lock dbMutex before this if you lock both + class LockMongoFilesShared { + friend class LockMongoFilesExclusive; + static RWLockRecursiveNongreedy mmmutex; + static unsigned era; + RWLockRecursive::Shared lk; + public: + LockMongoFilesShared() : lk(mmmutex) { } + + /** era changes anytime memory maps come and go. thus you can use this as a cheap way to verify + that things are still in the condition you expected. of course you must be shared locked + otherwise someone could be in progress. if you have unlocked this is a reasonable way to + check your memory mapped pointer is still good. + */ + static unsigned getEra() { return era; } + + static void assertExclusivelyLocked() { mmmutex.assertExclusivelyLocked(); } + }; + + class LockMongoFilesExclusive { + RWLockRecursive::Exclusive lk; + public: + LockMongoFilesExclusive() : lk(LockMongoFilesShared::mmmutex) { + LockMongoFilesShared::era++; + } + }; + + /* the administrative-ish stuff here */ + class MongoFile : boost::noncopyable { + public: + /** Flushable has to fail nicely if the underlying object gets killed */ + class Flushable { + public: + virtual ~Flushable() {} + virtual void flush() = 0; + }; + + virtual ~MongoFile() {} + + enum Options { + SEQUENTIAL = 1, // hint - e.g. FILE_FLAG_SEQUENTIAL_SCAN on windows + READONLY = 2 // not contractually guaranteed, but if specified the impl has option to fault writes + }; + + /** @param fun is called for each MongoFile. + calledl from within a mutex that MongoFile uses. so be careful not to deadlock. + */ + template < class F > + static void forEach( F fun ); + + /** note: you need to be in mmmutex when using this. forEach (above) handles that for you automatically. +*/ + static set<MongoFile*>& getAllFiles() { return mmfiles; } + + // callbacks if you need them + static void (*notifyPreFlush)(); + static void (*notifyPostFlush)(); + + static int flushAll( bool sync ); // returns n flushed + static long long totalMappedLength(); + static void closeAllFiles( stringstream &message ); + +#if defined(_DEBUG) + static void markAllWritable(); + static void unmarkAllWritable(); +#else + static void markAllWritable() { } + static void unmarkAllWritable() { } +#endif + + static bool exists(boost::filesystem::path p) { return boost::filesystem::exists(p); } + + virtual bool isMongoMMF() { return false; } + + string filename() const { return _filename; } + void setFilename(string fn); + + private: + string _filename; + static int _flushAll( bool sync ); // returns n flushed + protected: + virtual void close() = 0; + virtual void flush(bool sync) = 0; + /** + * returns a thread safe object that you can call flush on + * Flushable has to fail nicely if the underlying object gets killed + */ + virtual Flushable * prepareFlush() = 0; + + void created(); /* subclass must call after create */ + + /* subclass must call in destructor (or at close). + removes this from pathToFile and other maps + safe to call more than once, albeit might be wasted work + ideal to call close to the close, if the close is well before object destruction + */ + void destroyed(); + + virtual unsigned long long length() const = 0; + + // only supporting on posix mmap + virtual void _lock() {} + virtual void _unlock() {} + + static set<MongoFile*> mmfiles; + public: + static map<string,MongoFile*> pathToFile; + }; + + /** look up a MMF by filename. scoped mutex locking convention. + example: + MMFFinderByName finder; + MongoMMF *a = finder.find("file_name_a"); + MongoMMF *b = finder.find("file_name_b"); + */ + class MongoFileFinder : boost::noncopyable { + public: + MongoFileFinder() { } + + /** @return The MongoFile object associated with the specified file name. If no file is open + with the specified name, returns null. + */ + MongoFile* findByPath(string path) { + map<string,MongoFile*>::iterator i = MongoFile::pathToFile.find(path); + return i == MongoFile::pathToFile.end() ? NULL : i->second; + } + + private: + LockMongoFilesShared _lk; + }; + + struct MongoFileAllowWrites { + MongoFileAllowWrites() { + MongoFile::markAllWritable(); + } + ~MongoFileAllowWrites() { + MongoFile::unmarkAllWritable(); + } + }; + + class MemoryMappedFile : public MongoFile { + protected: + virtual void* viewForFlushing() { + if( views.size() == 0 ) + return 0; + assert( views.size() == 1 ); + return views[0]; + } + public: + MemoryMappedFile(); + + virtual ~MemoryMappedFile() { + LockMongoFilesExclusive lk; + close(); + } + + virtual void close(); + + // Throws exception if file doesn't exist. (dm may2010: not sure if this is always true?) + void* map(const char *filename); + + /** @param options see MongoFile::Options + */ + void* mapWithOptions(const char *filename, int options); + + /* Creates with length if DNE, otherwise uses existing file length, + passed length. + @param options MongoFile::Options bits + */ + void* map(const char *filename, unsigned long long &length, int options = 0 ); + + /* Create. Must not exist. + @param zero fill file with zeros when true + */ + void* create(string filename, unsigned long long len, bool zero); + + void flush(bool sync); + virtual Flushable * prepareFlush(); + + long shortLength() const { return (long) len; } + unsigned long long length() const { return len; } + + /** create a new view with the specified properties. + automatically cleaned up upon close/destruction of the MemoryMappedFile object. + */ + void* createReadOnlyMap(); + void* createPrivateMap(); + + /** make the private map range writable (necessary for our windows implementation) */ + static void makeWritable(void *, unsigned len) +#if defined(_WIN32) + ; +#else + { } +#endif + + private: + static void updateLength( const char *filename, unsigned long long &length ); + + HANDLE fd; + HANDLE maphandle; + vector<void *> views; + unsigned long long len; + +#ifdef _WIN32 + boost::shared_ptr<mutex> _flushMutex; + void clearWritableBits(void *privateView); + public: + static const unsigned ChunkSize = 64 * 1024 * 1024; + static const unsigned NChunks = 1024 * 1024; +#else + void clearWritableBits(void *privateView) { } +#endif + + protected: + // only posix mmap implementations will support this + virtual void _lock(); + virtual void _unlock(); + + /** close the current private view and open a new replacement */ + void* remapPrivateView(void *oldPrivateAddr); + }; + + typedef MemoryMappedFile MMF; + + /** p is called from within a mutex that MongoFile uses. so be careful not to deadlock. */ + template < class F > + inline void MongoFile::forEach( F p ) { + LockMongoFilesShared lklk; + for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) + p(*i); + } + +#if defined(_WIN32) + class ourbitset { + volatile unsigned bits[MemoryMappedFile::NChunks]; // volatile as we are doing double check locking + public: + ourbitset() { + memset((void*) bits, 0, sizeof(bits)); + } + bool get(unsigned i) const { + unsigned x = i / 32; + assert( x < MemoryMappedFile::NChunks ); + return (bits[x] & (1 << (i%32))) != 0; + } + void set(unsigned i) { + unsigned x = i / 32; + wassert( x < (MemoryMappedFile::NChunks*2/3) ); // warn if getting close to limit + assert( x < MemoryMappedFile::NChunks ); + bits[x] |= (1 << (i%32)); + } + void clear(unsigned i) { + unsigned x = i / 32; + assert( x < MemoryMappedFile::NChunks ); + bits[x] &= ~(1 << (i%32)); + } + }; + extern ourbitset writable; + void makeChunkWritable(size_t chunkno); + inline void MemoryMappedFile::makeWritable(void *_p, unsigned len) { + size_t p = (size_t) _p; + unsigned a = p/ChunkSize; + unsigned b = (p+len)/ChunkSize; + for( unsigned i = a; i <= b; i++ ) { + if( !writable.get(i) ) { + makeChunkWritable(i); + } + } + } + +#endif + +} // namespace mongo diff --git a/src/mongo/util/mmap_mm.cpp b/src/mongo/util/mmap_mm.cpp new file mode 100644 index 00000000000..ec2400e02d3 --- /dev/null +++ b/src/mongo/util/mmap_mm.cpp @@ -0,0 +1,52 @@ +// mmap_mm.cpp - in memory (no file) version + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "mmap.h" + +/* in memory (no file) version */ + +namespace mongo { + + MemoryMappedFile::MemoryMappedFile() { + fd = 0; + maphandle = 0; + view = 0; + len = 0; + } + + void MemoryMappedFile::close() { + if ( view ) + free( view ); + view = 0; + len = 0; + } + + void* MemoryMappedFile::map(const char *filename, long& length , int options ) { + assert( length ); + view = malloc( length ); + return view; + } + + void MemoryMappedFile::flush(bool sync) { + } + + void MemoryMappedFile::_lock() {} + void MemoryMappedFile::_unlock() {} + +} + diff --git a/src/mongo/util/mmap_posix.cpp b/src/mongo/util/mmap_posix.cpp new file mode 100644 index 00000000000..8097ef1b370 --- /dev/null +++ b/src/mongo/util/mmap_posix.cpp @@ -0,0 +1,214 @@ +// mmap_posix.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "mmap.h" +#include "file_allocator.h" +#include "../db/concurrency.h" +#include <errno.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "../util/processinfo.h" +#include "mongoutils/str.h" +using namespace mongoutils; + +namespace mongo { + + MemoryMappedFile::MemoryMappedFile() { + fd = 0; + maphandle = 0; + len = 0; + created(); + } + + void MemoryMappedFile::close() { + LockMongoFilesShared::assertExclusivelyLocked(); + for( vector<void*>::iterator i = views.begin(); i != views.end(); i++ ) { + munmap(*i,len); + } + views.clear(); + + if ( fd ) + ::close(fd); + fd = 0; + destroyed(); // cleans up from the master list of mmaps + } + +#ifndef O_NOATIME +#define O_NOATIME (0) +#endif + +#ifndef MAP_NORESERVE +#define MAP_NORESERVE (0) +#endif + +#if defined(__sunos__) + MAdvise::MAdvise(void *,unsigned, Advice) { } + MAdvise::~MAdvise() { } +#else + MAdvise::MAdvise(void *p, unsigned len, Advice a) : _p(p), _len(len) { + assert( a == Sequential ); // more later + madvise(_p,_len,MADV_SEQUENTIAL); + } + MAdvise::~MAdvise() { + madvise(_p,_len,MADV_NORMAL); + } +#endif + + void* MemoryMappedFile::map(const char *filename, unsigned long long &length, int options) { + // length may be updated by callee. + setFilename(filename); + FileAllocator::get()->allocateAsap( filename, length ); + len = length; + + massert( 10446 , str::stream() << "mmap: can't map area of size 0 file: " << filename, length > 0 ); + + fd = open(filename, O_RDWR | O_NOATIME); + if ( fd <= 0 ) { + log() << "couldn't open " << filename << ' ' << errnoWithDescription() << endl; + fd = 0; // our sentinel for not opened + return 0; + } + + unsigned long long filelen = lseek(fd, 0, SEEK_END); + uassert(10447, str::stream() << "map file alloc failed, wanted: " << length << " filelen: " << filelen << ' ' << sizeof(size_t), filelen == length ); + lseek( fd, 0, SEEK_SET ); + + void * view = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if ( view == MAP_FAILED ) { + error() << " mmap() failed for " << filename << " len:" << length << " " << errnoWithDescription() << endl; + if ( errno == ENOMEM ) { + if( sizeof(void*) == 4 ) + error() << "mmap failed with out of memory. You are using a 32-bit build and probably need to upgrade to 64" << endl; + else + error() << "mmap failed with out of memory. (64 bit build)" << endl; + } + return 0; + } + + +#if defined(__sunos__) +#warning madvise not supported on solaris yet +#else + if ( options & SEQUENTIAL ) { + if ( madvise( view , length , MADV_SEQUENTIAL ) ) { + warning() << "map: madvise failed for " << filename << ' ' << errnoWithDescription() << endl; + } + } +#endif + + views.push_back( view ); + + DEV if (! d.dbMutex.info().isLocked()) { + _unlock(); + } + + return view; + } + + void* MemoryMappedFile::createReadOnlyMap() { + void * x = mmap( /*start*/0 , len , PROT_READ , MAP_SHARED , fd , 0 ); + if( x == MAP_FAILED ) { + if ( errno == ENOMEM ) { + if( sizeof(void*) == 4 ) + error() << "mmap ro failed with out of memory. You are using a 32-bit build and probably need to upgrade to 64" << endl; + else + error() << "mmap ro failed with out of memory. (64 bit build)" << endl; + } + return 0; + } + return x; + } + + void* MemoryMappedFile::createPrivateMap() { + void * x = mmap( /*start*/0 , len , PROT_READ|PROT_WRITE , MAP_PRIVATE|MAP_NORESERVE , fd , 0 ); + if( x == MAP_FAILED ) { + if ( errno == ENOMEM ) { + if( sizeof(void*) == 4 ) { + error() << "mmap private failed with out of memory. You are using a 32-bit build and probably need to upgrade to 64" << endl; + } + else { + error() << "mmap private failed with out of memory. (64 bit build)" << endl; + } + } + else { + error() << "mmap private failed " << errnoWithDescription() << endl; + } + return 0; + } + + views.push_back(x); + return x; + } + + void* MemoryMappedFile::remapPrivateView(void *oldPrivateAddr) { + // don't unmap, just mmap over the old region + void * x = mmap( oldPrivateAddr, len , PROT_READ|PROT_WRITE , MAP_PRIVATE|MAP_NORESERVE|MAP_FIXED , fd , 0 ); + if( x == MAP_FAILED ) { + int err = errno; + error() << "13601 Couldn't remap private view: " << errnoWithDescription(err) << endl; + log() << "aborting" << endl; + printMemInfo(); + abort(); + } + assert( x == oldPrivateAddr ); + return x; + } + + void MemoryMappedFile::flush(bool sync) { + if ( views.empty() || fd == 0 ) + return; + if ( msync(viewForFlushing(), len, sync ? MS_SYNC : MS_ASYNC) ) + problem() << "msync " << errnoWithDescription() << endl; + } + + class PosixFlushable : public MemoryMappedFile::Flushable { + public: + PosixFlushable( void * view , HANDLE fd , long len ) + : _view( view ) , _fd( fd ) , _len(len) { + } + + void flush() { + if ( _view && _fd ) + if ( msync(_view, _len, MS_SYNC ) ) + problem() << "msync " << errnoWithDescription() << endl; + + } + + void * _view; + HANDLE _fd; + long _len; + }; + + MemoryMappedFile::Flushable * MemoryMappedFile::prepareFlush() { + return new PosixFlushable( viewForFlushing() , fd , len ); + } + + void MemoryMappedFile::_lock() { + if (! views.empty() && isMongoMMF() ) + assert(mprotect(views[0], len, PROT_READ | PROT_WRITE) == 0); + } + + void MemoryMappedFile::_unlock() { + if (! views.empty() && isMongoMMF() ) + assert(mprotect(views[0], len, PROT_READ) == 0); + } + +} // namespace mongo + diff --git a/src/mongo/util/mmap_win.cpp b/src/mongo/util/mmap_win.cpp new file mode 100644 index 00000000000..26115d096c1 --- /dev/null +++ b/src/mongo/util/mmap_win.cpp @@ -0,0 +1,202 @@ +// mmap_win.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "mmap.h" +#include "text.h" +#include "../db/mongommf.h" +#include "../db/concurrency.h" + +namespace mongo { + + mutex mapViewMutex("mapView"); + ourbitset writable; + + MAdvise::MAdvise(void *,unsigned, Advice) { } + MAdvise::~MAdvise() { } + + /** notification on unmapping so we can clear writable bits */ + void MemoryMappedFile::clearWritableBits(void *p) { + for( unsigned i = ((size_t)p)/ChunkSize; i <= (((size_t)p)+len)/ChunkSize; i++ ) { + writable.clear(i); + assert( !writable.get(i) ); + } + } + + MemoryMappedFile::MemoryMappedFile() + : _flushMutex(new mutex("flushMutex")) { + fd = 0; + maphandle = 0; + len = 0; + created(); + } + + void MemoryMappedFile::close() { + LockMongoFilesShared::assertExclusivelyLocked(); + for( vector<void*>::iterator i = views.begin(); i != views.end(); i++ ) { + clearWritableBits(*i); + UnmapViewOfFile(*i); + } + views.clear(); + if ( maphandle ) + CloseHandle(maphandle); + maphandle = 0; + if ( fd ) + CloseHandle(fd); + fd = 0; + destroyed(); // cleans up from the master list of mmaps + } + + unsigned long long mapped = 0; + + void* MemoryMappedFile::createReadOnlyMap() { + assert( maphandle ); + scoped_lock lk(mapViewMutex); + void *p = MapViewOfFile(maphandle, FILE_MAP_READ, /*f ofs hi*/0, /*f ofs lo*/ 0, /*dwNumberOfBytesToMap 0 means to eof*/0); + if ( p == 0 ) { + DWORD e = GetLastError(); + log() << "FILE_MAP_READ MapViewOfFile failed " << filename() << " " << errnoWithDescription(e) << endl; + } + else { + views.push_back(p); + } + return p; + } + + void* MemoryMappedFile::map(const char *filenameIn, unsigned long long &length, int options) { + assert( fd == 0 && len == 0 ); // can't open more than once + setFilename(filenameIn); + /* big hack here: Babble uses db names with colons. doesn't seem to work on windows. temporary perhaps. */ + char filename[256]; + strncpy(filename, filenameIn, 255); + filename[255] = 0; + { + size_t len = strlen( filename ); + for ( size_t i=len-1; i>=0; i-- ) { + if ( filename[i] == '/' || + filename[i] == '\\' ) + break; + + if ( filename[i] == ':' ) + filename[i] = '_'; + } + } + + updateLength( filename, length ); + + { + DWORD createOptions = FILE_ATTRIBUTE_NORMAL; + if ( options & SEQUENTIAL ) + createOptions |= FILE_FLAG_SEQUENTIAL_SCAN; + DWORD rw = GENERIC_READ | GENERIC_WRITE; + fd = CreateFile( + toNativeString(filename).c_str(), + rw, // desired access + FILE_SHARE_WRITE | FILE_SHARE_READ, // share mode + NULL, // security + OPEN_ALWAYS, // create disposition + createOptions , // flags + NULL); // hTempl + if ( fd == INVALID_HANDLE_VALUE ) { + DWORD e = GetLastError(); + log() << "Create/OpenFile failed " << filename << " errno:" << e << endl; + return 0; + } + } + + mapped += length; + + { + DWORD flProtect = PAGE_READWRITE; //(options & READONLY)?PAGE_READONLY:PAGE_READWRITE; + maphandle = CreateFileMapping(fd, NULL, flProtect, + length >> 32 /*maxsizehigh*/, + (unsigned) length /*maxsizelow*/, + NULL/*lpName*/); + if ( maphandle == NULL ) { + DWORD e = GetLastError(); // log() call was killing lasterror before we get to that point in the stream + log() << "CreateFileMapping failed " << filename << ' ' << errnoWithDescription(e) << endl; + close(); + return 0; + } + } + + void *view = 0; + { + scoped_lock lk(mapViewMutex); + DWORD access = (options&READONLY)? FILE_MAP_READ : FILE_MAP_ALL_ACCESS; + view = MapViewOfFile(maphandle, access, /*f ofs hi*/0, /*f ofs lo*/ 0, /*dwNumberOfBytesToMap 0 means to eof*/0); + } + if ( view == 0 ) { + DWORD e = GetLastError(); + log() << "MapViewOfFile failed " << filename << " " << errnoWithDescription(e) << + ((sizeof(void*)==4)?" (32 bit build)":"") << endl; + close(); + } + else { + views.push_back(view); + } + len = length; + + return view; + } + + class WindowsFlushable : public MemoryMappedFile::Flushable { + public: + WindowsFlushable( void * view , HANDLE fd , string filename , boost::shared_ptr<mutex> flushMutex ) + : _view(view) , _fd(fd) , _filename(filename) , _flushMutex(flushMutex) + {} + + void flush() { + if (!_view || !_fd) + return; + + scoped_lock lk(*_flushMutex); + + BOOL success = FlushViewOfFile(_view, 0); // 0 means whole mapping + if (!success) { + int err = GetLastError(); + out() << "FlushViewOfFile failed " << err << " file: " << _filename << endl; + } + + success = FlushFileBuffers(_fd); + if (!success) { + int err = GetLastError(); + out() << "FlushFileBuffers failed " << err << " file: " << _filename << endl; + } + } + + void * _view; + HANDLE _fd; + string _filename; + boost::shared_ptr<mutex> _flushMutex; + }; + + void MemoryMappedFile::flush(bool sync) { + uassert(13056, "Async flushing not supported on windows", sync); + if( !views.empty() ) { + WindowsFlushable f( viewForFlushing() , fd , filename() , _flushMutex); + f.flush(); + } + } + + MemoryMappedFile::Flushable * MemoryMappedFile::prepareFlush() { + return new WindowsFlushable( viewForFlushing() , fd , filename() , _flushMutex ); + } + void MemoryMappedFile::_lock() {} + void MemoryMappedFile::_unlock() {} + +} diff --git a/src/mongo/util/mongoutils/README b/src/mongo/util/mongoutils/README new file mode 100755 index 00000000000..f61277c7409 --- /dev/null +++ b/src/mongo/util/mongoutils/README @@ -0,0 +1,15 @@ + mongoutils namespace requirements: + + (1) code is not database specific, rather, true utilities + (2) are cross platform + (3) may require boost headers, but not libs + (4) are clean and easy to use in any c++ project without pulling in lots of other stuff. + specifically, does not use anything in the mongo namespace! + (5) apache license + (6) must be documented! if you aren't going to bother (but don't do that), stick it in util. + (7) ideally header only (in the spirit of #3) + + So basically, easy to use, general purpose stuff, with no arduous dependencies to drop into + any new project. + + *** PLACE UNIT TESTS IN mongoutils/test.cpp *** diff --git a/src/mongo/util/mongoutils/checksum.h b/src/mongo/util/mongoutils/checksum.h new file mode 100644 index 00000000000..ea3d05131ce --- /dev/null +++ b/src/mongo/util/mongoutils/checksum.h @@ -0,0 +1,32 @@ +/** @file checksum.h */ + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongoutils { + + /** + * this is a silly temporary implementation + */ + inline int checksum( const char* x , int size ) { + int ck = 0; + for ( int i=0; i<size; i++ ) + ck += ( (int)x[i] * ( i + 1 ) ); + return ck; + } + +} diff --git a/src/mongo/util/mongoutils/hash.h b/src/mongo/util/mongoutils/hash.h new file mode 100644 index 00000000000..49f30b3242a --- /dev/null +++ b/src/mongo/util/mongoutils/hash.h @@ -0,0 +1,41 @@ +/** @file hash.h */ + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongoutils { + + /** @return hash of a pointer to an unsigned. so you get a 32 bit hash out, regardless of whether + pointers are 32 or 64 bit on the particular platform. + + is there a faster way to impl this that hashes just as well? + */ + inline unsigned hashPointer(void *v) { + unsigned x = 0; + unsigned char *p = (unsigned char *) &v; + for( unsigned i = 0; i < sizeof(void*); i++ ) { + x = x * 131 + p[i]; + } + return x; + } + + inline unsigned hash(unsigned u) { + unsigned char *p = (unsigned char *) &u; + return (((((p[3] * 131) + p[2]) * 131) + p[1]) * 131) + p[0]; + } + +} diff --git a/src/mongo/util/mongoutils/html.h b/src/mongo/util/mongoutils/html.h new file mode 100644 index 00000000000..f79e6ca514f --- /dev/null +++ b/src/mongo/util/mongoutils/html.h @@ -0,0 +1,158 @@ +// @file html.h + +#pragma once + +/* Things in the mongoutils namespace + (1) are not database specific, rather, true utilities + (2) are cross platform + (3) may require boost headers, but not libs + (4) are clean and easy to use in any c++ project without pulling in lots of other stuff +*/ + +/* Copyright 2010 10gen 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. + */ + +#include <sstream> + +namespace mongoutils { + + namespace html { + + using namespace std; + + inline string _end() { return "</body></html>"; } + inline string _table() { return "</table>\n\n"; } + inline string _tr() { return "</tr>\n"; } + + inline string tr() { return "<tr>"; } + inline string tr(string a, string b) { + stringstream ss; + ss << "<tr><td>" << a << "</td><td>" << b << "</td></tr>\n"; + return ss.str(); + } + template <class T> + inline string td(T x) { + stringstream ss; + ss << "<td>" << x << "</td>"; + return ss.str(); + } + inline string td(string x) { + return "<td>" + x + "</td>"; + } + inline string th(string x) { + return "<th>" + x + "</th>"; + } + + inline void tablecell( stringstream& ss , bool b ) { + ss << "<td>" << (b ? "<b>X</b>" : "") << "</td>"; + } + + template< typename T> + inline void tablecell( stringstream& ss , const T& t ) { + ss << "<td>" << t << "</td>"; + } + + inline string table(const char *headers[] = 0, bool border = true) { + stringstream ss; + ss << "\n<table " + << (border?"border=1 ":"") + << "cellpadding=2 cellspacing=0>\n"; + if( headers ) { + ss << "<tr>"; + while( *headers ) { + ss << "<th>" << *headers << "</th>"; + headers++; + } + ss << "</tr>\n"; + } + return ss.str(); + } + + inline string start(string title) { + stringstream ss; + ss << "<html><head>\n<title>"; + ss << title; + ss << "</title>\n"; + + ss << "<style type=\"text/css\" media=\"screen\">" + "body { font-family: helvetica, arial, san-serif }\n" + "table { border-collapse:collapse; border-color:#999; margin-top:.5em }\n" + "th { background-color:#bbb; color:#000 }\n" + "td,th { padding:.25em }\n" + "</style>\n"; + + ss << "</head>\n<body>\n"; + return ss.str(); + } + + inline string red(string contentHtml, bool color=true) { + if( !color ) return contentHtml; + stringstream ss; + ss << "<span style=\"color:#A00;\">" << contentHtml << "</span>"; + return ss.str(); + } + inline string grey(string contentHtml, bool color=true) { + if( !color ) return contentHtml; + stringstream ss; + ss << "<span style=\"color:#888;\">" << contentHtml << "</span>"; + return ss.str(); + } + inline string blue(string contentHtml, bool color=true) { + if( !color ) return contentHtml; + stringstream ss; + ss << "<span style=\"color:#00A;\">" << contentHtml << "</span>"; + return ss.str(); + } + inline string yellow(string contentHtml, bool color=true) { + if( !color ) return contentHtml; + stringstream ss; + ss << "<span style=\"color:#A80;\">" << contentHtml << "</span>"; + return ss.str(); + } + inline string green(string contentHtml, bool color=true) { + if( !color ) return contentHtml; + stringstream ss; + ss << "<span style=\"color:#0A0;\">" << contentHtml << "</span>"; + return ss.str(); + } + + inline string p(string contentHtml) { + stringstream ss; + ss << "<p>" << contentHtml << "</p>\n"; + return ss.str(); + } + + inline string h2(string contentHtml) { + stringstream ss; + ss << "<h2>" << contentHtml << "</h2>\n"; + return ss.str(); + } + + /* does NOT escape the strings. */ + inline string a(string href, string title="", string contentHtml = "") { + stringstream ss; + ss << "<a"; + if( !href.empty() ) ss << " href=\"" << href << '"'; + if( !title.empty() ) ss << " title=\"" << title << '"'; + ss << '>'; + if( !contentHtml.empty() ) { + ss << contentHtml << "</a>"; + } + return ss.str(); + } + + } + +} diff --git a/src/mongo/util/mongoutils/mongoutils.vcxproj b/src/mongo/util/mongoutils/mongoutils.vcxproj new file mode 100755 index 00000000000..f6ec0935ca9 --- /dev/null +++ b/src/mongo/util/mongoutils/mongoutils.vcxproj @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{7B84584E-92BC-4DB9-971B-A1A8F93E5053}</ProjectGuid>
+ <RootNamespace>mongoutils</RootNamespace>
+ <ProjectName>mongoutils test program</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup />
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>c:\boost;\boost</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>c:\boost;\boost</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="test.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="html.h" />
+ <ClInclude Include="str.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file diff --git a/src/mongo/util/mongoutils/mongoutils.vcxproj.filters b/src/mongo/util/mongoutils/mongoutils.vcxproj.filters new file mode 100755 index 00000000000..84ecbffede0 --- /dev/null +++ b/src/mongo/util/mongoutils/mongoutils.vcxproj.filters @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="test.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="html.h" />
+ <ClInclude Include="str.h" />
+ </ItemGroup>
+</Project>
\ No newline at end of file diff --git a/src/mongo/util/mongoutils/str.h b/src/mongo/util/mongoutils/str.h new file mode 100644 index 00000000000..97b121b0068 --- /dev/null +++ b/src/mongo/util/mongoutils/str.h @@ -0,0 +1,216 @@ +// @file str.h + +/* Copyright 2010 10gen 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. + */ + +#pragma once + +/* Things in the mongoutils namespace + (1) are not database specific, rather, true utilities + (2) are cross platform + (3) may require boost headers, but not libs + (4) are clean and easy to use in any c++ project without pulling in lots of other stuff + + Note: within this module, we use int for all offsets -- there are no unsigned offsets + and no size_t's. If you need 3 gigabyte long strings, don't use this module. +*/ + +#include <string> +#include <sstream> + +// this violates the README rules for mongoutils: +#include "../../bson/util/builder.h" + +namespace mongoutils { + + namespace str { + + typedef std::string string; + + /** the idea here is to make one liners easy. e.g.: + + return str::stream() << 1 << ' ' << 2; + + since the following doesn't work: + + (stringstream() << 1).str(); + */ + class stream { + public: + mongo::StringBuilder ss; + template<class T> + stream& operator<<(const T& v) { + ss << v; + return *this; + } + operator std::string () const { return ss.str(); } + }; + + inline bool startsWith(const char *str, const char *prefix) { + const char *s = str; + const char *p = prefix; + while( *p ) { + if( *p != *s ) return false; + p++; s++; + } + return true; + } + inline bool startsWith(string s, string p) { return startsWith(s.c_str(), p.c_str()); } + + // while these are trivial today use in case we do different wide char things later + inline bool startsWith(const char *p, char ch) { return *p == ch; } + inline bool startsWith(string s, char ch) { return startsWith(s.c_str(), ch); } + + inline bool endsWith(string s, string p) { + int l = p.size(); + int x = s.size(); + if( x < l ) return false; + return strncmp(s.c_str()+x-l, p.c_str(), l) == 0; + } + inline bool endsWith(const char *s, char p) { + size_t len = strlen(s); + return len && s[len-1] == p; + } + + inline bool equals( const char * a , const char * b ) { return strcmp( a , b ) == 0; } + + /** find char x, and return rest of string thereafter, or "" if not found */ + inline const char * after(const char *s, char x) { + const char *p = strchr(s, x); + return (p != 0) ? p+1 : ""; + } + inline string after(const string& s, char x) { + const char *p = strchr(s.c_str(), x); + return (p != 0) ? string(p+1) : ""; + } + + /** find string x, and return rest of string thereafter, or "" if not found */ + inline const char * after(const char *s, const char *x) { + const char *p = strstr(s, x); + return (p != 0) ? p+strlen(x) : ""; + } + inline string after(string s, string x) { + const char *p = strstr(s.c_str(), x.c_str()); + return (p != 0) ? string(p+x.size()) : ""; + } + + /** @return true if s contains x */ + inline bool contains(string s, string x) { + return strstr(s.c_str(), x.c_str()) != 0; + } + inline bool contains(string s, char x) { + return strchr(s.c_str(), x) != 0; + } + + /** @return everything before the character x, else entire string */ + inline string before(const string& s, char x) { + const char *p = strchr(s.c_str(), x); + return (p != 0) ? s.substr(0, p-s.c_str()) : s; + } + + /** @return everything before the string x, else entire string */ + inline string before(const string& s, const string& x) { + const char *p = strstr(s.c_str(), x.c_str()); + return (p != 0) ? s.substr(0, p-s.c_str()) : s; + } + + /** check if if strings share a common starting prefix + @return offset of divergence (or length if equal). 0=nothing in common. */ + inline int shareCommonPrefix(const char *p, const char *q) { + int ofs = 0; + while( 1 ) { + if( *p == 0 || *q == 0 ) + break; + if( *p != *q ) + break; + p++; q++; ofs++; + } + return ofs; + } + inline int shareCommonPrefix(const string &a, const string &b) + { return shareCommonPrefix(a.c_str(), b.c_str()); } + + /** string to unsigned. zero if not a number. can end with non-num chars */ + inline unsigned toUnsigned(const string& a) { + unsigned x = 0; + const char *p = a.c_str(); + while( 1 ) { + if( !isdigit(*p) ) + break; + x = x * 10 + (*p - '0'); + p++; + } + return x; + } + + /** split a string on a specific char. We don't split N times, just once + on the first occurrence. If char not present entire string is in L + and R is empty. + @return true if char found + */ + inline bool splitOn(const string &s, char c, string& L, string& R) { + const char *start = s.c_str(); + const char *p = strchr(start, c); + if( p == 0 ) { + L = s; R.clear(); + return false; + } + L = string(start, p-start); + R = string(p+1); + return true; + } + /** split scanning reverse direction. Splits ONCE ONLY. */ + inline bool rSplitOn(const string &s, char c, string& L, string& R) { + const char *start = s.c_str(); + const char *p = strrchr(start, c); + if( p == 0 ) { + L = s; R.clear(); + return false; + } + L = string(start, p-start); + R = string(p+1); + return true; + } + + /** @return number of occurrences of c in s */ + inline unsigned count( const string& s , char c ) { + unsigned n=0; + for ( unsigned i=0; i<s.size(); i++ ) + if ( s[i] == c ) + n++; + return n; + } + + /** trim leading spaces. spaces only, not tabs etc. */ + inline string ltrim(const string& s) { + const char *p = s.c_str(); + while( *p == ' ' ) p++; + return p; + } + + /** remove trailing chars in place */ + inline void stripTrailing(string& s, const char *chars) { + string::iterator i = s.end(); + while( s.begin() != i ) { + i--; + if( contains(chars, *i) ) { + s.erase(i); + } + } + } + + } + +} diff --git a/src/mongo/util/mongoutils/test.cpp b/src/mongo/util/mongoutils/test.cpp new file mode 100644 index 00000000000..45268c5ca49 --- /dev/null +++ b/src/mongo/util/mongoutils/test.cpp @@ -0,0 +1,45 @@ +/* @file test.cpp + utils/mongoutils/test.cpp + unit tests for mongoutils +*/ + +/* + * Copyright 2010 10gen 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. + */ + +#include <assert.h> +#include "str.h" +#include "html.h" + +using namespace std; +using namespace mongoutils; + +int main() { + { + string s = "abcde"; + str::stripTrailing(s, "ef"); + assert( s == "abcd" ); + str::stripTrailing(s, "abcd"); + assert( s.empty() ); + s = "abcddd"; + str::stripTrailing(s, "d"); + assert( s == "abc" ); + } + + string x = str::after("abcde", 'c'); + assert( x == "de" ); + assert( str::after("abcde", 'x') == "" ); + return 0; +} diff --git a/src/mongo/util/moveablebuffer.h b/src/mongo/util/moveablebuffer.h new file mode 100644 index 00000000000..e01f2d8d9a4 --- /dev/null +++ b/src/mongo/util/moveablebuffer.h @@ -0,0 +1,51 @@ +/* moveablebuffer.h +*/ + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + + /** this is a sort of smart pointer class where we can move where something is and all the pointers will adjust. + not threadsafe. + */ + struct MoveableBuffer { + MoveableBuffer(); + MoveableBuffer(void *); + MoveableBuffer& operator=(const MoveableBuffer&); + ~MoveableBuffer(); + + void *p; + }; + + /* implementation (inlines) below */ + + // this is a temp stub implementation...not really done yet - just having everything compile & such for checkpointing into git + + inline MoveableBuffer::MoveableBuffer() : p(0) { } + + inline MoveableBuffer::MoveableBuffer(void *_p) : p(_p) { } + + inline MoveableBuffer& MoveableBuffer::operator=(const MoveableBuffer& r) { + p = r.p; + return *this; + } + + inline MoveableBuffer::~MoveableBuffer() { + } + +} diff --git a/src/mongo/util/net/hostandport.h b/src/mongo/util/net/hostandport.h new file mode 100644 index 00000000000..3f4a64b79a9 --- /dev/null +++ b/src/mongo/util/net/hostandport.h @@ -0,0 +1,239 @@ +// hostandport.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "sock.h" +#include "../../db/cmdline.h" +#include "../mongoutils/str.h" + +namespace mongo { + + using namespace mongoutils; + + void dynHostResolve(string& name, int& port); + string dynHostMyName(); + + /** helper for manipulating host:port connection endpoints. + */ + struct HostAndPort { + HostAndPort() : _port(-1) { } + + /** From a string hostname[:portnumber] or a #dynname + Throws user assertion if bad config string or bad port #. + */ + HostAndPort(string s); + + /** @param p port number. -1 is ok to use default. */ + HostAndPort(string h, int p /*= -1*/) : _host(h), _port(p) { + assert( !str::startsWith(h, '#') ); + } + + HostAndPort(const SockAddr& sock ) : _host( sock.getAddr() ) , _port( sock.getPort() ) { } + + static HostAndPort me() { return HostAndPort("localhost", cmdLine.port); } + + /* uses real hostname instead of localhost */ + static HostAndPort Me(); + + bool operator<(const HostAndPort& r) const { + string h = host(); + string rh = r.host(); + if( h < rh ) + return true; + if( h == rh ) + return port() < r.port(); + return false; + } + + bool operator==(const HostAndPort& r) const { + return host() == r.host() && port() == r.port(); + } + + bool operator!=(const HostAndPort& r) const { return !(*this == r); } + + /* returns true if the host/port combo identifies this process instance. */ + bool isSelf() const; // defined in isself.cpp + + bool isLocalHost() const; + + /** + * @param includePort host:port if true, host otherwise + */ + string toString( bool includePort=true ) const; + + // get the logical name if using a #dynhostname instead of resolving to current actual name + string dynString() const; + string toStringLong() const; + + operator string() const { return toString(); } + + bool empty() const { + return _dynName.empty() && _host.empty() && _port < 0; + } + string host() const { + if( !dyn() ) + return _host; + string h = _dynName; + int p; + dynHostResolve(h, p); + return h; + } + int port() const { + int p = -2; + if( dyn() ) { + string h = _dynName; + dynHostResolve(h,p); + } + else { + p = _port; + } + return p >= 0 ? p : CmdLine::DefaultDBPort; + } + bool hasPort() const { + int p = -2; + if( dyn() ) { + string h = _dynName; + dynHostResolve(h,p); + } + else { + p = _port; + } + return p >= 0; + } + void setPort( int port ) { + if( dyn() ) { + log() << "INFO skipping setPort() HostAndPort dyn()=true" << endl; + return; + } + _port = port; + } + + private: + bool dyn() const { return !_dynName.empty(); } + void init(const char *); + // invariant (except full obj assignment): + string _dynName; // when this is set, _host and _port aren't used, rather, we look up the dyn info every time. + string _host; + int _port; // -1 indicates unspecified + }; + + inline HostAndPort HostAndPort::Me() { + { + string s = dynHostMyName(); + if( !s.empty() ) + return HostAndPort(s); + } + + const char* ips = cmdLine.bind_ip.c_str(); + while(*ips) { + string ip; + const char * comma = strchr(ips, ','); + if (comma) { + ip = string(ips, comma - ips); + ips = comma + 1; + } + else { + ip = string(ips); + ips = ""; + } + HostAndPort h = HostAndPort(ip, cmdLine.port); + if (!h.isLocalHost()) { + return h; + } + } + + string h = getHostName(); + assert( !h.empty() ); + assert( h != "localhost" ); + return HostAndPort(h, cmdLine.port); + } + + inline string HostAndPort::dynString() const { + return dyn() ? _dynName : toString(); + } + + inline string HostAndPort::toStringLong() const { + return _dynName + ':' + toString(); + } + + inline string HostAndPort::toString( bool includePort ) const { + string h = host(); + int p = port(); + + if ( ! includePort ) + return h; + + stringstream ss; + ss << h; + if ( p != -1 ) { + ss << ':'; +#if defined(_DEBUG) + if( p >= 44000 && p < 44100 ) { + log() << "warning: special debug port 44xxx used" << endl; + ss << p+1; + } + else + ss << p; +#else + ss << p; +#endif + } + return ss.str(); + } + + inline bool HostAndPort::isLocalHost() const { + string _host = host(); + return ( _host == "localhost" + || startsWith(_host.c_str(), "127.") + || _host == "::1" + || _host == "anonymous unix socket" + || _host.c_str()[0] == '/' // unix socket + ); + } + + inline void HostAndPort::init(const char *p) { + uassert(13110, "HostAndPort: bad host:port config string", *p); + assert( *p != '#' ); + assert( _dynName.empty() ); + const char *colon = strrchr(p, ':'); + if( colon ) { + int port = atoi(colon+1); + uassert(13095, "HostAndPort: bad port #", port > 0); + _host = string(p,colon-p); + _port = port; + } + else { + // no port specified. + _host = p; + _port = -1; + } + } + + inline HostAndPort::HostAndPort(string s) { + const char *p = s.c_str(); + if( *p == '#' ) { + _dynName = s; + _port = -2; + _host = "invalid_hostname_dyn_in_use"; + } + else { + init(p); + } + } + +} diff --git a/src/mongo/util/net/httpclient.cpp b/src/mongo/util/net/httpclient.cpp new file mode 100644 index 00000000000..16eaa0ae80a --- /dev/null +++ b/src/mongo/util/net/httpclient.cpp @@ -0,0 +1,177 @@ +// httpclient.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "httpclient.h" +#include "sock.h" +#include "message.h" +#include "message_port.h" +#include "../mongoutils/str.h" +#include "../../bson/util/builder.h" + +namespace mongo { + + //#define HD(x) cout << x << endl; +#define HD(x) + + + int HttpClient::get( string url , Result * result ) { + return _go( "GET" , url , 0 , result ); + } + + int HttpClient::post( string url , string data , Result * result ) { + return _go( "POST" , url , data.c_str() , result ); + } + + int HttpClient::_go( const char * command , string url , const char * body , Result * result ) { + bool ssl = false; + if ( url.find( "https://" ) == 0 ) { + ssl = true; + url = url.substr( 8 ); + } + else { + uassert( 10271 , "invalid url" , url.find( "http://" ) == 0 ); + url = url.substr( 7 ); + } + + string host , path; + if ( url.find( "/" ) == string::npos ) { + host = url; + path = "/"; + } + else { + host = url.substr( 0 , url.find( "/" ) ); + path = url.substr( url.find( "/" ) ); + } + + + HD( "host [" << host << "]" ); + HD( "path [" << path << "]" ); + + string server = host; + int port = ssl ? 443 : 80; + + string::size_type idx = host.find( ":" ); + if ( idx != string::npos ) { + server = host.substr( 0 , idx ); + string t = host.substr( idx + 1 ); + port = atoi( t.c_str() ); + } + + HD( "server [" << server << "]" ); + HD( "port [" << port << "]" ); + + string req; + { + stringstream ss; + ss << command << " " << path << " HTTP/1.1\r\n"; + ss << "Host: " << host << "\r\n"; + ss << "Connection: Close\r\n"; + ss << "User-Agent: mongodb http client\r\n"; + if ( body ) { + ss << "Content-Length: " << strlen( body ) << "\r\n"; + } + ss << "\r\n"; + if ( body ) { + ss << body; + } + + req = ss.str(); + } + + SockAddr addr( server.c_str() , port ); + HD( "addr: " << addr.toString() ); + + Socket sock; + if ( ! sock.connect( addr ) ) + return -1; + + if ( ssl ) { +#ifdef MONGO_SSL + _checkSSLManager(); + sock.secure( _sslManager.get() ); +#else + uasserted( 15862 , "no ssl support" ); +#endif + } + + { + const char * out = req.c_str(); + int toSend = req.size(); + sock.send( out , toSend, "_go" ); + } + + char buf[4096]; + int got = sock.unsafe_recv( buf , 4096 ); + buf[got] = 0; + + int rc; + char version[32]; + assert( sscanf( buf , "%s %d" , version , &rc ) == 2 ); + HD( "rc: " << rc ); + + StringBuilder sb; + if ( result ) + sb << buf; + + while ( ( got = sock.unsafe_recv( buf , 4096 ) ) > 0) { + if ( result ) + sb << buf; + } + + if ( result ) { + result->_init( rc , sb.str() ); + } + + return rc; + } + + void HttpClient::Result::_init( int code , string entire ) { + _code = code; + _entireResponse = entire; + + while ( true ) { + size_t i = entire.find( '\n' ); + if ( i == string::npos ) { + // invalid + break; + } + + string h = entire.substr( 0 , i ); + entire = entire.substr( i + 1 ); + + if ( h.size() && h[h.size()-1] == '\r' ) + h = h.substr( 0 , h.size() - 1 ); + + if ( h.size() == 0 ) + break; + + i = h.find( ':' ); + if ( i != string::npos ) + _headers[h.substr(0,i)] = str::ltrim(h.substr(i+1)); + } + + _body = entire; + } + +#ifdef MONGO_SSL + void HttpClient::_checkSSLManager() { + _sslManager.reset( new SSLManager( true ) ); + } +#endif + +} diff --git a/src/mongo/util/net/httpclient.h b/src/mongo/util/net/httpclient.h new file mode 100644 index 00000000000..126969f4f70 --- /dev/null +++ b/src/mongo/util/net/httpclient.h @@ -0,0 +1,78 @@ +// httpclient.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../../pch.h" +#include "sock.h" + +namespace mongo { + + class HttpClient : boost::noncopyable { + public: + + typedef map<string,string> Headers; + + class Result { + public: + Result() {} + + const string& getEntireResponse() const { + return _entireResponse; + } + + Headers getHeaders() const { + return _headers; + } + + const string& getBody() const { + return _body; + } + + private: + + void _init( int code , string entire ); + + int _code; + string _entireResponse; + + Headers _headers; + string _body; + + friend class HttpClient; + }; + + /** + * @return response code + */ + int get( string url , Result * result = 0 ); + + /** + * @return response code + */ + int post( string url , string body , Result * result = 0 ); + + private: + int _go( const char * command , string url , const char * body , Result * result ); + +#ifdef MONGO_SSL + void _checkSSLManager(); + + scoped_ptr<SSLManager> _sslManager; +#endif + }; +} diff --git a/src/mongo/util/net/listen.cpp b/src/mongo/util/net/listen.cpp new file mode 100644 index 00000000000..ec3e4fa0ee8 --- /dev/null +++ b/src/mongo/util/net/listen.cpp @@ -0,0 +1,394 @@ +// listen.h + +/* Copyright 2009 10gen 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. + */ + + +#include "pch.h" +#include "listen.h" +#include "message_port.h" + +#ifndef _WIN32 + +# ifndef __sunos__ +# include <ifaddrs.h> +# endif +# include <sys/resource.h> +# include <sys/stat.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <errno.h> +#include <netdb.h> +#ifdef __openbsd__ +# include <sys/uio.h> +#endif + +#else + +// errno doesn't work for winsock. +#undef errno +#define errno WSAGetLastError() + +#endif + +namespace mongo { + + + void checkTicketNumbers(); + + + // ----- Listener ------- + + const Listener* Listener::_timeTracker; + + vector<SockAddr> ipToAddrs(const char* ips, int port, bool useUnixSockets) { + vector<SockAddr> out; + if (*ips == '\0') { + out.push_back(SockAddr("0.0.0.0", port)); // IPv4 all + + if (IPv6Enabled()) + out.push_back(SockAddr("::", port)); // IPv6 all +#ifndef _WIN32 + if (useUnixSockets) + out.push_back(SockAddr(makeUnixSockPath(port).c_str(), port)); // Unix socket +#endif + return out; + } + + while(*ips) { + string ip; + const char * comma = strchr(ips, ','); + if (comma) { + ip = string(ips, comma - ips); + ips = comma + 1; + } + else { + ip = string(ips); + ips = ""; + } + + SockAddr sa(ip.c_str(), port); + out.push_back(sa); + +#ifndef _WIN32 + if (useUnixSockets && (sa.getAddr() == "127.0.0.1" || sa.getAddr() == "0.0.0.0")) // only IPv4 + out.push_back(SockAddr(makeUnixSockPath(port).c_str(), port)); +#endif + } + return out; + + } + + Listener::Listener(const string& name, const string &ip, int port, bool logConnect ) + : _port(port), _name(name), _ip(ip), _logConnect(logConnect), _elapsedTime(0) { +#ifdef MONGO_SSL + _ssl = 0; + _sslPort = 0; + + if ( cmdLine.sslOnNormalPorts && cmdLine.sslServerManager ) { + secure( cmdLine.sslServerManager ); + } +#endif + } + + Listener::~Listener() { + if ( _timeTracker == this ) + _timeTracker = 0; + } + +#ifdef MONGO_SSL + void Listener::secure( SSLManager* manager ) { + _ssl = manager; + } + + void Listener::addSecurePort( SSLManager* manager , int additionalPort ) { + _ssl = manager; + _sslPort = additionalPort; + } + +#endif + + bool Listener::_setupSockets( const vector<SockAddr>& mine , vector<SOCKET>& socks ) { + for (vector<SockAddr>::const_iterator it=mine.begin(), end=mine.end(); it != end; ++it) { + const SockAddr& me = *it; + + SOCKET sock = ::socket(me.getType(), SOCK_STREAM, 0); + massert( 15863 , str::stream() << "listen(): invalid socket? " << errnoWithDescription() , sock >= 0 ); + + if (me.getType() == AF_UNIX) { +#if !defined(_WIN32) + if (unlink(me.getAddr().c_str()) == -1) { + int x = errno; + if (x != ENOENT) { + log() << "couldn't unlink socket file " << me << errnoWithDescription(x) << " skipping" << endl; + continue; + } + } +#endif + } + else if (me.getType() == AF_INET6) { + // IPv6 can also accept IPv4 connections as mapped addresses (::ffff:127.0.0.1) + // That causes a conflict if we don't do set it to IPV6_ONLY + const int one = 1; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*) &one, sizeof(one)); + } + +#if !defined(_WIN32) + { + const int one = 1; + if ( setsockopt( sock , SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0 ) + out() << "Failed to set socket opt, SO_REUSEADDR" << endl; + } +#endif + + if ( ::bind(sock, me.raw(), me.addressSize) != 0 ) { + int x = errno; + error() << "listen(): bind() failed " << errnoWithDescription(x) << " for socket: " << me.toString() << endl; + if ( x == EADDRINUSE ) + error() << " addr already in use" << endl; + closesocket(sock); + return false; + } + +#if !defined(_WIN32) + if (me.getType() == AF_UNIX) { + if (chmod(me.getAddr().c_str(), 0777) == -1) { + error() << "couldn't chmod socket file " << me << errnoWithDescription() << endl; + } + ListeningSockets::get()->addPath( me.getAddr() ); + } +#endif + + if ( ::listen(sock, 128) != 0 ) { + error() << "listen(): listen() failed " << errnoWithDescription() << endl; + closesocket(sock); + return false; + } + + ListeningSockets::get()->add( sock ); + + socks.push_back(sock); + } + + return true; + } + + void Listener::initAndListen() { + checkTicketNumbers(); + vector<SOCKET> socks; + set<int> sslSocks; + + { // normal sockets + vector<SockAddr> mine = ipToAddrs(_ip.c_str(), _port, (!cmdLine.noUnixSocket && useUnixSockets())); + if ( ! _setupSockets( mine , socks ) ) + return; + } + +#ifdef MONGO_SSL + if ( _ssl && _sslPort > 0 ) { + unsigned prev = socks.size(); + + vector<SockAddr> mine = ipToAddrs(_ip.c_str(), _sslPort, false ); + if ( ! _setupSockets( mine , socks ) ) + return; + + for ( unsigned i=prev; i<socks.size(); i++ ) { + sslSocks.insert( socks[i] ); + } + + } +#endif + + SOCKET maxfd = 0; // needed for select() + for ( unsigned i=0; i<socks.size(); i++ ) { + if ( socks[i] > maxfd ) + maxfd = socks[i]; + } + +#ifdef MONGO_SSL + if ( _ssl == 0 ) { + _logListen( _port , false ); + } + else if ( _sslPort == 0 ) { + _logListen( _port , true ); + } + else { + // both + _logListen( _port , false ); + _logListen( _sslPort , true ); + } +#else + _logListen( _port , false ); +#endif + + static long connNumber = 0; + struct timeval maxSelectTime; + while ( ! inShutdown() ) { + fd_set fds[1]; + FD_ZERO(fds); + + for (vector<SOCKET>::iterator it=socks.begin(), end=socks.end(); it != end; ++it) { + FD_SET(*it, fds); + } + + maxSelectTime.tv_sec = 0; + maxSelectTime.tv_usec = 10000; + const int ret = select(maxfd+1, fds, NULL, NULL, &maxSelectTime); + + if (ret == 0) { +#if defined(__linux__) + _elapsedTime += ( 10000 - maxSelectTime.tv_usec ) / 1000; +#else + _elapsedTime += 10; +#endif + continue; + } + + if (ret < 0) { + int x = errno; +#ifdef EINTR + if ( x == EINTR ) { + log() << "select() signal caught, continuing" << endl; + continue; + } +#endif + if ( ! inShutdown() ) + log() << "select() failure: ret=" << ret << " " << errnoWithDescription(x) << endl; + return; + } + +#if defined(__linux__) + _elapsedTime += max(ret, (int)(( 10000 - maxSelectTime.tv_usec ) / 1000)); +#else + _elapsedTime += ret; // assume 1ms to grab connection. very rough +#endif + + for (vector<SOCKET>::iterator it=socks.begin(), end=socks.end(); it != end; ++it) { + if (! (FD_ISSET(*it, fds))) + continue; + + SockAddr from; + int s = accept(*it, from.raw(), &from.addressSize); + if ( s < 0 ) { + int x = errno; // so no global issues + if ( x == ECONNABORTED || x == EBADF ) { + log() << "Listener on port " << _port << " aborted" << endl; + return; + } + if ( x == 0 && inShutdown() ) { + return; // socket closed + } + if( !inShutdown() ) { + log() << "Listener: accept() returns " << s << " " << errnoWithDescription(x) << endl; + if (x == EMFILE || x == ENFILE) { + // Connection still in listen queue but we can't accept it yet + error() << "Out of file descriptors. Waiting one second before trying to accept more connections." << warnings; + sleepsecs(1); + } + } + continue; + } + if (from.getType() != AF_UNIX) + disableNagle(s); + if ( _logConnect && ! cmdLine.quiet ){ + int conns = connTicketHolder.used()+1; + const char* word = (conns == 1 ? " connection" : " connections"); + log() << "connection accepted from " << from.toString() << " #" << ++connNumber << " (" << conns << word << " now open)" << endl; + } + + Socket newSock = Socket(s, from); +#ifdef MONGO_SSL + if ( _ssl && ( _sslPort == 0 || sslSocks.count(*it) ) ) { + newSock.secureAccepted( _ssl ); + } +#endif + accepted( newSock ); + } + } + } + + void Listener::_logListen( int port , bool ssl ) { + log() << _name << ( _name.size() ? " " : "" ) << "waiting for connections on port " << port << ( ssl ? " ssl" : "" ) << endl; + } + + + void Listener::accepted(Socket socket) { + accepted( new MessagingPort(socket) ); + } + + void Listener::accepted(MessagingPort *mp) { + assert(!"You must overwrite one of the accepted methods"); + } + + // ----- ListeningSockets ------- + + ListeningSockets* ListeningSockets::_instance = new ListeningSockets(); + + ListeningSockets* ListeningSockets::get() { + return _instance; + } + + // ------ connection ticket and control ------ + + const int DEFAULT_MAX_CONN = 20000; + const int MAX_MAX_CONN = 20000; + + int getMaxConnections() { +#ifdef _WIN32 + return DEFAULT_MAX_CONN; +#else + struct rlimit limit; + assert( getrlimit(RLIMIT_NOFILE,&limit) == 0 ); + + int max = (int)(limit.rlim_cur * .8); + + log(1) << "fd limit" + << " hard:" << limit.rlim_max + << " soft:" << limit.rlim_cur + << " max conn: " << max + << endl; + + if ( max > MAX_MAX_CONN ) + max = MAX_MAX_CONN; + + return max; +#endif + } + + void checkTicketNumbers() { + int want = getMaxConnections(); + int current = connTicketHolder.outof(); + if ( current != DEFAULT_MAX_CONN ) { + if ( current < want ) { + // they want fewer than they can handle + // which is fine + log(1) << " only allowing " << current << " connections" << endl; + return; + } + if ( current > want ) { + log() << " --maxConns too high, can only handle " << want << endl; + } + } + connTicketHolder.resize( want ); + } + + TicketHolder connTicketHolder(DEFAULT_MAX_CONN); + +} diff --git a/src/mongo/util/net/listen.h b/src/mongo/util/net/listen.h new file mode 100644 index 00000000000..ca90e835b97 --- /dev/null +++ b/src/mongo/util/net/listen.h @@ -0,0 +1,190 @@ +// listen.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "sock.h" + +namespace mongo { + + class MessagingPort; + + class Listener : boost::noncopyable { + public: + + Listener(const string& name, const string &ip, int port, bool logConnect=true ); + + virtual ~Listener(); + +#ifdef MONGO_SSL + /** + * make this an ssl socket + * ownership of SSLManager remains with the caller + */ + void secure( SSLManager* manager ); + + void addSecurePort( SSLManager* manager , int additionalPort ); +#endif + + void initAndListen(); // never returns unless error (start a thread) + + /* spawn a thread, etc., then return */ + virtual void accepted(Socket socket); + virtual void accepted(MessagingPort *mp); + + const int _port; + + /** + * @return a rough estimate of elapsed time since the server started + */ + long long getMyElapsedTimeMillis() const { return _elapsedTime; } + + void setAsTimeTracker() { + _timeTracker = this; + } + + static const Listener* getTimeTracker() { + return _timeTracker; + } + + static long long getElapsedTimeMillis() { + if ( _timeTracker ) + return _timeTracker->getMyElapsedTimeMillis(); + + // should this assert or throw? seems like callers may not expect to get zero back, certainly not forever. + return 0; + } + + private: + string _name; + string _ip; + bool _logConnect; + long long _elapsedTime; + +#ifdef MONGO_SSL + SSLManager* _ssl; + int _sslPort; +#endif + + /** + * @return true iff everything went ok + */ + bool _setupSockets( const vector<SockAddr>& mine , vector<SOCKET>& socks ); + + void _logListen( int port , bool ssl ); + + static const Listener* _timeTracker; + + virtual bool useUnixSockets() const { return false; } + }; + + /** + * keep track of elapsed time + * after a set amount of time, tells you to do something + * only in this file because depends on Listener + */ + class ElapsedTracker { + public: + ElapsedTracker( int hitsBetweenMarks , int msBetweenMarks ) + : _h( hitsBetweenMarks ) , _ms( msBetweenMarks ) , _pings(0) { + _last = Listener::getElapsedTimeMillis(); + } + + /** + * call this for every iteration + * returns true if one of the triggers has gone off + */ + bool intervalHasElapsed() { + if ( ( ++_pings % _h ) == 0 ) { + _last = Listener::getElapsedTimeMillis(); + return true; + } + + long long now = Listener::getElapsedTimeMillis(); + if ( now - _last > _ms ) { + _last = now; + return true; + } + + return false; + } + + private: + const int _h; + const int _ms; + + unsigned long long _pings; + + long long _last; + + }; + + class ListeningSockets { + public: + ListeningSockets() + : _mutex("ListeningSockets") + , _sockets( new set<int>() ) + , _socketPaths( new set<string>() ) + { } + void add( int sock ) { + scoped_lock lk( _mutex ); + _sockets->insert( sock ); + } + void addPath( string path ) { + scoped_lock lk( _mutex ); + _socketPaths->insert( path ); + } + void remove( int sock ) { + scoped_lock lk( _mutex ); + _sockets->erase( sock ); + } + void closeAll() { + set<int>* sockets; + set<string>* paths; + + { + scoped_lock lk( _mutex ); + sockets = _sockets; + _sockets = new set<int>(); + paths = _socketPaths; + _socketPaths = new set<string>(); + } + + for ( set<int>::iterator i=sockets->begin(); i!=sockets->end(); i++ ) { + int sock = *i; + log() << "closing listening socket: " << sock << endl; + closesocket( sock ); + } + + for ( set<string>::iterator i=paths->begin(); i!=paths->end(); i++ ) { + string path = *i; + log() << "removing socket file: " << path << endl; + ::remove( path.c_str() ); + } + } + static ListeningSockets* get(); + private: + mongo::mutex _mutex; + set<int>* _sockets; + set<string>* _socketPaths; // for unix domain sockets + static ListeningSockets* _instance; + }; + + + extern TicketHolder connTicketHolder; + +} diff --git a/src/mongo/util/net/message.cpp b/src/mongo/util/net/message.cpp new file mode 100644 index 00000000000..a84e5c48c5c --- /dev/null +++ b/src/mongo/util/net/message.cpp @@ -0,0 +1,64 @@ +// message.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" + +#include <fcntl.h> +#include <errno.h> +#include <time.h> + +#include "message.h" +#include "message_port.h" +#include "listen.h" + +#include "../goodies.h" +#include "../../client/dbclient.h" + +namespace mongo { + + void Message::send( MessagingPort &p, const char *context ) { + if ( empty() ) { + return; + } + if ( _buf != 0 ) { + p.send( (char*)_buf, _buf->len, context ); + } + else { + p.send( _data, context ); + } + } + + MSGID NextMsgId; + + /*struct MsgStart { + MsgStart() { + NextMsgId = (((unsigned) time(0)) << 16) ^ curTimeMillis(); + assert(MsgDataHeaderSize == 16); + } + } msgstart;*/ + + MSGID nextMessageId() { + MSGID msgid = NextMsgId++; + return msgid; + } + + bool doesOpGetAResponse( int op ) { + return op == dbQuery || op == dbGetMore; + } + + +} // namespace mongo diff --git a/src/mongo/util/net/message.h b/src/mongo/util/net/message.h new file mode 100644 index 00000000000..14b0241f21d --- /dev/null +++ b/src/mongo/util/net/message.h @@ -0,0 +1,312 @@ +// message.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "sock.h" +#include "../../bson/util/atomic_int.h" +#include "hostandport.h" + +namespace mongo { + + class Message; + class MessagingPort; + class PiggyBackData; + + typedef AtomicUInt MSGID; + + enum Operations { + opReply = 1, /* reply. responseTo is set. */ + dbMsg = 1000, /* generic msg command followed by a string */ + dbUpdate = 2001, /* update object */ + dbInsert = 2002, + //dbGetByOID = 2003, + dbQuery = 2004, + dbGetMore = 2005, + dbDelete = 2006, + dbKillCursors = 2007 + }; + + bool doesOpGetAResponse( int op ); + + inline const char * opToString( int op ) { + switch ( op ) { + case 0: return "none"; + case opReply: return "reply"; + case dbMsg: return "msg"; + case dbUpdate: return "update"; + case dbInsert: return "insert"; + case dbQuery: return "query"; + case dbGetMore: return "getmore"; + case dbDelete: return "remove"; + case dbKillCursors: return "killcursors"; + default: + PRINT(op); + assert(0); + return ""; + } + } + + inline bool opIsWrite( int op ) { + switch ( op ) { + + case 0: + case opReply: + case dbMsg: + case dbQuery: + case dbGetMore: + case dbKillCursors: + return false; + + case dbUpdate: + case dbInsert: + case dbDelete: + return true; + + default: + PRINT(op); + assert(0); + return ""; + } + + } + +#pragma pack(1) + /* see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol + */ + struct MSGHEADER { + int messageLength; // total message size, including this + int requestID; // identifier for this message + int responseTo; // requestID from the original request + // (used in reponses from db) + int opCode; + }; + struct OP_GETMORE : public MSGHEADER { + MSGHEADER header; // standard message header + int ZERO_or_flags; // 0 - reserved for future use + //cstring fullCollectionName; // "dbname.collectionname" + //int32 numberToReturn; // number of documents to return + //int64 cursorID; // cursorID from the OP_REPLY + }; +#pragma pack() + +#pragma pack(1) + /* todo merge this with MSGHEADER (or inherit from it). */ + struct MsgData { + int len; /* len of the msg, including this field */ + MSGID id; /* request/reply id's match... */ + MSGID responseTo; /* id of the message we are responding to */ + short _operation; + char _flags; + char _version; + int operation() const { + return _operation; + } + void setOperation(int o) { + _flags = 0; + _version = 0; + _operation = o; + } + char _data[4]; + + int& dataAsInt() { + return *((int *) _data); + } + + bool valid() { + if ( len <= 0 || len > ( 4 * BSONObjMaxInternalSize ) ) + return false; + if ( _operation < 0 || _operation > 30000 ) + return false; + return true; + } + + long long getCursor() { + assert( responseTo > 0 ); + assert( _operation == opReply ); + long long * l = (long long *)(_data + 4); + return l[0]; + } + + int dataLen(); // len without header + }; + const int MsgDataHeaderSize = sizeof(MsgData) - 4; + inline int MsgData::dataLen() { + return len - MsgDataHeaderSize; + } +#pragma pack() + + class Message { + public: + // we assume here that a vector with initial size 0 does no allocation (0 is the default, but wanted to make it explicit). + Message() : _buf( 0 ), _data( 0 ), _freeIt( false ) {} + Message( void * data , bool freeIt ) : + _buf( 0 ), _data( 0 ), _freeIt( false ) { + _setData( reinterpret_cast< MsgData* >( data ), freeIt ); + }; + Message(Message& r) : _buf( 0 ), _data( 0 ), _freeIt( false ) { + *this = r; + } + ~Message() { + reset(); + } + + SockAddr _from; + + MsgData *header() const { + assert( !empty() ); + return _buf ? _buf : reinterpret_cast< MsgData* > ( _data[ 0 ].first ); + } + int operation() const { return header()->operation(); } + + MsgData *singleData() const { + massert( 13273, "single data buffer expected", _buf ); + return header(); + } + + bool empty() const { return !_buf && _data.empty(); } + + int size() const { + int res = 0; + if ( _buf ) { + res = _buf->len; + } + else { + for (MsgVec::const_iterator it = _data.begin(); it != _data.end(); ++it) { + res += it->second; + } + } + return res; + } + + int dataSize() const { return size() - sizeof(MSGHEADER); } + + // concat multiple buffers - noop if <2 buffers already, otherwise can be expensive copy + // can get rid of this if we make response handling smarter + void concat() { + if ( _buf || empty() ) { + return; + } + + assert( _freeIt ); + int totalSize = 0; + for( vector< pair< char *, int > >::const_iterator i = _data.begin(); i != _data.end(); ++i ) { + totalSize += i->second; + } + char *buf = (char*)malloc( totalSize ); + char *p = buf; + for( vector< pair< char *, int > >::const_iterator i = _data.begin(); i != _data.end(); ++i ) { + memcpy( p, i->first, i->second ); + p += i->second; + } + reset(); + _setData( (MsgData*)buf, true ); + } + + // vector swap() so this is fast + Message& operator=(Message& r) { + assert( empty() ); + assert( r._freeIt ); + _buf = r._buf; + r._buf = 0; + if ( r._data.size() > 0 ) { + _data.swap( r._data ); + } + r._freeIt = false; + _freeIt = true; + return *this; + } + + void reset() { + if ( _freeIt ) { + if ( _buf ) { + free( _buf ); + } + for( vector< pair< char *, int > >::const_iterator i = _data.begin(); i != _data.end(); ++i ) { + free(i->first); + } + } + _buf = 0; + _data.clear(); + _freeIt = false; + } + + // use to add a buffer + // assumes message will free everything + void appendData(char *d, int size) { + if ( size <= 0 ) { + return; + } + if ( empty() ) { + MsgData *md = (MsgData*)d; + md->len = size; // can be updated later if more buffers added + _setData( md, true ); + return; + } + assert( _freeIt ); + if ( _buf ) { + _data.push_back( make_pair( (char*)_buf, _buf->len ) ); + _buf = 0; + } + _data.push_back( make_pair( d, size ) ); + header()->len += size; + } + + // use to set first buffer if empty + void setData(MsgData *d, bool freeIt) { + assert( empty() ); + _setData( d, freeIt ); + } + void setData(int operation, const char *msgtxt) { + setData(operation, msgtxt, strlen(msgtxt)+1); + } + void setData(int operation, const char *msgdata, size_t len) { + assert( empty() ); + size_t dataLen = len + sizeof(MsgData) - 4; + MsgData *d = (MsgData *) malloc(dataLen); + memcpy(d->_data, msgdata, len); + d->len = fixEndian(dataLen); + d->setOperation(operation); + _setData( d, true ); + } + + bool doIFreeIt() { + return _freeIt; + } + + void send( MessagingPort &p, const char *context ); + + string toString() const; + + private: + void _setData( MsgData *d, bool freeIt ) { + _freeIt = freeIt; + _buf = d; + } + // if just one buffer, keep it in _buf, otherwise keep a sequence of buffers in _data + MsgData * _buf; + // byte buffer(s) - the first must contain at least a full MsgData unless using _buf for storage instead + typedef vector< pair< char*, int > > MsgVec; + MsgVec _data; + bool _freeIt; + }; + + + MSGID nextMessageId(); + + +} // namespace mongo diff --git a/src/mongo/util/net/message_port.cpp b/src/mongo/util/net/message_port.cpp new file mode 100644 index 00000000000..c342ed3c8b7 --- /dev/null +++ b/src/mongo/util/net/message_port.cpp @@ -0,0 +1,303 @@ +// message_port.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" + +#include <fcntl.h> +#include <errno.h> +#include <time.h> + +#include "message.h" +#include "message_port.h" +#include "listen.h" + +#include "../goodies.h" +#include "../background.h" +#include "../time_support.h" +#include "../../db/cmdline.h" +#include "../../client/dbclient.h" + + +#ifndef _WIN32 +# ifndef __sunos__ +# include <ifaddrs.h> +# endif +# include <sys/resource.h> +# include <sys/stat.h> +#else + +// errno doesn't work for winsock. +#undef errno +#define errno WSAGetLastError() + +#endif + +namespace mongo { + + +// if you want trace output: +#define mmm(x) + + /* messagingport -------------------------------------------------------------- */ + + class PiggyBackData { + public: + PiggyBackData( MessagingPort * port ) { + _port = port; + _buf = new char[1300]; + _cur = _buf; + } + + ~PiggyBackData() { + DESTRUCTOR_GUARD ( + flush(); + delete[]( _cur ); + ); + } + + void append( Message& m ) { + assert( m.header()->len <= 1300 ); + + if ( len() + m.header()->len > 1300 ) + flush(); + + memcpy( _cur , m.singleData() , m.header()->len ); + _cur += m.header()->len; + } + + void flush() { + if ( _buf == _cur ) + return; + + _port->send( _buf , len(), "flush" ); + _cur = _buf; + } + + int len() const { return _cur - _buf; } + + private: + MessagingPort* _port; + char * _buf; + char * _cur; + }; + + class Ports { + set<MessagingPort*> ports; + mongo::mutex m; + public: + Ports() : ports(), m("Ports") {} + void closeAll(unsigned skip_mask) { + scoped_lock bl(m); + for ( set<MessagingPort*>::iterator i = ports.begin(); i != ports.end(); i++ ) { + if( (*i)->tag & skip_mask ) + continue; + (*i)->shutdown(); + } + } + void insert(MessagingPort* p) { + scoped_lock bl(m); + ports.insert(p); + } + void erase(MessagingPort* p) { + scoped_lock bl(m); + ports.erase(p); + } + }; + + // we "new" this so it is still be around when other automatic global vars + // are being destructed during termination. + Ports& ports = *(new Ports()); + + void MessagingPort::closeAllSockets(unsigned mask) { + ports.closeAll(mask); + } + + MessagingPort::MessagingPort(int fd, const SockAddr& remote) + : Socket( fd , remote ) , piggyBackData(0) { + ports.insert(this); + } + + MessagingPort::MessagingPort( double timeout, int ll ) + : Socket( timeout, ll ) { + ports.insert(this); + piggyBackData = 0; + } + + MessagingPort::MessagingPort( Socket& sock ) + : Socket( sock ) , piggyBackData( 0 ) { + ports.insert(this); + } + + void MessagingPort::shutdown() { + close(); + } + + MessagingPort::~MessagingPort() { + if ( piggyBackData ) + delete( piggyBackData ); + shutdown(); + ports.erase(this); + } + + bool MessagingPort::recv(Message& m) { + try { +again: + mmm( log() << "* recv() sock:" << this->sock << endl; ) + int len = -1; + + char *lenbuf = (char *) &len; + int lft = 4; + Socket::recv( lenbuf, lft ); + + if ( len < 16 || len > 48000000 ) { // messages must be large enough for headers + if ( len == -1 ) { + // Endian check from the client, after connecting, to see what mode server is running in. + unsigned foo = 0x10203040; + send( (char *) &foo, 4, "endian" ); + goto again; + } + + if ( len == 542393671 ) { + // an http GET + log(_logLevel) << "looks like you're trying to access db over http on native driver port. please add 1000 for webserver" << endl; + string msg = "You are trying to access MongoDB on the native driver port. For http diagnostic access, add 1000 to the port number\n"; + stringstream ss; + ss << "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: " << msg.size() << "\r\n\r\n" << msg; + string s = ss.str(); + send( s.c_str(), s.size(), "http" ); + return false; + } + log(0) << "recv(): message len " << len << " is too large" << len << endl; + return false; + } + + int z = (len+1023)&0xfffffc00; + assert(z>=len); + MsgData *md = (MsgData *) malloc(z); + assert(md); + md->len = len; + + char *p = (char *) &md->id; + int left = len -4; + + try { + Socket::recv( p, left ); + } + catch (...) { + free(md); + throw; + } + + m.setData(md, true); + return true; + + } + catch ( const SocketException & e ) { + log(_logLevel + (e.shouldPrint() ? 0 : 1) ) << "SocketException: remote: " << remote() << " error: " << e << endl; + m.reset(); + return false; + } + } + + void MessagingPort::reply(Message& received, Message& response) { + say(/*received.from, */response, received.header()->id); + } + + void MessagingPort::reply(Message& received, Message& response, MSGID responseTo) { + say(/*received.from, */response, responseTo); + } + + bool MessagingPort::call(Message& toSend, Message& response) { + mmm( log() << "*call()" << endl; ) + say(toSend); + return recv( toSend , response ); + } + + bool MessagingPort::recv( const Message& toSend , Message& response ) { + while ( 1 ) { + bool ok = recv(response); + if ( !ok ) + return false; + //log() << "got response: " << response.data->responseTo << endl; + if ( response.header()->responseTo == toSend.header()->id ) + break; + error() << "MessagingPort::call() wrong id got:" << hex << (unsigned)response.header()->responseTo << " expect:" << (unsigned)toSend.header()->id << '\n' + << dec + << " toSend op: " << (unsigned)toSend.operation() << '\n' + << " response msgid:" << (unsigned)response.header()->id << '\n' + << " response len: " << (unsigned)response.header()->len << '\n' + << " response op: " << response.operation() << '\n' + << " remote: " << remoteString() << endl; + assert(false); + response.reset(); + } + mmm( log() << "*call() end" << endl; ) + return true; + } + + void MessagingPort::assertStillConnected() { + uassert(15901, "client disconnected during operation", Socket::stillConnected()); + } + + void MessagingPort::say(Message& toSend, int responseTo) { + assert( !toSend.empty() ); + mmm( log() << "* say() sock:" << this->sock << " thr:" << GetCurrentThreadId() << endl; ) + toSend.header()->id = nextMessageId(); + toSend.header()->responseTo = responseTo; + + if ( piggyBackData && piggyBackData->len() ) { + mmm( log() << "* have piggy back" << endl; ) + if ( ( piggyBackData->len() + toSend.header()->len ) > 1300 ) { + // won't fit in a packet - so just send it off + piggyBackData->flush(); + } + else { + piggyBackData->append( toSend ); + piggyBackData->flush(); + return; + } + } + + toSend.send( *this, "say" ); + } + + void MessagingPort::piggyBack( Message& toSend , int responseTo ) { + + if ( toSend.header()->len > 1300 ) { + // not worth saving because its almost an entire packet + say( toSend ); + return; + } + + // we're going to be storing this, so need to set it up + toSend.header()->id = nextMessageId(); + toSend.header()->responseTo = responseTo; + + if ( ! piggyBackData ) + piggyBackData = new PiggyBackData( this ); + + piggyBackData->append( toSend ); + } + + HostAndPort MessagingPort::remote() const { + if ( ! _remoteParsed.hasPort() ) + _remoteParsed = HostAndPort( remoteAddr() ); + return _remoteParsed; + } + + +} // namespace mongo diff --git a/src/mongo/util/net/message_port.h b/src/mongo/util/net/message_port.h new file mode 100644 index 00000000000..5d404d84f8a --- /dev/null +++ b/src/mongo/util/net/message_port.h @@ -0,0 +1,108 @@ +// message_port.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "sock.h" +#include "message.h" + +namespace mongo { + + class MessagingPort; + class PiggyBackData; + + typedef AtomicUInt MSGID; + + class AbstractMessagingPort : boost::noncopyable { + public: + AbstractMessagingPort() : tag(0) {} + virtual ~AbstractMessagingPort() { } + virtual void reply(Message& received, Message& response, MSGID responseTo) = 0; // like the reply below, but doesn't rely on received.data still being available + virtual void reply(Message& received, Message& response) = 0; + + virtual HostAndPort remote() const = 0; + virtual unsigned remotePort() const = 0; + + virtual void assertStillConnected() = 0; + + public: + // TODO make this private with some helpers + + /* ports can be tagged with various classes. see closeAllSockets(tag). defaults to 0. */ + unsigned tag; + + }; + + class MessagingPort : public AbstractMessagingPort , public Socket { + public: + MessagingPort(int fd, const SockAddr& remote); + + // in some cases the timeout will actually be 2x this value - eg we do a partial send, + // then the timeout fires, then we try to send again, then the timeout fires again with + // no data sent, then we detect that the other side is down + MessagingPort(double so_timeout = 0, int logLevel = 0 ); + + MessagingPort(Socket& socket); + + virtual ~MessagingPort(); + + void shutdown(); + + /* it's assumed if you reuse a message object, that it doesn't cross MessagingPort's. + also, the Message data will go out of scope on the subsequent recv call. + */ + bool recv(Message& m); + void reply(Message& received, Message& response, MSGID responseTo); + void reply(Message& received, Message& response); + bool call(Message& toSend, Message& response); + + void say(Message& toSend, int responseTo = -1); + + /** + * this is used for doing 'async' queries + * instead of doing call( to , from ) + * you would do + * say( to ) + * recv( from ) + * Note: if you fail to call recv and someone else uses this port, + * horrible things will happend + */ + bool recv( const Message& sent , Message& response ); + + void piggyBack( Message& toSend , int responseTo = -1 ); + + unsigned remotePort() const { return Socket::remotePort(); } + virtual HostAndPort remote() const; + + void assertStillConnected(); + + private: + + PiggyBackData * piggyBackData; + + // this is the parsed version of remote + // mutable because its initialized only on call to remote() + mutable HostAndPort _remoteParsed; + + public: + static void closeAllSockets(unsigned tagMask = 0xffffffff); + + friend class PiggyBackData; + }; + + +} // namespace mongo diff --git a/src/mongo/util/net/message_server.h b/src/mongo/util/net/message_server.h new file mode 100644 index 00000000000..ae77b97bb0f --- /dev/null +++ b/src/mongo/util/net/message_server.h @@ -0,0 +1,66 @@ +// message_server.h + +/* Copyright 2009 10gen 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. + */ + +/* + abstract database server + async io core, worker thread system + */ + +#pragma once + +#include "../../pch.h" + +namespace mongo { + + class MessageHandler { + public: + virtual ~MessageHandler() {} + + /** + * called once when a socket is connected + */ + virtual void connected( AbstractMessagingPort* p ) = 0; + + /** + * called every time a message comes in + * handler is responsible for responding to client + */ + virtual void process( Message& m , AbstractMessagingPort* p , LastError * err ) = 0; + + /** + * called once when a socket is disconnected + */ + virtual void disconnected( AbstractMessagingPort* p ) = 0; + }; + + class MessageServer { + public: + struct Options { + int port; // port to bind to + string ipList; // addresses to bind to + + Options() : port(0), ipList("") {} + }; + + virtual ~MessageServer() {} + virtual void run() = 0; + virtual void setAsTimeTracker() = 0; + }; + + // TODO use a factory here to decide between port and asio variations + MessageServer * createServer( const MessageServer::Options& opts , MessageHandler * handler ); +} diff --git a/src/mongo/util/net/message_server_asio.cpp b/src/mongo/util/net/message_server_asio.cpp new file mode 100644 index 00000000000..0c6a7d925da --- /dev/null +++ b/src/mongo/util/net/message_server_asio.cpp @@ -0,0 +1,261 @@ +// message_server_asio.cpp + +/* Copyright 2009 10gen 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. + */ + +#ifdef USE_ASIO + +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <boost/shared_ptr.hpp> + +#include <iostream> +#include <vector> + +#include "message.h" +#include "message_server.h" +#include "../util/concurrency/mvar.h" + +using namespace boost; +using namespace boost::asio; +using namespace boost::asio::ip; + +namespace mongo { + class MessageServerSession; + + namespace { + class StickyThread { + public: + StickyThread() + : _thread(boost::ref(*this)) + {} + + ~StickyThread() { + _mss.put(boost::shared_ptr<MessageServerSession>()); + _thread.join(); + } + + void ready(boost::shared_ptr<MessageServerSession> mss) { + _mss.put(mss); + } + + void operator() () { + boost::shared_ptr<MessageServerSession> mss; + while((mss = _mss.take())) { // intentionally not using == + task(mss.get()); + mss.reset(); + } + } + + private: + boost::thread _thread; + inline void task(MessageServerSession* mss); // must be defined after MessageServerSession + + MVar<boost::shared_ptr<MessageServerSession> > _mss; // populated when given a task + }; + + vector<boost::shared_ptr<StickyThread> > thread_pool; + mongo::mutex tp_mutex; // this is only needed if io_service::run() is called from multiple threads + } + + class MessageServerSession : public boost::enable_shared_from_this<MessageServerSession> , public AbstractMessagingPort { + public: + MessageServerSession( MessageHandler * handler , io_service& ioservice ) + : _handler( handler ) + , _socket( ioservice ) + , _portCache(0) + { } + + ~MessageServerSession() { + cout << "disconnect from: " << _socket.remote_endpoint() << endl; + } + + tcp::socket& socket() { + return _socket; + } + + void start() { + cout << "MessageServerSession start from:" << _socket.remote_endpoint() << endl; + _startHeaderRead(); + } + + void handleReadHeader( const boost::system::error_code& error ) { + if ( _inHeader.len == 0 ) + return; + + if ( ! _inHeader.valid() ) { + cout << " got invalid header from: " << _socket.remote_endpoint() << " closing connected" << endl; + return; + } + + char * raw = (char*)malloc( _inHeader.len ); + + MsgData * data = (MsgData*)raw; + memcpy( data , &_inHeader , sizeof( _inHeader ) ); + assert( data->len == _inHeader.len ); + + uassert( 10273 , "_cur not empty! pipelining requests not supported" , ! _cur.data ); + + _cur.setData( data , true ); + async_read( _socket , + buffer( raw + sizeof( _inHeader ) , _inHeader.len - sizeof( _inHeader ) ) , + boost::bind( &MessageServerSession::handleReadBody , shared_from_this() , boost::asio::placeholders::error ) ); + } + + void handleReadBody( const boost::system::error_code& error ) { + if (!_myThread) { + mongo::mutex::scoped_lock(tp_mutex); + if (!thread_pool.empty()) { + _myThread = thread_pool.back(); + thread_pool.pop_back(); + } + } + + if (!_myThread) // pool is empty + _myThread.reset(new StickyThread()); + + assert(_myThread); + + _myThread->ready(shared_from_this()); + } + + void process() { + _handler->process( _cur , this ); + + if (_reply.data) { + async_write( _socket , + buffer( (char*)_reply.data , _reply.data->len ) , + boost::bind( &MessageServerSession::handleWriteDone , shared_from_this() , boost::asio::placeholders::error ) ); + } + else { + _cur.reset(); + _startHeaderRead(); + } + } + + void handleWriteDone( const boost::system::error_code& error ) { + { + // return thread to pool after we have sent data to the client + mongo::mutex::scoped_lock(tp_mutex); + assert(_myThread); + thread_pool.push_back(_myThread); + _myThread.reset(); + } + _cur.reset(); + _reply.reset(); + _startHeaderRead(); + } + + virtual void reply( Message& received, Message& response ) { + reply( received , response , received.data->id ); + } + + virtual void reply( Message& query , Message& toSend, MSGID responseTo ) { + _reply = toSend; + + _reply.data->id = nextMessageId(); + _reply.data->responseTo = responseTo; + uassert( 10274 , "pipelining requests doesn't work yet" , query.data->id == _cur.data->id ); + } + + + virtual unsigned remotePort() { + if (!_portCache) + _portCache = _socket.remote_endpoint().port(); //this is expensive + return _portCache; + } + + private: + + void _startHeaderRead() { + _inHeader.len = 0; + async_read( _socket , + buffer( &_inHeader , sizeof( _inHeader ) ) , + boost::bind( &MessageServerSession::handleReadHeader , shared_from_this() , boost::asio::placeholders::error ) ); + } + + MessageHandler * _handler; + tcp::socket _socket; + MsgData _inHeader; + Message _cur; + Message _reply; + + unsigned _portCache; + + boost::shared_ptr<StickyThread> _myThread; + }; + + void StickyThread::task(MessageServerSession* mss) { + mss->process(); + } + + + class AsyncMessageServer : public MessageServer { + public: + // TODO accept an IP address to bind to + AsyncMessageServer( const MessageServer::Options& opts , MessageHandler * handler ) + : _port( opts.port ) + , _handler(handler) + , _endpoint( tcp::v4() , opts.port ) + , _acceptor( _ioservice , _endpoint ) { + _accept(); + } + virtual ~AsyncMessageServer() { + + } + + void run() { + cout << "AsyncMessageServer starting to listen on: " << _port << endl; + boost::thread other(boost::bind(&io_service::run, &_ioservice)); + _ioservice.run(); + cout << "AsyncMessageServer done listening on: " << _port << endl; + } + + void handleAccept( shared_ptr<MessageServerSession> session , + const boost::system::error_code& error ) { + if ( error ) { + cout << "handleAccept error!" << endl; + return; + } + session->start(); + _accept(); + } + + void _accept( ) { + shared_ptr<MessageServerSession> session( new MessageServerSession( _handler , _ioservice ) ); + _acceptor.async_accept( session->socket() , + boost::bind( &AsyncMessageServer::handleAccept, + this, + session, + boost::asio::placeholders::error ) + ); + } + + private: + int _port; + MessageHandler * _handler; + io_service _ioservice; + tcp::endpoint _endpoint; + tcp::acceptor _acceptor; + }; + + MessageServer * createServer( const MessageServer::Options& opts , MessageHandler * handler ) { + return new AsyncMessageServer( opts , handler ); + } + +} + +#endif diff --git a/src/mongo/util/net/message_server_port.cpp b/src/mongo/util/net/message_server_port.cpp new file mode 100644 index 00000000000..7e6a731529b --- /dev/null +++ b/src/mongo/util/net/message_server_port.cpp @@ -0,0 +1,204 @@ +// message_server_port.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" + +#ifndef USE_ASIO + +#include "message.h" +#include "message_port.h" +#include "message_server.h" +#include "listen.h" + +#include "../../db/cmdline.h" +#include "../../db/lasterror.h" +#include "../../db/stats/counters.h" + +#ifdef __linux__ // TODO: consider making this ifndef _WIN32 +# include <sys/resource.h> +#endif + +namespace mongo { + + namespace pms { + + MessageHandler * handler; + + void threadRun( MessagingPort * inPort) { + TicketHolderReleaser connTicketReleaser( &connTicketHolder ); + + setThreadName( "conn" ); + + assert( inPort ); + inPort->setLogLevel(1); + scoped_ptr<MessagingPort> p( inPort ); + + p->postFork(); + + string otherSide; + + Message m; + try { + LastError * le = new LastError(); + lastError.reset( le ); // lastError now has ownership + + otherSide = p->remoteString(); + + handler->connected( p.get() ); + + while ( ! inShutdown() ) { + m.reset(); + p->clearCounters(); + + if ( ! p->recv(m) ) { + if( !cmdLine.quiet ){ + int conns = connTicketHolder.used()-1; + const char* word = (conns == 1 ? " connection" : " connections"); + log() << "end connection " << otherSide << " (" << conns << word << " now open)" << endl; + } + p->shutdown(); + break; + } + + handler->process( m , p.get() , le ); + networkCounter.hit( p->getBytesIn() , p->getBytesOut() ); + } + } + catch ( AssertionException& e ) { + log() << "AssertionException handling request, closing client connection: " << e << endl; + p->shutdown(); + } + catch ( SocketException& e ) { + log() << "SocketException handling request, closing client connection: " << e << endl; + p->shutdown(); + } + catch ( const ClockSkewException & ) { + log() << "ClockSkewException - shutting down" << endl; + exitCleanly( EXIT_CLOCK_SKEW ); + } + catch ( const DBException& e ) { // must be right above std::exception to avoid catching subclasses + log() << "DBException handling request, closing client connection: " << e << endl; + p->shutdown(); + } + catch ( std::exception &e ) { + error() << "Uncaught std::exception: " << e.what() << ", terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + catch ( ... ) { + error() << "Uncaught exception, terminating" << endl; + dbexit( EXIT_UNCAUGHT ); + } + + handler->disconnected( p.get() ); + } + + } + + class PortMessageServer : public MessageServer , public Listener { + public: + PortMessageServer( const MessageServer::Options& opts, MessageHandler * handler ) : + Listener( "" , opts.ipList, opts.port ) { + + uassert( 10275 , "multiple PortMessageServer not supported" , ! pms::handler ); + pms::handler = handler; + } + + virtual void accepted(MessagingPort * p) { + + if ( ! connTicketHolder.tryAcquire() ) { + log() << "connection refused because too many open connections: " << connTicketHolder.used() << endl; + + // TODO: would be nice if we notified them... + p->shutdown(); + delete p; + + sleepmillis(2); // otherwise we'll hard loop + return; + } + + try { +#ifndef __linux__ // TODO: consider making this ifdef _WIN32 + boost::thread thr( boost::bind( &pms::threadRun , p ) ); +#else + pthread_attr_t attrs; + pthread_attr_init(&attrs); + pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED); + + static const size_t STACK_SIZE = 1024*1024; // if we change this we need to update the warning + + struct rlimit limits; + verify(15887, getrlimit(RLIMIT_STACK, &limits) == 0); + if (limits.rlim_cur > STACK_SIZE) { + pthread_attr_setstacksize(&attrs, (DEBUG_BUILD + ? (STACK_SIZE / 2) + : STACK_SIZE)); + } else if (limits.rlim_cur < 1024*1024) { + warning() << "Stack size set to " << (limits.rlim_cur/1024) << "KB. We suggest 1MB" << endl; + } + + + pthread_t thread; + int failed = pthread_create(&thread, &attrs, (void*(*)(void*)) &pms::threadRun, p); + + pthread_attr_destroy(&attrs); + + if (failed) { + log() << "pthread_create failed: " << errnoWithDescription(failed) << endl; + throw boost::thread_resource_error(); // for consistency with boost::thread + } +#endif + } + catch ( boost::thread_resource_error& ) { + connTicketHolder.release(); + log() << "can't create new thread, closing connection" << endl; + + p->shutdown(); + delete p; + + sleepmillis(2); + } + catch ( ... ) { + connTicketHolder.release(); + log() << "unknown error accepting new socket" << endl; + + p->shutdown(); + delete p; + + sleepmillis(2); + } + + } + + virtual void setAsTimeTracker() { + Listener::setAsTimeTracker(); + } + + void run() { + initAndListen(); + } + + virtual bool useUnixSockets() const { return true; } + }; + + + MessageServer * createServer( const MessageServer::Options& opts , MessageHandler * handler ) { + return new PortMessageServer( opts , handler ); + } + +} + +#endif diff --git a/src/mongo/util/net/miniwebserver.cpp b/src/mongo/util/net/miniwebserver.cpp new file mode 100644 index 00000000000..f0b58569d22 --- /dev/null +++ b/src/mongo/util/net/miniwebserver.cpp @@ -0,0 +1,212 @@ +// miniwebserver.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "miniwebserver.h" +#include "../hex.h" + +#include "pcrecpp.h" + +namespace mongo { + + MiniWebServer::MiniWebServer(const string& name, const string &ip, int port) + : Listener(name, ip, port, false) + {} + + string MiniWebServer::parseURL( const char * buf ) { + const char * urlStart = strchr( buf , ' ' ); + if ( ! urlStart ) + return "/"; + + urlStart++; + + const char * end = strchr( urlStart , ' ' ); + if ( ! end ) { + end = strchr( urlStart , '\r' ); + if ( ! end ) { + end = strchr( urlStart , '\n' ); + } + } + + if ( ! end ) + return "/"; + + int diff = (int)(end-urlStart); + if ( diff < 0 || diff > 255 ) + return "/"; + + return string( urlStart , (int)(end-urlStart) ); + } + + void MiniWebServer::parseParams( BSONObj & params , string query ) { + if ( query.size() == 0 ) + return; + + BSONObjBuilder b; + while ( query.size() ) { + + string::size_type amp = query.find( "&" ); + + string cur; + if ( amp == string::npos ) { + cur = query; + query = ""; + } + else { + cur = query.substr( 0 , amp ); + query = query.substr( amp + 1 ); + } + + string::size_type eq = cur.find( "=" ); + if ( eq == string::npos ) + continue; + + b.append( urlDecode(cur.substr(0,eq)) , urlDecode(cur.substr(eq+1) ) ); + } + + params = b.obj(); + } + + string MiniWebServer::parseMethod( const char * headers ) { + const char * end = strchr( headers , ' ' ); + if ( ! end ) + return "GET"; + return string( headers , (int)(end-headers) ); + } + + const char *MiniWebServer::body( const char *buf ) { + const char *ret = strstr( buf, "\r\n\r\n" ); + return ret ? ret + 4 : ret; + } + + bool MiniWebServer::fullReceive( const char *buf ) { + const char *bod = body( buf ); + if ( !bod ) + return false; + const char *lenString = "Content-Length:"; + const char *lengthLoc = strstr( buf, lenString ); + if ( !lengthLoc ) + return true; + lengthLoc += strlen( lenString ); + long len = strtol( lengthLoc, 0, 10 ); + if ( long( strlen( bod ) ) == len ) + return true; + return false; + } + + void MiniWebServer::accepted(Socket sock) { + sock.postFork(); + sock.setTimeout(8); + char buf[4096]; + int len = 0; + while ( 1 ) { + int left = sizeof(buf) - 1 - len; + if( left == 0 ) + break; + int x = sock.unsafe_recv( buf + len , left ); + if ( x <= 0 ) { + sock.close(); + return; + } + len += x; + buf[ len ] = 0; + if ( fullReceive( buf ) ) { + break; + } + } + buf[len] = 0; + + string responseMsg; + int responseCode = 599; + vector<string> headers; + + try { + doRequest(buf, parseURL( buf ), responseMsg, responseCode, headers, sock.remoteAddr() ); + } + catch ( std::exception& e ) { + responseCode = 500; + responseMsg = "error loading page: "; + responseMsg += e.what(); + } + catch ( ... ) { + responseCode = 500; + responseMsg = "unknown error loading page"; + } + + stringstream ss; + ss << "HTTP/1.0 " << responseCode; + if ( responseCode == 200 ) ss << " OK"; + ss << "\r\n"; + if ( headers.empty() ) { + ss << "Content-Type: text/html\r\n"; + } + else { + for ( vector<string>::iterator i = headers.begin(); i != headers.end(); i++ ) { + assert( strncmp("Content-Length", i->c_str(), 14) ); + ss << *i << "\r\n"; + } + } + ss << "Connection: close\r\n"; + ss << "Content-Length: " << responseMsg.size() << "\r\n"; + ss << "\r\n"; + ss << responseMsg; + string response = ss.str(); + + try { + sock.send( response.c_str(), response.size() , "http response" ); + sock.close(); + } + catch ( SocketException& e ) { + log(1) << "couldn't send data to http client: " << e << endl; + } + } + + string MiniWebServer::getHeader( const char * req , string wanted ) { + const char * headers = strchr( req , '\n' ); + if ( ! headers ) + return ""; + pcrecpp::StringPiece input( headers + 1 ); + + string name; + string val; + pcrecpp::RE re("([\\w\\-]+): (.*?)\r?\n"); + while ( re.Consume( &input, &name, &val) ) { + if ( name == wanted ) + return val; + } + return ""; + } + + string MiniWebServer::urlDecode(const char* s) { + stringstream out; + while(*s) { + if (*s == '+') { + out << ' '; + } + else if (*s == '%') { + out << fromHex(s+1); + s+=2; + } + else { + out << *s; + } + s++; + } + return out.str(); + } + +} // namespace mongo diff --git a/src/mongo/util/net/miniwebserver.h b/src/mongo/util/net/miniwebserver.h new file mode 100644 index 00000000000..1fb6b3f2e65 --- /dev/null +++ b/src/mongo/util/net/miniwebserver.h @@ -0,0 +1,60 @@ +// miniwebserver.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../../pch.h" +#include "message.h" +#include "message_port.h" +#include "listen.h" +#include "../../db/jsobj.h" + +namespace mongo { + + class MiniWebServer : public Listener { + public: + MiniWebServer(const string& name, const string &ip, int _port); + virtual ~MiniWebServer() {} + + virtual void doRequest( + const char *rq, // the full request + string url, + // set these and return them: + string& responseMsg, + int& responseCode, + vector<string>& headers, // if completely empty, content-type: text/html will be added + const SockAddr &from + ) = 0; + + // --- static helpers ---- + + static void parseParams( BSONObj & params , string query ); + + static string parseURL( const char * buf ); + static string parseMethod( const char * headers ); + static string getHeader( const char * headers , string name ); + static const char *body( const char *buf ); + + static string urlDecode(const char* s); + static string urlDecode(string s) {return urlDecode(s.c_str());} + + private: + void accepted(Socket socket); + static bool fullReceive( const char *buf ); + }; + +} // namespace mongo diff --git a/src/mongo/util/net/sock.cpp b/src/mongo/util/net/sock.cpp new file mode 100644 index 00000000000..bd08e6c64b9 --- /dev/null +++ b/src/mongo/util/net/sock.cpp @@ -0,0 +1,763 @@ +// @file sock.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "sock.h" +#include "../background.h" +#include "../concurrency/value.h" +#include "../mongoutils/str.h" + +#if !defined(_WIN32) +# include <sys/socket.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/un.h> +# include <netinet/in.h> +# include <netinet/tcp.h> +# include <arpa/inet.h> +# include <errno.h> +# include <netdb.h> +# if defined(__openbsd__) +# include <sys/uio.h> +# endif +#endif + +#ifdef MONGO_SSL +#include <openssl/err.h> +#include <openssl/ssl.h> +#endif + +using namespace mongoutils; + +namespace mongo { + + void dynHostResolve(string& name, int& port); + string dynHostMyName(); + + static bool ipv6 = false; + void enableIPv6(bool state) { ipv6 = state; } + bool IPv6Enabled() { return ipv6; } + + void setSockTimeouts(int sock, double secs) { + struct timeval tv; + tv.tv_sec = (int)secs; + tv.tv_usec = (int)((long long)(secs*1000*1000) % (1000*1000)); + bool report = logLevel > 3; // solaris doesn't provide these + DEV report = true; +#if defined(_WIN32) + tv.tv_sec *= 1000; // Windows timeout is a DWORD, in milliseconds. + int status = setsockopt( sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv.tv_sec, sizeof(DWORD) ) == 0; + if( report && (status == SOCKET_ERROR) ) log() << "unable to set SO_RCVTIMEO" << endl; + status = setsockopt( sock, SOL_SOCKET, SO_SNDTIMEO, (char *) &tv.tv_sec, sizeof(DWORD) ) == 0; + DEV if( report && (status == SOCKET_ERROR) ) log() << "unable to set SO_SNDTIMEO" << endl; +#else + bool ok = setsockopt( sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv) ) == 0; + if( report && !ok ) log() << "unable to set SO_RCVTIMEO" << endl; + ok = setsockopt( sock, SOL_SOCKET, SO_SNDTIMEO, (char *) &tv, sizeof(tv) ) == 0; + DEV if( report && !ok ) log() << "unable to set SO_SNDTIMEO" << endl; +#endif + } + +#if defined(_WIN32) + void disableNagle(int sock) { + int x = 1; + if ( setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &x, sizeof(x)) ) + error() << "disableNagle failed" << endl; + if ( setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &x, sizeof(x)) ) + error() << "SO_KEEPALIVE failed" << endl; + } +#else + + void disableNagle(int sock) { + int x = 1; + +#ifdef SOL_TCP + int level = SOL_TCP; +#else + int level = SOL_SOCKET; +#endif + + if ( setsockopt(sock, level, TCP_NODELAY, (char *) &x, sizeof(x)) ) + error() << "disableNagle failed: " << errnoWithDescription() << endl; + +#ifdef SO_KEEPALIVE + if ( setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &x, sizeof(x)) ) + error() << "SO_KEEPALIVE failed: " << errnoWithDescription() << endl; + +# ifdef __linux__ + socklen_t len = sizeof(x); + if ( getsockopt(sock, level, TCP_KEEPIDLE, (char *) &x, &len) ) + error() << "can't get TCP_KEEPIDLE: " << errnoWithDescription() << endl; + + if (x > 300) { + x = 300; + if ( setsockopt(sock, level, TCP_KEEPIDLE, (char *) &x, sizeof(x)) ) { + error() << "can't set TCP_KEEPIDLE: " << errnoWithDescription() << endl; + } + } + + len = sizeof(x); // just in case it changed + if ( getsockopt(sock, level, TCP_KEEPINTVL, (char *) &x, &len) ) + error() << "can't get TCP_KEEPINTVL: " << errnoWithDescription() << endl; + + if (x > 300) { + x = 300; + if ( setsockopt(sock, level, TCP_KEEPINTVL, (char *) &x, sizeof(x)) ) { + error() << "can't set TCP_KEEPINTVL: " << errnoWithDescription() << endl; + } + } +# endif +#endif + + } + +#endif + + string getAddrInfoStrError(int code) { +#if !defined(_WIN32) + return gai_strerror(code); +#else + /* gai_strerrorA is not threadsafe on windows. don't use it. */ + return errnoWithDescription(code); +#endif + } + + + // --- SockAddr + + SockAddr::SockAddr(int sourcePort) { + memset(as<sockaddr_in>().sin_zero, 0, sizeof(as<sockaddr_in>().sin_zero)); + as<sockaddr_in>().sin_family = AF_INET; + as<sockaddr_in>().sin_port = htons(sourcePort); + as<sockaddr_in>().sin_addr.s_addr = htonl(INADDR_ANY); + addressSize = sizeof(sockaddr_in); + } + + SockAddr::SockAddr(const char * _iporhost , int port) { + string target = _iporhost; + bool cloudName = *_iporhost == '#'; + if( target == "localhost" ) { + target = "127.0.0.1"; + } + else if( cloudName ) { + dynHostResolve(target, port); + } + + if( str::contains(target, '/') ) { +#ifdef _WIN32 + uassert(13080, "no unix socket support on windows", false); +#endif + uassert(13079, "path to unix socket too long", target.size() < sizeof(as<sockaddr_un>().sun_path)); + as<sockaddr_un>().sun_family = AF_UNIX; + strcpy(as<sockaddr_un>().sun_path, target.c_str()); + addressSize = sizeof(sockaddr_un); + } + else { + addrinfo* addrs = NULL; + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_socktype = SOCK_STREAM; + //hints.ai_flags = AI_ADDRCONFIG; // This is often recommended but don't do it. SERVER-1579 + hints.ai_flags |= AI_NUMERICHOST; // first pass tries w/o DNS lookup + hints.ai_family = (IPv6Enabled() ? AF_UNSPEC : AF_INET); + + StringBuilder ss; + ss << port; + int ret = getaddrinfo(target.c_str(), ss.str().c_str(), &hints, &addrs); + + // old C compilers on IPv6-capable hosts return EAI_NODATA error +#ifdef EAI_NODATA + int nodata = (ret == EAI_NODATA); +#else + int nodata = false; +#endif + if ( (ret == EAI_NONAME || nodata) && !cloudName ) { + // iporhost isn't an IP address, allow DNS lookup + hints.ai_flags &= ~AI_NUMERICHOST; + ret = getaddrinfo(target.c_str(), ss.str().c_str(), &hints, &addrs); + } + + if (ret) { + // we were unsuccessful + if( target != "0.0.0.0" ) { // don't log if this as it is a CRT construction and log() may not work yet. + log() << "getaddrinfo(\"" << target << "\") failed: " << gai_strerror(ret) << endl; + } + *this = SockAddr(port); + } + else { + //TODO: handle other addresses in linked list; + assert(addrs->ai_addrlen <= sizeof(sa)); + memcpy(&sa, addrs->ai_addr, addrs->ai_addrlen); + addressSize = addrs->ai_addrlen; + freeaddrinfo(addrs); + } + } + } + + bool SockAddr::isLocalHost() const { + switch (getType()) { + case AF_INET: return getAddr() == "127.0.0.1"; + case AF_INET6: return getAddr() == "::1"; + case AF_UNIX: return true; + default: return false; + } + assert(false); + return false; + } + + string SockAddr::toString(bool includePort) const { + string out = getAddr(); + if (includePort && getType() != AF_UNIX && getType() != AF_UNSPEC) + out += mongoutils::str::stream() << ':' << getPort(); + return out; + } + + sa_family_t SockAddr::getType() const { + return sa.ss_family; + } + + unsigned SockAddr::getPort() const { + switch (getType()) { + case AF_INET: return ntohs(as<sockaddr_in>().sin_port); + case AF_INET6: return ntohs(as<sockaddr_in6>().sin6_port); + case AF_UNIX: return 0; + case AF_UNSPEC: return 0; + default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported address family", false); return 0; + } + } + + string SockAddr::getAddr() const { + switch (getType()) { + case AF_INET: + case AF_INET6: { + const int buflen=128; + char buffer[buflen]; + int ret = getnameinfo(raw(), addressSize, buffer, buflen, NULL, 0, NI_NUMERICHOST); + massert(13082, str::stream() << "getnameinfo error " << getAddrInfoStrError(ret), ret == 0); + return buffer; + } + + case AF_UNIX: return (addressSize > 2 ? as<sockaddr_un>().sun_path : "anonymous unix socket"); + case AF_UNSPEC: return "(NONE)"; + default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported address family", false); return ""; + } + } + + bool SockAddr::operator==(const SockAddr& r) const { + if (getType() != r.getType()) + return false; + + if (getPort() != r.getPort()) + return false; + + switch (getType()) { + case AF_INET: return as<sockaddr_in>().sin_addr.s_addr == r.as<sockaddr_in>().sin_addr.s_addr; + case AF_INET6: return memcmp(as<sockaddr_in6>().sin6_addr.s6_addr, r.as<sockaddr_in6>().sin6_addr.s6_addr, sizeof(in6_addr)) == 0; + case AF_UNIX: return strcmp(as<sockaddr_un>().sun_path, r.as<sockaddr_un>().sun_path) == 0; + case AF_UNSPEC: return true; // assume all unspecified addresses are the same + default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported address family", false); + } + return false; + } + + bool SockAddr::operator!=(const SockAddr& r) const { + return !(*this == r); + } + + bool SockAddr::operator<(const SockAddr& r) const { + if (getType() < r.getType()) + return true; + else if (getType() > r.getType()) + return false; + + if (getPort() < r.getPort()) + return true; + else if (getPort() > r.getPort()) + return false; + + switch (getType()) { + case AF_INET: return as<sockaddr_in>().sin_addr.s_addr < r.as<sockaddr_in>().sin_addr.s_addr; + case AF_INET6: return memcmp(as<sockaddr_in6>().sin6_addr.s6_addr, r.as<sockaddr_in6>().sin6_addr.s6_addr, sizeof(in6_addr)) < 0; + case AF_UNIX: return strcmp(as<sockaddr_un>().sun_path, r.as<sockaddr_un>().sun_path) < 0; + case AF_UNSPEC: return false; + default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported address family", false); + } + return false; + } + + SockAddr unknownAddress( "0.0.0.0", 0 ); + + // If an ip address is passed in, just return that. If a hostname is passed + // in, look up its ip and return that. Returns "" on failure. + string hostbyname(const char *hostname) { + if( *hostname == '#' ) { + string s = hostname; + int port; + dynHostResolve(s, port); + return s; + } + + string addr = SockAddr(hostname, 0).getAddr(); + if (addr == "0.0.0.0") + return ""; + else + return addr; + } + + // --- my -- + + DiagStr _hostNameCached; + + string getHostName() { + { + string s = dynHostMyName(); + if( !s.empty() ) + return s; + } + + char buf[256]; + int ec = gethostname(buf, 127); + if ( ec || *buf == 0 ) { + log() << "can't get this server's hostname " << errnoWithDescription() << endl; + return ""; + } + return buf; + } + + static void _hostNameCachedInit() { + _hostNameCached = getHostName(); + } + boost::once_flag _hostNameCachedInitFlags = BOOST_ONCE_INIT; + + /** we store our host name once */ + // ok w dynhosts map? + string getHostNameCached() { + boost::call_once( _hostNameCachedInit , _hostNameCachedInitFlags ); + return _hostNameCached; + } + + // --------- SocketException ---------- + +#ifdef MSG_NOSIGNAL + const int portSendFlags = MSG_NOSIGNAL; + const int portRecvFlags = MSG_NOSIGNAL; +#else + const int portSendFlags = 0; + const int portRecvFlags = 0; +#endif + + string SocketException::toString() const { + stringstream ss; + ss << _ei.code << " socket exception [" << _type << "] "; + + if ( _server.size() ) + ss << "server [" << _server << "] "; + + if ( _extra.size() ) + ss << _extra; + + return ss.str(); + } + + + // ------------ SSLManager ----------------- + +#ifdef MONGO_SSL + SSLManager::SSLManager( bool client ) { + _client = client; + SSL_library_init(); + SSL_load_error_strings(); + ERR_load_crypto_strings(); + + _context = SSL_CTX_new( client ? SSLv23_client_method() : SSLv23_server_method() ); + massert( 15864 , mongoutils::str::stream() << "can't create SSL Context: " << ERR_error_string(ERR_get_error(), NULL) , _context ); + + SSL_CTX_set_options( _context, SSL_OP_ALL); + } + + void SSLManager::setupPubPriv( const string& privateKeyFile , const string& publicKeyFile ) { + massert( 15865 , + mongoutils::str::stream() << "Can't read SSL certificate from file " + << publicKeyFile << ":" << ERR_error_string(ERR_get_error(), NULL) , + SSL_CTX_use_certificate_file(_context, publicKeyFile.c_str(), SSL_FILETYPE_PEM) ); + + + massert( 15866 , + mongoutils::str::stream() << "Can't read SSL private key from file " + << privateKeyFile << " : " << ERR_error_string(ERR_get_error(), NULL) , + SSL_CTX_use_PrivateKey_file(_context, privateKeyFile.c_str(), SSL_FILETYPE_PEM) ); + } + + + int SSLManager::password_cb(char *buf,int num, int rwflag,void *userdata){ + SSLManager* sm = (SSLManager*)userdata; + string pass = sm->_password; + strcpy(buf,pass.c_str()); + return(pass.size()); + } + + void SSLManager::setupPEM( const string& keyFile , const string& password ) { + _password = password; + + massert( 15867 , "Can't read certificate file" , SSL_CTX_use_certificate_chain_file( _context , keyFile.c_str() ) ); + + SSL_CTX_set_default_passwd_cb_userdata( _context , this ); + SSL_CTX_set_default_passwd_cb( _context, &SSLManager::password_cb ); + + massert( 15868 , "Can't read key file" , SSL_CTX_use_PrivateKey_file( _context , keyFile.c_str() , SSL_FILETYPE_PEM ) ); + } + + SSL * SSLManager::secure( int fd ) { + SSL * ssl = SSL_new( _context ); + massert( 15861 , "can't create SSL" , ssl ); + SSL_set_fd( ssl , fd ); + return ssl; + } + + +#endif + + // ------------ Socket ----------------- + + Socket::Socket(int fd , const SockAddr& remote) : + _fd(fd), _remote(remote), _timeout(0) { + _logLevel = 0; + _init(); + } + + Socket::Socket( double timeout, int ll ) { + _logLevel = ll; + _fd = -1; + _timeout = timeout; + _init(); + } + + void Socket::_init() { + _bytesOut = 0; + _bytesIn = 0; +#ifdef MONGO_SSL + _sslAccepted = 0; +#endif + } + + void Socket::close() { +#ifdef MONGO_SSL + _ssl.reset(); +#endif + if ( _fd >= 0 ) { + closesocket( _fd ); + _fd = -1; + } + } + +#ifdef MONGO_SSL + void Socket::secure( SSLManager * ssl ) { + assert( ssl ); + assert( _fd >= 0 ); + _ssl.reset( ssl->secure( _fd ) ); + SSL_connect( _ssl.get() ); + } + + void Socket::secureAccepted( SSLManager * ssl ) { + _sslAccepted = ssl; + } +#endif + + void Socket::postFork() { +#ifdef MONGO_SSL + if ( _sslAccepted ) { + assert( _fd ); + _ssl.reset( _sslAccepted->secure( _fd ) ); + SSL_accept( _ssl.get() ); + _sslAccepted = 0; + } +#endif + } + + class ConnectBG : public BackgroundJob { + public: + ConnectBG(int sock, SockAddr remote) : _sock(sock), _remote(remote) { } + + void run() { _res = ::connect(_sock, _remote.raw(), _remote.addressSize); } + string name() const { return "ConnectBG"; } + int inError() const { return _res; } + + private: + int _sock; + int _res; + SockAddr _remote; + }; + + bool Socket::connect(SockAddr& remote) { + _remote = remote; + + _fd = socket(remote.getType(), SOCK_STREAM, 0); + if ( _fd == INVALID_SOCKET ) { + log(_logLevel) << "ERROR: connect invalid socket " << errnoWithDescription() << endl; + return false; + } + + if ( _timeout > 0 ) { + setTimeout( _timeout ); + } + + ConnectBG bg(_fd, remote); + bg.go(); + if ( bg.wait(5000) ) { + if ( bg.inError() ) { + close(); + return false; + } + } + else { + // time out the connect + close(); + bg.wait(); // so bg stays in scope until bg thread terminates + return false; + } + + if (remote.getType() != AF_UNIX) + disableNagle(_fd); + +#ifdef SO_NOSIGPIPE + // osx + const int one = 1; + setsockopt( _fd , SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(int)); +#endif + + return true; + } + + int Socket::_send( const char * data , int len ) { +#ifdef MONGO_SSL + if ( _ssl ) { + return SSL_write( _ssl.get() , data , len ); + } +#endif + return ::send( _fd , data , len , portSendFlags ); + } + + bool Socket::stillConnected() { +#ifdef MONGO_SSL + DEV log() << "TODO stillConnected() w/SSL" << endl; +#else + int r = _send("", 0); + if( r < 0 ) { +#if defined(_WIN32) + if ( WSAGetLastError() == WSAETIMEDOUT ) { +#else + if ( ( errno == EAGAIN || errno == EWOULDBLOCK ) ) { +#endif + ; + } + else { + return false; + } + } +#endif + return true; + } + + // sends all data or throws an exception + void Socket::send( const char * data , int len, const char *context ) { + while( len > 0 ) { + int ret = _send( data , len ); + if ( ret == -1 ) { + +#ifdef MONGO_SSL + if ( _ssl ) { + log() << "SSL Error ret: " << ret << " err: " << SSL_get_error( _ssl.get() , ret ) + << " " << ERR_error_string(ERR_get_error(), NULL) + << endl; + } +#endif + +#if defined(_WIN32) + if ( WSAGetLastError() == WSAETIMEDOUT && _timeout != 0 ) { +#else + if ( ( errno == EAGAIN || errno == EWOULDBLOCK ) && _timeout != 0 ) { +#endif + log(_logLevel) << "Socket " << context << " send() timed out " << _remote.toString() << endl; + throw SocketException( SocketException::SEND_TIMEOUT , remoteString() ); + } + else { + SocketException::Type t = SocketException::SEND_ERROR; + log(_logLevel) << "Socket " << context << " send() " + << errnoWithDescription() << ' ' << remoteString() << endl; + throw SocketException( t , remoteString() ); + } + } + else { + _bytesOut += ret; + + assert( ret <= len ); + len -= ret; + data += ret; + } + } + } + + void Socket::_send( const vector< pair< char *, int > > &data, const char *context ) { + for( vector< pair< char *, int > >::const_iterator i = data.begin(); i != data.end(); ++i ) { + char * data = i->first; + int len = i->second; + send( data, len, context ); + } + } + + /** sends all data or throws an exception + * @param context descriptive for logging + */ + void Socket::send( const vector< pair< char *, int > > &data, const char *context ) { + +#ifdef MONGO_SSL + if ( _ssl ) { + _send( data , context ); + return; + } +#endif + +#if defined(_WIN32) + // TODO use scatter/gather api + _send( data , context ); +#else + vector< struct iovec > d( data.size() ); + int i = 0; + for( vector< pair< char *, int > >::const_iterator j = data.begin(); j != data.end(); ++j ) { + if ( j->second > 0 ) { + d[ i ].iov_base = j->first; + d[ i ].iov_len = j->second; + ++i; + _bytesOut += j->second; + } + } + struct msghdr meta; + memset( &meta, 0, sizeof( meta ) ); + meta.msg_iov = &d[ 0 ]; + meta.msg_iovlen = d.size(); + + while( meta.msg_iovlen > 0 ) { + int ret = ::sendmsg( _fd , &meta , portSendFlags ); + if ( ret == -1 ) { + if ( errno != EAGAIN || _timeout == 0 ) { + log(_logLevel) << "Socket " << context << " send() " << errnoWithDescription() << ' ' << remoteString() << endl; + throw SocketException( SocketException::SEND_ERROR , remoteString() ); + } + else { + log(_logLevel) << "Socket " << context << " send() remote timeout " << remoteString() << endl; + throw SocketException( SocketException::SEND_TIMEOUT , remoteString() ); + } + } + else { + struct iovec *& i = meta.msg_iov; + while( ret > 0 ) { + if ( i->iov_len > unsigned( ret ) ) { + i->iov_len -= ret; + i->iov_base = (char*)(i->iov_base) + ret; + ret = 0; + } + else { + ret -= i->iov_len; + ++i; + --(meta.msg_iovlen); + } + } + } + } +#endif + } + + void Socket::recv( char * buf , int len ) { + unsigned retries = 0; + while( len > 0 ) { + int ret = unsafe_recv( buf , len ); + if ( ret > 0 ) { + if ( len <= 4 && ret != len ) + log(_logLevel) << "Socket recv() got " << ret << " bytes wanted len=" << len << endl; + assert( ret <= len ); + len -= ret; + buf += ret; + } + else if ( ret == 0 ) { + log(3) << "Socket recv() conn closed? " << remoteString() << endl; + throw SocketException( SocketException::CLOSED , remoteString() ); + } + else { /* ret < 0 */ +#if defined(_WIN32) + int e = WSAGetLastError(); +#else + int e = errno; +# if defined(EINTR) + if( e == EINTR ) { + if( ++retries == 1 ) { + log() << "EINTR retry" << endl; + continue; + } + } +# endif +#endif + if ( ( e == EAGAIN +#if defined(_WIN32) + || e == WSAETIMEDOUT +#endif + ) && _timeout > 0 ) + { + // this is a timeout + log(_logLevel) << "Socket recv() timeout " << remoteString() <<endl; + throw SocketException( SocketException::RECV_TIMEOUT, remoteString() ); + } + + log(_logLevel) << "Socket recv() " << errnoWithDescription(e) << " " << remoteString() <<endl; + throw SocketException( SocketException::RECV_ERROR , remoteString() ); + } + } + } + + int Socket::unsafe_recv( char *buf, int max ) { + int x = _recv( buf , max ); + _bytesIn += x; + return x; + } + + + int Socket::_recv( char *buf, int max ) { +#ifdef MONGO_SSL + if ( _ssl ){ + return SSL_read( _ssl.get() , buf , max ); + } +#endif + return ::recv( _fd , buf , max , portRecvFlags ); + } + + void Socket::setTimeout( double secs ) { + setSockTimeouts( _fd, secs ); + } + +#if defined(_WIN32) + struct WinsockInit { + WinsockInit() { + WSADATA d; + if ( WSAStartup(MAKEWORD(2,2), &d) != 0 ) { + out() << "ERROR: wsastartup failed " << errnoWithDescription() << endl; + problem() << "ERROR: wsastartup failed " << errnoWithDescription() << endl; + dbexit( EXIT_NTSERVICE_ERROR ); + } + } + } winsock_init; +#endif + +} // namespace mongo diff --git a/src/mongo/util/net/sock.h b/src/mongo/util/net/sock.h new file mode 100644 index 00000000000..2053768cbd5 --- /dev/null +++ b/src/mongo/util/net/sock.h @@ -0,0 +1,261 @@ +// @file sock.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../../pch.h" + +#include <stdio.h> +#include <sstream> +#include "../goodies.h" +#include "../../db/cmdline.h" +#include "../mongoutils/str.h" + +#ifndef _WIN32 + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#ifdef __openbsd__ +# include <sys/uio.h> +#endif + +#endif // _WIN32 + +#ifdef MONGO_SSL +#include <openssl/ssl.h> +#endif + +namespace mongo { + + const int SOCK_FAMILY_UNKNOWN_ERROR=13078; + + void disableNagle(int sock); + +#if defined(_WIN32) + + typedef short sa_family_t; + typedef int socklen_t; + + // This won't actually be used on windows + struct sockaddr_un { + short sun_family; + char sun_path[108]; // length from unix header + }; + +#else // _WIN32 + + inline void closesocket(int s) { close(s); } + const int INVALID_SOCKET = -1; + typedef int SOCKET; + +#endif // _WIN32 + + inline string makeUnixSockPath(int port) { + return mongoutils::str::stream() << cmdLine.socket << "/mongodb-" << port << ".sock"; + } + + // If an ip address is passed in, just return that. If a hostname is passed + // in, look up its ip and return that. Returns "" on failure. + string hostbyname(const char *hostname); + + void enableIPv6(bool state=true); + bool IPv6Enabled(); + void setSockTimeouts(int sock, double secs); + + /** + * wrapped around os representation of network address + */ + struct SockAddr { + SockAddr() { + addressSize = sizeof(sa); + memset(&sa, 0, sizeof(sa)); + sa.ss_family = AF_UNSPEC; + } + SockAddr(int sourcePort); /* listener side */ + SockAddr(const char *ip, int port); /* EndPoint (remote) side, or if you want to specify which interface locally */ + + template <typename T> T& as() { return *(T*)(&sa); } + template <typename T> const T& as() const { return *(const T*)(&sa); } + + string toString(bool includePort=true) const; + + /** + * @return one of AF_INET, AF_INET6, or AF_UNIX + */ + sa_family_t getType() const; + + unsigned getPort() const; + + string getAddr() const; + + bool isLocalHost() const; + + bool operator==(const SockAddr& r) const; + + bool operator!=(const SockAddr& r) const; + + bool operator<(const SockAddr& r) const; + + const sockaddr* raw() const {return (sockaddr*)&sa;} + sockaddr* raw() {return (sockaddr*)&sa;} + + socklen_t addressSize; + private: + struct sockaddr_storage sa; + }; + + extern SockAddr unknownAddress; // ( "0.0.0.0", 0 ) + + /** this is not cache and does a syscall */ + string getHostName(); + + /** this is cached, so if changes during the process lifetime + * will be stale */ + string getHostNameCached(); + + /** + * thrown by Socket and SockAddr + */ + class SocketException : public DBException { + public: + const enum Type { CLOSED , RECV_ERROR , SEND_ERROR, RECV_TIMEOUT, SEND_TIMEOUT, FAILED_STATE, CONNECT_ERROR } _type; + + SocketException( Type t , string server , int code = 9001 , string extra="" ) + : DBException( "socket exception" , code ) , _type(t) , _server(server), _extra(extra){ } + virtual ~SocketException() throw() {} + + bool shouldPrint() const { return _type != CLOSED; } + virtual string toString() const; + + private: + string _server; + string _extra; + }; + +#ifdef MONGO_SSL + class SSLManager : boost::noncopyable { + public: + SSLManager( bool client ); + + void setupPEM( const string& keyFile , const string& password ); + void setupPubPriv( const string& privateKeyFile , const string& publicKeyFile ); + + /** + * creates an SSL context to be used for this file descriptor + * caller should delete + */ + SSL * secure( int fd ); + + static int password_cb( char *buf,int num, int rwflag,void *userdata ); + + private: + bool _client; + SSL_CTX* _context; + string _password; + }; +#endif + + /** + * thin wrapped around file descriptor and system calls + * todo: ssl + */ + class Socket { + public: + Socket(int sock, const SockAddr& farEnd); + + /** In some cases the timeout will actually be 2x this value - eg we do a partial send, + then the timeout fires, then we try to send again, then the timeout fires again with + no data sent, then we detect that the other side is down. + + Generally you don't want a timeout, you should be very prepared for errors if you set one. + */ + Socket(double so_timeout = 0, int logLevel = 0 ); + + bool connect(SockAddr& farEnd); + void close(); + + void send( const char * data , int len, const char *context ); + void send( const vector< pair< char *, int > > &data, const char *context ); + + // recv len or throw SocketException + void recv( char * data , int len ); + int unsafe_recv( char *buf, int max ); + + int getLogLevel() const { return _logLevel; } + void setLogLevel( int ll ) { _logLevel = ll; } + + SockAddr remoteAddr() const { return _remote; } + string remoteString() const { return _remote.toString(); } + unsigned remotePort() const { return _remote.getPort(); } + + void clearCounters() { _bytesIn = 0; _bytesOut = 0; } + long long getBytesIn() const { return _bytesIn; } + long long getBytesOut() const { return _bytesOut; } + + void setTimeout( double secs ); + + bool stillConnected(); + +#ifdef MONGO_SSL + /** secures inline */ + void secure( SSLManager * ssl ); + + void secureAccepted( SSLManager * ssl ); +#endif + + /** + * call this after a fork for server sockets + */ + void postFork(); + + private: + void _init(); + + /** raw send, same semantics as ::send */ + public: + int _send( const char * data , int len ); + private: + + /** sends dumbly, just each buffer at a time */ + void _send( const vector< pair< char *, int > > &data, const char *context ); + + /** raw recv, same semantics as ::recv */ + int _recv( char * buf , int max ); + + int _fd; + SockAddr _remote; + double _timeout; + + long long _bytesIn; + long long _bytesOut; + +#ifdef MONGO_SSL + shared_ptr<SSL> _ssl; + SSLManager * _sslAccepted; +#endif + + protected: + int _logLevel; // passed to log() when logging errors + + }; + + +} // namespace mongo diff --git a/src/mongo/util/ntservice.cpp b/src/mongo/util/ntservice.cpp new file mode 100644 index 00000000000..93cfd4a2de0 --- /dev/null +++ b/src/mongo/util/ntservice.cpp @@ -0,0 +1,408 @@ +// ntservice.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "ntservice.h" +#include "../db/client.h" +#include "winutil.h" +#include "text.h" +#include <direct.h> + +#if defined(_WIN32) + +namespace mongo { + + void shutdownServer(); + + SERVICE_STATUS_HANDLE ServiceController::_statusHandle = NULL; + std::wstring ServiceController::_serviceName; + ServiceCallback ServiceController::_serviceCallback = NULL; + + ServiceController::ServiceController() {} + + bool initService(); + + // returns true if the service is started. + bool serviceParamsCheck( boost::program_options::variables_map& params, const std::string dbpath, int argc, char* argv[] ) { + bool installService = false; + bool removeService = false; + bool reinstallService = false; + bool startService = false; + + std::wstring windowsServiceName = L"MongoDB"; + std::wstring windowsServiceDisplayName = L"Mongo DB"; + std::wstring windowsServiceDescription = L"Mongo DB Server"; + std::wstring windowsServiceUser = L""; + std::wstring windowsServicePassword = L""; + + if (params.count("install")) { + if ( ! params.count( "logpath" ) ) { + cerr << "--install has to be used with --logpath" << endl; + ::exit(-1); + } + installService = true; + } + if (params.count("reinstall")) { + if ( ! params.count( "logpath" ) ) { + cerr << "--reinstall has to be used with --logpath" << endl; + ::exit(-1); + } + reinstallService = true; + } + if (params.count("remove")) { + removeService = true; + } + if (params.count("service")) { + startService = true; + } + + if (params.count("serviceName")) { + string x = params["serviceName"].as<string>(); + windowsServiceName = wstring(x.size(),L' '); + for ( size_t i=0; i<x.size(); i++) { + windowsServiceName[i] = x[i]; + } + } + if (params.count("serviceDisplayName")) { + string x = params["serviceDisplayName"].as<string>(); + windowsServiceDisplayName = wstring(x.size(),L' '); + for ( size_t i=0; i<x.size(); i++) { + windowsServiceDisplayName[i] = x[i]; + } + } + if (params.count("serviceDescription")) { + string x = params["serviceDescription"].as<string>(); + windowsServiceDescription = wstring(x.size(),L' '); + for ( size_t i=0; i<x.size(); i++) { + windowsServiceDescription[i] = x[i]; + } + } + if (params.count("serviceUser")) { + string x = params["serviceUser"].as<string>(); + windowsServiceUser = wstring(x.size(),L' '); + for ( size_t i=0; i<x.size(); i++) { + windowsServiceUser[i] = x[i]; + } + } + if (params.count("servicePassword")) { + string x = params["servicePassword"].as<string>(); + windowsServicePassword = wstring(x.size(),L' '); + for ( size_t i=0; i<x.size(); i++) { + windowsServicePassword[i] = x[i]; + } + } + + if ( reinstallService ) { + ServiceController::removeService( windowsServiceName ); + } + if ( installService || reinstallService ) { + if ( !ServiceController::installService( windowsServiceName , windowsServiceDisplayName, windowsServiceDescription, windowsServiceUser, windowsServicePassword, dbpath, argc, argv ) ) + dbexit( EXIT_NTSERVICE_ERROR ); + dbexit( EXIT_CLEAN ); + } + else if ( removeService ) { + if ( !ServiceController::removeService( windowsServiceName ) ) + dbexit( EXIT_NTSERVICE_ERROR ); + dbexit( EXIT_CLEAN ); + } + else if ( startService ) { + if ( !ServiceController::startService( windowsServiceName , mongo::initService ) ) + dbexit( EXIT_NTSERVICE_ERROR ); + return true; + } + return false; + } + + bool ServiceController::installService( const std::wstring& serviceName, const std::wstring& displayName, const std::wstring& serviceDesc, const std::wstring& serviceUser, const std::wstring& servicePassword, const std::string dbpath, int argc, char* argv[] ) { + assert(argc >= 1); + + stringstream commandLine; + + char exePath[1024]; + GetModuleFileNameA( NULL, exePath, sizeof exePath ); + commandLine << '"' << exePath << "\" "; + + for ( int i = 1; i < argc; i++ ) { + std::string arg( argv[ i ] ); + // replace install command to indicate process is being started as a service + if ( arg == "--install" || arg == "--reinstall" ) { + arg = "--service"; + } + else if ( arg == "--dbpath" && i + 1 < argc ) { + commandLine << arg << " \"" << dbpath << "\" "; + i++; + continue; + } + else if ( arg == "--logpath" && i + 1 < argc ) { + commandLine << arg << " \"" << argv[i+1] << "\" "; + i++; + continue; + } + else if ( arg == "-f" && i + 1 < argc ) { + commandLine << arg << " \"" << argv[i+1] << "\" "; + i++; + continue; + } + else if ( arg == "--config" && i + 1 < argc ) { + commandLine << arg << " \"" << argv[i+1] << "\" "; + i++; + continue; + } + else if ( arg == "--pidfilepath" && i + 1 < argc ) { + commandLine << arg << " \"" << argv[i+1] << "\" "; + i++; + continue; + } + else if ( arg == "--repairpath" && i + 1 < argc ) { + commandLine << arg << " \"" << argv[i+1] << "\" "; + i++; + continue; + } + else if ( arg == "--keyfile" && i + 1 < argc ) { + commandLine << arg << " \"" << argv[i+1] << "\" "; + i++; + continue; + } + else if ( arg.length() > 9 && arg.substr(0, 9) == "--service" ) { + // Strip off --service(Name|User|Password) arguments + i++; + continue; + } + commandLine << arg << " "; + } + + SC_HANDLE schSCManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + if ( schSCManager == NULL ) { + DWORD err = ::GetLastError(); + cerr << "Error connecting to the Service Control Manager: " << GetWinErrMsg(err) << endl; + return false; + } + + // Make sure servise doesn't already exist. + // TODO: Check to see if service is in "Deleting" status, suggest the user close down Services MMC snap-ins. + SC_HANDLE schService = ::OpenService( schSCManager, serviceName.c_str(), SERVICE_ALL_ACCESS ); + if ( schService != NULL ) { + cerr << "There is already a service named " << toUtf8String(serviceName) << ". Aborting" << endl; + ::CloseServiceHandle( schService ); + ::CloseServiceHandle( schSCManager ); + return false; + } + std::basic_ostringstream< TCHAR > commandLineWide; + commandLineWide << commandLine.str().c_str(); + + cerr << "Creating service " << toUtf8String(serviceName) << "." << endl; + + // create new service + schService = ::CreateService( schSCManager, serviceName.c_str(), displayName.c_str(), + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, + commandLineWide.str().c_str(), NULL, NULL, L"\0\0", NULL, NULL ); + if ( schService == NULL ) { + DWORD err = ::GetLastError(); + cerr << "Error creating service: " << GetWinErrMsg(err) << endl; + ::CloseServiceHandle( schSCManager ); + return false; + } + + cerr << "Service creation successful." << endl; + cerr << "Service can be started from the command line via 'net start \"" << toUtf8String(serviceName) << "\"'." << endl; + + bool serviceInstalled; + + // TODO: If neccessary grant user "Login as a Service" permission. + if ( !serviceUser.empty() ) { + std::wstring actualServiceUser; + if ( serviceUser.find(L"\\") == string::npos ) { + actualServiceUser = L".\\" + serviceUser; + } + else { + actualServiceUser = serviceUser; + } + + cerr << "Setting service login credentials. User: " << toUtf8String(actualServiceUser) << endl; + serviceInstalled = ::ChangeServiceConfig( schService, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, actualServiceUser.c_str(), servicePassword.c_str(), NULL ); + if ( !serviceInstalled ) { + cerr << "Setting service login failed. Service has 'LocalService' permissions." << endl; + } + } + + // set the service description + SERVICE_DESCRIPTION serviceDescription; + serviceDescription.lpDescription = (LPTSTR)serviceDesc.c_str(); + serviceInstalled = ::ChangeServiceConfig2( schService, SERVICE_CONFIG_DESCRIPTION, &serviceDescription ); + +#if 1 + if ( ! serviceInstalled ) { +#else + // This code sets the mongod service to auto-restart, forever. + // This might be a fine thing to do except that when mongod or Windows has a crash, the mongo.lock + // file is still around, so any attempt at a restart will immediately fail. With auto-restart, we + // go into a loop, crashing and restarting, crashing and restarting, until someone comes in and + // disables the service or deletes the mongod.lock file. + // + // I'm leaving the old code here for now in case we solve this and are able to turn SC_ACTION_RESTART + // back on. + // + if ( serviceInstalled ) { + SC_ACTION aActions[ 3 ] = { { SC_ACTION_RESTART, 0 }, { SC_ACTION_RESTART, 0 }, { SC_ACTION_RESTART, 0 } }; + + SERVICE_FAILURE_ACTIONS serviceFailure; + ZeroMemory( &serviceFailure, sizeof( SERVICE_FAILURE_ACTIONS ) ); + serviceFailure.cActions = 3; + serviceFailure.lpsaActions = aActions; + + // set service recovery options + serviceInstalled = ::ChangeServiceConfig2( schService, SERVICE_CONFIG_FAILURE_ACTIONS, &serviceFailure ); + + } + else { +#endif + cerr << "Could not set service description. Check the event log for more details." << endl; + } + + ::CloseServiceHandle( schService ); + ::CloseServiceHandle( schSCManager ); + + return serviceInstalled; + } + + bool ServiceController::removeService( const std::wstring& serviceName ) { + SC_HANDLE schSCManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + if ( schSCManager == NULL ) { + DWORD err = ::GetLastError(); + cerr << "Error connecting to the Service Control Manager: " << GetWinErrMsg(err) << endl; + return false; + } + + SC_HANDLE schService = ::OpenService( schSCManager, serviceName.c_str(), SERVICE_ALL_ACCESS ); + if ( schService == NULL ) { + cerr << "Could not find a service named " << toUtf8String(serviceName) << " to uninstall." << endl; + ::CloseServiceHandle( schSCManager ); + return false; + } + + SERVICE_STATUS serviceStatus; + + // stop service if its running + if ( ::ControlService( schService, SERVICE_CONTROL_STOP, &serviceStatus ) ) { + cerr << "Service " << toUtf8String(serviceName) << " is currently running. Stopping service." << endl; + while ( ::QueryServiceStatus( schService, &serviceStatus ) ) { + if ( serviceStatus.dwCurrentState == SERVICE_STOP_PENDING ) { + Sleep( 1000 ); + } + else { break; } + } + cerr << "Service stopped." << endl; + } + + cerr << "Deleting service " << toUtf8String(serviceName) << "." << endl; + bool serviceRemoved = ::DeleteService( schService ); + + ::CloseServiceHandle( schService ); + ::CloseServiceHandle( schSCManager ); + + if (serviceRemoved) { + cerr << "Service deleted successfully." << endl; + } + else { + cerr << "Failed to delete service." << endl; + } + + return serviceRemoved; + } + + bool ServiceController::startService( const std::wstring& serviceName, ServiceCallback startService ) { + _serviceName = serviceName; + _serviceCallback = startService; + + SERVICE_TABLE_ENTRY dispTable[] = { + { (LPTSTR)serviceName.c_str(), (LPSERVICE_MAIN_FUNCTION)ServiceController::initService }, + { NULL, NULL } + }; + + return StartServiceCtrlDispatcher( dispTable ); + } + + bool ServiceController::reportStatus( DWORD reportState, DWORD waitHint ) { + if ( _statusHandle == NULL ) + return false; + + static DWORD checkPoint = 1; + + SERVICE_STATUS ssStatus; + + DWORD dwControlsAccepted; + switch ( reportState ) { + case SERVICE_START_PENDING: + case SERVICE_STOP_PENDING: + case SERVICE_STOPPED: + dwControlsAccepted = 0; + break; + default: + dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + break; + } + + ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ssStatus.dwServiceSpecificExitCode = 0; + ssStatus.dwControlsAccepted = dwControlsAccepted; + ssStatus.dwCurrentState = reportState; + ssStatus.dwWin32ExitCode = NO_ERROR; + ssStatus.dwWaitHint = waitHint; + ssStatus.dwCheckPoint = ( reportState == SERVICE_RUNNING || reportState == SERVICE_STOPPED ) ? 0 : checkPoint++; + + return SetServiceStatus( _statusHandle, &ssStatus ); + } + + void WINAPI ServiceController::initService( DWORD argc, LPTSTR *argv ) { + _statusHandle = RegisterServiceCtrlHandler( _serviceName.c_str(), serviceCtrl ); + if ( !_statusHandle ) + return; + + reportStatus( SERVICE_START_PENDING, 1000 ); + + _serviceCallback(); + dbexit( EXIT_CLEAN ); + + reportStatus( SERVICE_STOPPED ); + } + + static void serviceShutdown( const char* controlCodeName ) { + Client::initThread( "serviceShutdown" ); + log() << "got " << controlCodeName << " request from Windows Service Controller, " << + ( inShutdown() ? "already in shutdown" : "will terminate after current cmd ends" ) << endl; + ServiceController::reportStatus( SERVICE_STOP_PENDING ); + if ( ! inShutdown() ) { + exitCleanly( EXIT_WINDOWS_SERVICE_STOP ); + ServiceController::reportStatus( SERVICE_STOPPED ); + } + } + + void WINAPI ServiceController::serviceCtrl( DWORD ctrlCode ) { + switch ( ctrlCode ) { + case SERVICE_CONTROL_STOP: + serviceShutdown( "SERVICE_CONTROL_STOP" ); + break; + case SERVICE_CONTROL_SHUTDOWN: + serviceShutdown( "SERVICE_CONTROL_SHUTDOWN" ); + break; + } + } + +} // namespace mongo + +#endif diff --git a/src/mongo/util/ntservice.h b/src/mongo/util/ntservice.h new file mode 100644 index 00000000000..2570dfa9bef --- /dev/null +++ b/src/mongo/util/ntservice.h @@ -0,0 +1,49 @@ +// ntservice.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#if defined(_WIN32) +#include <windows.h> + +namespace mongo { + + typedef bool ( *ServiceCallback )( void ); + bool serviceParamsCheck( boost::program_options::variables_map& params, const std::string dbpath, int argc, char* argv[] ); + + class ServiceController { + public: + ServiceController(); + virtual ~ServiceController() {} + + static bool installService( const std::wstring& serviceName, const std::wstring& displayName, const std::wstring& serviceDesc, const std::wstring& serviceUser, const std::wstring& servicePassword, const std::string dbpath, int argc, char* argv[] ); + static bool removeService( const std::wstring& serviceName ); + static bool startService( const std::wstring& serviceName, ServiceCallback startService ); + static bool reportStatus( DWORD reportState, DWORD waitHint = 0 ); + + static void WINAPI initService( DWORD argc, LPTSTR *argv ); + static void WINAPI serviceCtrl( DWORD ctrlCode ); + + protected: + static std::wstring _serviceName; + static SERVICE_STATUS_HANDLE _statusHandle; + static ServiceCallback _serviceCallback; + }; + +} // namespace mongo + +#endif diff --git a/src/mongo/util/optime.h b/src/mongo/util/optime.h new file mode 100644 index 00000000000..031ad960d20 --- /dev/null +++ b/src/mongo/util/optime.h @@ -0,0 +1,170 @@ +// optime.h - OpTime class + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +//#include "../db/concurrency.h" + +namespace mongo { + void exitCleanly( ExitCode code ); + + struct ClockSkewException : public DBException { + ClockSkewException() : DBException( "clock skew exception" , 20001 ) {} + }; + + /* replsets used to use RSOpTime. + M/S uses OpTime. + But this is useable from both. + */ + typedef unsigned long long ReplTime; + + /* Operation sequence #. A combination of current second plus an ordinal value. + */ +#pragma pack(4) + class OpTime { + unsigned i; // ordinal comes first so we can do a single 64 bit compare on little endian + unsigned secs; + static OpTime last; + static OpTime skewed(); + public: + static void setLast(const Date_t &date) { + notifier().notify_all(); // won't really do anything until write-lock released + + last = OpTime(date); + } + unsigned getSecs() const { + return secs; + } + unsigned getInc() const { + return i; + } + OpTime(Date_t date) { + reinterpret_cast<unsigned long long&>(*this) = date.millis; + dassert( (int)secs >= 0 ); + } + OpTime(ReplTime x) { + reinterpret_cast<unsigned long long&>(*this) = x; + dassert( (int)secs >= 0 ); + } + OpTime(unsigned a, unsigned b) { + secs = a; + i = b; + dassert( (int)secs >= 0 ); + } + OpTime( const OpTime& other ) { + secs = other.secs; + i = other.i; + dassert( (int)secs >= 0 ); + } + OpTime() { + secs = 0; + i = 0; + } + // it isn't generally safe to not be locked for this. so use now(). some tests use this. + static OpTime now_inlock() { + notifier().notify_all(); // won't really do anything until write-lock released + + unsigned t = (unsigned) time(0); + if ( last.secs == t ) { + last.i++; + return last; + } + if ( t < last.secs ) { + return skewed(); // separate function to keep out of the hot code path + } + last = OpTime(t, 1); + return last; + } + static OpTime now(); + static OpTime last_inlock(); + + // Waits for global OpTime to be different from *this + // Must be atLeastReadLocked + // Defined in instance.cpp (only current user) as it needs dbtemprelease + void waitForDifferent(unsigned millis); + + /* We store OpTime's in the database as BSON Date datatype -- we needed some sort of + 64 bit "container" for these values. While these are not really "Dates", that seems a + better choice for now than say, Number, which is floating point. Note the BinData type + is perhaps the cleanest choice, lacking a true unsigned64 datatype, but BinData has 5 + bytes of overhead. + */ + unsigned long long asDate() const { + return reinterpret_cast<const unsigned long long*>(&i)[0]; + } + long long asLL() const { + return reinterpret_cast<const long long*>(&i)[0]; + } + + bool isNull() const { return secs == 0; } + + string toStringLong() const { + char buf[64]; + time_t_to_String(secs, buf); + stringstream ss; + ss << time_t_to_String_short(secs) << ' '; + ss << hex << secs << ':' << i; + return ss.str(); + } + + string toStringPretty() const { + stringstream ss; + ss << time_t_to_String_short(secs) << ':' << hex << i; + return ss.str(); + } + + string toString() const { + stringstream ss; + ss << hex << secs << ':' << i; + return ss.str(); + } + + bool operator==(const OpTime& r) const { + return i == r.i && secs == r.secs; + } + bool operator!=(const OpTime& r) const { + return !(*this == r); + } + bool operator<(const OpTime& r) const { + if ( secs != r.secs ) + return secs < r.secs; + return i < r.i; + } + bool operator<=(const OpTime& r) const { + return *this < r || *this == r; + } + bool operator>(const OpTime& r) const { + return !(*this <= r); + } + bool operator>=(const OpTime& r) const { + return !(*this < r); + } + private: + + // The following functions are to get around the need to define class-level statics in a cpp + static boost::condition& notifier() { + static boost::condition* holder = new boost::condition(); + return *holder; + }; + static boost::mutex& notifyMutex() { + static boost::mutex* holder = new boost::mutex(); + return *holder; + }; + }; +#pragma pack() + +} // namespace mongo diff --git a/src/mongo/util/password.cpp b/src/mongo/util/password.cpp new file mode 100644 index 00000000000..18164c3aa0a --- /dev/null +++ b/src/mongo/util/password.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2010 10gen 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. + */ + +#include "pch.h" +#include "password.h" +#include <iostream> + +#ifndef _WIN32 +#include <termios.h> +#endif + +using namespace std; + +namespace mongo { + + string askPassword() { + + std::string password; + cout << "Enter password: "; +#ifndef _WIN32 + const int stdinfd = 0; + termios termio; + tcflag_t old = 0; + if ( isatty( stdinfd ) ) { + int i = tcgetattr( stdinfd, &termio ); + if( i == -1 ) { + cerr << "Cannot get terminal attributes " << errnoWithDescription() << endl; + return string(); + } + old = termio.c_lflag; + termio.c_lflag &= ~ECHO; + i = tcsetattr( stdinfd, TCSANOW, &termio ); + if( i == -1 ) { + cerr << "Cannot set terminal attributes " << errnoWithDescription() << endl; + return string(); + } + } + + getline( cin, password ); + + if ( isatty( stdinfd ) ) { + termio.c_lflag = old; + int i = tcsetattr( stdinfd, TCSANOW, &termio ); + if( i == -1 ) { + cerr << "Cannot set terminal attributes " << errnoWithDescription() << endl; + return string(); + } + } +#else + HANDLE stdinh = GetStdHandle( STD_INPUT_HANDLE ); + if ( stdinh == INVALID_HANDLE_VALUE) { + cerr << "Cannot get stdin handle " << GetLastError() << "\n"; + return string(); + } + + DWORD old; + if ( !GetConsoleMode( stdinh, &old ) ) { + cerr << "Cannot get console mode " << GetLastError() << "\n"; + return string(); + } + + DWORD noecho = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; + if ( !SetConsoleMode( stdinh, noecho ) ) { + cerr << "Cannot set console mode " << GetLastError() << "\n"; + return string(); + } + + getline( cin, password ); + + if ( !SetConsoleMode( stdinh, old ) ) { + cerr << "Cannot set console mode " << GetLastError() << "\n"; + return string(); + } +#endif + cout << "\n"; + return password; + } +} diff --git a/src/mongo/util/password.h b/src/mongo/util/password.h new file mode 100644 index 00000000000..519f712ee7e --- /dev/null +++ b/src/mongo/util/password.h @@ -0,0 +1,61 @@ +/* + * Copyright 2010 10gen 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. + */ + + +#pragma once + +#include <boost/program_options.hpp> +#include <string> + +namespace mongo { + + struct PasswordValue : public boost::program_options::typed_value<std::string> { + + PasswordValue( std::string* val ) + : boost::program_options::typed_value<std::string>( val ) { } + + unsigned min_tokens() const { + return 0; + } + + unsigned max_tokens() const { + return 1; + } + + bool is_required() const { + return false; + } + + void xparse( boost::any& value_store, + const std::vector<std::string>& new_tokens ) const { + if ( !value_store.empty() ) +#if BOOST_VERSION >= 104200 + boost::throw_exception( boost::program_options::validation_error( boost::program_options::validation_error::multiple_values_not_allowed ) ); +#else + boost::throw_exception( boost::program_options::validation_error( "multiple values not allowed" ) ); +#endif + else if ( !new_tokens.empty() ) + boost::program_options::typed_value<std::string>::xparse + (value_store, new_tokens); + else + value_store = std::string(); + } + + }; + + std::string askPassword(); + +} diff --git a/src/mongo/util/paths.h b/src/mongo/util/paths.h new file mode 100644 index 00000000000..bb82df0c730 --- /dev/null +++ b/src/mongo/util/paths.h @@ -0,0 +1,124 @@ +// @file paths.h +// file paths and directory handling + +/* Copyright 2010 10gen 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. + */ + +#pragma once + +#include "mongoutils/str.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +namespace mongo { + + using namespace mongoutils; + + extern string dbpath; + + /** this is very much like a boost::path. however, we define a new type to get some type + checking. if you want to say 'my param MUST be a relative path", use this. + */ + struct RelativePath { + string _p; + + bool empty() const { return _p.empty(); } + + static RelativePath fromRelativePath(string f) { + RelativePath rp; + rp._p = f; + return rp; + } + + /** from a full path */ + static RelativePath fromFullPath(path f) { + path dbp(dbpath); // normalizes / and backslash + string fullpath = f.string(); + string relative = str::after(fullpath, dbp.string()); + if( relative.empty() ) { + log() << "warning file is not under db path? " << fullpath << ' ' << dbp.string() << endl; + RelativePath rp; + rp._p = fullpath; + return rp; + } + /*uassert(13600, + str::stream() << "file path is not under the db path? " << fullpath << ' ' << dbpath, + relative != fullpath);*/ + if( str::startsWith(relative, "/") || str::startsWith(relative, "\\") ) { + relative.erase(0, 1); + } + RelativePath rp; + rp._p = relative; + return rp; + } + + string toString() const { return _p; } + + bool operator!=(const RelativePath& r) const { return _p != r._p; } + bool operator==(const RelativePath& r) const { return _p == r._p; } + bool operator<(const RelativePath& r) const { return _p < r._p; } + + string asFullPath() const { + path x(dbpath); + x /= _p; + return x.string(); + } + + }; + + inline dev_t getPartition(const string& path){ + struct stat stats; + + if (stat(path.c_str(), &stats) != 0){ + uasserted(13646, str::stream() << "stat() failed for file: " << path << " " << errnoWithDescription()); + } + + return stats.st_dev; + } + + inline bool onSamePartition(const string& path1, const string& path2){ + dev_t dev1 = getPartition(path1); + dev_t dev2 = getPartition(path2); + + return dev1 == dev2; + } + + inline void flushMyDirectory(const boost::filesystem::path& file){ +#ifdef __linux__ // this isn't needed elsewhere + // if called without a fully qualified path it asserts; that makes mongoperf fail. so make a warning. need a better solution longer term. + // massert(13652, str::stream() << "Couldn't find parent dir for file: " << file.string(), ); + if( !file.has_branch_path() ) { + log() << "warning flushMYDirectory couldn't find parent dir for file: " << file.string() << endl; + return; + } + + + boost::filesystem::path dir = file.branch_path(); // parent_path in new boosts + + log(1) << "flushing directory " << dir.string() << endl; + + int fd = ::open(dir.string().c_str(), O_RDONLY); // DO NOT THROW OR ASSERT BEFORE CLOSING + massert(13650, str::stream() << "Couldn't open directory '" << dir.string() << "' for flushing: " << errnoWithDescription(), fd >= 0); + if (fsync(fd) != 0){ + int e = errno; + close(fd); + massert(13651, str::stream() << "Couldn't fsync directory '" << dir.string() << "': " << errnoWithDescription(e), false); + } + close(fd); +#endif + } + +} diff --git a/src/mongo/util/processinfo.cpp b/src/mongo/util/processinfo.cpp new file mode 100644 index 00000000000..082d42b3bc0 --- /dev/null +++ b/src/mongo/util/processinfo.cpp @@ -0,0 +1,48 @@ +// processinfo.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "processinfo.h" +#include "mmap.h" + +#include <iostream> +using namespace std; + +namespace mongo { + + class PidFileWiper { + public: + ~PidFileWiper() { + ofstream out( path.c_str() , ios_base::out ); + out.close(); + } + + void write( const string& p ) { + path = p; + ofstream out( path.c_str() , ios_base::out ); + out << getpid() << endl; + out.close(); + } + + string path; + } pidFileWiper; + + void writePidFile( const string& path ) { + pidFileWiper.write( path ); + } + +} diff --git a/src/mongo/util/processinfo.h b/src/mongo/util/processinfo.h new file mode 100644 index 00000000000..5272831eb74 --- /dev/null +++ b/src/mongo/util/processinfo.h @@ -0,0 +1,67 @@ +// processinfo.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include <sys/types.h> +#include <string> + +#ifndef _WIN32 +#include <unistd.h> +#else +typedef int pid_t; +int getpid(); +#endif + +namespace mongo { + + class BSONObjBuilder; + + class ProcessInfo { + public: + ProcessInfo( pid_t pid = getpid() ); + ~ProcessInfo(); + + /** + * @return mbytes + */ + int getVirtualMemorySize(); + + /** + * @return mbytes + */ + int getResidentSize(); + + /** + * Append platform-specific data to obj + */ + void getExtraInfo(BSONObjBuilder& info); + + bool supported(); + + static bool blockCheckSupported(); + static bool blockInMemory( char * start ); + + private: + pid_t _pid; + }; + + void writePidFile( const std::string& path ); + + void printMemInfo( const char * whereContextStr = 0 ); + +} diff --git a/src/mongo/util/processinfo_darwin.cpp b/src/mongo/util/processinfo_darwin.cpp new file mode 100644 index 00000000000..9f73cbffd4f --- /dev/null +++ b/src/mongo/util/processinfo_darwin.cpp @@ -0,0 +1,116 @@ +// processinfo_darwin.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "../pch.h" +#include "processinfo.h" +#include "log.h" + +#include <mach/vm_statistics.h> +#include <mach/task_info.h> +#include <mach/mach_init.h> +#include <mach/mach_host.h> +#include <mach/mach_traps.h> +#include <mach/task.h> +#include <mach/vm_map.h> +#include <mach/shared_region.h> +#include <iostream> + +#include <sys/types.h> +#include <sys/mman.h> + +using namespace std; + +namespace mongo { + + ProcessInfo::ProcessInfo( pid_t pid ) : _pid( pid ) { + } + + ProcessInfo::~ProcessInfo() { + } + + bool ProcessInfo::supported() { + return true; + } + + int ProcessInfo::getVirtualMemorySize() { + task_t result; + + mach_port_t task; + + if ( ( result = task_for_pid( mach_task_self() , _pid , &task) ) != KERN_SUCCESS ) { + cout << "error getting task\n"; + return 0; + } + +#if !defined(__LP64__) + task_basic_info_32 ti; +#else + task_basic_info_64 ti; +#endif + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if ( ( result = task_info( task , TASK_BASIC_INFO , (task_info_t)&ti, &count ) ) != KERN_SUCCESS ) { + cout << "error getting task_info: " << result << endl; + return 0; + } + return (int)((double)ti.virtual_size / (1024.0 * 1024 ) ); + } + + int ProcessInfo::getResidentSize() { + task_t result; + + mach_port_t task; + + if ( ( result = task_for_pid( mach_task_self() , _pid , &task) ) != KERN_SUCCESS ) { + cout << "error getting task\n"; + return 0; + } + + +#if !defined(__LP64__) + task_basic_info_32 ti; +#else + task_basic_info_64 ti; +#endif + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if ( ( result = task_info( task , TASK_BASIC_INFO , (task_info_t)&ti, &count ) ) != KERN_SUCCESS ) { + cout << "error getting task_info: " << result << endl; + return 0; + } + return (int)( ti.resident_size / (1024 * 1024 ) ); + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} + + bool ProcessInfo::blockCheckSupported() { + return true; + } + + bool ProcessInfo::blockInMemory( char * start ) { + static long pageSize = 0; + if ( pageSize == 0 ) { + pageSize = sysconf( _SC_PAGESIZE ); + } + start = start - ( (unsigned long long)start % pageSize ); + char x = 0; + if ( mincore( start , 128 , &x ) ) { + log() << "mincore failed: " << errnoWithDescription() << endl; + return 1; + } + return x & 0x1; + } + +} diff --git a/src/mongo/util/processinfo_linux2.cpp b/src/mongo/util/processinfo_linux2.cpp new file mode 100644 index 00000000000..3eaccafd030 --- /dev/null +++ b/src/mongo/util/processinfo_linux2.cpp @@ -0,0 +1,244 @@ +// processinfo_linux2.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "processinfo.h" + +#include <iostream> +#include <stdio.h> +#include <malloc.h> +#include <db/jsobj.h> +#include <unistd.h> +#include <sys/mman.h> + +using namespace std; + +#define KLONG long +#define KLF "l" + +namespace mongo { + + class LinuxProc { + public: + LinuxProc( pid_t pid = getpid() ) { + char name[128]; + sprintf( name , "/proc/%d/stat" , pid ); + + FILE * f = fopen( name , "r"); + if ( ! f ) { + stringstream ss; + ss << "couldn't open [" << name << "] " << errnoWithDescription(); + string s = ss.str(); + // help the assert# control uasserted( 13538 , s.c_str() ); + msgassertedNoTrace( 13538 , s.c_str() ); + } + int found = fscanf(f, + "%d %s %c " + "%d %d %d %d %d " + "%lu %lu %lu %lu %lu " + "%lu %lu %ld %ld " /* utime stime cutime cstime */ + "%ld %ld " + "%ld " + "%ld " + "%lu " /* start_time */ + "%lu " + "%ld " // rss + "%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u " + /* + "%*s %*s %*s %*s " + "%"KLF"u %*lu %*lu " + "%d %d " + "%lu %lu" + */ + + , + + &_pid, + _comm, + &_state, + &_ppid, &_pgrp, &_session, &_tty, &_tpgid, + &_flags, &_min_flt, &_cmin_flt, &_maj_flt, &_cmaj_flt, + &_utime, &_stime, &_cutime, &_cstime, + &_priority, &_nice, + &_alarm, + &_nlwp, + &_start_time, + &_vsize, + &_rss, + &_rss_rlim, &_start_code, &_end_code, &_start_stack, &_kstk_esp, &_kstk_eip + + /* + &_wchan, + &_exit_signal, &_processor, + &_rtprio, &_sched + */ + ); + if ( found == 0 ) { + cout << "system error: reading proc info" << endl; + } + fclose( f ); + } + + unsigned long getVirtualMemorySize() { + return _vsize; + } + + unsigned long getResidentSize() { + return (unsigned long)_rss * 4 * 1024; + } + + int _pid; + // The process ID. + + char _comm[128]; + // The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out. + + char _state; + //One character from the string "RSDZTW" where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible + // disk sleep, Z is zombie, T is traced or stopped (on a signal), and W is paging. + + int _ppid; + // The PID of the parent. + + int _pgrp; + // The process group ID of the process. + + int _session; + // The session ID of the process. + + int _tty; + // The tty the process uses. + + int _tpgid; + // The process group ID of the process which currently owns the tty that the process is connected to. + + unsigned long _flags; // %lu + // The kernel flags word of the process. For bit meanings, see the PF_* defines in <linux/sched.h>. Details depend on the kernel version. + + unsigned long _min_flt; // %lu + // The number of minor faults the process has made which have not required loading a memory page from disk. + + unsigned long _cmin_flt; // %lu + // The number of minor faults that the process + + unsigned long _maj_flt; // %lu + // The number of major faults the process has made which have required loading a memory page from disk. + + unsigned long _cmaj_flt; // %lu + // The number of major faults that the process + + unsigned long _utime; // %lu + // The number of jiffies that this process has been scheduled in user mode. + + unsigned long _stime; // %lu + // The number of jiffies that this process has been scheduled in kernel mode. + + long _cutime; // %ld + // The number of jiffies that this removed field. + + long _cstime; // %ld + + long _priority; + long _nice; + + long _nlwp; // %ld + // The time in jiffies before the next SIGALRM is sent to the process due to an interval timer. + + unsigned long _alarm; + + unsigned long _start_time; // %lu + // The time in jiffies the process started after system boot. + + unsigned long _vsize; // %lu + // Virtual memory size in bytes. + + long _rss; // %ld + // Resident Set Size: number of pages the process has in real memory, minus 3 for administrative purposes. This is just the pages which + // count towards text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out + + unsigned long _rss_rlim; // %lu + // Current limit in bytes on the rss of the process (usually 4294967295 on i386). + + unsigned long _start_code; // %lu + // The address above which program text can run. + + unsigned long _end_code; // %lu + // The address below which program text can run. + + unsigned long _start_stack; // %lu + // The address of the start of the stack. + + unsigned long _kstk_esp; // %lu + // The current value of esp (stack pointer), as found in the kernel stack page for the process. + + unsigned long _kstk_eip; // %lu + // The current EIP (instruction pointer). + + + + }; + + + ProcessInfo::ProcessInfo( pid_t pid ) : _pid( pid ) { + } + + ProcessInfo::~ProcessInfo() { + } + + bool ProcessInfo::supported() { + return true; + } + + int ProcessInfo::getVirtualMemorySize() { + LinuxProc p(_pid); + return (int)( p.getVirtualMemorySize() / ( 1024.0 * 1024 ) ); + } + + int ProcessInfo::getResidentSize() { + LinuxProc p(_pid); + return (int)( p.getResidentSize() / ( 1024.0 * 1024 ) ); + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) { + // [dm] i don't think mallinfo works. (64 bit.) ?? + struct mallinfo malloc_info = mallinfo(); // structure has same name as function that returns it. (see malloc.h) + info.append("heap_usage_bytes", malloc_info.uordblks/*main arena*/ + malloc_info.hblkhd/*mmap blocks*/); + //docs claim hblkhd is included in uordblks but it isn't + + LinuxProc p(_pid); + info.append("page_faults", (int)p._maj_flt); + } + + bool ProcessInfo::blockCheckSupported() { + return true; + } + + bool ProcessInfo::blockInMemory( char * start ) { + static long pageSize = 0; + if ( pageSize == 0 ) { + pageSize = sysconf( _SC_PAGESIZE ); + } + start = start - ( (unsigned long long)start % pageSize ); + unsigned char x = 0; + if ( mincore( start , 128 , &x ) ) { + log() << "mincore failed: " << errnoWithDescription() << endl; + return 1; + } + return x & 0x1; + } + + +} diff --git a/src/mongo/util/processinfo_none.cpp b/src/mongo/util/processinfo_none.cpp new file mode 100644 index 00000000000..7d1e84d377c --- /dev/null +++ b/src/mongo/util/processinfo_none.cpp @@ -0,0 +1,55 @@ +// processinfo_none.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "processinfo.h" + +#include <iostream> +using namespace std; + +namespace mongo { + + ProcessInfo::ProcessInfo( pid_t pid ) { + } + + ProcessInfo::~ProcessInfo() { + } + + bool ProcessInfo::supported() { + return false; + } + + int ProcessInfo::getVirtualMemorySize() { + return -1; + } + + int ProcessInfo::getResidentSize() { + return -1; + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) {} + + bool ProcessInfo::blockCheckSupported() { + return false; + } + + bool ProcessInfo::blockInMemory( char * start ) { + assert(0); + return true; + } + +} diff --git a/src/mongo/util/processinfo_win32.cpp b/src/mongo/util/processinfo_win32.cpp new file mode 100644 index 00000000000..87d92db7e18 --- /dev/null +++ b/src/mongo/util/processinfo_win32.cpp @@ -0,0 +1,102 @@ +// processinfo_win32.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "processinfo.h" +#include <iostream> +#include <psapi.h> +#include "../bson/bsonobjbuilder.h" +using namespace std; + +int getpid() { + return GetCurrentProcessId(); +} + +namespace mongo { + + int _wconvertmtos( SIZE_T s ) { + return (int)( s / ( 1024 * 1024 ) ); + } + + ProcessInfo::ProcessInfo( pid_t pid ) { + } + + ProcessInfo::~ProcessInfo() { + } + + bool ProcessInfo::supported() { + return true; + } + + int ProcessInfo::getVirtualMemorySize() { + MEMORYSTATUSEX mse; + mse.dwLength = sizeof(mse); + assert( GlobalMemoryStatusEx( &mse ) ); + DWORDLONG x = (mse.ullTotalVirtual - mse.ullAvailVirtual) / (1024 * 1024) ; + assert( x <= 0x7fffffff ); + return (int) x; + } + + int ProcessInfo::getResidentSize() { + PROCESS_MEMORY_COUNTERS pmc; + assert( GetProcessMemoryInfo( GetCurrentProcess() , &pmc, sizeof(pmc) ) ); + return _wconvertmtos( pmc.WorkingSetSize ); + } + + void ProcessInfo::getExtraInfo(BSONObjBuilder& info) { + MEMORYSTATUSEX mse; + mse.dwLength = sizeof(mse); + PROCESS_MEMORY_COUNTERS pmc; + if( GetProcessMemoryInfo( GetCurrentProcess() , &pmc, sizeof(pmc) ) ) { + info.append("page_faults", static_cast<int>(pmc.PageFaultCount)); + info.append("usagePageFileMB", static_cast<int>(pmc.PagefileUsage / 1024 / 1024)); + } + if( GlobalMemoryStatusEx( &mse ) ) { + info.append("totalPageFileMB", static_cast<int>(mse.ullTotalPageFile / 1024 / 1024)); + info.append("availPageFileMB", static_cast<int>(mse.ullAvailPageFile / 1024 / 1024)); + info.append("ramMB", static_cast<int>(mse.ullTotalPhys / 1024 / 1024)); + } + } + + bool ProcessInfo::blockCheckSupported() { + return true; + } + + bool ProcessInfo::blockInMemory( char * start ) { +#if 0 + // code for printing out page fault addresses and pc's -- + // this could be useful for targetting heavy pagefault locations in the code + static BOOL bstat = InitializeProcessForWsWatch( GetCurrentProcess() ); + PSAPI_WS_WATCH_INFORMATION_EX wiex[30]; + DWORD bufsize = sizeof(wiex); + bstat = GetWsChangesEx( GetCurrentProcess(), &wiex[0], &bufsize ); + if (bstat) { + for (int i=0; i<30; i++) { + if (wiex[i].BasicInfo.FaultingPc == 0) break; + cout << "faulting pc = " << wiex[i].BasicInfo.FaultingPc << " address = " << wiex[i].BasicInfo.FaultingVa << " thread id = " << wiex[i].FaultingThreadId << endl; + } + } +#endif + PSAPI_WORKING_SET_EX_INFORMATION wsinfo; + wsinfo.VirtualAddress = start; + BOOL result = QueryWorkingSetEx( GetCurrentProcess(), &wsinfo, sizeof(wsinfo) ); + if ( result ) + if ( wsinfo.VirtualAttributes.Valid ) + return true; + return false; + } +} diff --git a/src/mongo/util/queue.h b/src/mongo/util/queue.h new file mode 100644 index 00000000000..4223bd6c256 --- /dev/null +++ b/src/mongo/util/queue.h @@ -0,0 +1,106 @@ +// @file queue.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "../pch.h" + +#include <queue> + +#include "../util/timer.h" + +namespace mongo { + + /** + * simple blocking queue + */ + template<typename T> class BlockingQueue : boost::noncopyable { + public: + BlockingQueue() : _lock("BlockingQueue") { } + + void push(T const& t) { + scoped_lock l( _lock ); + _queue.push( t ); + _condition.notify_one(); + } + + bool empty() const { + scoped_lock l( _lock ); + return _queue.empty(); + } + + size_t size() const { + scoped_lock l( _lock ); + return _queue.size(); + } + + + bool tryPop( T & t ) { + scoped_lock l( _lock ); + if ( _queue.empty() ) + return false; + + t = _queue.front(); + _queue.pop(); + + return true; + } + + T blockingPop() { + + scoped_lock l( _lock ); + while( _queue.empty() ) + _condition.wait( l.boost() ); + + T t = _queue.front(); + _queue.pop(); + return t; + } + + + /** + * blocks waiting for an object until maxSecondsToWait passes + * if got one, return true and set in t + * otherwise return false and t won't be changed + */ + bool blockingPop( T& t , int maxSecondsToWait ) { + + Timer timer; + + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += maxSecondsToWait; + + scoped_lock l( _lock ); + while( _queue.empty() ) { + if ( ! _condition.timed_wait( l.boost() , xt ) ) + return false; + } + + t = _queue.front(); + _queue.pop(); + return true; + } + + private: + std::queue<T> _queue; + + mutable mongo::mutex _lock; + boost::condition _condition; + }; + +} diff --git a/src/mongo/util/ramlog.cpp b/src/mongo/util/ramlog.cpp new file mode 100644 index 00000000000..d7a839a3fff --- /dev/null +++ b/src/mongo/util/ramlog.cpp @@ -0,0 +1,190 @@ +// ramlog.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "log.h" +#include "ramlog.h" +#include "mongoutils/html.h" +#include "mongoutils/str.h" + +namespace mongo { + + using namespace mongoutils; + + RamLog::RamLog( string name ) : _name(name), _lastWrite(0) { + h = 0; n = 0; + for( int i = 0; i < N; i++ ) + lines[i][C-1] = 0; + + if ( name.size() ) { + + if ( ! _namedLock ) + _namedLock = new mongo::mutex("RamLog::_namedLock"); + + scoped_lock lk( *_namedLock ); + if ( ! _named ) + _named = new RM(); + (*_named)[name] = this; + } + + } + + RamLog::~RamLog() { + + } + + void RamLog::write(LogLevel ll, const string& str) { + _lastWrite = time(0); + + char *p = lines[(h+n)%N]; + + unsigned sz = str.size(); + if( sz < C ) { + if ( str.c_str()[sz-1] == '\n' ) { + memcpy(p, str.c_str(), sz-1); + p[sz-1] = 0; + } + else + strcpy(p, str.c_str()); + } + else { + memcpy(p, str.c_str(), C-1); + } + + if( n < N ) n++; + else h = (h+1) % N; + } + + void RamLog::get( vector<const char*>& v) const { + for( unsigned x=0, i=h; x++ < n; i=(i+1)%N ) + v.push_back(lines[i]); + } + + int RamLog::repeats(const vector<const char *>& v, int i) { + for( int j = i-1; j >= 0 && j+8 > i; j-- ) { + if( strcmp(v[i]+20,v[j]+20) == 0 ) { + for( int x = 1; ; x++ ) { + if( j+x == i ) return j; + if( i+x>=(int) v.size() ) return -1; + if( strcmp(v[i+x]+20,v[j+x]+20) ) return -1; + } + return -1; + } + } + return -1; + } + + + string RamLog::clean(const vector<const char *>& v, int i, string line ) { + if( line.empty() ) line = v[i]; + if( i > 0 && strncmp(v[i], v[i-1], 11) == 0 ) + return string(" ") + line.substr(11); + return v[i]; + } + + string RamLog::color(string line) { + string s = str::after(line, "replSet "); + if( str::startsWith(s, "warning") || startsWith(s, "error") ) + return html::red(line); + if( str::startsWith(s, "info") ) { + if( str::endsWith(s, " up\n") ) + return html::green(line); + else if( str::contains(s, " down ") || str::endsWith(s, " down\n") ) + return html::yellow(line); + return line; //html::blue(line); + } + + return line; + } + + /* turn http:... into an anchor */ + string RamLog::linkify(const char *s) { + const char *p = s; + const char *h = strstr(p, "http://"); + if( h == 0 ) return s; + + const char *sp = h + 7; + while( *sp && *sp != ' ' ) sp++; + + string url(h, sp-h); + stringstream ss; + ss << string(s, h-s) << "<a href=\"" << url << "\">" << url << "</a>" << sp; + return ss.str(); + } + + void RamLog::toHTML(stringstream& s) { + vector<const char*> v; + get( v ); + + s << "<pre>\n"; + for( int i = 0; i < (int)v.size(); i++ ) { + assert( strlen(v[i]) > 20 ); + int r = repeats(v, i); + if( r < 0 ) { + s << color( linkify( clean(v,i).c_str() ) ) << '\n'; + } + else { + stringstream x; + x << string(v[i], 0, 20); + int nr = (i-r); + int last = i+nr-1; + for( ; r < i ; r++ ) x << '.'; + if( 1 ) { + stringstream r; + if( nr == 1 ) r << "repeat last line"; + else r << "repeats last " << nr << " lines; ends " << string(v[last]+4,0,15); + s << html::a("", r.str(), clean(v,i,x.str())); + } + else s << x.str(); + s << '\n'; + i = last; + } + } + s << "</pre>\n"; + } + + // --------------- + // static things + // --------------- + + RamLog* RamLog::get( string name ) { + if ( ! _named ) + return 0; + + scoped_lock lk( *_namedLock ); + RM::iterator i = _named->find( name ); + if ( i == _named->end() ) + return 0; + return i->second; + } + + void RamLog::getNames( vector<string>& names ) { + if ( ! _named ) + return; + + scoped_lock lk( *_namedLock ); + for ( RM::iterator i=_named->begin(); i!=_named->end(); ++i ) { + if ( i->second->n ) + names.push_back( i->first ); + } + } + + mongo::mutex* RamLog::_namedLock; + RamLog::RM* RamLog::_named = 0; + + Tee* const warnings = new RamLog("warnings"); // Things put here go in serverStatus +} diff --git a/src/mongo/util/ramlog.h b/src/mongo/util/ramlog.h new file mode 100644 index 00000000000..d3d5c8fbb4e --- /dev/null +++ b/src/mongo/util/ramlog.h @@ -0,0 +1,65 @@ +// ramlog.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +#include "log.h" + +namespace mongo { + + class RamLog : public Tee { + public: + RamLog( string name ); + + virtual void write(LogLevel ll, const string& str); + + void get( vector<const char*>& v) const; + + void toHTML(stringstream& s); + + static RamLog* get( string name ); + static void getNames( vector<string>& names ); + + time_t lastWrite() { return _lastWrite; } // 0 if no writes + + protected: + static int repeats(const vector<const char *>& v, int i); + static string clean(const vector<const char *>& v, int i, string line=""); + static string color(string line); + + /* turn http:... into an anchor */ + static string linkify(const char *s); + + private: + ~RamLog(); // want this private as we want to leak so we can use them till the very end + + enum { + N = 128, // number of links + C = 256 // max size of line + }; + char lines[N][C]; + unsigned h; // current position + unsigned n; // numer of lines stores 0 o N + string _name; + + typedef map<string,RamLog*> RM; + static mongo::mutex* _namedLock; + static RM* _named; + time_t _lastWrite; + }; + +} diff --git a/src/mongo/util/scopeguard.h b/src/mongo/util/scopeguard.h new file mode 100644 index 00000000000..b87a4b51871 --- /dev/null +++ b/src/mongo/util/scopeguard.h @@ -0,0 +1,427 @@ +//////////////////////////////////////////////////////////////////////////////// +// The Loki Library +// Copyright (c) 2000 Andrei Alexandrescu +// Copyright (c) 2000 Petru Marginean +// Copyright (c) 2005 Joshua Lehrer +// +// Permission to use, copy, modify, distribute and sell this software for any +// purpose is hereby granted without fee, provided that the above copyright +// notice appear in all copies and that both that copyright notice and this +// permission notice appear in supporting documentation. +// The author makes no representations about the +// suitability of this software for any purpose. It is provided "as is" +// without express or implied warranty. +//////////////////////////////////////////////////////////////////////////////// +#ifndef LOKI_SCOPEGUARD_H_ +#define LOKI_SCOPEGUARD_H_ + +namespace mongo +{ + + //////////////////////////////////////////////////////////////////////////////// + /// \class RefToValue + /// + /// Transports a reference as a value + /// Serves to implement the Colvin/Gibbons trick for SmartPtr/ScopeGuard + //////////////////////////////////////////////////////////////////////////////// + + template <class T> + class RefToValue + { + public: + + RefToValue(T& ref) : ref_(ref) + {} + + RefToValue(const RefToValue& rhs) : ref_(rhs.ref_) + {} + + operator T& () const + { + return ref_; + } + + private: + // Disable - not implemented + RefToValue(); + RefToValue& operator=(const RefToValue&); + + T& ref_; + }; + + + //////////////////////////////////////////////////////////////////////////////// + /// RefToValue creator. + //////////////////////////////////////////////////////////////////////////////// + + template <class T> + inline RefToValue<T> ByRef(T& t) + { + return RefToValue<T>(t); + } + + + + + //////////////////////////////////////////// + /// ScopeGuard + /* + Trivial example for use: + + FILE* f = fopen("myfile.txt", "w+"); + if (!f) + return error; + ON_BLOCK_EXIT(fclose, f); + + + More complicated example: + + ScopeGuard guard = MakeGuard(my_rollback_func, myparam); + ... + if (successful) { + guard.Dismiss(); + return; + } + // guard is still active here and will fire at scope exit + ... + + + */ + + + class ScopeGuardImplBase + { + ScopeGuardImplBase& operator =(const ScopeGuardImplBase&); + + protected: + + ~ScopeGuardImplBase() + {} + + ScopeGuardImplBase(const ScopeGuardImplBase& other) throw() + : dismissed_(other.dismissed_) + { + other.Dismiss(); + } + + template <typename J> + static void SafeExecute(J& j) throw() + { + if (!j.dismissed_) + try + { + j.Execute(); + } + catch(...) + {} + } + + mutable bool dismissed_; + + public: + ScopeGuardImplBase() throw() : dismissed_(false) + {} + + void Dismiss() const throw() + { + dismissed_ = true; + } + }; + + //////////////////////////////////////////////////////////////// + /// + /// \typedef typedef const ScopeGuardImplBase& ScopeGuard + /// + /// See Andrei's and Petru Marginean's CUJ article + /// http://www.cuj.com/documents/s=8000/cujcexp1812alexandr/alexandr.htm + /// + /// Changes to the original code by Joshua Lehrer: + /// http://www.lehrerfamily.com/scopeguard.html + //////////////////////////////////////////////////////////////// + + typedef const ScopeGuardImplBase& ScopeGuard; + + template <typename F> + class ScopeGuardImpl0 : public ScopeGuardImplBase + { + public: + static ScopeGuardImpl0<F> MakeGuard(F fun) + { + return ScopeGuardImpl0<F>(fun); + } + + ~ScopeGuardImpl0() throw() + { + SafeExecute(*this); + } + + void Execute() + { + fun_(); + } + + protected: + ScopeGuardImpl0(F fun) : fun_(fun) + {} + + F fun_; + }; + + template <typename F> + inline ScopeGuardImpl0<F> MakeGuard(F fun) + { + return ScopeGuardImpl0<F>::MakeGuard(fun); + } + + template <typename F, typename P1> + class ScopeGuardImpl1 : public ScopeGuardImplBase + { + public: + static ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1) + { + return ScopeGuardImpl1<F, P1>(fun, p1); + } + + ~ScopeGuardImpl1() throw() + { + SafeExecute(*this); + } + + void Execute() + { + fun_(p1_); + } + + protected: + ScopeGuardImpl1(F fun, P1 p1) : fun_(fun), p1_(p1) + {} + + F fun_; + const P1 p1_; + }; + + template <typename F, typename P1> + inline ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1) + { + return ScopeGuardImpl1<F, P1>::MakeGuard(fun, p1); + } + + template <typename F, typename P1, typename P2> + class ScopeGuardImpl2: public ScopeGuardImplBase + { + public: + static ScopeGuardImpl2<F, P1, P2> MakeGuard(F fun, P1 p1, P2 p2) + { + return ScopeGuardImpl2<F, P1, P2>(fun, p1, p2); + } + + ~ScopeGuardImpl2() throw() + { + SafeExecute(*this); + } + + void Execute() + { + fun_(p1_, p2_); + } + + protected: + ScopeGuardImpl2(F fun, P1 p1, P2 p2) : fun_(fun), p1_(p1), p2_(p2) + {} + + F fun_; + const P1 p1_; + const P2 p2_; + }; + + template <typename F, typename P1, typename P2> + inline ScopeGuardImpl2<F, P1, P2> MakeGuard(F fun, P1 p1, P2 p2) + { + return ScopeGuardImpl2<F, P1, P2>::MakeGuard(fun, p1, p2); + } + + template <typename F, typename P1, typename P2, typename P3> + class ScopeGuardImpl3 : public ScopeGuardImplBase + { + public: + static ScopeGuardImpl3<F, P1, P2, P3> MakeGuard(F fun, P1 p1, P2 p2, P3 p3) + { + return ScopeGuardImpl3<F, P1, P2, P3>(fun, p1, p2, p3); + } + + ~ScopeGuardImpl3() throw() + { + SafeExecute(*this); + } + + void Execute() + { + fun_(p1_, p2_, p3_); + } + + protected: + ScopeGuardImpl3(F fun, P1 p1, P2 p2, P3 p3) : fun_(fun), p1_(p1), p2_(p2), p3_(p3) + {} + + F fun_; + const P1 p1_; + const P2 p2_; + const P3 p3_; + }; + + template <typename F, typename P1, typename P2, typename P3> + inline ScopeGuardImpl3<F, P1, P2, P3> MakeGuard(F fun, P1 p1, P2 p2, P3 p3) + { + return ScopeGuardImpl3<F, P1, P2, P3>::MakeGuard(fun, p1, p2, p3); + } + + //************************************************************ + + template <class Obj, typename MemFun> + class ObjScopeGuardImpl0 : public ScopeGuardImplBase + { + public: + static ObjScopeGuardImpl0<Obj, MemFun> MakeObjGuard(Obj& obj, MemFun memFun) + { + return ObjScopeGuardImpl0<Obj, MemFun>(obj, memFun); + } + + ~ObjScopeGuardImpl0() throw() + { + SafeExecute(*this); + } + + void Execute() + { + (obj_.*memFun_)(); + } + + protected: + ObjScopeGuardImpl0(Obj& obj, MemFun memFun) : obj_(obj), memFun_(memFun) + {} + + Obj& obj_; + MemFun memFun_; + }; + + template <class Obj, typename MemFun> + inline ObjScopeGuardImpl0<Obj, MemFun> MakeObjGuard(Obj& obj, MemFun memFun) + { + return ObjScopeGuardImpl0<Obj, MemFun>::MakeObjGuard(obj, memFun); + } + + template <typename Ret, class Obj1, class Obj2> + inline ObjScopeGuardImpl0<Obj1,Ret(Obj2::*)()> MakeGuard(Ret(Obj2::*memFun)(), Obj1 &obj) + { + return ObjScopeGuardImpl0<Obj1,Ret(Obj2::*)()>::MakeObjGuard(obj,memFun); + } + + template <typename Ret, class Obj1, class Obj2> + inline ObjScopeGuardImpl0<Obj1,Ret(Obj2::*)()> MakeGuard(Ret(Obj2::*memFun)(), Obj1 *obj) + { + return ObjScopeGuardImpl0<Obj1,Ret(Obj2::*)()>::MakeObjGuard(*obj,memFun); + } + + template <class Obj, typename MemFun, typename P1> + class ObjScopeGuardImpl1 : public ScopeGuardImplBase + { + public: + static ObjScopeGuardImpl1<Obj, MemFun, P1> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1) + { + return ObjScopeGuardImpl1<Obj, MemFun, P1>(obj, memFun, p1); + } + + ~ObjScopeGuardImpl1() throw() + { + SafeExecute(*this); + } + + void Execute() + { + (obj_.*memFun_)(p1_); + } + + protected: + ObjScopeGuardImpl1(Obj& obj, MemFun memFun, P1 p1) : obj_(obj), memFun_(memFun), p1_(p1) + {} + + Obj& obj_; + MemFun memFun_; + const P1 p1_; + }; + + template <class Obj, typename MemFun, typename P1> + inline ObjScopeGuardImpl1<Obj, MemFun, P1> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1) + { + return ObjScopeGuardImpl1<Obj, MemFun, P1>::MakeObjGuard(obj, memFun, p1); + } + + template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b> + inline ObjScopeGuardImpl1<Obj1,Ret(Obj2::*)(P1a),P1b> MakeGuard(Ret(Obj2::*memFun)(P1a), Obj1 &obj, P1b p1) + { + return ObjScopeGuardImpl1<Obj1,Ret(Obj2::*)(P1a),P1b>::MakeObjGuard(obj,memFun,p1); + } + + template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b> + inline ObjScopeGuardImpl1<Obj1,Ret(Obj2::*)(P1a),P1b> MakeGuard(Ret(Obj2::*memFun)(P1a), Obj1 *obj, P1b p1) + { + return ObjScopeGuardImpl1<Obj1,Ret(Obj2::*)(P1a),P1b>::MakeObjGuard(*obj,memFun,p1); + } + + template <class Obj, typename MemFun, typename P1, typename P2> + class ObjScopeGuardImpl2 : public ScopeGuardImplBase + { + public: + static ObjScopeGuardImpl2<Obj, MemFun, P1, P2> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1, P2 p2) + { + return ObjScopeGuardImpl2<Obj, MemFun, P1, P2>(obj, memFun, p1, p2); + } + + ~ObjScopeGuardImpl2() throw() + { + SafeExecute(*this); + } + + void Execute() + { + (obj_.*memFun_)(p1_, p2_); + } + + protected: + ObjScopeGuardImpl2(Obj& obj, MemFun memFun, P1 p1, P2 p2) : obj_(obj), memFun_(memFun), p1_(p1), p2_(p2) + {} + + Obj& obj_; + MemFun memFun_; + const P1 p1_; + const P2 p2_; + }; + + template <class Obj, typename MemFun, typename P1, typename P2> + inline ObjScopeGuardImpl2<Obj, MemFun, P1, P2> MakeObjGuard(Obj& obj, MemFun memFun, P1 p1, P2 p2) + { + return ObjScopeGuardImpl2<Obj, MemFun, P1, P2>::MakeObjGuard(obj, memFun, p1, p2); + } + + template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b, typename P2a, typename P2b> + inline ObjScopeGuardImpl2<Obj1,Ret(Obj2::*)(P1a,P2a),P1b,P2b> MakeGuard(Ret(Obj2::*memFun)(P1a,P2a), Obj1 &obj, P1b p1, P2b p2) + { + return ObjScopeGuardImpl2<Obj1,Ret(Obj2::*)(P1a,P2a),P1b,P2b>::MakeObjGuard(obj,memFun,p1,p2); + } + + template <typename Ret, class Obj1, class Obj2, typename P1a, typename P1b, typename P2a, typename P2b> + inline ObjScopeGuardImpl2<Obj1,Ret(Obj2::*)(P1a,P2a),P1b,P2b> MakeGuard(Ret(Obj2::*memFun)(P1a,P2a), Obj1 *obj, P1b p1, P2b p2) + { + return ObjScopeGuardImpl2<Obj1,Ret(Obj2::*)(P1a,P2a),P1b,P2b>::MakeObjGuard(*obj,memFun,p1,p2); + } + +} // namespace Loki + +#define LOKI_CONCATENATE_DIRECT(s1, s2) s1##s2 +#define LOKI_CONCATENATE(s1, s2) LOKI_CONCATENATE_DIRECT(s1, s2) +#define LOKI_ANONYMOUS_VARIABLE(str) LOKI_CONCATENATE(str, __LINE__) + +#define ON_BLOCK_EXIT ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = MakeGuard +#define ON_BLOCK_EXIT_OBJ ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = MakeObjGuard + +#endif //LOKI_SCOPEGUARD_H_ diff --git a/src/mongo/util/signal_handlers.cpp b/src/mongo/util/signal_handlers.cpp new file mode 100644 index 00000000000..0e9ec7a9b15 --- /dev/null +++ b/src/mongo/util/signal_handlers.cpp @@ -0,0 +1,122 @@ +// signal_handlers.cpp + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" + +#include <cstdarg> +#include <cstdio> +#include <cstdlib> + +#if !defined(_WIN32) // TODO: windows support +#include <unistd.h> +#endif + +#if !defined(_WIN32) && !defined(NOEXECINFO) +#include <execinfo.h> +#endif + +#include "log.h" +#include "signal_handlers.h" + +namespace mongo { + + /* + * WARNING: PLEASE READ BEFORE CHANGING THIS MODULE + * + * All code in this module should be singal-friendly. Before adding any system + * call or other dependency, please make sure the latter still holds. + * + */ + + static int rawWrite( int fd , char* c , int size ) { +#if !defined(_WIN32) + + int toWrite = size; + int writePos = 0; + int wrote; + while ( toWrite > 0 ) { + wrote = write( fd , &c[writePos] , toWrite ); + if ( wrote < 1 ) break; + toWrite -= wrote; + writePos += wrote; + } + return writePos; + +#else + + return -1; + +#endif + } + + static int formattedWrite( int fd , const char* format, ... ) { + const int MAX_ENTRY = 256; + static char entryBuf[MAX_ENTRY]; + + va_list ap; + va_start( ap , format ); + int entrySize = vsnprintf( entryBuf , MAX_ENTRY-1 , format , ap ); + if ( entrySize < 0 ) { + return -1; + } + + if ( rawWrite( fd , entryBuf , entrySize ) < 0 ) { + return -1; + } + + return 0; + } + + static void formattedBacktrace( int fd ) { + +#if !defined(_WIN32) && !defined(NOEXECINFO) + + int numFrames; + const int MAX_DEPTH = 20; + void* stackFrames[MAX_DEPTH]; + + numFrames = backtrace( stackFrames , 20 ); + for ( int i = 0; i < numFrames; i++ ) { + formattedWrite( fd , "%p " , stackFrames[i] ); + } + formattedWrite( fd , "\n" ); + + backtrace_symbols_fd( stackFrames , numFrames , fd ); + +#else + + formattedWrite( fd, "backtracing not implemented for this platform yet\n" ); + +#endif + + } + + void printStackAndExit( int signalNum ) { + int fd = Logstream::getLogDesc(); + + if ( fd >= 0 ) { + formattedWrite( fd , "Received signal %d\n" , signalNum ); + formattedWrite( fd , "Backtrace: " ); + formattedBacktrace( fd ); + formattedWrite( fd , "===\n" ); + } + + ::exit( EXIT_ABRUPT ); + } + +} // namespace mongo diff --git a/src/mongo/util/signal_handlers.h b/src/mongo/util/signal_handlers.h new file mode 100644 index 00000000000..9d3a735a723 --- /dev/null +++ b/src/mongo/util/signal_handlers.h @@ -0,0 +1,34 @@ +// signal_handlers.h + +/** +* Copyright (C) 2010 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "../pch.h" + +namespace mongo { + + /** + * Obtains the log file handler and writes the current thread's stack trace to + * it. This call issues an exit(). The function can safely be called from within a + * signal handler. + * + * @param signal that this hadler is called for + */ + void printStackAndExit( int signalNum ); + +} // namespace mongo diff --git a/src/mongo/util/string_writer.h b/src/mongo/util/string_writer.h new file mode 100755 index 00000000000..e83881bf6f6 --- /dev/null +++ b/src/mongo/util/string_writer.h @@ -0,0 +1,28 @@ +/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pch.h"
+
+namespace mongo {
+
+ class StringWriter {
+ public:
+ virtual ~StringWriter() {};
+ virtual void writeString(stringstream &ss) const = 0;
+ };
+}
diff --git a/src/mongo/util/stringutils.cpp b/src/mongo/util/stringutils.cpp new file mode 100644 index 00000000000..229f57bb3cb --- /dev/null +++ b/src/mongo/util/stringutils.cpp @@ -0,0 +1,44 @@ +// stringutils.cpp + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "pch.h" + +namespace mongo { + + void splitStringDelim( const string& str , vector<string>* res , char delim ) { + if ( str.empty() ) + return; + + size_t beg = 0; + size_t pos = str.find( delim ); + while ( pos != string::npos ) { + res->push_back( str.substr( beg, pos - beg) ); + beg = ++pos; + pos = str.find( delim, beg ); + } + res->push_back( str.substr( beg ) ); + } + + void joinStringDelim( const vector<string>& strs , string* res , char delim ) { + for ( vector<string>::const_iterator it = strs.begin(); it != strs.end(); ++it ) { + if ( it !=strs.begin() ) res->push_back( delim ); + res->append( *it ); + } + } + +} // namespace mongo diff --git a/src/mongo/util/stringutils.h b/src/mongo/util/stringutils.h new file mode 100644 index 00000000000..93598aa520b --- /dev/null +++ b/src/mongo/util/stringutils.h @@ -0,0 +1,139 @@ +// stringutils.h + +/* Copyright 2010 10gen 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. + */ + +#pragma once + +namespace mongo { + + // see also mongoutils/str.h - perhaps move these there? + // see also text.h + + void splitStringDelim( const string& str , vector<string>* res , char delim ); + + void joinStringDelim( const vector<string>& strs , string* res , char delim ); + + inline string tolowerString( const string& input ) { + string::size_type sz = input.size(); + + boost::scoped_array<char> line(new char[sz+1]); + char * copy = line.get(); + + for ( string::size_type i=0; i<sz; i++ ) { + char c = input[i]; + copy[i] = (char)tolower( (int)c ); + } + copy[sz] = 0; + return string(copy); + } + + /** + * Non numeric characters are compared lexicographically; numeric substrings + * are compared numerically; dots separate ordered comparable subunits. + * For convenience, character 255 is greater than anything else. + */ + inline int lexNumCmp( const char *s1, const char *s2 ) { + //cout << "START : " << s1 << "\t" << s2 << endl; + + bool startWord = true; + + while( *s1 && *s2 ) { + + bool d1 = ( *s1 == '.' ); + bool d2 = ( *s2 == '.' ); + if ( d1 && !d2 ) + return -1; + if ( d2 && !d1 ) + return 1; + if ( d1 && d2 ) { + ++s1; ++s2; + startWord = true; + continue; + } + + bool p1 = ( *s1 == (char)255 ); + bool p2 = ( *s2 == (char)255 ); + //cout << "\t\t " << p1 << "\t" << p2 << endl; + if ( p1 && !p2 ) + return 1; + if ( p2 && !p1 ) + return -1; + + bool n1 = isNumber( *s1 ); + bool n2 = isNumber( *s2 ); + + if ( n1 && n2 ) { + // get rid of leading 0s + if ( startWord ) { + while ( *s1 == '0' ) s1++; + while ( *s2 == '0' ) s2++; + } + + char * e1 = (char*)s1; + char * e2 = (char*)s2; + + // find length + // if end of string, will break immediately ('\0') + while ( isNumber (*e1) ) e1++; + while ( isNumber (*e2) ) e2++; + + int len1 = (int)(e1-s1); + int len2 = (int)(e2-s2); + + int result; + // if one is longer than the other, return + if ( len1 > len2 ) { + return 1; + } + else if ( len2 > len1 ) { + return -1; + } + // if the lengths are equal, just strcmp + else if ( (result = strncmp(s1, s2, len1)) != 0 ) { + return result; + } + + // otherwise, the numbers are equal + s1 = e1; + s2 = e2; + startWord = false; + continue; + } + + if ( n1 ) + return 1; + + if ( n2 ) + return -1; + + if ( *s1 > *s2 ) + return 1; + + if ( *s2 > *s1 ) + return -1; + + s1++; s2++; + startWord = false; + } + + if ( *s1 ) + return 1; + if ( *s2 ) + return -1; + return 0; + } + +} // namespace mongo diff --git a/src/mongo/util/systeminfo.h b/src/mongo/util/systeminfo.h new file mode 100755 index 00000000000..be4404ff785 --- /dev/null +++ b/src/mongo/util/systeminfo.h @@ -0,0 +1,41 @@ +/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <cstddef>
+
+namespace mongo {
+
+ class SystemInfo {
+ public:
+ /*
+ Get the amount of physical memory available on the host.
+
+ This should only be used for "advisory" purposes, and not as a hard
+ value, because this could be deceptive on virtual hosts, and because
+ this will return zero on platforms that do not support it.
+
+ @returns amount of physical memory, or zero
+ */
+ static size_t getPhysicalRam();
+
+ private:
+ // don't instantiate this class
+ SystemInfo(); // no implementation
+ };
+
+}
diff --git a/src/mongo/util/systeminfo_linux2.cpp b/src/mongo/util/systeminfo_linux2.cpp new file mode 100755 index 00000000000..c1b7c861768 --- /dev/null +++ b/src/mongo/util/systeminfo_linux2.cpp @@ -0,0 +1,47 @@ +/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "util/systeminfo.h"
+
+#include <unistd.h>
+
+namespace mongo {
+
+ size_t SystemInfo::getPhysicalRam() {
+ /*
+ The value of this should not be changing while the system is running,
+ so it should be safe to do this once for the lifetime of the
+ application.
+
+ This could present a race condition if multiple threads do this at
+ the same time, but all paths through here will produce the same
+ result, so it's not worth locking or worrying about it.
+ */
+ static bool unknown = true;
+ static size_t ramSize = 0;
+
+ if (unknown) {
+ long pages = sysconf(_SC_PHYS_PAGES);
+ long page_size = sysconf(_SC_PAGE_SIZE);
+ ramSize = pages * page_size;
+ unknown = false;
+ }
+
+ return ramSize;
+ }
+
+}
diff --git a/src/mongo/util/systeminfo_none.cpp b/src/mongo/util/systeminfo_none.cpp new file mode 100755 index 00000000000..d22ce17f6b9 --- /dev/null +++ b/src/mongo/util/systeminfo_none.cpp @@ -0,0 +1,26 @@ +/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "util/systeminfo.h"
+
+namespace mongo {
+
+ size_t SystemInfo::getPhysicalRam() {
+ return 0;
+ }
+
+}
diff --git a/src/mongo/util/systeminfo_win32.cpp b/src/mongo/util/systeminfo_win32.cpp new file mode 100755 index 00000000000..19c182878ee --- /dev/null +++ b/src/mongo/util/systeminfo_win32.cpp @@ -0,0 +1,48 @@ +/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "util/systeminfo.h"
+
+#include <windows.h>
+
+namespace mongo {
+
+ size_t SystemInfo::getPhysicalRam() {
+ /*
+ The value of this should not be changing while the system is running,
+ so it should be safe to do this once for the lifetime of the
+ application.
+
+ This could present a race condition if multiple threads do this at
+ the same time, but all paths through here will produce the same
+ result, so it's not worth locking or worrying about it.
+ */
+ static bool unknown = true;
+ static size_t ramSize = 0;
+
+ if (unknown) {
+ MEMORYSTATUSEX status;
+ status.dwLength = sizeof(status);
+ GlobalMemoryStatusEx(&status);
+ ramSize = static_cast<size_t>(status.ullTotalPhys);
+ unknown = false;
+ }
+
+ return ramSize;
+ }
+
+}
diff --git a/src/mongo/util/text.cpp b/src/mongo/util/text.cpp new file mode 100644 index 00000000000..51a2556afdc --- /dev/null +++ b/src/mongo/util/text.cpp @@ -0,0 +1,115 @@ +// text.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "text.h" +#include "unittest.h" + +namespace mongo { + + inline int leadingOnes(unsigned char c) { + if (c < 0x80) return 0; + static const char _leadingOnes[128] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 - 0x8F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 - 0x99 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xA0 - 0xA9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xB0 - 0xB9 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xC9 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xD9 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xE9 + 4, 4, 4, 4, 4, 4, 4, 4, // 0xF0 - 0xF7 + 5, 5, 5, 5, // 0xF8 - 0xFB + 6, 6, // 0xFC - 0xFD + 7, // 0xFE + 8, // 0xFF + }; + return _leadingOnes[c & 0x7f]; + + } + + bool isValidUTF8(const char *s) { + int left = 0; // how many bytes are left in the current codepoint + while (*s) { + const unsigned char c = (unsigned char) *(s++); + const int ones = leadingOnes(c); + if (left) { + if (ones != 1) return false; // should be a continuation byte + left--; + } + else { + if (ones == 0) continue; // ASCII byte + if (ones == 1) return false; // unexpected continuation byte + if (c > 0xF4) return false; // codepoint too large (< 0x10FFFF) + if (c == 0xC0 || c == 0xC1) return false; // codepoints <= 0x7F shouldn't be 2 bytes + + // still valid + left = ones-1; + } + } + if (left!=0) return false; // string ended mid-codepoint + return true; + } + +#if defined(_WIN32) + + std::string toUtf8String(const std::wstring& wide) { + if (wide.size() > boost::integer_traits<int>::const_max) + throw std::length_error( + "Wide string cannot be more than INT_MAX characters long."); + if (wide.size() == 0) + return ""; + + // Calculate necessary buffer size + int len = ::WideCharToMultiByte( + CP_UTF8, 0, wide.c_str(), static_cast<int>(wide.size()), + NULL, 0, NULL, NULL); + + // Perform actual conversion + if (len > 0) { + std::vector<char> buffer(len); + len = ::WideCharToMultiByte( + CP_UTF8, 0, wide.c_str(), static_cast<int>(wide.size()), + &buffer[0], static_cast<int>(buffer.size()), NULL, NULL); + if (len > 0) { + assert(len == static_cast<int>(buffer.size())); + return std::string(&buffer[0], buffer.size()); + } + } + + throw boost::system::system_error( + ::GetLastError(), boost::system::system_category); + } + +#if defined(_UNICODE) + std::wstring toWideString(const char *s) { + std::basic_ostringstream<TCHAR> buf; + buf << s; + return buf.str(); + } +#endif + +#endif + + struct TextUnitTest : public UnitTest { + void run() { + assert( parseLL("123") == 123 ); + assert( parseLL("-123000000000") == -123000000000LL ); + } + } textUnitTest; + +} + diff --git a/src/mongo/util/text.h b/src/mongo/util/text.h new file mode 100644 index 00000000000..bf25c86fd39 --- /dev/null +++ b/src/mongo/util/text.h @@ -0,0 +1,148 @@ +// text.h +/* + * Copyright 2010 10gen 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. + */ + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + + class StringSplitter { + public: + /** @param big the string to be split + @param splitter the delimiter + */ + StringSplitter( const char * big , const char * splitter ) + : _big( big ) , _splitter( splitter ) { + } + + /** @return true if more to be taken via next() */ + bool more() { + return _big[0] != 0; + } + + /** get next split string fragment */ + string next() { + const char * foo = strstr( _big , _splitter ); + if ( foo ) { + string s( _big , foo - _big ); + _big = foo + 1; + while ( *_big && strstr( _big , _splitter ) == _big ) + _big++; + return s; + } + + string s = _big; + _big += strlen( _big ); + return s; + } + + void split( vector<string>& l ) { + while ( more() ) { + l.push_back( next() ); + } + } + + vector<string> split() { + vector<string> l; + split( l ); + return l; + } + + static vector<string> split( const string& big , const string& splitter ) { + StringSplitter ss( big.c_str() , splitter.c_str() ); + return ss.split(); + } + + static string join( vector<string>& l , const string& split ) { + stringstream ss; + for ( unsigned i=0; i<l.size(); i++ ) { + if ( i > 0 ) + ss << split; + ss << l[i]; + } + return ss.str(); + } + + private: + const char * _big; + const char * _splitter; + }; + + /* This doesn't defend against ALL bad UTF8, but it will guarantee that the + * string can be converted to sequence of codepoints. However, it doesn't + * guarantee that the codepoints are valid. + */ + bool isValidUTF8(const char *s); + inline bool isValidUTF8(string s) { return isValidUTF8(s.c_str()); } + +#if defined(_WIN32) + + std::string toUtf8String(const std::wstring& wide); + + std::wstring toWideString(const char *s); + + /* like toWideString but UNICODE macro sensitive */ +# if !defined(_UNICODE) +#error temp error + inline std::string toNativeString(const char *s) { return s; } +# else + inline std::wstring toNativeString(const char *s) { return toWideString(s); } +# endif + +#endif + + // expect that n contains a base ten number and nothing else after it + // NOTE win version hasn't been tested directly + inline long long parseLL( const char *n ) { + long long ret; + uassert( 13307, "cannot convert empty string to long long", *n != 0 ); +#if !defined(_WIN32) + char *endPtr = 0; + errno = 0; + ret = strtoll( n, &endPtr, 10 ); + uassert( 13305, "could not convert string to long long", *endPtr == 0 && errno == 0 ); +#elif _MSC_VER>=1600 // 1600 is VS2k10 1500 is VS2k8 + size_t endLen = 0; + try { + ret = stoll( n, &endLen, 10 ); + } + catch ( ... ) { + endLen = 0; + } + uassert( 13306, "could not convert string to long long", endLen != 0 && n[ endLen ] == 0 ); +#else // stoll() wasn't introduced until VS 2010. + char* endPtr = 0; + ret = _strtoi64( n, &endPtr, 10 ); + uassert( 13310, "could not convert string to long long", (*endPtr == 0) && (ret != _I64_MAX) && (ret != _I64_MIN) ); +#endif // !defined(_WIN32) + return ret; + } +} diff --git a/src/mongo/util/time_support.h b/src/mongo/util/time_support.h new file mode 100644 index 00000000000..18181eb805a --- /dev/null +++ b/src/mongo/util/time_support.h @@ -0,0 +1,255 @@ +// @file time_support.h + +/* Copyright 2010 10gen 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. + */ + +#pragma once + +#include <cstdio> // sscanf +#include <ctime> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread/xtime.hpp> +#undef assert +#define assert MONGO_assert + +namespace mongo { + + inline void time_t_to_Struct(time_t t, struct tm * buf , bool local = false ) { +#if defined(_WIN32) + if ( local ) + localtime_s( buf , &t ); + else + gmtime_s(buf, &t); +#else + if ( local ) + localtime_r(&t, buf); + else + gmtime_r(&t, buf); +#endif + } + + // uses ISO 8601 dates without trailing Z + // colonsOk should be false when creating filenames + inline string terseCurrentTime(bool colonsOk=true) { + struct tm t; + time_t_to_Struct( time(0) , &t ); + + const char* fmt = (colonsOk ? "%Y-%m-%dT%H:%M:%S" : "%Y-%m-%dT%H-%M-%S"); + char buf[32]; + assert(strftime(buf, sizeof(buf), fmt, &t) == 19); + return buf; + } + + inline string timeToISOString(time_t time) { + struct tm t; + time_t_to_Struct( time, &t ); + + const char* fmt = "%Y-%m-%dT%H:%M:%SZ"; + char buf[32]; + assert(strftime(buf, sizeof(buf), fmt, &t) == 20); + return buf; + } + + inline boost::gregorian::date currentDate() { + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + return now.date(); + } + + // parses time of day in "hh:mm" format assuming 'hh' is 00-23 + inline bool toPointInTime( const string& str , boost::posix_time::ptime* timeOfDay ) { + int hh = 0; + int mm = 0; + if ( 2 != sscanf( str.c_str() , "%d:%d" , &hh , &mm ) ) { + return false; + } + + // verify that time is well formed + if ( ( hh / 24 ) || ( mm / 60 ) ) { + return false; + } + + boost::posix_time::ptime res( currentDate() , boost::posix_time::hours( hh ) + boost::posix_time::minutes( mm ) ); + *timeOfDay = res; + return true; + } + +#define MONGO_asctime _asctime_not_threadsafe_ +#define asctime MONGO_asctime +#define MONGO_gmtime _gmtime_not_threadsafe_ +#define gmtime MONGO_gmtime +#define MONGO_localtime _localtime_not_threadsafe_ +#define localtime MONGO_localtime +#define MONGO_ctime _ctime_is_not_threadsafe_ +#define ctime MONGO_ctime + +#if defined(_WIN32) + inline void sleepsecs(int s) { + // todo : add an assert here that we are not locked in d.dbMutex. there may be debugging things where we + // are but otherwise it's quite likely that would be a mistake. + Sleep(s*1000); + } + inline void sleepmillis(long long s) { + assert( s <= 0xffffffff ); + Sleep((DWORD) s); + } + inline void sleepmicros(long long s) { + if ( s <= 0 ) + return; + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += (int)( s / 1000000 ); + xt.nsec += (int)(( s % 1000000 ) * 1000); + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + boost::thread::sleep(xt); + } +#elif defined(__sunos__) + inline void sleepsecs(int s) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += s; + boost::thread::sleep(xt); + } + inline void sleepmillis(long long s) { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += (int)( s / 1000 ); + xt.nsec += (int)(( s % 1000 ) * 1000000); + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + boost::thread::sleep(xt); + } + inline void sleepmicros(long long s) { + if ( s <= 0 ) + return; + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + xt.sec += (int)( s / 1000000 ); + xt.nsec += (int)(( s % 1000000 ) * 1000); + if ( xt.nsec >= 1000000000 ) { + xt.nsec -= 1000000000; + xt.sec++; + } + boost::thread::sleep(xt); + } +#else + inline void sleepsecs(int s) { + struct timespec t; + t.tv_sec = s; + t.tv_nsec = 0; + if ( nanosleep( &t , 0 ) ) { + cout << "nanosleep failed" << endl; + } + } + inline void sleepmicros(long long s) { + if ( s <= 0 ) + return; + struct timespec t; + t.tv_sec = (int)(s / 1000000); + t.tv_nsec = 1000 * ( s % 1000000 ); + struct timespec out; + if ( nanosleep( &t , &out ) ) { + cout << "nanosleep failed" << endl; + } + } + inline void sleepmillis(long long s) { + sleepmicros( s * 1000 ); + } +#endif + + extern long long jsTime_virtual_skew; + extern boost::thread_specific_ptr<long long> jsTime_virtual_thread_skew; + + // DO NOT TOUCH except for testing + inline void jsTimeVirtualSkew( long long skew ){ + jsTime_virtual_skew = skew; + } + inline long long getJSTimeVirtualSkew(){ + return jsTime_virtual_skew; + } + + inline void jsTimeVirtualThreadSkew( long long skew ){ + jsTime_virtual_thread_skew.reset(new long long(skew)); + } + inline long long getJSTimeVirtualThreadSkew(){ + if(jsTime_virtual_thread_skew.get()){ + return *(jsTime_virtual_thread_skew.get()); + } + else return 0; + } + + /** Date_t is milliseconds since epoch */ + inline Date_t jsTime(); + + /** warning this will wrap */ + inline unsigned curTimeMicros(); + + inline unsigned long long curTimeMicros64(); +#ifdef _WIN32 // no gettimeofday on windows + inline unsigned long long curTimeMillis64() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + return ((unsigned long long)xt.sec) * 1000 + xt.nsec / 1000000; + } + inline Date_t jsTime() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned long long t = xt.nsec / 1000000; + return ((unsigned long long) xt.sec * 1000) + t + getJSTimeVirtualSkew() + getJSTimeVirtualThreadSkew(); + } + inline unsigned long long curTimeMicros64() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned long long t = xt.nsec / 1000; + return (((unsigned long long) xt.sec) * 1000000) + t; + } + inline unsigned curTimeMicros() { + boost::xtime xt; + boost::xtime_get(&xt, boost::TIME_UTC); + unsigned t = xt.nsec / 1000; + unsigned secs = xt.sec % 1024; + return secs*1000000 + t; + } +#else +# include <sys/time.h> + inline unsigned long long curTimeMillis64() { + timeval tv; + gettimeofday(&tv, NULL); + return ((unsigned long long)tv.tv_sec) * 1000 + tv.tv_usec / 1000; + } + inline Date_t jsTime() { + timeval tv; + gettimeofday(&tv, NULL); + unsigned long long t = tv.tv_usec / 1000; + return ((unsigned long long) tv.tv_sec * 1000) + t + getJSTimeVirtualSkew() + getJSTimeVirtualThreadSkew(); + } + inline unsigned long long curTimeMicros64() { + timeval tv; + gettimeofday(&tv, NULL); + return (((unsigned long long) tv.tv_sec) * 1000*1000) + tv.tv_usec; + } + inline unsigned curTimeMicros() { + timeval tv; + gettimeofday(&tv, NULL); + unsigned secs = tv.tv_sec % 1024; + return secs*1000*1000 + tv.tv_usec; + } +#endif + +} // namespace mongo diff --git a/src/mongo/util/timer.h b/src/mongo/util/timer.h new file mode 100644 index 00000000000..224651ac224 --- /dev/null +++ b/src/mongo/util/timer.h @@ -0,0 +1,115 @@ +// @file timer.h + +/* Copyright 2010 10gen 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. + */ + +#pragma once + +#include "time_support.h" + +namespace mongo { + +#if !defined(_WIN32) + + /** + * simple scoped timer + */ + class Timer /*copyable*/ { + public: + Timer() { reset(); } + int seconds() const { return (int)(micros() / 1000000); } + int millis() const { return (int)(micros() / 1000); } + int minutes() const { return seconds() / 60; } + + + /** gets time interval and resets at the same time. this way we can call curTimeMicros + once instead of twice if one wanted millis() and then reset(). + @return time in millis + */ + int millisReset() { + unsigned long long now = curTimeMicros64(); + int m = (int)((now-old)/1000); + old = now; + return m; + } + + // note: dubious that the resolution is as anywhere near as high as ethod name implies! + unsigned long long micros() const { + unsigned long long n = curTimeMicros64(); + return n - old; + } + unsigned long long micros(unsigned long long & n) const { // returns cur time in addition to timer result + n = curTimeMicros64(); + return n - old; + } + + void reset() { old = curTimeMicros64(); } + private: + unsigned long long old; + }; + +#else + + class Timer /*copyable*/ { + public: + Timer() { reset(); } + + int seconds() const { + int s = static_cast<int>((now() - old) / countsPerSecond); + return s; + } + + int millis() const { + return (int) + ((now() - old) * 1000.0 / countsPerSecond); + } + + int minutes() const { return seconds() / 60; } + + /** gets time interval and resets at the same time. this way we can call curTimeMicros + once instead of twice if one wanted millis() and then reset(). + @return time in millis + */ + int millisReset() { + unsigned long long nw = now(); + int m = static_cast<int>((nw - old) * 1000.0 / countsPerSecond); + old = nw; + return m; + } + + void reset() { + old = now(); + } + + unsigned long long micros() const { + return (unsigned long long) + ((now() - old) * 1000000.0 / countsPerSecond); + } + + static unsigned long long countsPerSecond; + + private: + unsigned long long now() const { + LARGE_INTEGER i; + QueryPerformanceCounter(&i); + return i.QuadPart; + } + + unsigned long long old; + }; + +#endif + +} // namespace mongo diff --git a/src/mongo/util/unittest.h b/src/mongo/util/unittest.h new file mode 100644 index 00000000000..94be444363f --- /dev/null +++ b/src/mongo/util/unittest.h @@ -0,0 +1,62 @@ +// unittest.h + +/* Copyright 2009 10gen 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. + */ + +#pragma once + +namespace mongo { + + /* The idea here is to let all initialization of global variables (classes inheriting from UnitTest) + complete before we run the tests -- otherwise order of initilization being arbitrary may mess + us up. The app's main() function should call runTests(). + + To define a unit test, inherit from this and implement run. instantiate one object for the new class + as a global. + + These tests are ran on *every* startup of mongod, so they have to be very lightweight. But it is a + good quick check for a bad build. + */ + struct UnitTest { + UnitTest() { + registerTest(this); + } + virtual ~UnitTest() {} + + // assert if fails + virtual void run() = 0; + + static bool testsInProgress() { return running; } + private: + static vector<UnitTest*> *tests; + static bool running; + public: + static void registerTest(UnitTest *t) { + if ( tests == 0 ) + tests = new vector<UnitTest*>(); + tests->push_back(t); + } + + static void runTests() { + running = true; + for ( vector<UnitTest*>::iterator i = tests->begin(); i != tests->end(); i++ ) { + (*i)->run(); + } + running = false; + } + }; + + +} // namespace mongo diff --git a/src/mongo/util/util.cpp b/src/mongo/util/util.cpp new file mode 100644 index 00000000000..356c640f449 --- /dev/null +++ b/src/mongo/util/util.cpp @@ -0,0 +1,220 @@ +// @file util.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include "goodies.h" +#include "unittest.h" +#include "file_allocator.h" +#include "optime.h" +#include "time_support.h" +#include "mongoutils/str.h" +#include "timer.h" + +namespace mongo { + +#if defined(_WIN32) + unsigned long long Timer::countsPerSecond; + struct AtStartup { + AtStartup() { + LARGE_INTEGER x; + bool ok = QueryPerformanceFrequency(&x); + assert(ok); + Timer::countsPerSecond = x.QuadPart; + } + } atstartuputil; +#endif + + string hexdump(const char *data, unsigned len) { + assert( len < 1000000 ); + const unsigned char *p = (const unsigned char *) data; + stringstream ss; + for( unsigned i = 0; i < 4 && i < len; i++ ) { + ss << std::hex << setw(2) << setfill('0'); + unsigned n = p[i]; + ss << n; + ss << ' '; + } + string s = ss.str(); + return s; + } + + boost::thread_specific_ptr<string> _threadName; + + unsigned _setThreadName( const char * name ) { + if ( ! name ) name = "NONE"; + + static unsigned N = 0; + + if ( strcmp( name , "conn" ) == 0 ) { + string* x = _threadName.get(); + if ( x && mongoutils::str::startsWith( *x , "conn" ) ) { + int n = atoi( x->c_str() + 4 ); + if ( n > 0 ) + return n; + warning() << "unexpected thread name [" << *x << "] parsed to " << n << endl; + } + unsigned n = ++N; + stringstream ss; + ss << name << n; + _threadName.reset( new string( ss.str() ) ); + return n; + } + + _threadName.reset( new string(name) ); + return 0; + } + +#if defined(_WIN32) +#define MS_VC_EXCEPTION 0x406D1388 +#pragma pack(push,8) + typedef struct tagTHREADNAME_INFO { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; +#pragma pack(pop) + + void setWinThreadName(const char *name) { + /* is the sleep here necessary??? + Sleep(10); + */ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = -1; + info.dwFlags = 0; + __try { + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); + } + __except(EXCEPTION_EXECUTE_HANDLER) { + } + } + + unsigned setThreadName(const char *name) { + unsigned n = _setThreadName( name ); +#if !defined(_DEBUG) + // naming might be expensive so don't do "conn*" over and over + if( string("conn") == name ) + return n; +#endif + setWinThreadName(name); + return n; + } + +#else + + unsigned setThreadName(const char * name ) { + return _setThreadName( name ); + } + +#endif + + string getThreadName() { + string * s = _threadName.get(); + if ( s ) + return *s; + return ""; + } + + vector<UnitTest*> *UnitTest::tests = 0; + bool UnitTest::running = false; + + const char *default_getcurns() { return ""; } + const char * (*getcurns)() = default_getcurns; + + int logLevel = 0; + int tlogLevel = 0; + mongo::mutex Logstream::mutex("Logstream"); + int Logstream::doneSetup = Logstream::magicNumber(); + + bool isPrime(int n) { + int z = 2; + while ( 1 ) { + if ( z*z > n ) + break; + if ( n % z == 0 ) + return false; + z++; + } + return true; + } + + int nextPrime(int n) { + n |= 1; // 2 goes to 3...don't care... + while ( !isPrime(n) ) + n += 2; + return n; + } + + struct UtilTest : public UnitTest { + void run() { + assert( isPrime(3) ); + assert( isPrime(2) ); + assert( isPrime(13) ); + assert( isPrime(17) ); + assert( !isPrime(9) ); + assert( !isPrime(6) ); + assert( nextPrime(4) == 5 ); + assert( nextPrime(8) == 11 ); + + assert( endsWith("abcde", "de") ); + assert( !endsWith("abcde", "dasdfasdfashkfde") ); + + assert( swapEndian(0x01020304) == 0x04030201 ); + + } + } utilTest; + + OpTime OpTime::last(0, 0); + + /* this is a good place to set a breakpoint when debugging, as lots of warning things + (assert, wassert) call it. + */ + void sayDbContext(const char *errmsg) { + if ( errmsg ) { + problem() << errmsg << endl; + } + printStackTrace(); + } + + /* note: can't use malloc herein - may be in signal handler. + logLockless() likely does not comply and should still be fixed todo + likewise class string? + */ + void rawOut( const string &s ) { + if( s.empty() ) return; + + char buf[64]; + time_t_to_String( time(0) , buf ); + /* truncate / don't show the year: */ + buf[19] = ' '; + buf[20] = 0; + + Logstream::logLockless(buf); + Logstream::logLockless(s); + Logstream::logLockless("\n"); + } + + ostream& operator<<( ostream &s, const ThreadSafeString &o ) { + s << o.toString(); + return s; + } + + bool StaticObserver::_destroyingStatics = false; + +} // namespace mongo diff --git a/src/mongo/util/version.cpp b/src/mongo/util/version.cpp new file mode 100644 index 00000000000..1e4bc457f91 --- /dev/null +++ b/src/mongo/util/version.cpp @@ -0,0 +1,288 @@ +// @file version.cpp + +/* Copyright 2009 10gen 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. + */ + +#include "pch.h" +#include <cstdlib> +#include <iostream> +#include <iomanip> +#include <sstream> +#include <string> +#include "unittest.h" +#include "version.h" +#include "stringutils.h" +#include "../db/jsobj.h" +#include "file.h" +#include "ramlog.h" +#include "../db/cmdline.h" + +namespace mongo { + + /* Approved formats for versionString: + * 1.2.3 + * 1.2.3-pre- + * 1.2.3-rc4 (up to rc9) + * 1.2.3-rc4-pre- + * If you really need to do something else you'll need to fix _versionArray() + */ + const char versionString[] = "2.1.0-pre-"; + + // See unit test for example outputs + static BSONArray _versionArray(const char* version){ + // this is inefficient, but cached so it doesn't matter + BSONArrayBuilder b; + string curPart; + const char* c = version; + int finalPart = 0; // 0 = final release, -100 = pre, -10 to -1 = -10 + X for rcX + do { //walks versionString including NUL byte + if (!(*c == '.' || *c == '-' || *c == '\0')){ + curPart += *c; + continue; + } + + try { + unsigned num = stringToNum(curPart.c_str()); + b.append((int) num); + } + catch (...){ // not a number + if (curPart.empty()){ + assert(*c == '\0'); + break; + } + else if (startsWith(curPart, "rc")){ + finalPart = -10 + stringToNum(curPart.c_str()+2); + break; + } + else if (curPart == "pre"){ + finalPart = -100; + break; + } + } + + curPart = ""; + } while (*c++); + + b.append(finalPart); + return b.arr(); + } + + const BSONArray versionArray = _versionArray(versionString); + + string mongodVersion() { + stringstream ss; + ss << "db version v" << versionString << ", pdfile version " << PDFILE_VERSION << "." << PDFILE_VERSION_MINOR; + return ss.str(); + } + +#ifndef _SCONS + // only works in scons + const char * gitVersion() { return "not-scons"; } +#endif + + void printGitVersion() { log() << "git version: " << gitVersion() << endl; } + +#ifndef _SCONS +#if defined(_WIN32) + string sysInfo() { + stringstream ss; + ss << "not-scons win"; + ss << " mscver:" << _MSC_FULL_VER << " built:" << __DATE__; + ss << " boostver:" << BOOST_VERSION; +#if( !defined(_MT) ) +#error _MT is not defined +#endif + ss << (sizeof(char *) == 8) ? " 64bit" : " 32bit"; + return ss.str(); + } +#else + string sysInfo() { return ""; } +#endif +#endif + + void printSysInfo() { + log() << "build info: " << sysInfo() << endl; + } + + + static Tee * startupWarningsLog = new RamLog("startupWarnings"); //intentionally leaked + + // + // system warnings + // + void show_warnings() { + // each message adds a leading and a trailing newline + + bool warned = false; + { + const char * foo = strchr( versionString , '.' ) + 1; + int bar = atoi( foo ); + if ( ( 2 * ( bar / 2 ) ) != bar ) { + log() << startupWarningsLog; + log() << "** NOTE: This is a development version (" << versionString << ") of MongoDB." << startupWarningsLog; + log() << "** Not recommended for production." << startupWarningsLog; + warned = true; + } + } + + if ( sizeof(int*) == 4 ) { + log() << startupWarningsLog; + log() << "** NOTE: when using MongoDB 32 bit, you are limited to about 2 gigabytes of data" << startupWarningsLog; + log() << "** see http://blog.mongodb.org/post/137788967/32-bit-limitations" << startupWarningsLog; + log() << "** with --journal, the limit is lower" << startupWarningsLog; + warned = true; + } + +#ifdef __linux__ + if (boost::filesystem::exists("/proc/vz") && !boost::filesystem::exists("/proc/bc")) { + log() << startupWarningsLog; + log() << "** WARNING: You are running in OpenVZ. This is known to be broken!!!" << startupWarningsLog; + warned = true; + } + + if (boost::filesystem::exists("/sys/devices/system/node/node1")){ + // We are on a box with a NUMA enabled kernel and more than 1 numa node (they start at node0) + // Now we look at the first line of /proc/self/numa_maps + // + // Bad example: + // $ cat /proc/self/numa_maps + // 00400000 default file=/bin/cat mapped=6 N4=6 + // + // Good example: + // $ numactl --interleave=all cat /proc/self/numa_maps + // 00400000 interleave:0-7 file=/bin/cat mapped=6 N4=6 + + File f; + f.open("/proc/self/numa_maps", /*read_only*/true); + if ( f.is_open() && ! f.bad() ) { + char line[100]; //we only need the first line + if (read(f.fd, line, sizeof(line)) < 0){ + warning() << "failed to read from /proc/self/numa_maps: " << errnoWithDescription() << startupWarningsLog; + warned = true; + } + else { + // just in case... + line[98] = ' '; + line[99] = '\0'; + + // skip over pointer + const char* space = strchr(line, ' '); + + if ( ! space ) { + log() << startupWarningsLog; + log() << "** WARNING: cannot parse numa_maps" << startupWarningsLog; + warned = true; + } + else if ( ! startsWith(space+1, "interleave") ) { + log() << startupWarningsLog; + log() << "** WARNING: You are running on a NUMA machine." << startupWarningsLog; + log() << "** We suggest launching mongod like this to avoid performance problems:" << startupWarningsLog; + log() << "** numactl --interleave=all mongod [other options]" << startupWarningsLog; + warned = true; + } + } + } + } + + if (cmdLine.dur){ + fstream f ("/proc/sys/vm/overcommit_memory", ios_base::in); + unsigned val; + f >> val; + + if (val == 2) { + log() << startupWarningsLog; + log() << "** WARNING: /proc/sys/vm/overcommit_memory is " << val << startupWarningsLog; + log() << "** Journaling works best with it set to 0 or 1" << startupWarningsLog; + } + } + + if (boost::filesystem::exists("/proc/sys/vm/zone_reclaim_mode")){ + fstream f ("/proc/sys/vm/zone_reclaim_mode", ios_base::in); + unsigned val; + f >> val; + + if (val != 0) { + log() << startupWarningsLog; + log() << "** WARNING: /proc/sys/vm/zone_reclaim_mode is " << val << startupWarningsLog; + log() << "** We suggest setting it to 0" << startupWarningsLog; + log() << "** http://www.kernel.org/doc/Documentation/sysctl/vm.txt" << startupWarningsLog; + } + } +#endif + + if (warned) { + log() << startupWarningsLog; + } + } + + int versionCmp(StringData rhs, StringData lhs) { + if (strcmp(rhs.data(),lhs.data()) == 0) + return 0; + + // handle "1.2.3-" and "1.2.3-pre" + if (rhs.size() < lhs.size()) { + if (strncmp(rhs.data(), lhs.data(), rhs.size()) == 0 && lhs.data()[rhs.size()] == '-') + return +1; + } + else if (rhs.size() > lhs.size()) { + if (strncmp(rhs.data(), lhs.data(), lhs.size()) == 0 && rhs.data()[lhs.size()] == '-') + return -1; + } + + return lexNumCmp(rhs.data(), lhs.data()); + } + + class VersionCmpTest : public UnitTest { + public: + void run() { + assert( versionCmp("1.2.3", "1.2.3") == 0 ); + assert( versionCmp("1.2.3", "1.2.4") < 0 ); + assert( versionCmp("1.2.3", "1.2.20") < 0 ); + assert( versionCmp("1.2.3", "1.20.3") < 0 ); + assert( versionCmp("2.2.3", "10.2.3") < 0 ); + assert( versionCmp("1.2.3", "1.2.3-") > 0 ); + assert( versionCmp("1.2.3", "1.2.3-pre") > 0 ); + assert( versionCmp("1.2.3", "1.2.4-") < 0 ); + assert( versionCmp("1.2.3-", "1.2.3") < 0 ); + assert( versionCmp("1.2.3-pre", "1.2.3") < 0 ); + + log(1) << "versionCmpTest passed" << endl; + } + } versionCmpTest; + + class VersionArrayTest : public UnitTest { + public: + void run() { + assert( _versionArray("1.2.3") == BSON_ARRAY(1 << 2 << 3 << 0) ); + assert( _versionArray("1.2.0") == BSON_ARRAY(1 << 2 << 0 << 0) ); + assert( _versionArray("2.0.0") == BSON_ARRAY(2 << 0 << 0 << 0) ); + + assert( _versionArray("1.2.3-pre-") == BSON_ARRAY(1 << 2 << 3 << -100) ); + assert( _versionArray("1.2.0-pre-") == BSON_ARRAY(1 << 2 << 0 << -100) ); + assert( _versionArray("2.0.0-pre-") == BSON_ARRAY(2 << 0 << 0 << -100) ); + + assert( _versionArray("1.2.3-rc0") == BSON_ARRAY(1 << 2 << 3 << -10) ); + assert( _versionArray("1.2.0-rc1") == BSON_ARRAY(1 << 2 << 0 << -9) ); + assert( _versionArray("2.0.0-rc2") == BSON_ARRAY(2 << 0 << 0 << -8) ); + + // Note that the pre of an rc is the same as the rc itself + assert( _versionArray("1.2.3-rc3-pre-") == BSON_ARRAY(1 << 2 << 3 << -7) ); + assert( _versionArray("1.2.0-rc4-pre-") == BSON_ARRAY(1 << 2 << 0 << -6) ); + assert( _versionArray("2.0.0-rc5-pre-") == BSON_ARRAY(2 << 0 << 0 << -5) ); + + log(1) << "versionArrayTest passed" << endl; + } + } versionArrayTest; +} diff --git a/src/mongo/util/version.h b/src/mongo/util/version.h new file mode 100644 index 00000000000..64f8b140fd5 --- /dev/null +++ b/src/mongo/util/version.h @@ -0,0 +1,27 @@ +#ifndef UTIL_VERSION_HEADER +#define UTIL_VERSION_HEADER + +#include <string> + +namespace mongo { + struct BSONArray; + + using std::string; + + // mongo version + extern const char versionString[]; + extern const BSONArray versionArray; + string mongodVersion(); + int versionCmp(StringData rhs, StringData lhs); // like strcmp + + const char * gitVersion(); + void printGitVersion(); + + string sysInfo(); + void printSysInfo(); + + void show_warnings(); + +} // namespace mongo + +#endif // UTIL_VERSION_HEADER diff --git a/src/mongo/util/winutil.h b/src/mongo/util/winutil.h new file mode 100644 index 00000000000..b69b69a630d --- /dev/null +++ b/src/mongo/util/winutil.h @@ -0,0 +1,44 @@ +// @file winutil.cpp : Windows related utility functions +// +// /** +// * Copyright (C) 2008 10gen Inc. +// * +// * This program is free software: you can redistribute it and/or modify +// * it under the terms of the GNU Affero General Public License, version 3, +// * as published by the Free Software Foundation. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see <http://www.gnu.org/licenses/>. +// */ +// +// #include "pch.h" + +#pragma once + +#if defined(_WIN32) +#include <windows.h> +#include "text.h" + +namespace mongo { + + inline string GetWinErrMsg(DWORD err) { + LPTSTR errMsg; + ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (LPTSTR)&errMsg, 0, NULL ); + std::string errMsgStr = toUtf8String( errMsg ); + ::LocalFree( errMsg ); + // FormatMessage() appends a newline to the end of error messages, we trim it because endl flushes the buffer. + errMsgStr = errMsgStr.erase( errMsgStr.length() - 2 ); + std::ostringstream output; + output << errMsgStr << " (" << err << ")"; + + return output.str(); + } +} + +#endif + |