mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-10 06:36:02 +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 "util/mach/mach_message.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "base/basictypes.h"
|
||||||
|
#include "util/misc/clock.h"
|
||||||
|
|
||||||
namespace crashpad {
|
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,
|
void PrepareMIGReplyFromRequest(const mach_msg_header_t* in_header,
|
||||||
mach_msg_header_t* out_header) {
|
mach_msg_header_t* out_header) {
|
||||||
out_header->msgh_bits =
|
out_header->msgh_bits =
|
||||||
|
@ -16,9 +16,83 @@
|
|||||||
#define CRASHPAD_UTIL_MACH_MACH_MESSAGE_H_
|
#define CRASHPAD_UTIL_MACH_MACH_MESSAGE_H_
|
||||||
|
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace crashpad {
|
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
|
//! \brief Initializes a reply message for a MIG server routine based on its
|
||||||
//! corresponding request.
|
//! corresponding request.
|
||||||
//!
|
//!
|
||||||
|
@ -22,6 +22,20 @@ namespace crashpad {
|
|||||||
namespace test {
|
namespace test {
|
||||||
namespace {
|
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) {
|
TEST(MachMessage, PrepareMIGReplyFromRequest_SetMIGReplyError) {
|
||||||
mach_msg_header_t request;
|
mach_msg_header_t request;
|
||||||
request.msgh_bits =
|
request.msgh_bits =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user