mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
c0d5d87785
commit
dce497446e
@ -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 didn’t specify these options, the
|
||||
// calls will be retried with a recomputed deadline. If these bits weren’t
|
||||
// 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, don’t 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 =
|
||||
|
@ -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.
|
||||
//!
|
||||
|
@ -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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user