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

Side by Side Diff: runtime/bin/eventhandler_macos.cc

Issue 169383003: Make event-handlers edge-triggered and move socket-state to Dart. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months 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 | Annotate | Revision Log
« no previous file with comments | « runtime/bin/eventhandler_macos.h ('k') | runtime/bin/eventhandler_win.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 (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 #include "platform/globals.h" 5 #include "platform/globals.h"
6 #if defined(TARGET_OS_MACOS) 6 #if defined(TARGET_OS_MACOS)
7 7
8 #include "bin/eventhandler.h" 8 #include "bin/eventhandler.h"
9 9
10 #include <errno.h> // NOLINT 10 #include <errno.h> // NOLINT
(...skipping 16 matching lines...) Expand all
27 namespace dart { 27 namespace dart {
28 namespace bin { 28 namespace bin {
29 29
30 static const int kInterruptMessageSize = sizeof(InterruptMessage); 30 static const int kInterruptMessageSize = sizeof(InterruptMessage);
31 static const int kInfinityTimeout = -1; 31 static const int kInfinityTimeout = -1;
32 static const int kTimerId = -1; 32 static const int kTimerId = -1;
33 static const int kShutdownId = -2; 33 static const int kShutdownId = -2;
34 34
35 35
36 bool SocketData::HasReadEvent() { 36 bool SocketData::HasReadEvent() {
37 return !IsClosedRead() && ((mask_ & (1 << kInEvent)) != 0); 37 return (mask_ & (1 << kInEvent)) != 0;
38 } 38 }
39 39
40 40
41 bool SocketData::HasWriteEvent() { 41 bool SocketData::HasWriteEvent() {
42 return !IsClosedWrite() && ((mask_ & (1 << kOutEvent)) != 0); 42 return (mask_ & (1 << kOutEvent)) != 0;
43 } 43 }
44 44
45 45
46 // Unregister the file descriptor for a SocketData structure with kqueue. 46 // Unregister the file descriptor for a SocketData structure with kqueue.
47 static void RemoveFromKqueue(intptr_t kqueue_fd_, SocketData* sd) { 47 static void RemoveFromKqueue(intptr_t kqueue_fd_, SocketData* sd) {
48 if (!sd->tracked_by_kqueue()) return;
48 static const intptr_t kMaxChanges = 2; 49 static const intptr_t kMaxChanges = 2;
49 intptr_t changes = 0; 50 intptr_t changes = 0;
50 struct kevent events[kMaxChanges]; 51 struct kevent events[kMaxChanges];
51 if (sd->read_tracked_by_kqueue()) { 52 if (sd->HasReadEvent()) {
52 EV_SET(events + changes, sd->fd(), EVFILT_READ, EV_DELETE, 0, 0, NULL); 53 EV_SET(events + changes, sd->fd(), EVFILT_READ, EV_DELETE, 0, 0, NULL);
53 ++changes; 54 ++changes;
54 sd->set_read_tracked_by_kqueue(false);
55 } 55 }
56 if (sd->write_tracked_by_kqueue()) { 56 if (sd->HasWriteEvent()) {
57 EV_SET(events + changes, sd->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, sd); 57 EV_SET(events + changes, sd->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
58 ++changes; 58 ++changes;
59 sd->set_write_tracked_by_kqueue(false);
60 } 59 }
61 if (changes > 0) { 60 ASSERT(changes > 0);
62 ASSERT(changes <= kMaxChanges); 61 ASSERT(changes <= kMaxChanges);
63 int status = 62 int status =
64 TEMP_FAILURE_RETRY(kevent(kqueue_fd_, events, changes, NULL, 0, NULL)); 63 TEMP_FAILURE_RETRY(kevent(kqueue_fd_, events, changes, NULL, 0, NULL));
65 if (status == -1) { 64 if (status == -1) {
66 const int kBufferSize = 1024; 65 const int kBufferSize = 1024;
67 char error_message[kBufferSize]; 66 char error_message[kBufferSize];
68 strerror_r(errno, error_message, kBufferSize); 67 strerror_r(errno, error_message, kBufferSize);
69 FATAL1("Failed deleting events from kqueue: %s\n", error_message); 68 FATAL1("Failed deleting events from kqueue: %s\n", error_message);
70 }
71 } 69 }
70 sd->set_tracked_by_kqueue(false);
72 } 71 }
73 72
74 73
75 // Update the kqueue registration for SocketData structure to reflect 74 // Update the kqueue registration for SocketData structure to reflect
76 // the events currently of interest. 75 // the events currently of interest.
77 static void UpdateKqueue(intptr_t kqueue_fd_, SocketData* sd) { 76 static void AddToKqueue(intptr_t kqueue_fd_, SocketData* sd) {
78 static const intptr_t kMaxChanges = 2; 77 static const intptr_t kMaxChanges = 2;
79 intptr_t changes = 0; 78 intptr_t changes = 0;
80 struct kevent events[kMaxChanges]; 79 struct kevent events[kMaxChanges];
81 // Only report events once and wait for them to be re-enabled after the 80 // Register or unregister READ filter if needed.
82 // event has been handled by the Dart code. This is done by using EV_ONESHOT. 81 if (sd->HasReadEvent()) {
83 if (sd->port() != 0) { 82 EV_SET(events + changes,
84 // Register or unregister READ filter if needed. 83 sd->fd(),
85 if (sd->HasReadEvent()) { 84 EVFILT_READ,
86 if (!sd->read_tracked_by_kqueue()) { 85 EV_ADD | EV_CLEAR,
87 EV_SET(events + changes, 86 0,
88 sd->fd(), 87 0,
89 EVFILT_READ, 88 sd);
90 EV_ADD | EV_ONESHOT, 89 ++changes;
91 0,
92 0,
93 sd);
94 ++changes;
95 sd->set_read_tracked_by_kqueue(true);
96 }
97 } else if (sd->read_tracked_by_kqueue()) {
98 EV_SET(events + changes, sd->fd(), EVFILT_READ, EV_DELETE, 0, 0, NULL);
99 ++changes;
100 sd->set_read_tracked_by_kqueue(false);
101 }
102 // Register or unregister WRITE filter if needed.
103 if (sd->HasWriteEvent()) {
104 if (!sd->write_tracked_by_kqueue()) {
105 EV_SET(events + changes,
106 sd->fd(),
107 EVFILT_WRITE,
108 EV_ADD | EV_ONESHOT,
109 0,
110 0,
111 sd);
112 ++changes;
113 sd->set_write_tracked_by_kqueue(true);
114 }
115 } else if (sd->write_tracked_by_kqueue()) {
116 EV_SET(events + changes, sd->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
117 ++changes;
118 sd->set_write_tracked_by_kqueue(false);
119 }
120 } 90 }
121 if (changes > 0) { 91 // Register or unregister WRITE filter if needed.
122 ASSERT(changes <= kMaxChanges); 92 if (sd->HasWriteEvent()) {
123 int status = 93 EV_SET(events + changes,
124 TEMP_FAILURE_RETRY(kevent(kqueue_fd_, events, changes, NULL, 0, NULL)); 94 sd->fd(),
125 if (status == -1) { 95 EVFILT_WRITE,
126 // kQueue does not accept the file descriptor. It could be due to 96 EV_ADD | EV_CLEAR,
127 // already closed file descriptor, or unuspported devices, such 97 0,
128 // as /dev/null. In such case, mark the file descriptor as closed, 98 0,
129 // so dart will handle it accordingly. 99 sd);
130 sd->set_write_tracked_by_kqueue(false); 100 ++changes;
131 sd->set_read_tracked_by_kqueue(false); 101 }
132 sd->ShutdownRead(); 102 ASSERT(changes > 0);
133 sd->ShutdownWrite(); 103 ASSERT(changes <= kMaxChanges);
134 DartUtils::PostInt32(sd->port(), 1 << kCloseEvent); 104 int status =
135 } 105 TEMP_FAILURE_RETRY(kevent(kqueue_fd_, events, changes, NULL, 0, NULL));
106 if (status == -1) {
107 // kQueue does not accept the file descriptor. It could be due to
108 // already closed file descriptor, or unuspported devices, such
109 // as /dev/null. In such case, mark the file descriptor as closed,
110 // so dart will handle it accordingly.
111 DartUtils::PostInt32(sd->port(), 1 << kCloseEvent);
112 } else {
113 sd->set_tracked_by_kqueue(true);
136 } 114 }
137 } 115 }
138 116
139 117
140 EventHandlerImplementation::EventHandlerImplementation() 118 EventHandlerImplementation::EventHandlerImplementation()
141 : socket_map_(&HashMap::SamePointerValue, 16) { 119 : socket_map_(&HashMap::SamePointerValue, 16) {
142 intptr_t result; 120 intptr_t result;
143 result = TEMP_FAILURE_RETRY(pipe(interrupt_fds_)); 121 result = TEMP_FAILURE_RETRY(pipe(interrupt_fds_));
144 if (result != 0) { 122 if (result != 0) {
145 FATAL("Pipe creation failed"); 123 FATAL("Pipe creation failed");
(...skipping 21 matching lines...) Expand all
167 } 145 }
168 146
169 147
170 EventHandlerImplementation::~EventHandlerImplementation() { 148 EventHandlerImplementation::~EventHandlerImplementation() {
171 VOID_TEMP_FAILURE_RETRY(close(kqueue_fd_)); 149 VOID_TEMP_FAILURE_RETRY(close(kqueue_fd_));
172 VOID_TEMP_FAILURE_RETRY(close(interrupt_fds_[0])); 150 VOID_TEMP_FAILURE_RETRY(close(interrupt_fds_[0]));
173 VOID_TEMP_FAILURE_RETRY(close(interrupt_fds_[1])); 151 VOID_TEMP_FAILURE_RETRY(close(interrupt_fds_[1]));
174 } 152 }
175 153
176 154
177 SocketData* EventHandlerImplementation::GetSocketData(intptr_t fd) { 155 SocketData* EventHandlerImplementation::GetSocketData(intptr_t fd,
156 bool* is_new) {
178 ASSERT(fd >= 0); 157 ASSERT(fd >= 0);
179 HashMap::Entry* entry = socket_map_.Lookup( 158 HashMap::Entry* entry = socket_map_.Lookup(
180 GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd), true); 159 GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd), true);
181 ASSERT(entry != NULL); 160 ASSERT(entry != NULL);
182 SocketData* sd = reinterpret_cast<SocketData*>(entry->value); 161 SocketData* sd = reinterpret_cast<SocketData*>(entry->value);
183 if (sd == NULL) { 162 if (sd == NULL) {
184 // If there is no data in the hash map for this file descriptor a 163 // If there is no data in the hash map for this file descriptor a
185 // new SocketData for the file descriptor is inserted. 164 // new SocketData for the file descriptor is inserted.
186 sd = new SocketData(fd); 165 sd = new SocketData(fd);
187 entry->value = sd; 166 entry->value = sd;
167 *is_new = true;
188 } 168 }
189 ASSERT(fd == sd->fd()); 169 ASSERT(fd == sd->fd());
190 return sd; 170 return sd;
191 } 171 }
192 172
193 173
194 void EventHandlerImplementation::WakeupHandler(intptr_t id, 174 void EventHandlerImplementation::WakeupHandler(intptr_t id,
195 Dart_Port dart_port, 175 Dart_Port dart_port,
196 int64_t data) { 176 int64_t data) {
197 InterruptMessage msg; 177 InterruptMessage msg;
(...skipping 18 matching lines...) Expand all
216 const intptr_t MAX_MESSAGES = kInterruptMessageSize; 196 const intptr_t MAX_MESSAGES = kInterruptMessageSize;
217 InterruptMessage msg[MAX_MESSAGES]; 197 InterruptMessage msg[MAX_MESSAGES];
218 ssize_t bytes = TEMP_FAILURE_RETRY( 198 ssize_t bytes = TEMP_FAILURE_RETRY(
219 read(interrupt_fds_[0], msg, MAX_MESSAGES * kInterruptMessageSize)); 199 read(interrupt_fds_[0], msg, MAX_MESSAGES * kInterruptMessageSize));
220 for (ssize_t i = 0; i < bytes / kInterruptMessageSize; i++) { 200 for (ssize_t i = 0; i < bytes / kInterruptMessageSize; i++) {
221 if (msg[i].id == kTimerId) { 201 if (msg[i].id == kTimerId) {
222 timeout_queue_.UpdateTimeout(msg[i].dart_port, msg[i].data); 202 timeout_queue_.UpdateTimeout(msg[i].dart_port, msg[i].data);
223 } else if (msg[i].id == kShutdownId) { 203 } else if (msg[i].id == kShutdownId) {
224 shutdown_ = true; 204 shutdown_ = true;
225 } else { 205 } else {
226 SocketData* sd = GetSocketData(msg[i].id); 206 bool is_new = false;
207 SocketData* sd = GetSocketData(msg[i].id, &is_new);
208 if (is_new) {
209 sd->SetPortAndMask(msg[i].dart_port, msg[i].data);
210 }
227 if ((msg[i].data & (1 << kShutdownReadCommand)) != 0) { 211 if ((msg[i].data & (1 << kShutdownReadCommand)) != 0) {
228 ASSERT(msg[i].data == (1 << kShutdownReadCommand)); 212 ASSERT(msg[i].data == (1 << kShutdownReadCommand));
229 // Close the socket for reading. 213 // Close the socket for reading.
230 sd->ShutdownRead(); 214 sd->ShutdownRead();
231 UpdateKqueue(kqueue_fd_, sd);
232 } else if ((msg[i].data & (1 << kShutdownWriteCommand)) != 0) { 215 } else if ((msg[i].data & (1 << kShutdownWriteCommand)) != 0) {
233 ASSERT(msg[i].data == (1 << kShutdownWriteCommand)); 216 ASSERT(msg[i].data == (1 << kShutdownWriteCommand));
234 // Close the socket for writing. 217 // Close the socket for writing.
235 sd->ShutdownWrite(); 218 sd->ShutdownWrite();
236 UpdateKqueue(kqueue_fd_, sd);
237 } else if ((msg[i].data & (1 << kCloseCommand)) != 0) { 219 } else if ((msg[i].data & (1 << kCloseCommand)) != 0) {
238 ASSERT(msg[i].data == (1 << kCloseCommand)); 220 ASSERT(msg[i].data == (1 << kCloseCommand));
239 // Close the socket and free system resources. 221 // Close the socket and free system resources.
240 RemoveFromKqueue(kqueue_fd_, sd); 222 RemoveFromKqueue(kqueue_fd_, sd);
241 intptr_t fd = sd->fd(); 223 intptr_t fd = sd->fd();
242 sd->Close(); 224 sd->Close();
243 socket_map_.Remove(GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd)); 225 socket_map_.Remove(GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd));
244 delete sd; 226 delete sd;
245 DartUtils::PostInt32(msg[i].dart_port, 1 << kDestroyedEvent); 227 DartUtils::PostInt32(msg[i].dart_port, 1 << kDestroyedEvent);
246 } else { 228 } else {
247 if ((msg[i].data & (1 << kInEvent)) != 0 && sd->IsClosedRead()) { 229 if (is_new) {
248 DartUtils::PostInt32(msg[i].dart_port, 1 << kCloseEvent); 230 AddToKqueue(kqueue_fd_, sd);
249 } else {
250 // Setup events to wait for.
251 ASSERT((msg[i].data > 0) && (msg[i].data < kIntptrMax));
252 sd->SetPortAndMask(msg[i].dart_port,
253 static_cast<intptr_t>(msg[i].data));
254 UpdateKqueue(kqueue_fd_, sd);
255 } 231 }
256 } 232 }
257 } 233 }
258 } 234 }
259 } 235 }
260 236
261 #ifdef DEBUG_KQUEUE 237 #ifdef DEBUG_KQUEUE
262 static void PrintEventMask(intptr_t fd, struct kevent* event) { 238 static void PrintEventMask(intptr_t fd, struct kevent* event) {
263 Log::Print("%d ", static_cast<int>(fd)); 239 Log::Print("%d ", static_cast<int>(fd));
240 Log::Print("filter=0x%x:", event->filter);
264 if (event->filter == EVFILT_READ) Log::Print("EVFILT_READ "); 241 if (event->filter == EVFILT_READ) Log::Print("EVFILT_READ ");
265 if (event->filter == EVFILT_WRITE) Log::Print("EVFILT_WRITE "); 242 if (event->filter == EVFILT_WRITE) Log::Print("EVFILT_WRITE ");
266 Log::Print("flags: %x: ", event->flags); 243 Log::Print("flags: %x: ", event->flags);
267 if ((event->flags & EV_EOF) != 0) Log::Print("EV_EOF "); 244 if ((event->flags & EV_EOF) != 0) Log::Print("EV_EOF ");
268 if ((event->flags & EV_ERROR) != 0) Log::Print("EV_ERROR "); 245 if ((event->flags & EV_ERROR) != 0) Log::Print("EV_ERROR ");
246 if ((event->flags & EV_CLEAR) != 0) Log::Print("EV_CLEAR ");
247 if ((event->flags & EV_ADD) != 0) Log::Print("EV_ADD ");
248 if ((event->flags & EV_DELETE) != 0) Log::Print("EV_DELETE ");
269 Log::Print("- fflags: %d ", event->fflags); 249 Log::Print("- fflags: %d ", event->fflags);
250 Log::Print("- data: %ld ", event->data);
270 Log::Print("(available %d) ", 251 Log::Print("(available %d) ",
271 static_cast<int>(FDUtils::AvailableBytes(fd))); 252 static_cast<int>(FDUtils::AvailableBytes(fd)));
272 Log::Print("\n"); 253 Log::Print("\n");
273 } 254 }
274 #endif 255 #endif
275 256
276 257
277 intptr_t EventHandlerImplementation::GetEvents(struct kevent* event, 258 intptr_t EventHandlerImplementation::GetEvents(struct kevent* event,
278 SocketData* sd) { 259 SocketData* sd) {
279 #ifdef DEBUG_KQUEUE 260 #ifdef DEBUG_KQUEUE
(...skipping 11 matching lines...) Expand all
291 event_mask |= (1 << kCloseEvent); 272 event_mask |= (1 << kCloseEvent);
292 } 273 }
293 } 274 }
294 if (event_mask == 0) event_mask |= (1 << kInEvent); 275 if (event_mask == 0) event_mask |= (1 << kInEvent);
295 } else { 276 } else {
296 UNREACHABLE(); 277 UNREACHABLE();
297 } 278 }
298 } else { 279 } else {
299 // Prioritize data events over close and error events. 280 // Prioritize data events over close and error events.
300 if (event->filter == EVFILT_READ) { 281 if (event->filter == EVFILT_READ) {
301 if (FDUtils::AvailableBytes(sd->fd()) != 0) { 282 event_mask = (1 << kInEvent);
302 event_mask = (1 << kInEvent); 283 if ((event->flags & EV_EOF) != 0) {
303 } else if ((event->flags & EV_EOF) != 0) {
304 if (event->fflags != 0) { 284 if (event->fflags != 0) {
305 event_mask |= (1 << kErrorEvent); 285 event_mask = (1 << kErrorEvent);
306 } else { 286 } else {
307 event_mask |= (1 << kCloseEvent); 287 event_mask |= (1 << kCloseEvent);
308 } 288 }
309 sd->MarkClosedRead();
310 } 289 }
311 } else if (event->filter == EVFILT_WRITE) { 290 } else if (event->filter == EVFILT_WRITE) {
291 event_mask |= (1 << kOutEvent);
312 if ((event->flags & EV_EOF) != 0) { 292 if ((event->flags & EV_EOF) != 0) {
313 if (event->fflags != 0) { 293 if (event->fflags != 0) {
314 event_mask |= (1 << kErrorEvent); 294 event_mask = (1 << kErrorEvent);
315 } else {
316 event_mask |= (1 << kCloseEvent);
317 } 295 }
318 // If the receiver closed for reading, close for writing,
319 // update the registration with kqueue, and do not report a
320 // write event.
321 sd->MarkClosedWrite();
322 UpdateKqueue(kqueue_fd_, sd);
323 } else {
324 event_mask |= (1 << kOutEvent);
325 } 296 }
326 } else { 297 } else {
327 UNREACHABLE(); 298 UNREACHABLE();
328 } 299 }
329 } 300 }
330 301
331 return event_mask; 302 return event_mask;
332 } 303 }
333 304
334 305
335 void EventHandlerImplementation::HandleEvents(struct kevent* events, 306 void EventHandlerImplementation::HandleEvents(struct kevent* events,
336 int size) { 307 int size) {
337 bool interrupt_seen = false; 308 bool interrupt_seen = false;
338 for (int i = 0; i < size; i++) { 309 for (int i = 0; i < size; i++) {
339 // If flag EV_ERROR is set it indicates an error in kevent processing. 310 // If flag EV_ERROR is set it indicates an error in kevent processing.
340 if ((events[i].flags & EV_ERROR) != 0) { 311 if ((events[i].flags & EV_ERROR) != 0) {
341 const int kBufferSize = 1024; 312 const int kBufferSize = 1024;
342 char error_message[kBufferSize]; 313 char error_message[kBufferSize];
343 strerror_r(events[i].data, error_message, kBufferSize); 314 strerror_r(events[i].data, error_message, kBufferSize);
344 FATAL1("kevent failed %s\n", error_message); 315 FATAL1("kevent failed %s\n", error_message);
345 } 316 }
346 if (events[i].udata == NULL) { 317 if (events[i].udata == NULL) {
347 interrupt_seen = true; 318 interrupt_seen = true;
348 } else { 319 } else {
349 SocketData* sd = reinterpret_cast<SocketData*>(events[i].udata); 320 SocketData* sd = reinterpret_cast<SocketData*>(events[i].udata);
350 sd->set_write_tracked_by_kqueue(false);
351 sd->set_read_tracked_by_kqueue(false);
352 intptr_t event_mask = GetEvents(events + i, sd); 321 intptr_t event_mask = GetEvents(events + i, sd);
353 if (event_mask == 0) { 322 if (event_mask != 0) {
354 // Event not handled, re-add to kqueue.
355 UpdateKqueue(kqueue_fd_, sd);
356 } else {
357 Dart_Port port = sd->port(); 323 Dart_Port port = sd->port();
358 ASSERT(port != 0); 324 ASSERT(port != 0);
359 DartUtils::PostInt32(port, event_mask); 325 DartUtils::PostInt32(port, event_mask);
360 } 326 }
361 } 327 }
362 } 328 }
363 if (interrupt_seen) { 329 if (interrupt_seen) {
364 // Handle after socket events, so we avoid closing a socket before we handle 330 // Handle after socket events, so we avoid closing a socket before we handle
365 // the current events. 331 // the current events.
366 HandleInterruptFd(); 332 HandleInterruptFd();
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
461 427
462 uint32_t EventHandlerImplementation::GetHashmapHashFromFd(intptr_t fd) { 428 uint32_t EventHandlerImplementation::GetHashmapHashFromFd(intptr_t fd) {
463 // The hashmap does not support keys with value 0. 429 // The hashmap does not support keys with value 0.
464 return dart::Utils::WordHash(fd + 1); 430 return dart::Utils::WordHash(fd + 1);
465 } 431 }
466 432
467 } // namespace bin 433 } // namespace bin
468 } // namespace dart 434 } // namespace dart
469 435
470 #endif // defined(TARGET_OS_MACOS) 436 #endif // defined(TARGET_OS_MACOS)
OLDNEW
« no previous file with comments | « runtime/bin/eventhandler_macos.h ('k') | runtime/bin/eventhandler_win.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698