|
103 | 103 | use std::cell::Cell; |
104 | 104 | use std::mem; |
105 | 105 | use std::sync::Weak; |
106 | | -use std::sync::atomic::{AtomicUsize,ATOMIC_USIZE_INIT,Ordering}; |
| 106 | +use std::sync::atomic::{AtomicBool,ATOMIC_BOOL_INIT,Ordering}; |
| 107 | +use std::time::{Instant,Duration}; |
107 | 108 | use context; |
108 | 109 | use core_foundation; |
109 | 110 | use cocoa; |
@@ -195,6 +196,7 @@ thread_local!{ |
195 | 196 | // If we are inside the inner runloop, this contains the caller's context and their Timeout |
196 | 197 | static INSIDE_INNER_RUNLOOP_CONTEXT: Cell<Option<context::Context>> = Cell::new(None); |
197 | 198 | static INSIDE_INNER_RUNLOOP_TIMEOUT: Cell<Option<Timeout>> = Cell::new(None); |
| 199 | + static INSIDE_INNER_RUNLOOP_ENTERED_AT: Cell<Instant> = Cell::new(Instant::now()); |
198 | 200 | } |
199 | 201 |
|
200 | 202 | // This is the first function called from inside the coroutine. It must not return. |
@@ -255,48 +257,62 @@ fn yield_to_caller() -> bool { |
255 | 257 | unsafe { mem::transmute::<*mut Option<_>, &mut Option<_>>(timeout) } |
256 | 258 | .take(); |
257 | 259 |
|
258 | | - // Does the caller want their thread back soon? |
259 | | - if timeout == Some(Timeout::Now) { |
260 | | - // Try to ensure we'll yield again soon, regardless of what happens inside Cocoa |
261 | | - guard_against_lengthy_operations(); |
262 | | - } |
263 | | - |
264 | 260 | // Store the new values in the thread local cells until we yield back |
265 | 261 | INSIDE_INNER_RUNLOOP_CONTEXT.with(move |context_cell| { |
266 | 262 | context_cell.set(context); |
267 | 263 | }); |
268 | 264 | INSIDE_INNER_RUNLOOP_TIMEOUT.with(move |timeout_cell| { |
269 | 265 | timeout_cell.set(timeout); |
270 | 266 | }); |
| 267 | + INSIDE_INNER_RUNLOOP_ENTERED_AT.with(move |entered_at_cell| { |
| 268 | + entered_at_cell.set(Instant::now()); |
| 269 | + }); |
| 270 | + |
| 271 | + // Does the caller want their thread back soon? |
| 272 | + if timeout == Some(Timeout::Now) { |
| 273 | + // Try to ensure we'll yield again soon, regardless of what happens inside Cocoa |
| 274 | + guard_against_lengthy_operations(); |
| 275 | + } |
271 | 276 |
|
272 | 277 | true |
273 | 278 | } else { |
274 | 279 | false |
275 | 280 | } |
276 | 281 | } |
277 | 282 |
|
278 | | - |
279 | 283 | fn guard_against_lengthy_operations() { |
280 | | - // Schedule a block to run in the near future, just in case |
281 | | - // We can get called repeatedly, and we only want the most recent call to matter, so keep track |
282 | | - // using an atomic counter |
283 | | - static INVOCATIONS: AtomicUsize = ATOMIC_USIZE_INIT; |
284 | | - |
285 | | - // Get the current value of the counter, and increment it |
286 | | - let this_invocation = INVOCATIONS.fetch_add(1, Ordering::SeqCst); |
| 284 | + // We can get called repeatedly, and we only want a single block in the runloop's execution |
| 285 | + // queue, so keep track of if there is currently one queued |
| 286 | + static HAS_BLOCK_QUEUED: AtomicBool = ATOMIC_BOOL_INIT; |
| 287 | + |
| 288 | + // Is there currently a block queued? |
| 289 | + if HAS_BLOCK_QUEUED.load(Ordering::Acquire) { |
| 290 | + // Do nothing |
| 291 | + return; |
| 292 | + } |
287 | 293 |
|
288 | 294 | // Queue a block in two milliseconds |
289 | 295 | dispatch::Queue::main().after_ms(2, move || { |
290 | | - // Get the most recent invocation, which is one before the current value of the counter |
291 | | - let current_counter = INVOCATIONS.load(Ordering::Acquire); |
292 | | - let (most_recent_invocation, _) = current_counter.overflowing_sub(1); |
| 296 | + // Indicate that there is not currently a block queued |
| 297 | + HAS_BLOCK_QUEUED.store(false, Ordering::Release); |
| 298 | + |
| 299 | + // Are we in an invocation that's supposed to yield promptly? |
| 300 | + if current_timeout() == Some(Timeout::Now) { |
| 301 | + // Figure out when we entered the runloop as compared to now |
| 302 | + let runloop_entered_at = INSIDE_INNER_RUNLOOP_ENTERED_AT.with(move |entered_at_cell| { |
| 303 | + entered_at_cell.get() |
| 304 | + }); |
| 305 | + let duration_since_runloop_entry = runloop_entered_at.elapsed(); |
293 | 306 |
|
294 | | - // Are we the most recent call? |
295 | | - if most_recent_invocation == this_invocation { |
296 | | - yield_to_caller(); |
297 | | - } else { |
298 | | - // We have already yielded and returned |
299 | | - // Do nothing |
| 307 | + // Did we enter more than one millisecond ago? |
| 308 | + if duration_since_runloop_entry > Duration::from_millis(1) { |
| 309 | + // Return, even if this is a bit early |
| 310 | + yield_to_caller(); |
| 311 | + } else { |
| 312 | + // We haven't been in the runloop very long |
| 313 | + // Instead of returning, queue another block for the near future |
| 314 | + guard_against_lengthy_operations(); |
| 315 | + } |
300 | 316 | } |
301 | 317 | }); |
302 | 318 | } |
|
0 commit comments