Add MachMessageWithDeadline() and supporting characters.

MachMessageWithDeadline() is a mach_msg() wrapper that deals with
deadlines instead of timeouts. It is a slight simplification of the
mach_msg() interface because the deadline parameter implies the timeout
option bits, and because the caller does not need to specify send_size
during sends as the message itself already carries this information.

TEST=util_test MachMessage.MachMessageDeadlineFromTimeout
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/773943002
This commit is contained in:
Mark Mentovai 2014-12-02 17:09:08 -05:00
parent c0d5d87785
commit dce497446e
3 changed files with 257 additions and 0 deletions

View File

@ -14,8 +14,177 @@
#include "util/mach/mach_message.h"
#include <limits>
#include "base/basictypes.h"
#include "util/misc/clock.h"
namespace crashpad {
namespace {
const int kNanosecondsPerMillisecond = 1E6;
// TimerRunning() determines whether |deadline| has passed. If |deadline| is
// kMachMessageWaitIndefinitely, |*timeout_options| is set to
// MACH_MSG_OPTION_NONE, |*remaining_ms| is set to MACH_MSG_TIMEOUT_NONE, and
// this function returns true. When used with mach_msg(), this will cause
// indefinite waiting. In any other case, |*timeout_options| is set to
// MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT, so mach_msg() will enforce a timeout
// specified by |*remaining_ms|. If |deadline| is in the future, |*remaining_ms|
// is set to the number of milliseconds remaining, which will always be a
// positive value, and this function returns true. If |deadline| is
// kMachMessageNonblocking (indicating that no timer is in effect),
// |*remaining_ms| is set to zero and this function returns true. Otherwise,
// this function sets |*remaining_ms| to zero and returns false.
bool TimerRunning(uint64_t deadline,
mach_msg_timeout_t* remaining_ms,
mach_msg_option_t* timeout_options) {
if (deadline == kMachMessageWaitIndefinitely) {
*remaining_ms = MACH_MSG_TIMEOUT_NONE;
*timeout_options = MACH_MSG_OPTION_NONE;
return true;
}
*timeout_options = MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT;
if (deadline == kMachMessageNonblocking) {
*remaining_ms = 0;
return true;
}
uint64_t now = ClockMonotonicNanoseconds();
if (now >= deadline) {
*remaining_ms = 0;
} else {
uint64_t remaining = deadline - now;
// Round to the nearest millisecond, taking care not to overflow.
const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2;
if (remaining <=
std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) {
*remaining_ms = (remaining + kHalfMillisecondInNanoseconds) /
kNanosecondsPerMillisecond;
} else {
*remaining_ms = remaining / kNanosecondsPerMillisecond;
}
}
return *remaining_ms != 0;
}
// This is an internal implementation detail of MachMessageWithDeadline(). It
// determines whether |deadline| has expired, and what timeout value and
// timeout-related options to pass to mach_msg() based on the value of
// |deadline|. mach_msg() will only be called if TimerRunning() returns true or
// if run_even_if_expired is true.
mach_msg_return_t MachMessageWithDeadlineInternal(mach_msg_header_t* message,
mach_msg_option_t options,
mach_msg_size_t receive_size,
mach_port_name_t receive_port,
MachMessageDeadline deadline,
mach_port_name_t notify_port,
bool run_even_if_expired) {
mach_msg_timeout_t remaining_ms;
mach_msg_option_t timeout_options;
if (!TimerRunning(deadline, &remaining_ms, &timeout_options) &&
!run_even_if_expired) {
// Simulate the timed-out return values from mach_msg().
if (options & MACH_SEND_MSG) {
return MACH_SEND_TIMED_OUT;
}
if (options & MACH_RCV_MSG) {
return MACH_RCV_TIMED_OUT;
}
return MACH_MSG_SUCCESS;
}
// Turn off the passed-in timeout bits and replace them with the ones from
// TimerRunning(). Get the send_size value from message->msgh_size if sending
// a message.
return mach_msg(
message,
(options & ~(MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT)) | timeout_options,
options & MACH_SEND_MSG ? message->msgh_size : 0,
receive_size,
receive_port,
remaining_ms,
notify_port);
}
} // namespace
MachMessageDeadline MachMessageDeadlineFromTimeout(
mach_msg_timeout_t timeout_ms) {
if (timeout_ms == 0) {
return kMachMessageNonblocking;
}
return ClockMonotonicNanoseconds() +
implicit_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
}
mach_msg_return_t MachMessageWithDeadline(mach_msg_header_t* message,
mach_msg_option_t options,
mach_msg_size_t receive_size,
mach_port_name_t receive_port,
MachMessageDeadline deadline,
mach_port_name_t notify_port,
bool run_even_if_expired) {
// mach_msg() actaully does return MACH_MSG_SUCCESS when not asked to send or
// receive anything. See 10.9.5 xnu-1504.15.3/osfmk/ipc/mach_msg.c
// mach_msg_overwrite_trap().
mach_msg_return_t mr = MACH_MSG_SUCCESS;
// Break up the send and receive into separate operations, so that the timeout
// can be recomputed from the deadline for each. Otherwise, the computed
// timeout will apply individually to the send and then to the receive, and
// the desired deadline could be exceeded.
//
// During sends, always set MACH_SEND_INTERRUPT, and during receives, always
// set MACH_RCV_INTERRUPT. If the caller didnt specify these options, the
// calls will be retried with a recomputed deadline. If these bits werent
// set, the libsyscall wrapper (10.9.5
// xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg() would restart
// interrupted calls with the original timeout value computed from the
// deadline, which would no longer correspond to the actual deadline. If the
// caller did specify these bits, dont restart anything, because the caller
// wants to be notified of any interrupted calls.
if (options & MACH_SEND_MSG) {
do {
mr = MachMessageWithDeadlineInternal(
message,
(options & ~MACH_RCV_MSG) | MACH_SEND_INTERRUPT,
0,
MACH_PORT_NULL,
deadline,
notify_port,
run_even_if_expired);
} while (mr == MACH_SEND_INTERRUPTED && !(options & MACH_SEND_INTERRUPT));
if (mr != MACH_MSG_SUCCESS) {
return mr;
}
}
if (options & MACH_RCV_MSG) {
do {
mr = MachMessageWithDeadlineInternal(
message,
(options & ~MACH_SEND_MSG) | MACH_RCV_INTERRUPT,
receive_size,
receive_port,
deadline,
notify_port,
run_even_if_expired);
} while (mr == MACH_RCV_INTERRUPTED && !(options & MACH_RCV_INTERRUPT));
}
return mr;
}
void PrepareMIGReplyFromRequest(const mach_msg_header_t* in_header,
mach_msg_header_t* out_header) {
out_header->msgh_bits =

View File

@ -16,9 +16,83 @@
#define CRASHPAD_UTIL_MACH_MACH_MESSAGE_H_
#include <mach/mach.h>
#include <stdint.h>
#include <limits>
namespace crashpad {
//! \brief The time before which a MachMessageWithDeadline() call should
//! complete.
//!
//! A value of this type may be one of the special constants
//! #kMachMessageNonblocking or #kMachMessageWaitIndefinitely. Any other values
//! should be produced by calling MachMessageDeadlineFromTimeout().
//!
//! Internally, these are currently specified on the same time base as
//! ClockMonotonicNanoseconds(), although this is an implementation detail.
using MachMessageDeadline = uint64_t;
//! \brief Special constants used as \ref MachMessageDeadline values.
enum : MachMessageDeadline {
//! \brief MachMessageWithDeadline() should not block at all in its operation.
kMachMessageNonblocking = 0,
//! \brief MachMessageWithDeadline() should wait indefinitely for the
//! requested operation to complete.
kMachMessageWaitIndefinitely =
std::numeric_limits<MachMessageDeadline>::max(),
};
//! \brief Computes the deadline for a specified timeout value.
//!
//! While deadlines exist on an absolute time scale, timeouts are relative. This
//! function calculates the deadline as \a timeout_ms milliseconds after it
//! executes.
//!
//! If \a timeout_ms is `0`, this function will return #kMachMessageNonblocking.
MachMessageDeadline MachMessageDeadlineFromTimeout(
mach_msg_timeout_t timeout_ms);
//! \brief Runs `mach_msg()` with a deadline, as opposed to a timeout.
//!
//! This function is similar to `mach_msg()`, with the following differences:
//! - The `timeout` parameter has been replaced by \a deadline. The deadline
//! applies uniformly to a call that is requested to both send and receive
//! a message.
//! - The `MACH_SEND_TIMEOUT` and `MACH_RCV_TIMEOUT` bits in \a options are
//! not used. Timeouts are specified by the \a deadline argument.
//! - The `send_size` parameter has been removed. Its value is implied by
//! \a message when \a options contains `MACH_SEND_MSG`.
//! - The \a run_even_if_expired parameter has been added.
//!
//! Like the `mach_msg()` wrapper in `libsyscall`, this function will retry
//! operations when experiencing `MACH_SEND_INTERRUPTED` and
//! `MACH_RCV_INTERRUPTED`, unless \a options contains `MACH_SEND_INTERRUPT` or
//! `MACH_RCV_INTERRUPT`. Unlike `mach_msg()`, which restarts the call with the
//! full timeout when this occurs, this function continues enforcing the
//! user-specified \a deadline.
//!
//! Except as noted, the parameters and return value are identical to those of
//! `mach_msg()`.
//!
//! \param[in] deadline The time by which this call should complete. If the
//! deadline is exceeded, this call will return `MACH_SEND_TIMED_OUT` or
//! `MACH_RCV_TIMED_OUT`.
//! \param[in] run_even_if_expired If `true`, a deadline that is expired when
//! this function is called will be treated as though a deadline of
//! #kMachMessageNonblocking had been specified. When `false`, an expired
//! deadline will result in a `MACH_SEND_TIMED_OUT` or `MACH_RCV_TIMED_OUT`
//! return value, even if the deadline is already expired when the function
//! is called.
mach_msg_return_t MachMessageWithDeadline(mach_msg_header_t* message,
mach_msg_option_t options,
mach_msg_size_t receive_size,
mach_port_name_t receive_port,
MachMessageDeadline deadline,
mach_port_name_t notify_port,
bool run_even_if_expired);
//! \brief Initializes a reply message for a MIG server routine based on its
//! corresponding request.
//!

View File

@ -22,6 +22,20 @@ namespace crashpad {
namespace test {
namespace {
TEST(MachMessage, MachMessageDeadlineFromTimeout) {
MachMessageDeadline deadline_0 = MachMessageDeadlineFromTimeout(0);
EXPECT_EQ(kMachMessageNonblocking, deadline_0);
deadline_0 = MachMessageDeadlineFromTimeout(1);
MachMessageDeadline deadline_1 = MachMessageDeadlineFromTimeout(100);
EXPECT_NE(kMachMessageNonblocking, deadline_0);
EXPECT_NE(kMachMessageWaitIndefinitely, deadline_0);
EXPECT_NE(kMachMessageNonblocking, deadline_1);
EXPECT_NE(kMachMessageWaitIndefinitely, deadline_1);
EXPECT_GE(deadline_1, deadline_0);
}
TEST(MachMessage, PrepareMIGReplyFromRequest_SetMIGReplyError) {
mach_msg_header_t request;
request.msgh_bits =