Skip to content

Commit

Permalink
timer: process zero timeouts immediately (#48135)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkarneges authored Feb 25, 2025
1 parent 0d66c35 commit 3772af9
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/core/coretests.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

int httpheaders_test(int argc, char **argv);
int jwt_test(int argc, char **argv);
int timer_test(int argc, char **argv);
int eventloop_test(int argc, char **argv);

#endif
11 changes: 11 additions & 0 deletions src/core/eventloop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ EventLoop::~EventLoop()
g_instance = nullptr;
}

std::optional<int> EventLoop::step()
{
std::optional<int> code;

int x;
if(ffi::event_loop_step(inner_, &x) == 0)
code = x;

return code;
}

int EventLoop::exec()
{
return ffi::event_loop_exec(inner_);
Expand Down
2 changes: 2 additions & 0 deletions src/core/eventloop.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef EVENTLOOP_H
#define EVENTLOOP_H

#include <optional>
#include "rust/bindings.h"

class EventLoop
Expand All @@ -31,6 +32,7 @@ class EventLoop
EventLoop(int capacity);
~EventLoop();

std::optional<int> step();
int exec();
void exit(int code);

Expand Down
18 changes: 18 additions & 0 deletions src/core/eventloop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,24 @@ mod ffi {
}
}

#[allow(clippy::missing_safety_doc)]
#[no_mangle]
pub unsafe extern "C" fn event_loop_step(
l: *mut EventLoopRaw,
out_code: *mut libc::c_int,
) -> libc::c_int {
let l = l.as_mut().unwrap();

match l.step() {
Some(code) => {
unsafe { out_code.write(code) };

0
}
None => -1,
}
}

#[allow(clippy::missing_safety_doc)]
#[no_mangle]
pub unsafe extern "C" fn event_loop_exec(l: *mut EventLoopRaw) -> libc::c_int {
Expand Down
10 changes: 10 additions & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ mod tests {
unsafe { call_c_main(ffi::jwt_test, args) as u8 }
}

fn timer_test(args: &[&OsStr]) -> u8 {
// SAFETY: safe to call
unsafe { call_c_main(ffi::timer_test, args) as u8 }
}

fn eventloop_test(args: &[&OsStr]) -> u8 {
// SAFETY: safe to call
unsafe { call_c_main(ffi::eventloop_test, args) as u8 }
Expand All @@ -130,6 +135,11 @@ mod tests {
assert!(qtest::run(jwt_test));
}

#[test]
fn timer() {
assert!(qtest::run(timer_test));
}

#[test]
fn eventloop() {
assert!(qtest::run(eventloop_test));
Expand Down
1 change: 1 addition & 0 deletions src/core/tests.pri
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ INCLUDES += \
SOURCES += \
$$PWD/httpheaderstest.cpp \
$$PWD/jwttest.cpp \
$$PWD/timertest.cpp \
$$PWD/eventlooptest.cpp
15 changes: 12 additions & 3 deletions src/core/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,19 @@ int TimerManager::add(int msec, Timer *r)
{
qint64 currentTime = QDateTime::currentMSecsSinceEpoch();

// expireTime must be >= startTime_
qint64 expireTime = qMax(currentTime + msec, startTime_);
qint64 expiresTicks;
if(msec <= 0)
{
// for timeouts of zero, set immediate expiration with no rounding up
expiresTicks = currentTicks_;
}
else
{
// expireTime must be >= startTime_
qint64 expireTime = qMax(currentTime + msec, startTime_);

qint64 expiresTicks = durationToTicksRoundUp(expireTime - startTime_);
expiresTicks = durationToTicksRoundUp(expireTime - startTime_);
}

int id = wheel_.add(expiresTicks, (size_t)r);

Expand Down
103 changes: 103 additions & 0 deletions src/core/timertest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2025 Fastly, Inc.
*
* This file is part of Pushpin.
*
* $FANOUT_BEGIN_LICENSE:APACHE2$
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $FANOUT_END_LICENSE$
*/

#include <QtTest/QtTest>
#include "timer.h"
#include "eventloop.h"

class TimerTest : public QObject
{
Q_OBJECT

private:
// loop_advance should process enough events to cause the timers to
// activate, without sleeping, in order to prove timeouts of zero are
// processed immediately
int runZeroTimeout(std::function<void ()> loop_advance)
{
Timer t;
t.setSingleShot(true);

int count = 0;

t.timeout.connect([&] {
++count;
if(count < 2)
t.start(0);
});

t.start(0);

loop_advance();

return count;
}

private slots:
void zeroTimeout()
{
EventLoop loop(1);

int count = runZeroTimeout([&] {
// activate the first timer and queue the second
loop.step();

// activate the second
loop.step();
});

QCOMPARE(count, 2);
}

void zeroTimeoutQt()
{
Timer::init(1);

int count = runZeroTimeout([] {
// the timer's qt-based implementation will process both timeouts
// during a single timer processing pass. therefore, both
// timeouts should get processed within a single event loop pass
QCoreApplication::processEvents(QEventLoop::AllEvents);
});

QCOMPARE(count, 2);

Timer::deinit();
}
};

namespace {
namespace Main {
QTEST_MAIN(TimerTest)
}
}

extern "C" {

int timer_test(int argc, char **argv)
{
return Main::main(argc, argv);
}

}

#include "timertest.moc"
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub mod ffi {
import_cpptest! {
pub fn httpheaders_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int;
pub fn jwt_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int;
pub fn timer_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int;
pub fn eventloop_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int;
pub fn routesfile_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int;
pub fn proxyengine_test(argc: libc::c_int, argv: *const *const libc::c_char) -> libc::c_int;
Expand Down

0 comments on commit 3772af9

Please sign in to comment.