// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Author: kenton@google.com (Kenton Varda) #ifdef _WIN32 #include #else #include #include #endif #include #include #include namespace google { namespace protobuf { namespace { class OnceInitTest : public testing::Test { protected: void SetUp() { state_ = INIT_NOT_STARTED; current_test_ = this; } // Since ProtobufOnceType is only allowed to be allocated in static storage, // each test must use a different pair of ProtobufOnceType objects which it // must declare itself. void SetOnces(ProtobufOnceType* once, ProtobufOnceType* recursive_once) { once_ = once; recursive_once_ = recursive_once; } void InitOnce() { GoogleOnceInit(once_, &InitStatic); } void InitRecursiveOnce() { GoogleOnceInit(recursive_once_, &InitRecursiveStatic); } void BlockInit() { init_blocker_.Lock(); } void UnblockInit() { init_blocker_.Unlock(); } class TestThread { public: TestThread(Closure* callback) : done_(false), joined_(false), callback_(callback) { #ifdef _WIN32 thread_ = CreateThread(NULL, 0, &Start, this, 0, NULL); #else pthread_create(&thread_, NULL, &Start, this); #endif } ~TestThread() { if (!joined_) Join(); } bool IsDone() { MutexLock lock(&done_mutex_); return done_; } void Join() { joined_ = true; #ifdef _WIN32 WaitForSingleObject(thread_, INFINITE); CloseHandle(thread_); #else pthread_join(thread_, NULL); #endif } private: #ifdef _WIN32 HANDLE thread_; #else pthread_t thread_; #endif Mutex done_mutex_; bool done_; bool joined_; Closure* callback_; #ifdef _WIN32 static DWORD WINAPI Start(LPVOID arg) { #else static void* Start(void* arg) { #endif reinterpret_cast(arg)->Run(); return 0; } void Run() { callback_->Run(); MutexLock lock(&done_mutex_); done_ = true; } }; TestThread* RunInitOnceInNewThread() { return new TestThread(NewCallback(this, &OnceInitTest::InitOnce)); } TestThread* RunInitRecursiveOnceInNewThread() { return new TestThread( NewCallback(this, &OnceInitTest::InitRecursiveOnce)); } enum State { INIT_NOT_STARTED, INIT_STARTED, INIT_DONE }; State CurrentState() { MutexLock lock(&mutex_); return state_; } void WaitABit() { #ifdef _WIN32 Sleep(1000); #else sleep(1); #endif } private: Mutex mutex_; Mutex init_blocker_; State state_; ProtobufOnceType* once_; ProtobufOnceType* recursive_once_; void Init() { MutexLock lock(&mutex_); EXPECT_EQ(INIT_NOT_STARTED, state_); state_ = INIT_STARTED; mutex_.Unlock(); init_blocker_.Lock(); init_blocker_.Unlock(); mutex_.Lock(); state_ = INIT_DONE; } static OnceInitTest* current_test_; static void InitStatic() { current_test_->Init(); } static void InitRecursiveStatic() { current_test_->InitOnce(); } }; OnceInitTest* OnceInitTest::current_test_ = NULL; GOOGLE_PROTOBUF_DECLARE_ONCE(simple_once); TEST_F(OnceInitTest, Simple) { SetOnces(&simple_once, NULL); EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); InitOnce(); EXPECT_EQ(INIT_DONE, CurrentState()); // Calling again has no effect. InitOnce(); EXPECT_EQ(INIT_DONE, CurrentState()); } GOOGLE_PROTOBUF_DECLARE_ONCE(recursive_once1); GOOGLE_PROTOBUF_DECLARE_ONCE(recursive_once2); TEST_F(OnceInitTest, Recursive) { SetOnces(&recursive_once1, &recursive_once2); EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); InitRecursiveOnce(); EXPECT_EQ(INIT_DONE, CurrentState()); } GOOGLE_PROTOBUF_DECLARE_ONCE(multiple_threads_once); TEST_F(OnceInitTest, MultipleThreads) { SetOnces(&multiple_threads_once, NULL); scoped_ptr threads[4]; EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); for (int i = 0; i < 4; i++) { threads[i].reset(RunInitOnceInNewThread()); } for (int i = 0; i < 4; i++) { threads[i]->Join(); } EXPECT_EQ(INIT_DONE, CurrentState()); } GOOGLE_PROTOBUF_DECLARE_ONCE(multiple_threads_blocked_once1); GOOGLE_PROTOBUF_DECLARE_ONCE(multiple_threads_blocked_once2); TEST_F(OnceInitTest, MultipleThreadsBlocked) { SetOnces(&multiple_threads_blocked_once1, &multiple_threads_blocked_once2); scoped_ptr threads[8]; EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); BlockInit(); for (int i = 0; i < 4; i++) { threads[i].reset(RunInitOnceInNewThread()); } for (int i = 4; i < 8; i++) { threads[i].reset(RunInitRecursiveOnceInNewThread()); } WaitABit(); // We should now have one thread blocked inside Init(), four blocked waiting // for Init() to complete, and three blocked waiting for InitRecursive() to // complete. EXPECT_EQ(INIT_STARTED, CurrentState()); UnblockInit(); for (int i = 0; i < 8; i++) { threads[i]->Join(); } EXPECT_EQ(INIT_DONE, CurrentState()); } } // anonymous namespace } // namespace protobuf } // namespace google