Skip to content

Commit

Permalink
Android: Improve RN-timers idling resource such that it would skip ov…
Browse files Browse the repository at this point in the history
…er interval-timers
  • Loading branch information
d4vidi committed Nov 19, 2018
1 parent b0e3b0f commit 0500a2d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ public boolean isIdleNow() {
return true;
}

Object timingModule = Reflect.on(reactContext).call(METHOD_GET_NATIVE_MODULE, timingClass).get();
Object timerLock = Reflect.on(timingModule).field(LOCK_TIMER).get();
final Object timingModule = Reflect.on(reactContext).call(METHOD_GET_NATIVE_MODULE, timingClass).get();
final Object timerLock = Reflect.on(timingModule).field(LOCK_TIMER).get();
synchronized (timerLock) {
final PriorityQueue<?> timers = Reflect.on(timingModule).field(FIELD_TIMERS).get();
final Object nextTimer = timers.peek();
final Object nextTimer = findNextTimer(timers);
if (nextTimer == null) {
if (callback != null) {
callback.onTransitionToIdle();
Expand Down Expand Up @@ -147,22 +147,38 @@ public void resume() {
paused.set(false);
}

private Object findNextTimer(PriorityQueue<?> timers) {
Object nextTimer = timers.peek();
if (nextTimer == null) {
return null;
}

final boolean isRepetitive = Reflect.on(nextTimer).field(TIMER_FIELD_REPETITIVE).get();
if (!isRepetitive) {
return nextTimer;
}

Object timer = null;
long targetTime = Long.MAX_VALUE;
for (Object aTimer : timers) {
final boolean timerIsRepetitive = Reflect.on(aTimer).field(TIMER_FIELD_REPETITIVE).get();
final long timerTargetTime = Reflect.on(aTimer).field(TIMER_FIELD_TARGET_TIME).get();
if (!timerIsRepetitive && timerTargetTime < targetTime) {
targetTime = timerTargetTime;
timer = aTimer;
}
}
return timer;
}

private boolean isTimerOutsideBusyWindow(Object nextTimer) {
final long currentTimeMS = System.nanoTime() / 1000000L;
final Reflect nextTimerReflected = Reflect.on(nextTimer);
final long targetTimeMS = nextTimerReflected.field(TIMER_FIELD_TARGET_TIME).get();
final int intervalMS = nextTimerReflected.field(TIMER_FIELD_INTERVAL).get();
final boolean isRepetitive = nextTimerReflected.field(TIMER_FIELD_REPETITIVE).get();

// Log.i(LOG_TAG, "Next timer has duration of: " + intervalMS
// + "; due time is: " + targetTimeMS + ", current is: " + currentTimeMS
// + "; is " + (isRepetitive ? "repeating" : "a one-shot"));

// Before making any concrete checks, be sure to ignore repeating timers or we'd loop forever.
// TODO: Should we iterate to the first, non-repeating timer?
if (isRepetitive) {
return true;
}
// + "; due time is: " + targetTimeMS + ", current is: " + currentTimeMS);

// Core condition is for the timer interval (duration) to be set beyond our window.
// Note: we check the interval in an 'absolute' way rather than comparing to the 'current time'
Expand Down
5 changes: 5 additions & 0 deletions detox/test/e2e/09.stress-timeouts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ describe('StressTimeouts', () => {
await element(by.id('IntervalIgnore')).tap();
await expect(element(by.text('Interval Ignored!!!'))).toBeVisible();
});

it('should skip over setInterval', async () => {
await element(by.id('SkipOverInterval')).tap();
await expect(element(by.text('Interval Skipped-Over!!!'))).toBeVisible();
});
});
53 changes: 31 additions & 22 deletions detox/test/src/Screens/TimeoutsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export default class TimeoutsScreen extends Component {
<Text testID='IntervalIgnore' style={{color: 'blue', marginBottom: 20}}>Interval Ignore</Text>
</TouchableOpacity>

<TouchableOpacity onPress={this.onIntervalSkipOver.bind(this, 'Interval Skipped-Over')}>
<Text testID='SkipOverInterval' style={{color: 'blue', marginBottom: 20}}>Interval Skip-Over</Text>
</TouchableOpacity>

</View>
);
}
Expand All @@ -59,37 +63,42 @@ export default class TimeoutsScreen extends Component {
}

onTimeoutButtonPress(greeting, timeout) {
setTimeout(() => {
this.setState({
greeting: greeting
});
}, timeout);
setTimeout(() => this.setState({greeting}), timeout);
}

onTimeoutIgnoreButtonPress(greeting, timeout) {
setTimeout(() => {
console.log('this will happen soon');
}, timeout);
this.setState({
greeting: greeting
});
setTimeout(() => console.log('this will happen soon'), timeout);
this.setState({greeting});
}

onImmediateButtonPress(greeting) {
setImmediate(() => {
this.setState({
greeting: greeting
});
});
setImmediate(() => this.setState({greeting}));
}

onIntervalIgnoreButtonPress(greeting, interval) {
setInterval(() => {
console.log('this is recurring');
}, interval);
this.setState({
greeting: greeting
});
setInterval(() => console.log('this is recurring'), interval);
this.setState({greeting});
}

onIntervalSkipOver(greeting) {
const handleBusyKeepingTimeout = () => this.setState({greeting});
const handleIdlingTimeout = (timeMs) => console.log(`Non-awaited timer of ${timeMs}ms has expired`);

const interval = 88;
const busyKeepingTimer = 600;
const idlingTimerBase = 1500 + 1;
const foreverTimer = 2500;
let intervalId;

intervalId = setInterval(() => console.log(`this should show every ${interval}ms`), interval);
setTimeout(() => handleIdlingTimeout(idlingTimerBase), idlingTimerBase);
setTimeout(() => handleBusyKeepingTimeout(), busyKeepingTimer);
setTimeout(() => handleIdlingTimeout(idlingTimerBase + 10), idlingTimerBase + 10);
setTimeout(() => handleIdlingTimeout(idlingTimerBase + 100), idlingTimerBase + 100);
setTimeout(() => {
console.log('"Forever" timer expired - though it shouldn\'t have!');
this.setState({greeting: '???'});
clearInterval(intervalId);
}, foreverTimer);
}
}

0 comments on commit 0500a2d

Please # to comment.