diff --git a/util/mac/service_management.cc b/util/mac/service_management.cc index 0e191144..79493175 100644 --- a/util/mac/service_management.cc +++ b/util/mac/service_management.cc @@ -16,10 +16,10 @@ #include #include -#include #include "base/mac/scoped_launch_data.h" #include "util/mac/launchd.h" +#include "util/misc/clock.h" namespace { @@ -96,10 +96,7 @@ bool ServiceManagementRemoveJob(const std::string& label, bool wait) { // remove the job. Even so, the job’s PID may change between the time it’s // obtained and the time the kqueue is set up, so this is nontrivial. do { - timespec sleep_time; - sleep_time.tv_sec = 0; - sleep_time.tv_nsec = 1E5; // 100 microseconds - nanosleep(&sleep_time, NULL); + SleepNanoseconds(1E5); // 100 microseconds } while (ServiceManagementIsJobLoaded(label)); } diff --git a/util/mac/service_management_test.mm b/util/mac/service_management_test.mm index 3c1d6383..9ca2e15a 100644 --- a/util/mac/service_management_test.mm +++ b/util/mac/service_management_test.mm @@ -16,7 +16,6 @@ #import #include -#include #include #include @@ -27,6 +26,7 @@ #include "base/strings/sys_string_conversions.h" #include "base/rand_util.h" #include "gtest/gtest.h" +#include "util/misc/clock.h" #include "util/posix/process_util.h" #include "util/stdlib/objc.h" @@ -53,10 +53,7 @@ void ExpectProcessIsRunning(pid_t pid, std::string& last_arg) { break; } if (inner_tries > 0) { - timespec sleep_time; - sleep_time.tv_sec = 0; - sleep_time.tv_nsec = 1E6; // 1 millisecond - nanosleep(&sleep_time, NULL); + SleepNanoseconds(1E6); // 1 millisecond } } while (inner_tries--); ASSERT_TRUE(success); @@ -68,10 +65,7 @@ void ExpectProcessIsRunning(pid_t pid, std::string& last_arg) { } if (outer_tries > 0) { - timespec sleep_time; - sleep_time.tv_sec = 0; - sleep_time.tv_nsec = 1E6; // 1 millisecond - nanosleep(&sleep_time, NULL); + SleepNanoseconds(1E6); // 1 millisecond } } @@ -101,10 +95,7 @@ void ExpectProcessIsNotRunning(pid_t pid, std::string& last_arg) { } if (tries > 0) { - timespec sleep_time; - sleep_time.tv_sec = 0; - sleep_time.tv_nsec = 1E6; // 1 millisecond - nanosleep(&sleep_time, NULL); + SleepNanoseconds(1E6); // 1 millisecond } } diff --git a/util/mach/mach_message_server.cc b/util/mach/mach_message_server.cc index e79acfe4..d6b0d085 100644 --- a/util/mach/mach_message_server.cc +++ b/util/mach/mach_message_server.cc @@ -14,35 +14,15 @@ #include "util/mach/mach_message_server.h" -#include - #include -#include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_vm.h" +#include "util/misc/clock.h" namespace crashpad { namespace { -mach_timebase_info_data_t* TimebaseInternal() { - mach_timebase_info_data_t* timebase_info = new mach_timebase_info_data_t; - kern_return_t kr = mach_timebase_info(timebase_info); - MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info"; - return timebase_info; -} - -mach_timebase_info_data_t* Timebase() { - static mach_timebase_info_data_t* timebase_info = TimebaseInternal(); - return timebase_info; -} - -uint64_t NanosecondTicks() { - uint64_t absolute_time = mach_absolute_time(); - mach_timebase_info_data_t* timebase_info = Timebase(); - return absolute_time * timebase_info->numer / timebase_info->denom; -} - const int kNanosecondsPerMillisecond = 1E6; // TimerRunning determines whether |deadline| has passed. If |deadline| is in @@ -51,14 +31,14 @@ const int kNanosecondsPerMillisecond = 1E6; // |deadline| is zero (indicating that no timer is in effect), |*remaining_ms| // is set to zero and this function returns true. Otherwise, this function // returns false. |deadline| is specified on the same time base as is returned -// by NanosecondTicks(). +// by ClockMonotonicNanoseconds(). bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) { if (!deadline) { *remaining_ms = MACH_MSG_TIMEOUT_NONE; return true; } - uint64_t now = NanosecondTicks(); + uint64_t now = ClockMonotonicNanoseconds(); if (now >= deadline) { return false; @@ -114,7 +94,7 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface, deadline = 0; } else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) { options |= timeout_options; - deadline = NanosecondTicks() + + deadline = ClockMonotonicNanoseconds() + static_cast(timeout_ms) * kNanosecondsPerMillisecond; } else { options &= ~timeout_options; diff --git a/util/misc/clock.cc b/util/misc/clock.cc new file mode 100644 index 00000000..24171091 --- /dev/null +++ b/util/misc/clock.cc @@ -0,0 +1,51 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// 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 "util/misc/clock.h" + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" + +namespace { + +const uint64_t kNanosecondsPerSecond = 1E9; + +} // namespace + +namespace crashpad { + +#if !defined(OS_MACOSX) + +uint64_t ClockMonotonicNanoseconds() { + timespec now; + int rv = clock_gettime(CLOCK_MONOTONIC, &now); + DPCHECK(rv == 0) << "clock_gettime"; + + return now.tv_sec * kNanosecondsPerSecond + now.tv_nsec; +} + +#endif + +void SleepNanoseconds(uint64_t nanoseconds) { + timespec sleep_time; + sleep_time.tv_sec = nanoseconds / kNanosecondsPerSecond; + sleep_time.tv_nsec = nanoseconds % kNanosecondsPerSecond; + int rv = HANDLE_EINTR(nanosleep(&sleep_time, &sleep_time)); + DPCHECK(rv == 0) << "nanosleep"; +} + +} // namespace crashpad diff --git a/util/misc/clock.h b/util/misc/clock.h new file mode 100644 index 00000000..88bdcd50 --- /dev/null +++ b/util/misc/clock.h @@ -0,0 +1,46 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_UTIL_MISC_CLOCK_H_ +#define CRASHPAD_UTIL_MISC_CLOCK_H_ + +#include + +namespace crashpad { + +//! \brief Returns the value of the system’s monotonic clock. +//! +//! The monotonic clock is a tick counter whose epoch is unspecified. It is a +//! monotonically-increasing clock that cannot be set, and never jumps backwards +//! on a running system. The monotonic clock may stop while the system is +//! sleeping, and it may be reset when the system starts up. This clock is +//! suitable for computing durations of events. Subject to the underlying +//! clock’s resolution, successive calls to this function will result in a +//! series of increasing values. +//! +//! \return The value of the system’s monotonic clock, in nanoseconds. +uint64_t ClockMonotonicNanoseconds(); + +//! \brief Sleeps for the specified duration. +//! +//! \param[in] nanoseconds The number of nanoseconds to sleep. The actual sleep +//! may be slightly longer due to latencies and timer resolution. +//! +//! This function is resilient against the underlying `nanosleep()` system call +//! being interrupted by a signal. +void SleepNanoseconds(uint64_t nanoseconds); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_CLOCK_H_ diff --git a/util/misc/clock_mac.cc b/util/misc/clock_mac.cc new file mode 100644 index 00000000..bb52299e --- /dev/null +++ b/util/misc/clock_mac.cc @@ -0,0 +1,45 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// 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 "util/misc/clock.h" + +#include + +#include "base/mac/mach_logging.h" + +namespace { + +mach_timebase_info_data_t* TimebaseInternal() { + mach_timebase_info_data_t* timebase_info = new mach_timebase_info_data_t; + kern_return_t kr = mach_timebase_info(timebase_info); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info"; + return timebase_info; +} + +mach_timebase_info_data_t* Timebase() { + static mach_timebase_info_data_t* timebase_info = TimebaseInternal(); + return timebase_info; +} + +} // namespace + +namespace crashpad { + +uint64_t ClockMonotonicNanoseconds() { + uint64_t absolute_time = mach_absolute_time(); + mach_timebase_info_data_t* timebase_info = Timebase(); + return absolute_time * timebase_info->numer / timebase_info->denom; +} + +} // namespace crashpad diff --git a/util/misc/clock_test.cc b/util/misc/clock_test.cc new file mode 100644 index 00000000..35ec1ac1 --- /dev/null +++ b/util/misc/clock_test.cc @@ -0,0 +1,94 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// 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 "util/misc/clock.h" + +#include + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +TEST(Clock, ClockMonotonicNanoseconds) { + uint64_t start = ClockMonotonicNanoseconds(); + EXPECT_GT(start, 0u); + + uint64_t now = start; + for (size_t iteration = 0; iteration < 10; ++iteration) { + uint64_t last = now; + now = ClockMonotonicNanoseconds(); + + // Use EXPECT_GE instead of EXPECT_GT, because there are no guarantees about + // the clock’s resolution. + EXPECT_GE(now, last); + } + + // SleepNanoseconds() should sleep for at least the value of the clock’s + // resolution, so the clock’s value should definitely increase after a sleep. + // EXPECT_GT can be used instead of EXPECT_GE after the sleep. + SleepNanoseconds(1); + now = ClockMonotonicNanoseconds(); + EXPECT_GT(now, start); +} + +void TestSleepNanoseconds(uint64_t nanoseconds) { + uint64_t start = ClockMonotonicNanoseconds(); + + SleepNanoseconds(nanoseconds); + + uint64_t end = ClockMonotonicNanoseconds(); + uint64_t diff = end - start; + + // |nanoseconds| is the lower bound for the actual amount of time spent + // sleeping. + EXPECT_GE(diff, nanoseconds); + + // It’s difficult to set an upper bound for the time spent sleeping. Allow + // sleeps twice as long as requested, or sleeps a millisecond longer than + // requested, whichever is larger. This is quite a lot of slop, but the + // alternative would be test flakiness. + uint64_t slop = std::max(static_cast(1E6), nanoseconds); + EXPECT_LE(diff, nanoseconds + slop); +} + +TEST(Clock, SleepNanoseconds) { + const uint64_t kTestData[] = { + 0, + 1, + static_cast(1E3), // 1 microsecond + static_cast(1E4), // 10 microseconds + static_cast(1E5), // 100 microseconds + static_cast(1E6), // 1 millisecond + static_cast(1E7), // 10 milliseconds + static_cast(2E7), // 20 milliseconds + static_cast(5E7), // 50 milliseconds + }; + + for (size_t index = 0; index < arraysize(kTestData); ++index) { + const uint64_t nanoseconds = kTestData[index]; + SCOPED_TRACE( + base::StringPrintf("index %zu, nanoseconds %llu", index, nanoseconds)); + + TestSleepNanoseconds(nanoseconds); + } +} + +} // namespace diff --git a/util/util.gyp b/util/util.gyp index e96aa96c..8612e169 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -82,6 +82,9 @@ 'mach/symbolic_constants_mach.h', 'mach/task_memory.cc', 'mach/task_memory.h', + 'misc/clock.cc', + 'misc/clock.h', + 'misc/clock_mac.cc', 'misc/initialization_state.h', 'misc/initialization_state_dcheck.cc', 'misc/initialization_state_dcheck.h', @@ -216,6 +219,7 @@ 'mach/mach_message_server_test.cc', 'mach/symbolic_constants_mach_test.cc', 'mach/task_memory_test.cc', + 'misc/clock_test.cc', 'misc/initialization_state_dcheck_test.cc', 'misc/initialization_state_test.cc', 'misc/scoped_forbid_return_test.cc',