Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/libsync/owncloudpropagator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ void OwncloudPropagator::scheduleNextJob()
{
if (_jobScheduled) return; // don't schedule more than 1
_jobScheduled = true;
QTimer::singleShot(3, this, &OwncloudPropagator::scheduleNextJobImpl);
QMetaObject::invokeMethod(this, &OwncloudPropagator::scheduleNextJobImpl, Qt::QueuedConnection);
}

void OwncloudPropagator::scheduleNextJobImpl()
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/propagatedownload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "config.h"

Check failure on line 7 in src/libsync/propagatedownload.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/propagatedownload.cpp:7:10 [clang-diagnostic-error]

'config.h' file not found
#include "libsync/creds/abstractcredentials.h"
#include "owncloudpropagator_p.h"
#include "propagatedownload.h"
Expand Down Expand Up @@ -288,7 +288,7 @@
{
if (!reply())
return;
int bufferSize = qMin(1024 * 8ll, reply()->bytesAvailable());
int bufferSize = qMin(1024 * 256ll, reply()->bytesAvailable());
QByteArray buffer(bufferSize, Qt::Uninitialized);

while (reply()->bytesAvailable() > 0 && _saveBodyToFile) {
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ endif()

nextcloud_add_test(LongPath)
nextcloud_add_benchmark(LargeSync)
nextcloud_add_benchmark(ScheduleDelay)

nextcloud_add_test(Account)
nextcloud_add_test(Folder)
Expand Down
116 changes: 116 additions & 0 deletions test/benchmarks/benchscheduledelay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: CC0-1.0
*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
*/

#include <QCoreApplication>

Check failure on line 10 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:10:10 [clang-diagnostic-error]

'QCoreApplication' file not found
#include <QObject>
#include <QTimer>
#include <QEventLoop>
#include <QElapsedTimer>
#include <QtGlobal>

#include <cstdio>

// Reproduces the scheduling cost that OwncloudPropagator::scheduleNextJob()
// used to pay. The propagator serializes scheduling passes with a single
// "_jobScheduled" flag, so exactly one deferred pass is in flight at a time
// and each of the N passes for an N-file sync pays the deferral latency
// back-to-back.
//
// Old path: QTimer::singleShot(3, ...) -> 3 ms per pass.
// New path: QMetaObject::invokeMethod(..., Qt::QueuedConnection) -> next
// event-loop iteration, no artificial delay.
//
// With rounds = 10000 the timer path takes ~30 s (10000 * 3 ms); the queued
// path completes in a few milliseconds.

namespace {
constexpr int kRounds = 10000;
constexpr int kDelayMs = 3; // value previously used in scheduleNextJob()
}

class ScheduleWorker : public QObject
{
Q_OBJECT
public:
enum class Mode { TimerSingleShot, QueuedInvoke };

explicit ScheduleWorker(Mode mode, QObject *parent = nullptr)

Check warning on line 43 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:43:5 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields:
: QObject(parent)
, _mode(mode)
{
}

// Runs kRounds deferred scheduling passes through the configured
// mechanism and returns the elapsed wall-clock time in milliseconds.
qint64 run()

Check warning on line 51 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:51:12 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
_count = 0;
QEventLoop loop;

Check warning on line 54 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:54:20 [cppcoreguidelines-init-variables]

variable 'loop' is not initialized
_loop = &loop;
QElapsedTimer timer;

Check warning on line 56 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:56:23 [cppcoreguidelines-init-variables]

variable 'timer' is not initialized
timer.start();
scheduleNext();
loop.exec();
_loop = nullptr;
return timer.elapsed();
}

private:
void scheduleNext()

Check warning on line 65 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:65:10 [readability-convert-member-functions-to-static]

method 'scheduleNext' can be made static
{
// Mirror the propagator's "one pass in flight" model: every pass
// schedules the next one through the deferral mechanism under test.
if (_mode == Mode::TimerSingleShot) {

Check warning on line 69 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:69:9 [bugprone-branch-clone]

if with identical then and else branches
QTimer::singleShot(kDelayMs, this, &ScheduleWorker::tick);
} else {
QMetaObject::invokeMethod(this, &ScheduleWorker::tick, Qt::QueuedConnection);
}
}

private slots:

Check warning on line 76 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:76:1 [readability-redundant-access-specifiers]

redundant access specifier has the same accessibility as the previous access specifier
void tick()
{
if (++_count >= kRounds) {
if (_loop) {
_loop->quit();
}
return;
}
scheduleNext();
}

private:
Mode _mode;
int _count = 0;
QEventLoop *_loop = nullptr;
};

int main(int argc, char *argv[])

Check warning on line 94 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:94:5 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
QCoreApplication app(argc, argv);

Check warning on line 96 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:96:22 [cppcoreguidelines-init-variables]

variable 'app' is not initialized

ScheduleWorker queued(ScheduleWorker::Mode::QueuedInvoke);

Check warning on line 98 in test/benchmarks/benchscheduledelay.cpp

View workflow job for this annotation

GitHub Actions / build

test/benchmarks/benchscheduledelay.cpp:98:20 [modernize-use-trailing-return-type]

use a trailing return type for this function
const auto queuedMs = queued.run();

ScheduleWorker timed(ScheduleWorker::Mode::TimerSingleShot);
const auto timerMs = timed.run();

std::printf("ROUNDS %d\n", kRounds);
std::printf("QTimer::singleShot(%dms): %lld ms\n", kDelayMs, static_cast<long long>(timerMs));
std::printf("QMetaObject::invokeMethod(QueuedConnection): %lld ms\n", static_cast<long long>(queuedMs));
std::printf("SAVED: %lld ms\n", static_cast<long long>(timerMs - queuedMs));
std::fflush(stdout);

// Sanity: both paths scheduled the same number of rounds; the queued path
// must be dramatically faster because it has no per-pass timer latency.
const bool ok = timerMs > queuedMs * 10;
return ok ? 0 : 1;
}

#include "benchscheduledelay.moc"
Loading