Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(641)

Unified Diff: util/mach/mach_message_server.cc

Issue 723853003: Rewrite MachMessageServer::Run() (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@mach_message_deadline
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: util/mach/mach_message_server.cc
diff --git a/util/mach/mach_message_server.cc b/util/mach/mach_message_server.cc
index b4913c2a13e56f5ca0e2391f2c1cc213f3067ce1..b2d99a143a90a0003102c9aaa879e46df2e4a4de 100644
--- a/util/mach/mach_message_server.cc
+++ b/util/mach/mach_message_server.cc
@@ -16,68 +16,99 @@
#include <limits>
+#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_vm.h"
-#include "util/misc/clock.h"
+#include "util/mach/mach_message.h"
namespace crashpad {
namespace {
-const int kNanosecondsPerMillisecond = 1E6;
-
-// TimerRunning determines whether |deadline| has passed. 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 zero (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. |deadline| is specified on the
-// same time base as is returned by ClockMonotonicNanoseconds().
-bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) {
- if (!deadline) {
- *remaining_ms = MACH_MSG_TIMEOUT_NONE;
- return true;
- }
+//! \brief Manages a dynamically-allocated buffer to be used for Mach messaging.
+class MachMessageBuffer {
+ public:
+ MachMessageBuffer() : vm_() {}
- uint64_t now = ClockMonotonicNanoseconds();
+ ~MachMessageBuffer() {}
- if (now >= deadline) {
- *remaining_ms = 0;
- return false;
+ //! \return A pointer to the buffer.
+ mach_msg_header_t* Header() const {
+ return reinterpret_cast<mach_msg_header_t*>(vm_.address());
}
- uint64_t remaining = deadline - now;
+ //! \brief Ensures that this object has a buffer of exactly \a size bytes
+ //! available.
+ //!
+ //! If the existing buffer is a different size, it will be reallocated without
+ //! copying any of the old buffer’s contents to the new buffer. The contents
+ //! of the buffer are unspecified after this call, even if no reallocation is
+ //! made.
+ kern_return_t Reallocate(vm_size_t size) {
+ // This test uses == instead of > so that a large reallocation to receive a
+ // large message doesn’t cause permanent memory bloat for the duration of
+ // a MachMessageServer::Run() loop.
+ if (size == vm_.size()) {
+ return KERN_SUCCESS;
+ }
- // Round to the nearest millisecond, taking care not to overflow.
- const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2;
- mach_msg_timeout_t remaining_mach;
- if (remaining <=
- std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) {
- remaining_mach = (remaining + kHalfMillisecondInNanoseconds) /
- kNanosecondsPerMillisecond;
- } else {
- remaining_mach = remaining / kNanosecondsPerMillisecond;
+ // reset() first, so that two allocations don’t exist simultaneously.
+ vm_.reset();
+
+ vm_address_t address;
+ kern_return_t kr =
+ vm_allocate(mach_task_self(),
+ &address,
+ size,
+ VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
+ if (kr != KERN_SUCCESS) {
+ return kr;
+ }
+
+ vm_.reset(address, size);
+ return KERN_SUCCESS;
}
- if (remaining_mach == MACH_MSG_TIMEOUT_NONE) {
- *remaining_ms = 0;
- return false;
+ private:
+ base::mac::ScopedMachVM vm_;
+
+ DISALLOW_COPY_AND_ASSIGN(MachMessageBuffer);
+};
+
+// Wraps MachMessageWithDeadline(), using a MachMessageBuffer argument which
+// will be resized to |receive_size| (after being page-rounded). MACH_RCV_MSG
+// is always combined into |options|.
+mach_msg_return_t MachMessageAllocateReceive(MachMessageBuffer* request,
+ 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_size_t request_alloc = round_page(receive_size);
+ kern_return_t kr = request->Reallocate(request_alloc);
+ if (kr != KERN_SUCCESS) {
+ return kr;
}
- *remaining_ms = remaining_mach;
- return true;
+ return MachMessageWithDeadline(request->Header(),
+ options | MACH_RCV_MSG,
+ receive_size,
+ receive_port,
+ deadline,
+ notify_port,
+ run_even_if_expired);
}
} // namespace
-// This implementation is based on 10.9.4
-// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but
-// adapted to local style using scopers, replacing the server callback function
-// and |max_size| parameter with a C++ interface, and with the addition of the
-// the |persistent| parameter allowing this function to serve as a stand-in for
-// mach_msg_server(), the |nonblocking| parameter to control blocking directly,
-// and the |timeout_ms| parameter allowing this function to not block
-// indefinitely.
+// This method implements a server similar to 10.9.4
+// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(). The server
+// callback function and |max_size| parameter have been replaced with a C++
+// interface. The |persistent| parameter has been added, allowing this method to
+// serve as a stand-in for mach_msg_server(). The |nonblocking| parameter has
+// been added, allowing blocking to be controlled directly. The |timeout_ms|
+// parameter has been added, allowing this function to not block indefinitely.
//
// static
mach_msg_return_t MachMessageServer::Run(Interface* interface,
@@ -89,20 +120,13 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
mach_msg_timeout_t timeout_ms) {
options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
- mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT |
- MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT;
-
- uint64_t deadline;
+ MachMessageDeadline deadline;
if (nonblocking == kNonblocking) {
- options |= timeout_options;
- deadline = 0;
- } else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) {
- options |= timeout_options;
- deadline = ClockMonotonicNanoseconds() +
- implicit_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
+ deadline = kMachMessageNonblocking;
+ } else if (timeout_ms == MACH_MSG_TIMEOUT_NONE) {
+ deadline = kMachMessageWaitIndefinitely;
} else {
- options &= ~timeout_options;
- deadline = 0;
+ deadline = MachMessageDeadlineFromTimeout(timeout_ms);
}
if (receive_large == kReceiveLargeResize) {
@@ -111,111 +135,82 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
options &= ~MACH_RCV_LARGE;
}
- mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
- mach_msg_size_t expected_request_size =
- interface->MachMessageServerRequestSize();
- mach_msg_size_t request_alloc =
- round_page(round_msg(expected_request_size) + trailer_alloc);
- mach_msg_size_t request_size =
- (receive_large == kReceiveLargeResize)
- ? request_alloc
- : round_msg(expected_request_size) + trailer_alloc;
-
- mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize();
+ const mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
+ const mach_msg_size_t expected_receive_size =
+ round_msg(interface->MachMessageServerRequestSize()) + trailer_alloc;
+ const mach_msg_size_t request_size = (receive_large == kReceiveLargeResize)
+ ? round_page(expected_receive_size)
+ : expected_receive_size;
// mach_msg_server() and mach_msg_server_once() would consider whether
// |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this
// computation if it does, but that option is ineffective on OS X.
- mach_msg_size_t reply_alloc = round_page(max_reply_size);
+ const mach_msg_size_t reply_alloc =
+ round_page(interface->MachMessageServerReplySize());
- base::mac::ScopedMachVM request_scoper;
- base::mac::ScopedMachVM reply_scoper;
+ MachMessageBuffer request;
+ MachMessageBuffer reply;
bool received_any_request = false;
+ bool retry;
kern_return_t kr;
do {
- mach_msg_size_t this_request_alloc = request_alloc;
- mach_msg_size_t this_request_size = request_size;
-
- mach_msg_header_t* request_header = nullptr;
-
- do {
- // This test uses != instead of < so that a large reallocation to receive
- // a large message doesn’t cause permanent memory bloat.
- if (request_scoper.size() != this_request_alloc) {
- // reset() first, so that two allocations don’t exist simultaneously.
- request_scoper.reset();
-
- vm_address_t request_addr;
- kr = vm_allocate(mach_task_self(),
- &request_addr,
- this_request_alloc,
- VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
- if (kr != KERN_SUCCESS) {
- return kr;
- }
-
- request_scoper.reset(request_addr, this_request_alloc);
- }
-
- request_header =
- reinterpret_cast<mach_msg_header_t*>(request_scoper.address());
-
- do {
- // If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop
- // when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms|
- // rather than allowing mach_msg() to retry using the original timeout
- // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
- // mach_msg(). Don’t return early here if nothing has ever been
- // received: this method should always attempt to dequeue at least one
- // message.
- mach_msg_timeout_t remaining_ms;
- if (!TimerRunning(deadline, &remaining_ms) && received_any_request) {
- return MACH_RCV_TIMED_OUT;
- }
-
- kr = mach_msg(request_header,
- options | MACH_RCV_MSG,
- 0,
- this_request_size,
- receive_port,
- remaining_ms,
- MACH_PORT_NULL);
-
- if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) {
+ retry = false;
+
+ kr = MachMessageAllocateReceive(&request,
+ options,
+ request_size,
+ receive_port,
+ deadline,
+ MACH_PORT_NULL,
+ !received_any_request);
+ if (kr == MACH_RCV_TOO_LARGE) {
+ switch (receive_large) {
+ case kReceiveLargeError:
+ break;
+
+ case kReceiveLargeIgnore:
+ // Try again, even in one-shot mode. The caller is expecting this
+ // method to take action on the first message in the queue, and has
+ // indicated that they want large messages to be ignored. The
+ // alternatives, which might involve returning MACH_MSG_SUCCESS,
+ // MACH_RCV_TIMED_OUT, or MACH_RCV_TOO_LARGE to a caller that
+ // specified one-shot behavior, all seem less correct than retrying.
MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message";
+ retry = true;
+ continue;
+
+ case kReceiveLargeResize: {
+ mach_msg_size_t this_request_size = round_page(
+ round_msg(request.Header()->msgh_size) + trailer_alloc);
+
+ kr = MachMessageAllocateReceive(&request,
+ options & ~MACH_RCV_LARGE,
+ this_request_size,
+ receive_port,
+ deadline,
+ MACH_PORT_NULL,
+ !received_any_request);
+
+ break;
}
- } while (
- (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) ||
- kr == MACH_RCV_INTERRUPTED);
-
- if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeResize) {
- this_request_size =
- round_page(round_msg(request_header->msgh_size) + trailer_alloc);
- this_request_alloc = this_request_size;
- } else if (kr != MACH_MSG_SUCCESS) {
- return kr;
}
- } while (kr != MACH_MSG_SUCCESS);
+ }
- received_any_request = true;
+ if (kr != MACH_MSG_SUCCESS) {
+ return kr;
+ }
- if (reply_scoper.size() != reply_alloc) {
- vm_address_t reply_addr;
- kr = vm_allocate(mach_task_self(),
- &reply_addr,
- reply_alloc,
- VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
- if (kr != KERN_SUCCESS) {
- return kr;
- }
+ received_any_request = true;
- reply_scoper.reset(reply_addr, reply_alloc);
+ kr = reply.Reallocate(reply_alloc);
+ if (kr != KERN_SUCCESS) {
+ return kr;
}
- mach_msg_header_t* reply_header =
- reinterpret_cast<mach_msg_header_t*>(reply_scoper.address());
+ mach_msg_header_t* request_header = request.Header();
+ mach_msg_header_t* reply_header = reply.Header();
bool destroy_complex_request = false;
interface->MachMessageServerFunction(
request_header, reply_header, &destroy_complex_request);
@@ -244,57 +239,34 @@ mach_msg_return_t MachMessageServer::Run(Interface* interface,
}
if (reply_header->msgh_remote_port != MACH_PORT_NULL) {
- // If the reply port right is a send-once right, the send won’t block even
- // if the remote side isn’t waiting for a message. No timeout is used,
- // which keeps the communication on the kernel’s fast path. If the reply
- // port right is a send right, MACH_SEND_TIMEOUT is used to avoid blocking
- // indefinitely. This duplicates the logic in 10.9.4
- // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once().
- mach_msg_option_t send_options =
- options | MACH_SEND_MSG |
- (MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) ==
- MACH_MSG_TYPE_MOVE_SEND_ONCE
- ? 0
- : MACH_SEND_TIMEOUT);
-
- bool running;
- do {
- // If |options| contains MACH_SEND_INTERRUPT, retry mach_msg() in a loop
- // when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms|
- // rather than allowing mach_msg() to retry using the original timeout
- // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
- // mach_msg().
- mach_msg_timeout_t remaining_ms;
- running = TimerRunning(deadline, &remaining_ms);
-
- // Don’t return just yet even if |running| is false. If the timer ran
- // out in between the time the request was received and now, at least
- // try to send the response.
-
- kr = mach_msg(reply_header,
- send_options,
- reply_header->msgh_size,
- 0,
- MACH_PORT_NULL,
- remaining_ms,
- MACH_PORT_NULL);
- } while (kr == MACH_SEND_INTERRUPTED);
-
- if (kr != KERN_SUCCESS) {
- if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) {
+ // Avoid blocking indefinitely. This duplicates the logic in 10.9.5
+ // xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg_server_once(),
+ // although the special provision for sending to a send-once right is not
+ // made, because kernel keeps sends to a send-once right on the fast path
+ // without considering the user-specified timeout. See 10.9.5
+ // xnu-2422.115.4/osfmk/ipc/ipc_mqueue.c ipc_mqueue_send().
+ const MachMessageDeadline send_deadline =
+ deadline == kMachMessageWaitIndefinitely ? kMachMessageNonblocking
+ : deadline;
+
+ kr = MachMessageWithDeadline(reply_header,
+ options | MACH_SEND_MSG,
+ 0,
+ MACH_PORT_NULL,
+ send_deadline,
+ MACH_PORT_NULL,
+ true);
+
+ if (kr != MACH_MSG_SUCCESS) {
+ if (kr == MACH_SEND_INVALID_DEST ||
+ kr == MACH_SEND_TIMED_OUT ||
+ kr == MACH_SEND_INTERRUPTED) {
mach_msg_destroy(reply_header);
}
return kr;
}
-
- if (!running) {
- // The reply message was sent successfuly, so act as though the deadline
- // was reached before or during the subsequent receive operation when in
- // persistent mode, and just return success when not in persistent mode.
- return (persistent == kPersistent) ? MACH_RCV_TIMED_OUT : kr;
- }
}
- } while (persistent == kPersistent);
+ } while (persistent == kPersistent || retry);
return kr;
}
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698