Skip to content

Commit cb5df71

Browse files
committed
Avoid overlap in guard_against_lengthy_operations()
1 parent 0bb7811 commit cb5df71

1 file changed

Lines changed: 40 additions & 24 deletions

File tree

src/platform/macos/events_loop/runloop_context.rs

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@
103103
use std::cell::Cell;
104104
use std::mem;
105105
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};
107108
use context;
108109
use core_foundation;
109110
use cocoa;
@@ -195,6 +196,7 @@ thread_local!{
195196
// If we are inside the inner runloop, this contains the caller's context and their Timeout
196197
static INSIDE_INNER_RUNLOOP_CONTEXT: Cell<Option<context::Context>> = Cell::new(None);
197198
static INSIDE_INNER_RUNLOOP_TIMEOUT: Cell<Option<Timeout>> = Cell::new(None);
199+
static INSIDE_INNER_RUNLOOP_ENTERED_AT: Cell<Instant> = Cell::new(Instant::now());
198200
}
199201

200202
// This is the first function called from inside the coroutine. It must not return.
@@ -255,48 +257,62 @@ fn yield_to_caller() -> bool {
255257
unsafe { mem::transmute::<*mut Option<_>, &mut Option<_>>(timeout) }
256258
.take();
257259

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-
264260
// Store the new values in the thread local cells until we yield back
265261
INSIDE_INNER_RUNLOOP_CONTEXT.with(move |context_cell| {
266262
context_cell.set(context);
267263
});
268264
INSIDE_INNER_RUNLOOP_TIMEOUT.with(move |timeout_cell| {
269265
timeout_cell.set(timeout);
270266
});
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+
}
271276

272277
true
273278
} else {
274279
false
275280
}
276281
}
277282

278-
279283
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+
}
287293

288294
// Queue a block in two milliseconds
289295
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();
293306

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+
}
300316
}
301317
});
302318
}

0 commit comments

Comments
 (0)