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

Side by Side Diff: util/mach/mach_message_server.cc

Issue 768233003: MachMessageDeadline WIP (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Self-review 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 unified diff | Download patch
« no previous file with comments | « util/mach/mach_message.cc ('k') | util/mach/mach_message_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Crashpad Authors. All rights reserved. 1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License. 4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 #include "util/mach/mach_message_server.h" 15 #include "util/mach/mach_message_server.h"
16 16
17 #include <limits> 17 #include <limits>
18 18
19 #include "base/logging.h"
19 #include "base/mac/mach_logging.h" 20 #include "base/mac/mach_logging.h"
20 #include "base/mac/scoped_mach_vm.h" 21 #include "base/mac/scoped_mach_vm.h"
21 #include "util/misc/clock.h" 22 #include "util/mach/mach_message.h"
22 23
23 namespace crashpad { 24 namespace crashpad {
24 25
25 namespace { 26 namespace {
26 27
27 const int kNanosecondsPerMillisecond = 1E6; 28 //! \brief Manages a dynamically-allocated buffer to be used for Mach messaging.
29 class MachMessageBuffer {
30 public:
31 MachMessageBuffer() : vm_() {}
28 32
29 // TimerRunning determines whether |deadline| has passed. If |deadline| is in 33 ~MachMessageBuffer() {}
30 // the future, |*remaining_ms| is set to the number of milliseconds remaining, 34
31 // which will always be a positive value, and this function returns true. If 35 //! \return A pointer to the buffer.
32 // |deadline| is zero (indicating that no timer is in effect), |*remaining_ms| 36 mach_msg_header_t* Header() const {
33 // is set to zero and this function returns true. Otherwise, this function sets 37 return reinterpret_cast<mach_msg_header_t*>(vm_.address());
34 // |*remaining_ms| to zero and returns false. |deadline| is specified on the
35 // same time base as is returned by ClockMonotonicNanoseconds().
36 bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) {
37 if (!deadline) {
38 *remaining_ms = MACH_MSG_TIMEOUT_NONE;
39 return true;
40 } 38 }
41 39
42 uint64_t now = ClockMonotonicNanoseconds(); 40 //! \brief Ensures that this object has a buffer of exactly \a size bytes
41 //! available.
42 //!
43 //! If the existing buffer is a different size, it will be reallocated without
44 //! copying any of the old buffer’s contents to the new buffer. The contents
45 //! of the buffer are unspecified after this call, even if no reallocation is
46 //! made.
47 kern_return_t Reallocate(vm_size_t size) {
48 // This test uses == instead of > so that a large reallocation to receive a
49 // large message doesn’t cause permanent memory bloat for the duration of
50 // a MachMessageServer::Run() loop.
51 if (size == vm_.size()) {
52 return KERN_SUCCESS;
53 }
43 54
44 if (now >= deadline) { 55 // reset() first, so that two allocations don’t exist simultaneously.
45 *remaining_ms = 0; 56 vm_.reset();
46 return false; 57
58 vm_address_t address;
59 kern_return_t kr =
60 vm_allocate(mach_task_self(),
61 &address,
62 size,
63 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
64 if (kr != KERN_SUCCESS) {
65 return kr;
66 }
67
68 vm_.reset(address, size);
69 return KERN_SUCCESS;
47 } 70 }
48 71
49 uint64_t remaining = deadline - now; 72 private:
73 base::mac::ScopedMachVM vm_;
50 74
51 // Round to the nearest millisecond, taking care not to overflow. 75 DISALLOW_COPY_AND_ASSIGN(MachMessageBuffer);
52 const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2; 76 };
53 mach_msg_timeout_t remaining_mach; 77
54 if (remaining <= 78 // Wraps MachMessageWithDeadline(), using a MachMessageBuffer argument which
55 std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) { 79 // will be resized to |receive_size| (after being page-rounded). MACH_RCV_MSG
56 remaining_mach = (remaining + kHalfMillisecondInNanoseconds) / 80 // is always combined into |options|.
57 kNanosecondsPerMillisecond; 81 mach_msg_return_t MachMessageAllocateReceive(MachMessageBuffer* request,
58 } else { 82 mach_msg_option_t options,
59 remaining_mach = remaining / kNanosecondsPerMillisecond; 83 mach_msg_size_t receive_size,
84 mach_port_name_t receive_port,
85 MachMessageDeadline deadline,
86 mach_port_name_t notify_port,
87 bool run_even_if_expired) {
88 mach_msg_size_t request_alloc = round_page(receive_size);
89 kern_return_t kr = request->Reallocate(request_alloc);
90 if (kr != KERN_SUCCESS) {
91 return kr;
60 } 92 }
61 93
62 if (remaining_mach == MACH_MSG_TIMEOUT_NONE) { 94 return MachMessageWithDeadline(request->Header(),
63 *remaining_ms = 0; 95 options | MACH_RCV_MSG,
64 return false; 96 receive_size,
65 } 97 receive_port,
66 98 deadline,
67 *remaining_ms = remaining_mach; 99 notify_port,
68 return true; 100 run_even_if_expired);
69 } 101 }
70 102
71 } // namespace 103 } // namespace
72 104
73 // This implementation is based on 10.9.4 105 // This implementation is based on 10.9.4
74 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but 106 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but
75 // adapted to local style using scopers, replacing the server callback function 107 // adapted to local style using scopers, replacing the server callback function
76 // and |max_size| parameter with a C++ interface, and with the addition of the 108 // and |max_size| parameter with a C++ interface, and with the addition of the
77 // the |persistent| parameter allowing this function to serve as a stand-in for 109 // the |persistent| parameter allowing this function to serve as a stand-in for
78 // mach_msg_server(), the |nonblocking| parameter to control blocking directly, 110 // mach_msg_server(), the |nonblocking| parameter to control blocking directly,
79 // and the |timeout_ms| parameter allowing this function to not block 111 // and the |timeout_ms| parameter allowing this function to not block
80 // indefinitely. 112 // indefinitely.
81 // 113 //
82 // static 114 // static
83 mach_msg_return_t MachMessageServer::Run(Interface* interface, 115 mach_msg_return_t MachMessageServer::Run(Interface* interface,
84 mach_port_t receive_port, 116 mach_port_t receive_port,
85 mach_msg_options_t options, 117 mach_msg_options_t options,
86 Persistent persistent, 118 Persistent persistent,
87 Nonblocking nonblocking, 119 Nonblocking nonblocking,
88 ReceiveLarge receive_large, 120 ReceiveLarge receive_large,
89 mach_msg_timeout_t timeout_ms) { 121 mach_msg_timeout_t timeout_ms) {
90 options &= ~(MACH_RCV_MSG | MACH_SEND_MSG); 122 options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
91 123
92 mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT | 124 MachMessageDeadline deadline;
93 MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT;
94
95 uint64_t deadline;
96 if (nonblocking == kNonblocking) { 125 if (nonblocking == kNonblocking) {
97 options |= timeout_options; 126 deadline = kMachMessageNonblocking;
98 deadline = 0; 127 } else if (timeout_ms == MACH_MSG_TIMEOUT_NONE) {
99 } else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) { 128 deadline = kMachMessageWaitIndefinitely;
100 options |= timeout_options;
101 deadline = ClockMonotonicNanoseconds() +
102 implicit_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
103 } else { 129 } else {
104 options &= ~timeout_options; 130 deadline = MachMessageDeadlineFromTimeout(timeout_ms);
105 deadline = 0;
106 } 131 }
107 132
108 if (receive_large == kReceiveLargeResize) { 133 if (receive_large == kReceiveLargeResize) {
109 options |= MACH_RCV_LARGE; 134 options |= MACH_RCV_LARGE;
110 } else { 135 } else {
111 options &= ~MACH_RCV_LARGE; 136 options &= ~MACH_RCV_LARGE;
112 } 137 }
113 138
114 mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options); 139 const mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
115 mach_msg_size_t expected_request_size = 140 const mach_msg_size_t expected_receive_size =
116 interface->MachMessageServerRequestSize(); 141 round_msg(interface->MachMessageServerRequestSize()) + trailer_alloc;
117 mach_msg_size_t request_alloc = 142 const mach_msg_size_t request_size = (receive_large == kReceiveLargeResize)
118 round_page(round_msg(expected_request_size) + trailer_alloc); 143 ? round_page(expected_receive_size)
119 mach_msg_size_t request_size = 144 : expected_receive_size;
120 (receive_large == kReceiveLargeResize)
121 ? request_alloc
122 : round_msg(expected_request_size) + trailer_alloc;
123
124 mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize();
125 145
126 // mach_msg_server() and mach_msg_server_once() would consider whether 146 // mach_msg_server() and mach_msg_server_once() would consider whether
127 // |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this 147 // |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this
128 // computation if it does, but that option is ineffective on OS X. 148 // computation if it does, but that option is ineffective on OS X.
129 mach_msg_size_t reply_alloc = round_page(max_reply_size); 149 const mach_msg_size_t reply_alloc =
150 round_page(interface->MachMessageServerReplySize());
130 151
131 base::mac::ScopedMachVM request_scoper; 152 MachMessageBuffer request;
132 base::mac::ScopedMachVM reply_scoper; 153 MachMessageBuffer reply;
133 bool received_any_request = false; 154 bool received_any_request = false;
155 bool retry;
134 156
135 kern_return_t kr; 157 kern_return_t kr;
136 158
137 do { 159 do {
138 mach_msg_size_t this_request_alloc = request_alloc; 160 retry = false;
139 mach_msg_size_t this_request_size = request_size;
140 161
141 mach_msg_header_t* request_header = nullptr; 162 kr = MachMessageAllocateReceive(&request,
163 options,
164 request_size,
165 receive_port,
166 deadline,
167 MACH_PORT_NULL,
168 !received_any_request);
169 if (kr == MACH_RCV_TOO_LARGE) {
170 switch (receive_large) {
171 case kReceiveLargeError:
172 break;
142 173
143 do { 174 case kReceiveLargeIgnore:
144 // This test uses != instead of < so that a large reallocation to receive 175 // Try again, even in one-shot mode. The caller is expecting this
145 // a large message doesn’t cause permanent memory bloat. 176 // method to take action on the first message in the queue, and has
146 if (request_scoper.size() != this_request_alloc) { 177 // indicated that they want large messages to be ignored. The
147 // reset() first, so that two allocations don’t exist simultaneously. 178 // alternatives, which might involve returning MACH_MSG_SUCCESS,
148 request_scoper.reset(); 179 // MACH_RCV_TIMED_OUT, or MACH_RCV_TOO_LARGE to a caller that
180 // specified one-shot behavior, all seem less correct than retrying.
181 MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message";
182 retry = true;
183 continue;
149 184
150 vm_address_t request_addr; 185 case kReceiveLargeResize: {
151 kr = vm_allocate(mach_task_self(), 186 mach_msg_size_t this_request_size = round_page(
152 &request_addr, 187 round_msg(request.Header()->msgh_size) + trailer_alloc);
153 this_request_alloc, 188
154 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); 189 kr = MachMessageAllocateReceive(&request,
155 if (kr != KERN_SUCCESS) { 190 options & ~MACH_RCV_LARGE,
156 return kr; 191 this_request_size,
192 receive_port,
193 deadline,
194 MACH_PORT_NULL,
195 !received_any_request);
196
197 break;
157 } 198 }
199 }
200 }
158 201
159 request_scoper.reset(request_addr, this_request_alloc); 202 if (kr != MACH_MSG_SUCCESS) {
160 } 203 return kr;
161 204 }
162 request_header =
163 reinterpret_cast<mach_msg_header_t*>(request_scoper.address());
164
165 do {
166 // If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop
167 // when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms|
168 // rather than allowing mach_msg() to retry using the original timeout
169 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
170 // mach_msg(). Don’t return early here if nothing has ever been
171 // received: this method should always attempt to dequeue at least one
172 // message.
173 mach_msg_timeout_t remaining_ms;
174 if (!TimerRunning(deadline, &remaining_ms) && received_any_request) {
175 return MACH_RCV_TIMED_OUT;
176 }
177
178 kr = mach_msg(request_header,
179 options | MACH_RCV_MSG,
180 0,
181 this_request_size,
182 receive_port,
183 remaining_ms,
184 MACH_PORT_NULL);
185
186 if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) {
187 MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message";
188 }
189 } while (
190 (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) ||
191 kr == MACH_RCV_INTERRUPTED);
192
193 if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeResize) {
194 this_request_size =
195 round_page(round_msg(request_header->msgh_size) + trailer_alloc);
196 this_request_alloc = this_request_size;
197 } else if (kr != MACH_MSG_SUCCESS) {
198 return kr;
199 }
200 } while (kr != MACH_MSG_SUCCESS);
201 205
202 received_any_request = true; 206 received_any_request = true;
203 207
204 if (reply_scoper.size() != reply_alloc) { 208 kr = reply.Reallocate(reply_alloc);
205 vm_address_t reply_addr; 209 if (kr != KERN_SUCCESS) {
206 kr = vm_allocate(mach_task_self(), 210 return kr;
207 &reply_addr,
208 reply_alloc,
209 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
210 if (kr != KERN_SUCCESS) {
211 return kr;
212 }
213
214 reply_scoper.reset(reply_addr, reply_alloc);
215 } 211 }
216 212
217 mach_msg_header_t* reply_header = 213 mach_msg_header_t* request_header = request.Header();
218 reinterpret_cast<mach_msg_header_t*>(reply_scoper.address()); 214 mach_msg_header_t* reply_header = reply.Header();
219 bool destroy_complex_request = false; 215 bool destroy_complex_request = false;
220 interface->MachMessageServerFunction( 216 interface->MachMessageServerFunction(
221 request_header, reply_header, &destroy_complex_request); 217 request_header, reply_header, &destroy_complex_request);
222 218
223 if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { 219 if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
224 // This only works if the reply message is not complex, because otherwise, 220 // This only works if the reply message is not complex, because otherwise,
225 // the location of the RetCode field is not known. It should be possible 221 // the location of the RetCode field is not known. It should be possible
226 // to locate the RetCode field by looking beyond the descriptors in a 222 // to locate the RetCode field by looking beyond the descriptors in a
227 // complex reply message, but this is not currently done. This behavior 223 // complex reply message, but this is not currently done. This behavior
228 // has not proven itself necessary in practice, and it’s not done by 224 // has not proven itself necessary in practice, and it’s not done by
229 // mach_msg_server() or mach_msg_server_once() either. 225 // mach_msg_server() or mach_msg_server_once() either.
230 mig_reply_error_t* reply_mig = 226 mig_reply_error_t* reply_mig =
231 reinterpret_cast<mig_reply_error_t*>(reply_header); 227 reinterpret_cast<mig_reply_error_t*>(reply_header);
232 if (reply_mig->RetCode == MIG_NO_REPLY) { 228 if (reply_mig->RetCode == MIG_NO_REPLY) {
233 reply_header->msgh_remote_port = MACH_PORT_NULL; 229 reply_header->msgh_remote_port = MACH_PORT_NULL;
234 } else if (reply_mig->RetCode != KERN_SUCCESS && 230 } else if (reply_mig->RetCode != KERN_SUCCESS &&
235 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { 231 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
236 destroy_complex_request = true; 232 destroy_complex_request = true;
237 } 233 }
238 } 234 }
239 235
240 if (destroy_complex_request && 236 if (destroy_complex_request &&
241 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { 237 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
242 request_header->msgh_remote_port = MACH_PORT_NULL; 238 request_header->msgh_remote_port = MACH_PORT_NULL;
243 mach_msg_destroy(request_header); 239 mach_msg_destroy(request_header);
244 } 240 }
245 241
246 if (reply_header->msgh_remote_port != MACH_PORT_NULL) { 242 if (reply_header->msgh_remote_port != MACH_PORT_NULL) {
247 // If the reply port right is a send-once right, the send won’t block even 243 // Avoid blocking indefinitely. This duplicates the logic in 10.9.5
248 // if the remote side isn’t waiting for a message. No timeout is used, 244 // xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg_server_once(),
249 // which keeps the communication on the kernel’s fast path. If the reply 245 // although the special provision for sending to a send-once right is not
250 // port right is a send right, MACH_SEND_TIMEOUT is used to avoid blocking 246 // made, because kernel keeps sends to a send-once right on the fast path
251 // indefinitely. This duplicates the logic in 10.9.4 247 // without considering the user-specified timeout. See 10.9.5
252 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(). 248 // xnu-2422.115.4/osfmk/ipc/ipc_mqueue.c ipc_mqueue_send().
253 mach_msg_option_t send_options = 249 const MachMessageDeadline send_deadline =
254 options | MACH_SEND_MSG | 250 deadline == kMachMessageWaitIndefinitely ? kMachMessageNonblocking
255 (MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) == 251 : deadline;
256 MACH_MSG_TYPE_MOVE_SEND_ONCE
257 ? 0
258 : MACH_SEND_TIMEOUT);
259 252
260 bool running; 253 kr = MachMessageWithDeadline(reply_header,
261 do { 254 options | MACH_SEND_MSG,
262 // If |options| contains MACH_SEND_INTERRUPT, retry mach_msg() in a loop 255 0,
263 // when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms| 256 MACH_PORT_NULL,
264 // rather than allowing mach_msg() to retry using the original timeout 257 send_deadline,
265 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c 258 MACH_PORT_NULL,
266 // mach_msg(). 259 true);
267 mach_msg_timeout_t remaining_ms;
268 running = TimerRunning(deadline, &remaining_ms);
269 260
270 // Don’t return just yet even if |running| is false. If the timer ran 261 if (kr != MACH_MSG_SUCCESS) {
271 // out in between the time the request was received and now, at least 262 if (kr == MACH_SEND_INVALID_DEST ||
272 // try to send the response. 263 kr == MACH_SEND_TIMED_OUT ||
273 264 kr == MACH_SEND_INTERRUPTED) {
274 kr = mach_msg(reply_header,
275 send_options,
276 reply_header->msgh_size,
277 0,
278 MACH_PORT_NULL,
279 remaining_ms,
280 MACH_PORT_NULL);
281 } while (kr == MACH_SEND_INTERRUPTED);
282
283 if (kr != KERN_SUCCESS) {
284 if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) {
285 mach_msg_destroy(reply_header); 265 mach_msg_destroy(reply_header);
286 } 266 }
287 return kr; 267 return kr;
288 } 268 }
289
290 if (!running) {
291 // The reply message was sent successfuly, so act as though the deadline
292 // was reached before or during the subsequent receive operation when in
293 // persistent mode, and just return success when not in persistent mode.
294 return (persistent == kPersistent) ? MACH_RCV_TIMED_OUT : kr;
295 }
296 } 269 }
297 } while (persistent == kPersistent); 270 } while (persistent == kPersistent || retry);
298 271
299 return kr; 272 return kr;
300 } 273 }
301 274
302 } // namespace crashpad 275 } // namespace crashpad
OLDNEW
« no previous file with comments | « util/mach/mach_message.cc ('k') | util/mach/mach_message_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698