diff --git a/src/core/core.pri b/src/core/core.pri index 796083b63..5cac8b5ff 100644 --- a/src/core/core.pri +++ b/src/core/core.pri @@ -43,6 +43,7 @@ HEADERS += \ $$PWD/timerwheel.h \ $$PWD/jwt.h \ $$PWD/rtimer.h \ + $$PWD/defercall.h \ $$PWD/logutil.h \ $$PWD/uuidutil.h \ $$PWD/zutil.h \ @@ -66,6 +67,7 @@ SOURCES += \ $$PWD/timerwheel.cpp \ $$PWD/jwt.cpp \ $$PWD/rtimer.cpp \ + $$PWD/defercall.cpp \ $$PWD/logutil.cpp \ $$PWD/uuidutil.cpp \ $$PWD/zutil.cpp \ diff --git a/src/core/defercall.cpp b/src/core/defercall.cpp new file mode 100644 index 000000000..95c257ff2 --- /dev/null +++ b/src/core/defercall.cpp @@ -0,0 +1,65 @@ +/* +* 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 "defercall.h" +#include + +static thread_local DeferCall *g_instance = nullptr; + +DeferCall::DeferCall() = default; + +DeferCall::~DeferCall() = default; + +void DeferCall::defer(std::function handler) +{ + Call c; + c.handler = handler; + + deferredCalls_.push_back(c); + + QMetaObject::invokeMethod(this, "callNext", Qt::QueuedConnection); +} + +DeferCall *DeferCall::global() +{ + if(!g_instance) + g_instance = new DeferCall; + + return g_instance; +} + +void DeferCall::cleanup() +{ + delete g_instance; + g_instance = nullptr; +} + +void DeferCall::callNext() +{ + // there can't be more invokeMethod resolutions than queued calls + assert(!deferredCalls_.empty()); + + Call c = deferredCalls_.front(); + deferredCalls_.pop_front(); + + c.handler(); +} diff --git a/src/core/defercall.h b/src/core/defercall.h new file mode 100644 index 000000000..cdd0f7d9a --- /dev/null +++ b/src/core/defercall.h @@ -0,0 +1,67 @@ +/* +* 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$ +*/ + +#ifndef DEFERCALL_H +#define DEFERCALL_H + +#include + +// queues calls to be run after returning to the event loop +class DeferCall : public QObject +{ + Q_OBJECT + +public: + DeferCall(); + ~DeferCall(); + + // queue handler to be called after returning to the event loop. if + // handler contains references, they must outlive DeferCall. the + // recommended usage is for each object needing to perform deferred calls + // to keep a DeferCall as a member variable, and only refer to the + // object's own data in the handler. that way, any references are + // guaranteed to live long enough. + void defer(std::function handler); + + static DeferCall *global(); + static void cleanup(); + + template + static void deleteLater(T *p) + { + global()->defer([=]() { delete p; }); + } + +private slots: + void callNext(); + +private: + class Call + { + public: + std::function handler; + }; + + std::list deferredCalls_; +}; + +#endif diff --git a/src/core/zhttprequest.cpp b/src/core/zhttprequest.cpp index 6d5b4b593..7081855b7 100644 --- a/src/core/zhttprequest.cpp +++ b/src/core/zhttprequest.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2012-2021 Fanout, Inc. - * Copyright (C) 2023 Fastly, Inc. + * Copyright (C) 2023-2025 Fastly, Inc. * * This file is part of Pushpin. * @@ -30,6 +30,7 @@ #include "bufferlist.h" #include "log.h" #include "rtimer.h" +#include "defercall.h" #include "zhttpmanager.h" #include "uuidutil.h" @@ -105,6 +106,7 @@ class ZhttpRequest::Private : public QObject bool quiet; Connection expTimerConnection; Connection keepAliveTimerConnection; + DeferCall deferCall; Private(ZhttpRequest *_q) : QObject(_q), @@ -163,7 +165,7 @@ class ZhttpRequest::Private : public QObject { expTimerConnection.disconnect(); expireTimer->setParent(0); - expireTimer->deleteLater(); + DeferCall::deleteLater(expireTimer); expireTimer = 0; } @@ -171,7 +173,7 @@ class ZhttpRequest::Private : public QObject { keepAliveTimerConnection.disconnect(); keepAliveTimer->setParent(0); - keepAliveTimer->deleteLater(); + DeferCall::deleteLater(keepAliveTimer); keepAliveTimer = 0; } @@ -367,7 +369,7 @@ class ZhttpRequest::Private : public QObject if(!pendingUpdate) { pendingUpdate = true; - QMetaObject::invokeMethod(this, "doUpdate", Qt::QueuedConnection); + deferCall.defer([&]() { doUpdate(); }); } } @@ -935,7 +937,6 @@ class ZhttpRequest::Private : public QObject return ErrorGeneric; } -public slots: void doUpdate() { pendingUpdate = false; @@ -1157,7 +1158,6 @@ public slots: } } -public: void expire_timeout() { state = Stopped; diff --git a/src/handler/handlerenginetest.cpp b/src/handler/handlerenginetest.cpp index 27968ab9b..ce3868f09 100644 --- a/src/handler/handlerenginetest.cpp +++ b/src/handler/handlerenginetest.cpp @@ -33,6 +33,7 @@ #include "zhttpresponsepacket.h" #include "packet/httpresponsedata.h" #include "rtimer.h" +#include "defercall.h" #include "handlerengine.h" namespace { @@ -309,6 +310,7 @@ private slots: delete wrapper; RTimer::deinit(); + DeferCall::cleanup(); } void acceptNoHold() diff --git a/src/handler/handlermain.cpp b/src/handler/handlermain.cpp index 3278619af..1bef51d41 100644 --- a/src/handler/handlermain.cpp +++ b/src/handler/handlermain.cpp @@ -24,6 +24,7 @@ #include #include #include "rtimer.h" +#include "defercall.h" #include "handlerapp.h" class HandlerAppMain @@ -60,6 +61,7 @@ int handler_main(int argc, char **argv) // deinit here, after all event loop activity has completed RTimer::deinit(); + DeferCall::cleanup(); return ret; } diff --git a/src/proxy/app.cpp b/src/proxy/app.cpp index 98cfa64b8..c212fc2d0 100644 --- a/src/proxy/app.cpp +++ b/src/proxy/app.cpp @@ -31,6 +31,7 @@ #include #include "processquit.h" #include "rtimer.h" +#include "defercall.h" #include "log.h" #include "settings.h" #include "xffrule.h" @@ -299,6 +300,7 @@ class EngineThread : public QThread // deinit here, after all event loop activity has completed RTimer::deinit(); + DeferCall::cleanup(); } private: diff --git a/src/proxy/domainmap.cpp b/src/proxy/domainmap.cpp index 2446b8e2c..2109f2966 100644 --- a/src/proxy/domainmap.cpp +++ b/src/proxy/domainmap.cpp @@ -35,6 +35,7 @@ #include #include "log.h" #include "rtimer.h" +#include "defercall.h" #include "routesfile.h" #define WORKER_THREAD_TIMERS 1 @@ -756,6 +757,7 @@ class DomainMap::Thread : public QThread delete worker; RTimer::deinit(); + DeferCall::cleanup(); } public: diff --git a/src/proxy/proxyenginetest.cpp b/src/proxy/proxyenginetest.cpp index e49bfdea6..8e75b9a63 100644 --- a/src/proxy/proxyenginetest.cpp +++ b/src/proxy/proxyenginetest.cpp @@ -38,6 +38,7 @@ #include "packet/httpresponsedata.h" #include "packet/statspacket.h" #include "rtimer.h" +#include "defercall.h" #include "zhttpmanager.h" #include "statsmanager.h" #include "domainmap.h" @@ -636,6 +637,7 @@ private slots: QCoreApplication::instance()->sendPostedEvents(); RTimer::deinit(); + DeferCall::cleanup(); } void passthrough()