From 2798d7de9cb7ea2a9319a1caed53297148390c40 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 09:33:10 +0300 Subject: [PATCH 01/26] fix gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1c093651..c420a4bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /src_main/piversion.h -/.svn +/.* /doc/rtf _unsused CMakeLists.txt.user* From 8ccc05ee785b9ce644eec05d40a631ea9d5e3280 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 17:33:27 +0300 Subject: [PATCH 02/26] simplify piprotectedvariable --- libs/main/thread/piprotectedvariable.h | 48 +- tests/CMakeLists.txt | 1 + tests/thread/piprotectedvariable_test.cpp | 936 ++++++++++++++++++++++ 3 files changed, 964 insertions(+), 21 deletions(-) create mode 100644 tests/thread/piprotectedvariable_test.cpp diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 5390fb5d..7c3f246c 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -4,22 +4,22 @@ //! \~english Thread-safe variable //! \~russian Потокобезопасная переменная /* - PIP - Platform Independent Primitives - Thread-safe variable - Ivan Pelipenko peri4ko@yandex.ru + PIP - Platform Independent Primitives + Thread-safe variable + Ivan Pelipenko peri4ko@yandex.ru - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ #ifndef PIPROTECTEDVARIABLE_H @@ -39,16 +39,22 @@ public: //! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным. class PIP_EXPORT Pointer { friend class PIProtectedVariable; + NO_COPY_CLASS(Pointer); public: - //! \~english Copies wrapper state for access to the same protected value. - //! \~russian Копирует состояние обертки для доступа к тому же защищенному значению. - Pointer(const Pointer & v): pv(v.pv), counter(v.counter + 1) {} + //! \~\english Move constructor - transfers ownership of the lock. + //! \~russian Конструктор перемещения - передает владение блокировкой. + Pointer(Pointer && other) noexcept: pv(other.pv), ownsLock(other.ownsLock) { other.ownsLock = false; } - //! \~english Destroys wrapper and releases the mutex when it owns the original lock. - //! \~russian Уничтожает обертку и освобождает мьютекс, когда она владеет исходной блокировкой. + + //! \~\english Move assignment is deleted - Pointer can only be moved once. + //! \~russian Оператор перемещения удален - Pointer можно переместить только один раз. + Pointer & operator=(Pointer &&) = delete; + + //! \~\english Destroys wrapper and releases the mutex. + //! \~russian Уничтожает обертку и освобождает мьютекс. ~Pointer() { - if (counter == 0) pv.mutex.unlock(); + if (ownsLock) pv.mutex.unlock(); } //! \~english Returns pointer access to the protected value. @@ -61,10 +67,10 @@ public: private: Pointer() = delete; - Pointer(PIProtectedVariable & v): pv(v) {} + Pointer(PIProtectedVariable & v): pv(v), ownsLock(true) {} PIProtectedVariable & pv; - int counter = 0; + bool ownsLock = true; }; //! \~english Replaces the protected value with \a v. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a5ba75e2..67f7d5f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,3 +37,4 @@ pip_test(piobject) pip_test(client_server pip_client_server) pip_test(io) pip_test(system) +pip_test(thread) diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp new file mode 100644 index 00000000..e9bdc1ff --- /dev/null +++ b/tests/thread/piprotectedvariable_test.cpp @@ -0,0 +1,936 @@ +//! \~\file piprotectedvariable_test.cpp +//! \~\brief Unit tests for PIProtectedVariable class +/* + PIP - Platform Independent Primitives + Unit tests for PIProtectedVariable + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include "pimutex.h" +#include "piprotectedvariable.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace { + +constexpr int THREAD_COUNT = 10; +constexpr int ITERATIONS_PER_THREAD = 1000; +constexpr int WAIT_THREAD_TIME_MS = 1000; + +} // anonymous namespace + +//! \~\brief Basic functionality tests +//! \~\english Tests for basic constructor, set, get, and operator= functionality +//! \~\russian Тесты базовой функциональности конструктора, set, get и оператора= +TEST(PIProtectedVariable_Basic, ConstructorCreatesValidObject) { + PIProtectedVariable pv; + // Note: PIProtectedVariable does not value-initialize its internal var, + // so we just verify that the object is constructible and we can set/get values + pv.set(42); + EXPECT_EQ(pv.get(), 42); +} + +TEST(PIProtectedVariable_Basic, SetCorrectlySetsValue) { + PIProtectedVariable pv; + pv.set(42); + EXPECT_EQ(pv.get(), 42); +} + +TEST(PIProtectedVariable_Basic, SetWithDifferentTypes) { + PIProtectedVariable pvInt; + PIProtectedVariable pvDouble; + PIProtectedVariable pvString; + + pvInt.set(123); + pvDouble.set(3.14159); + pvString.set("Hello, World!"); + + EXPECT_EQ(pvInt.get(), 123); + EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159); + EXPECT_EQ(pvString.get(), "Hello, World!"); +} + +TEST(PIProtectedVariable_Basic, GetReturnsCopy) { + PIProtectedVariable pv; + pv.set(100); + + int val1 = pv.get(); + int val2 = pv.get(); + + // Modifying copies should not affect the protected variable + val1 = 200; + val2 = 300; + + EXPECT_EQ(pv.get(), 100); + EXPECT_EQ(val1, 200); + EXPECT_EQ(val2, 300); +} + +TEST(PIProtectedVariable_Basic, GetReturnsCopyForObjectTypes) { + struct TestStruct { + int x; + int y; + TestStruct(): x(0), y(0) {} + TestStruct(int a, int b): x(a), y(b) {} + }; + + PIProtectedVariable pv; + pv.set(TestStruct(10, 20)); + + TestStruct val = pv.get(); + val.x = 100; + val.y = 200; + + // Original should be unchanged + TestStruct original = pv.get(); + EXPECT_EQ(original.x, 10); + EXPECT_EQ(original.y, 20); + + // Verify the copy was modified + EXPECT_EQ(val.x, 100); + EXPECT_EQ(val.y, 200); +} + +TEST(PIProtectedVariable_Basic, OperatorAssignmentWorks) { + PIProtectedVariable pv; + pv = 999; + EXPECT_EQ(pv.get(), 999); +} + +TEST(PIProtectedVariable_Basic, OperatorAssignmentReturnsReference) { + PIProtectedVariable pv; + PIProtectedVariable & ref = (pv = 42); + EXPECT_EQ(&ref, &pv); + EXPECT_EQ(pv.get(), 42); +} + +TEST(PIProtectedVariable_Basic, EmptyDefaultConstructedVariable) { + // Note: PIProtectedVariable does not value-initialize its internal var + // We can only test that it's constructible and works after setting values + PIProtectedVariable pv; + pv.set(0); // Set to zero explicitly + EXPECT_EQ(pv.get(), 0); + + PIProtectedVariable pvStr; + pvStr.set(""); // Set to empty explicitly + EXPECT_TRUE(pvStr.get().empty()); +} + +TEST(PIProtectedVariable_Basic, SetWithMoveSemantics) { + PIProtectedVariable pv; + string movable = "This is a movable string"; + pv.set(std::move(movable)); + EXPECT_EQ(pv.get(), "This is a movable string"); +} + +//! \~\brief Thread safety tests +//! \~\english Tests for concurrent access safety +//! \~\russian Тесты потокобезопасности при конкурентном доступе +TEST(PIProtectedVariable_ThreadSafety, ConcurrentSetOperations) { + PIProtectedVariable pv; + atomic start(false); + + vector threads; + for (int i = 0; i < THREAD_COUNT; ++i) { + threads.emplace_back([&pv, &start, i]() { + while (!start.load()) { + this_thread::yield(); + } + for (int j = 0; j < ITERATIONS_PER_THREAD; ++j) { + pv.set(i * ITERATIONS_PER_THREAD + j); + } + }); + } + + start.store(true); + for (auto & t: threads) { + t.join(); + } + + // The final value should be one of the values set by the threads + // We can't predict which one, but it should be valid + int finalVal = pv.get(); + EXPECT_GE(finalVal, 0); + EXPECT_LT(finalVal, THREAD_COUNT * ITERATIONS_PER_THREAD); +} + +TEST(PIProtectedVariable_ThreadSafety, ConcurrentGetOperations) { + PIProtectedVariable pv; + pv.set(42); + atomic readCount(0); + atomic wrongCount(0); + atomic start(false); + + vector threads; + for (int i = 0; i < THREAD_COUNT; ++i) { + threads.emplace_back([&pv, &readCount, &wrongCount, &start]() { + while (!start.load()) { + this_thread::yield(); + } + for (int j = 0; j < ITERATIONS_PER_THREAD; ++j) { + int val = pv.get(); + readCount++; + if (val != 42) { + wrongCount++; + } + } + }); + } + + start.store(true); + for (auto & t: threads) { + t.join(); + } + + EXPECT_EQ(readCount, THREAD_COUNT * ITERATIONS_PER_THREAD); + EXPECT_EQ(wrongCount, 0); +} + +TEST(PIProtectedVariable_ThreadSafety, MixedReadWriteOperations) { + PIProtectedVariable pv; + pv.set(0); // Initialize with a valid value + atomic counter(0); + atomic readCount(0); + atomic invalidReads(0); + atomic start(false); + const int NUM_WRITER_ITERATIONS = 1000; + + // Writer thread + thread writer([&pv, &counter, &start]() { + while (!start.load()) { + this_thread::yield(); + } + for (int i = 0; i < NUM_WRITER_ITERATIONS; ++i) { + int val = counter.fetch_add(1) + 1; + pv.set(val); + } + }); + + // Reader threads + vector readers; + for (int i = 0; i < THREAD_COUNT; ++i) { + readers.emplace_back([&pv, &readCount, &invalidReads, &start, NUM_WRITER_ITERATIONS]() { + while (!start.load()) { + this_thread::yield(); + } + for (int j = 0; j < NUM_WRITER_ITERATIONS; ++j) { + int val = pv.get(); + readCount++; + // Verify value is within expected range (0 to NUM_WRITER_ITERATIONS) + if (val < 0 || val > NUM_WRITER_ITERATIONS) { + invalidReads++; + } + } + }); + } + + start.store(true); + writer.join(); + for (auto & t: readers) { + t.join(); + } + + // Verify that counter reached expected value + EXPECT_EQ(counter, NUM_WRITER_ITERATIONS); + + // Verify that all reads were valid + EXPECT_EQ(readCount, THREAD_COUNT * NUM_WRITER_ITERATIONS); + EXPECT_EQ(invalidReads, 0); +} + +TEST(PIProtectedVariable_ThreadSafety, HeavyConcurrentAccess) { + PIProtectedVariable pv; + pv.set(0); // Initialize with a valid value + atomic writeCount(0); + atomic readCount(0); + atomic done(false); + atomic start(false); + + // Multiple writers + vector writers; + for (int i = 0; i < THREAD_COUNT / 2; ++i) { + writers.emplace_back([&pv, &writeCount, &done, &start, i]() { + while (!start.load()) { + this_thread::yield(); + } + while (!done.load()) { + pv.set(i * 1000 + writeCount.fetch_add(1)); + } + }); + } + + // Multiple readers + vector readers; + for (int i = 0; i < THREAD_COUNT / 2; ++i) { + readers.emplace_back([&pv, &readCount, &done, &start]() { + while (!start.load()) { + this_thread::yield(); + } + while (!done.load()) { + int val = pv.get(); + readCount++; + // Verify value is non-negative (writers only write positive values) + if (val < 0) { + FAIL() << "Invalid value read: " << val; + } + } + }); + } + + start.store(true); + + // Run for a short time + this_thread::sleep_for(chrono::milliseconds(500)); + done.store(true); + + for (auto & t: writers) { + t.join(); + } + for (auto & t: readers) { + t.join(); + } + + EXPECT_GT(writeCount, 0); + EXPECT_GT(readCount, 0); +} + +//! \~\brief Pointer wrapper tests +//! \~\english Tests for getRef() and Pointer wrapper functionality +//! \~\russian Тесты обертки getRef() и Pointer +TEST(PIProtectedVariable_Pointer, GetRefReturnsValidPointer) { + PIProtectedVariable pv; + pv.set(42); + + auto ptr = pv.getRef(); + EXPECT_NE(&(*ptr), nullptr); + EXPECT_EQ(*ptr, 42); +} + +TEST(PIProtectedVariable_Pointer, OperatorArrowWorks) { + struct TestStruct { + int x = 10; + int y = 20; + int getValue() const { return x + y; } + }; + + PIProtectedVariable pv; + pv.set(TestStruct()); + + auto ptr = pv.getRef(); + EXPECT_EQ(ptr->x, 10); + EXPECT_EQ(ptr->y, 20); + EXPECT_EQ(ptr->getValue(), 30); +} + +TEST(PIProtectedVariable_Pointer, OperatorDerefWorks) { + PIProtectedVariable pv; + pv.set("original"); + + auto ptr = pv.getRef(); + *ptr = "modified"; + + EXPECT_EQ(pv.get(), "modified"); +} + +TEST(PIProtectedVariable_Pointer, OperatorDerefAllowsModification) { + PIProtectedVariable pv; + pv.set(100); + + auto ptr = pv.getRef(); + (*ptr) += 50; + + EXPECT_EQ(pv.get(), 150); +} + +TEST(PIProtectedVariable_Pointer, MutexLockedWhenPointerCreated) { + PIProtectedVariable pv; + pv.set(42); + + atomic gotRef(false); + atomic start(false); + + // Start blocker thread but don't let it run yet + thread blocker([&pv, &gotRef, &start]() { + while (!start.load()) { + this_thread::yield(); + } + auto p = pv.getRef(); + gotRef.store(true); + }); + + // Get the lock in main thread + auto ptr = pv.getRef(); + + // Now let blocker try to acquire + start.store(true); + + // Give the thread time to try to acquire the lock + this_thread::sleep_for(chrono::milliseconds(100)); + + // The blocker should NOT have acquired the lock yet + EXPECT_FALSE(gotRef.load()); + + // Release the first pointer by letting it go out of scope + ptr.~Pointer(); + + blocker.join(); + EXPECT_TRUE(gotRef.load()); +} + +TEST(PIProtectedVariable_Pointer, MutexUnlockedWhenPointerDestroyed) { + PIProtectedVariable pv; + pv.set(42); + + { + auto ptr = pv.getRef(); + // ptr holds the lock + (*ptr) = 100; + } // ptr destroyed here, lock released + + // Now we should be able to get another reference immediately + auto ptr2 = pv.getRef(); + EXPECT_EQ(*ptr2, 100); +} + +TEST(PIProtectedVariable_Pointer, PointerModifiesProtectedValue) { + PIProtectedVariable pv; + pv.set("Hello"); + + { + auto ptr = pv.getRef(); + *ptr += " World"; + } + + EXPECT_EQ(pv.get(), "Hello World"); +} + +TEST(PIProtectedVariable_Pointer, PointerWithComplexTypes) { + struct ComplexType { + vector data; + map mapping; + + ComplexType() { + data = {1, 2, 3, 4, 5}; + mapping["one"] = 1; + mapping["two"] = 2; + } + }; + + PIProtectedVariable pv; + pv.set(ComplexType()); + + auto ptr = pv.getRef(); + ptr->data.push_back(6); + ptr->mapping["three"] = 3; + + EXPECT_EQ(pv.get().data.size(), 6u); + EXPECT_EQ(pv.get().mapping.size(), 3u); + EXPECT_EQ(pv.get().mapping["three"], 3); +} + +//! \~\brief Move semantics tests +//! \~\english Tests for move constructor and lock ownership transfer +//! \~\russian Тесты конструктора перемещения и передачи владения блокировкой +TEST(PIProtectedVariable_Move, MoveConstructorWorks) { + PIProtectedVariable pv; + pv.set(42); + + auto ptr1 = pv.getRef(); + auto ptr2 = std::move(ptr1); + + // ptr2 should have access + EXPECT_EQ(*ptr2, 42); + *ptr2 = 100; + EXPECT_EQ(pv.get(), 100); +} + +TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) { + PIProtectedVariable pv; + pv.set(42); + + atomic unlockCount(0); + + // We can't directly test unlock count without modifying the class, + // but we can test the behavior: after move, only the target should unlock + + auto ptr1 = pv.getRef(); + + // ptr1 holds the lock + bool canAcquire = false; + thread tryAcquire([&pv, &canAcquire]() { + auto p = pv.getRef(); + canAcquire = true; + }); + + this_thread::sleep_for(chrono::milliseconds(50)); + EXPECT_FALSE(canAcquire); // Lock still held by ptr1 + + // Move ptr1 to ptr2 + auto ptr2 = std::move(ptr1); + + // ptr2 should still hold the lock + this_thread::sleep_for(chrono::milliseconds(50)); + EXPECT_FALSE(canAcquire); // Lock still held (now by ptr2) + + // Destroy ptr2 + ptr2.~Pointer(); + + tryAcquire.join(); + EXPECT_TRUE(canAcquire); // Now lock is free +} + +TEST(PIProtectedVariable_Move, SourceDoesNotUnlockAfterMove) { + PIProtectedVariable pv; + pv.set(42); + + atomic deadlockOccurred(false); + + // If source unlocked after move, we would get a double-unlock error + // or undefined behavior. This test verifies that moving works correctly. + + auto ptr1 = pv.getRef(); + (*ptr1) = 100; + + // Move ptr1 + auto ptr2 = std::move(ptr1); + + // ptr2 should work correctly + (*ptr2) = 200; + EXPECT_EQ(pv.get(), 200); + + // When ptr1 is destroyed, it should NOT unlock (ownsLock should be false) + // When ptr2 is destroyed, it SHOULD unlock + // This should complete without any errors +} + +TEST(PIProtectedVariable_Move, TargetUnlocksInDestructor) { + PIProtectedVariable pv; + pv.set(42); + + bool lockReleased = false; + + { + auto ptr1 = pv.getRef(); + auto ptr2 = std::move(ptr1); + // ptr2 holds the lock, ptr1 does not + } // ptr2 destroyed here, should release lock + + // If we get here without deadlock, the lock was properly released + auto ptr3 = pv.getRef(); + lockReleased = true; + + EXPECT_TRUE(lockReleased); +} + +TEST(PIProtectedVariable_Move, UnlockCalledExactlyOnceAfterMove) { + PIProtectedVariable pv; + pv.set(42); + + // Test that after move: + // 1. Source Pointer's destructor does NOT call unlock() + // 2. Target Pointer's destructor DOES call unlock() + // 3. Total unlock() calls = 1 (not 2!) + + { + auto ptr1 = pv.getRef(); + auto ptr2 = std::move(ptr1); + // ptr1.ownsLock should be false + // ptr2.ownsLock should be true + + // Both go out of scope + // Only ptr2 should unlock + } + + // If we get here without issues, exactly one unlock happened + EXPECT_EQ(pv.get(), 42); +} + +TEST(PIProtectedVariable_Move, MoveWithRVODisabled) { + // This test should be compiled with -fno-elide-constructors + // to ensure move constructor is actually called + + PIProtectedVariable pv; + pv.set(42); + + auto createAndMove = [&pv]() -> decltype(pv.getRef()) { + auto ptr = pv.getRef(); + return std::move(ptr); + }; + + auto ptr = createAndMove(); + *ptr = 100; + + EXPECT_EQ(pv.get(), 100); +} + +TEST(PIProtectedVariable_Move, MoveConstructorWithComplexTypes) { + struct ComplexType { + vector data; + string name; + + ComplexType(): data{1, 2, 3}, name("test") {} + }; + + PIProtectedVariable pv; + pv.set(ComplexType()); + + auto ptr1 = pv.getRef(); + ptr1->data.push_back(4); + ptr1->name = "modified"; + + auto ptr2 = std::move(ptr1); + ptr2->data.push_back(5); + + EXPECT_EQ(pv.get().data.size(), 5u); + EXPECT_EQ(pv.get().name, "modified"); +} + +//! \~\brief Copy prevention tests +//! \~\english Tests that verify copy operations are deleted +//! \~\russian Тесты проверки что операции копирования удалены +TEST(PIProtectedVariable_CopyPrevention, CopyConstructorDeleted) { + PIProtectedVariable pv; + pv.set(42); + + auto ptr = pv.getRef(); + + // This should not compile: + // auto ptr2 = ptr; // Copy constructor is deleted + + // We verify this at compile time by ensuring the code compiles + // without attempting to copy + EXPECT_EQ(*ptr, 42); +} + +TEST(PIProtectedVariable_CopyPrevention, CopyAssignmentDeleted) { + PIProtectedVariable pv; + pv.set(42); + + auto ptr1 = pv.getRef(); + auto ptr2 = pv.getRef(); // This will block until ptr1 is released + + // This should not compile: + // ptr1 = ptr2; // Copy assignment is deleted + + (*ptr2) = 100; + EXPECT_EQ(pv.get(), 100); +} + +TEST(PIProtectedVariable_CopyPrevention, MoveAssignmentDeleted) { + PIProtectedVariable pv; + pv.set(42); + + auto ptr1 = pv.getRef(); + auto ptr2 = pv.getRef(); // This will block until ptr1 is released + + // This should not compile: + // ptr1 = std::move(ptr2); // Move assignment is deleted + + (*ptr2) = 100; + EXPECT_EQ(pv.get(), 100); +} + +TEST(PIProtectedVariable_CopyPrevention, CompileTimeCheck) { + // Compile-time verification that copy operations are deleted + // These static_assert statements will fail to compile if copy operations exist + + PIProtectedVariable pv; + + // The fact that this file compiles proves that: + // 1. Pointer copy constructor is deleted + // 2. Pointer copy assignment is deleted + // 3. Pointer move assignment is deleted + + EXPECT_TRUE(true); // Placeholder assertion +} + +//! \~\brief Edge case tests +//! \~\english Tests for edge cases and exception safety +//! \~\russian Тесты граничных случаев и исключительной безопасности +TEST(PIProtectedVariable_EdgeCases, MultipleGetRefCallsSequential) { + PIProtectedVariable pv; + pv.set(0); + + for (int i = 0; i < 100; ++i) { + auto ptr = pv.getRef(); + (*ptr)++; + } + + EXPECT_EQ(pv.get(), 100); +} + +TEST(PIProtectedVariable_EdgeCases, NestedGetRefInSeparateScopes) { + PIProtectedVariable pv; + pv.set(0); + + { + auto ptr1 = pv.getRef(); + (*ptr1) += 10; + } + + { + auto ptr2 = pv.getRef(); + (*ptr2) += 20; + } + + { + auto ptr3 = pv.getRef(); + (*ptr3) += 30; + } + + EXPECT_EQ(pv.get(), 60); +} + +TEST(PIProtectedVariable_EdgeCases, LargeNumberOfThreads) { + PIProtectedVariable pv; + atomic counter(0); + atomic start(false); + const int NUM_THREADS = 50; + + vector threads; + for (int i = 0; i < NUM_THREADS; ++i) { + threads.emplace_back([&pv, &counter, &start]() { + while (!start.load()) { + this_thread::yield(); + } + for (int j = 0; j < 100; ++j) { + int val = counter.fetch_add(1); + pv.set(val); + int readVal = pv.get(); + EXPECT_GE(readVal, 0); + } + }); + } + + start.store(true); + for (auto & t: threads) { + t.join(); + } + + EXPECT_EQ(counter, NUM_THREADS * 100); +} + +TEST(PIProtectedVariable_EdgeCases, ValueInitializationOfDifferentTypes) { + // Note: PIProtectedVariable does not value-initialize its internal var + // We test that setting and getting works for different types + PIProtectedVariable pvInt; + PIProtectedVariable pvDouble; + PIProtectedVariable pvBool; + PIProtectedVariable pvString; + PIProtectedVariable pvVoidPtr; + + pvInt.set(0); + pvDouble.set(0.0); + pvBool.set(false); + pvString.set(""); + pvVoidPtr.set(nullptr); + + EXPECT_EQ(pvInt.get(), 0); + EXPECT_DOUBLE_EQ(pvDouble.get(), 0.0); + EXPECT_FALSE(pvBool.get()); + EXPECT_TRUE(pvString.get().empty()); + EXPECT_EQ(pvVoidPtr.get(), nullptr); +} + +TEST(PIProtectedVariable_EdgeCases, NegativeAndExtremeValues) { + PIProtectedVariable pv; + + pv.set(INT_MIN); + EXPECT_EQ(pv.get(), INT_MIN); + + pv.set(INT_MAX); + EXPECT_EQ(pv.get(), INT_MAX); + + pv.set(-1); + EXPECT_EQ(pv.get(), -1); + + pv.set(0); + EXPECT_EQ(pv.get(), 0); +} + +TEST(PIProtectedVariable_EdgeCases, EmptyStringAndContainer) { + PIProtectedVariable pvStr; + PIProtectedVariable> pvVec; + + pvStr.set(""); + EXPECT_TRUE(pvStr.get().empty()); + + pvVec.set(vector()); + EXPECT_TRUE(pvVec.get().empty()); +} + +TEST(PIProtectedVariable_EdgeCases, PointerLifetime) { + PIProtectedVariable pv; + pv.set(42); + + int * rawPtr = nullptr; + { + auto ptr = pv.getRef(); + rawPtr = &(*ptr); + // ptr destroyed here + } + + // rawPtr still points to valid memory (pv.var) + // but we shouldn't modify through it without holding the lock + EXPECT_EQ(*rawPtr, 42); +} + +//! \~\brief Exception safety tests +//! \~\english Tests for exception safety +//! \~\russian Тесты исключительной безопасности +TEST(PIProtectedVariable_ExceptionSafety, SetWithMoveDoesNotLeaveInBadState) { + struct MovesOnly { + int value; + MovesOnly(): value(0) {} + MovesOnly(int v): value(v) {} + MovesOnly(const MovesOnly &) = delete; // Copy is deleted + MovesOnly(MovesOnly &&) noexcept = default; + MovesOnly & operator=(const MovesOnly &) = delete; + MovesOnly & operator=(MovesOnly &&) noexcept = default; + }; + + PIProtectedVariable pv; + pv.set(MovesOnly(42)); + + // Use getRef to access the value since get() requires copy + auto ptr = pv.getRef(); + EXPECT_EQ(ptr->value, 42); +} + +TEST(PIProtectedVariable_ExceptionSafety, GetRefDoesNotLeakLockOnException) { + PIProtectedVariable pv; + pv.set(0); + + bool exceptionThrown = false; + try { + auto ptr = pv.getRef(); + (*ptr) = 100; + // ptr goes out of scope normally, lock released + } catch (...) { + exceptionThrown = true; + } + + EXPECT_FALSE(exceptionThrown); + EXPECT_EQ(pv.get(), 100); + + // If lock was leaked, this would deadlock + auto ptr2 = pv.getRef(); + EXPECT_EQ(*ptr2, 100); +} + +//! \~\brief Integration tests +//! \~\english Integration tests combining multiple features +//! \~\russian Интеграционные тесты, сочетающие несколько функций +TEST(PIProtectedVariable_Integration, ProducerConsumerPattern) { + PIProtectedVariable buffer; + atomic done(false); + atomic produced(0); + atomic consumed(0); + atomic start(false); + const int NUM_ITEMS = 1000; + + // Producer + thread producer([&]() { + while (!start.load()) { + this_thread::yield(); + } + for (int i = 0; i < NUM_ITEMS; ++i) { + auto ptr = buffer.getRef(); + *ptr = i; + produced++; + } + done.store(true); + }); + + // Consumer - consumes exactly NUM_ITEMS + thread consumer([&]() { + while (!start.load()) { + this_thread::yield(); + } + for (int i = 0; i < NUM_ITEMS; ++i) { + // Wait for producer to produce something + while (produced.load() <= consumed.load() && !done.load()) { + this_thread::yield(); + } + auto ptr = buffer.getRef(); + int val = *ptr; + consumed++; + (void)val; // Suppress unused warning + } + }); + + start.store(true); + producer.join(); + consumer.join(); + + EXPECT_EQ(produced, NUM_ITEMS); + EXPECT_EQ(consumed, NUM_ITEMS); +} + +TEST(PIProtectedVariable_Integration, ReadMostlyPattern) { + PIProtectedVariable config; + config.set(100); + + atomic totalReads(0); + atomic wrongReads(0); + atomic start(false); + atomic done(false); + + // Readers + vector readers; + for (int i = 0; i < THREAD_COUNT; ++i) { + readers.emplace_back([&]() { + while (!start.load()) { + this_thread::yield(); + } + while (!done.load()) { + int val = config.get(); + totalReads++; + if (val < 100 || val > 200) { + wrongReads++; + } + } + }); + } + + // Writer + thread writer([&]() { + while (!start.load()) { + this_thread::yield(); + } + for (int i = 0; i < 100; ++i) { + config.set(100 + i); + this_thread::sleep_for(chrono::milliseconds(10)); + } + done.store(true); + }); + + start.store(true); + writer.join(); + for (auto & t: readers) { + t.join(); + } + + EXPECT_GT(totalReads, 0); + EXPECT_EQ(wrongReads, 0); +} From ac877f10240f547b6d5e3e3dc264e208c19b4d7b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 18:13:23 +0300 Subject: [PATCH 03/26] fixes --- libs/main/thread/piprotectedvariable.h | 35 ++++++++++++++--------- main.cpp | 21 ++++++++++++++ tests/thread/piprotectedvariable_test.cpp | 20 ++++--------- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 7c3f246c..650d79f5 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -25,6 +25,7 @@ #ifndef PIPROTECTEDVARIABLE_H #define PIPROTECTEDVARIABLE_H +#include "picout.h" #include "pimutex.h" //! \~\ingroup Thread @@ -34,6 +35,8 @@ template class PIP_EXPORT PIProtectedVariable { public: + PIProtectedVariable(T v = T()): var(std::move(v)) {} + //! \~\brief //! \~english Pointer-like wrapper returned by \a getRef() while the protected value remains locked. //! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным. @@ -44,17 +47,23 @@ public: public: //! \~\english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. - Pointer(Pointer && other) noexcept: pv(other.pv), ownsLock(other.ownsLock) { other.ownsLock = false; } + Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; - //! \~\english Move assignment is deleted - Pointer can only be moved once. - //! \~russian Оператор перемещения удален - Pointer можно переместить только один раз. - Pointer & operator=(Pointer &&) = delete; + //! \~\english Move assignment - transfers ownership of the lock. + //! \~russian Оператор перемещения - передает владение блокировкой. + Pointer & operator=(Pointer && other) { + pv = other.pv; + other.can_unlock = false; + }; //! \~\english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. ~Pointer() { - if (ownsLock) pv.mutex.unlock(); + if (can_unlock) { + pv.mutex.unlock(); + piCout << "mutex.unlock()" << &(pv.mutex); + } } //! \~english Returns pointer access to the protected value. @@ -66,11 +75,14 @@ public: T & operator*() { return pv.var; } private: - Pointer() = delete; - Pointer(PIProtectedVariable & v): pv(v), ownsLock(true) {} + explicit Pointer() = delete; + explicit Pointer(PIProtectedVariable & v): pv(v) { + pv.mutex.lock(); + piCout << "mutex.lock()" << &(pv.mutex); + } PIProtectedVariable & pv; - bool ownsLock = true; + bool can_unlock = true; }; //! \~english Replaces the protected value with \a v. @@ -82,10 +94,7 @@ public: //! \~english Locks the value and returns wrapper-based access to it. //! \~russian Блокирует значение и возвращает обертку для доступа к нему. - Pointer getRef() { - mutex.lock(); - return Pointer(*this); - } + Pointer getRef() { return Pointer(*this); } //! \~english Returns a copy of the protected value. //! \~russian Возвращает копию защищенного значения. @@ -104,7 +113,7 @@ public: private: mutable PIMutex mutex; - T var; + T var = {}; }; diff --git a/main.cpp b/main.cpp index cf2f6235..fd98c891 100644 --- a/main.cpp +++ b/main.cpp @@ -20,6 +20,27 @@ inline PIByteArray SMBusTypeInfo_genHash(PIString n) { int main(int argc, char * argv[]) { + PIProtectedVariable pv(3.0); + piCout << pv.get(); + { + auto ref = pv.getRef(); + piCout << *ref; + *ref = 11.; + piCout << *ref; + } + piCout << pv.get(); + { + auto ref = pv.getRef(); + piCout << *ref; + *ref = 12.; + piCout << *ref; + auto ref2 = std::move(ref); + piCout << *ref2; + } + piCout << pv.get(); + return 0; + + PICrypt _crypt; // auto ba = PIFile::readAll("logo.png"); PIString str = "hello!"_a; diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index e9bdc1ff..d0b15008 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -35,7 +34,6 @@ namespace { constexpr int THREAD_COUNT = 10; constexpr int ITERATIONS_PER_THREAD = 1000; -constexpr int WAIT_THREAD_TIME_MS = 1000; } // anonymous namespace @@ -229,7 +227,7 @@ TEST(PIProtectedVariable_ThreadSafety, MixedReadWriteOperations) { // Reader threads vector readers; for (int i = 0; i < THREAD_COUNT; ++i) { - readers.emplace_back([&pv, &readCount, &invalidReads, &start, NUM_WRITER_ITERATIONS]() { + readers.emplace_back([&pv, &readCount, &invalidReads, &start]() { while (!start.load()) { this_thread::yield(); } @@ -468,8 +466,6 @@ TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) { PIProtectedVariable pv; pv.set(42); - atomic unlockCount(0); - // We can't directly test unlock count without modifying the class, // but we can test the behavior: after move, only the target should unlock @@ -503,8 +499,6 @@ TEST(PIProtectedVariable_Move, SourceDoesNotUnlockAfterMove) { PIProtectedVariable pv; pv.set(42); - atomic deadlockOccurred(false); - // If source unlocked after move, we would get a double-unlock error // or undefined behavior. This test verifies that moving works correctly. @@ -569,15 +563,11 @@ TEST(PIProtectedVariable_Move, MoveWithRVODisabled) { // This test should be compiled with -fno-elide-constructors // to ensure move constructor is actually called - PIProtectedVariable pv; - pv.set(42); + PIProtectedVariable pv(42); - auto createAndMove = [&pv]() -> decltype(pv.getRef()) { - auto ptr = pv.getRef(); - return std::move(ptr); - }; - - auto ptr = createAndMove(); + auto ptr_src = pv.getRef(); + *ptr_src = 99; + auto ptr = std::move(ptr_src); *ptr = 100; EXPECT_EQ(pv.get(), 100); From 9f57f0107e96b351d54eb2f672791381c05b58fa Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 18:34:48 +0300 Subject: [PATCH 04/26] remove picout --- libs/main/thread/piprotectedvariable.h | 22 ++++++---------------- main.cpp | 4 ++-- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 650d79f5..754eb4e3 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -25,7 +25,6 @@ #ifndef PIPROTECTEDVARIABLE_H #define PIPROTECTEDVARIABLE_H -#include "picout.h" #include "pimutex.h" //! \~\ingroup Thread @@ -35,6 +34,8 @@ template class PIP_EXPORT PIProtectedVariable { public: + //! \~english Constructs %PIProtectedVariable and initialize variable by value `v`. + //! \~russian Создает %PIProtectedVariable и инициализирует переменную значением `v`. PIProtectedVariable(T v = T()): var(std::move(v)) {} //! \~\brief @@ -43,26 +44,18 @@ public: class PIP_EXPORT Pointer { friend class PIProtectedVariable; NO_COPY_CLASS(Pointer); + Pointer & operator=(Pointer && other) = delete; public: - //! \~\english Move constructor - transfers ownership of the lock. + //! \~english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; - - //! \~\english Move assignment - transfers ownership of the lock. - //! \~russian Оператор перемещения - передает владение блокировкой. - Pointer & operator=(Pointer && other) { - pv = other.pv; - other.can_unlock = false; - }; - - //! \~\english Destroys wrapper and releases the mutex. + //! \~english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. ~Pointer() { if (can_unlock) { pv.mutex.unlock(); - piCout << "mutex.unlock()" << &(pv.mutex); } } @@ -76,10 +69,7 @@ public: private: explicit Pointer() = delete; - explicit Pointer(PIProtectedVariable & v): pv(v) { - pv.mutex.lock(); - piCout << "mutex.lock()" << &(pv.mutex); - } + explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); } PIProtectedVariable & pv; bool can_unlock = true; diff --git a/main.cpp b/main.cpp index fd98c891..7dda6513 100644 --- a/main.cpp +++ b/main.cpp @@ -23,9 +23,9 @@ int main(int argc, char * argv[]) { PIProtectedVariable pv(3.0); piCout << pv.get(); { - auto ref = pv.getRef(); + auto ref = pv.getRef(); piCout << *ref; - *ref = 11.; + *ref = 11.; piCout << *ref; } piCout << pv.get(); From fe01c353e681e87c0513b02e0b2f0f9b0c91dfce Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 18:56:20 +0300 Subject: [PATCH 05/26] simplify tests --- tests/thread/piprotectedvariable_test.cpp | 926 ++-------------------- 1 file changed, 68 insertions(+), 858 deletions(-) diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index d0b15008..09f52d05 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -18,909 +18,119 @@ along with this program. If not, see . */ -#include "pimutex.h" #include "piprotectedvariable.h" +#include "pistring.h" +#include "pithread.h" #include "gtest/gtest.h" #include -#include -#include -#include -#include using namespace std; -namespace { - -constexpr int THREAD_COUNT = 10; -constexpr int ITERATIONS_PER_THREAD = 1000; - -} // anonymous namespace - -//! \~\brief Basic functionality tests -//! \~\english Tests for basic constructor, set, get, and operator= functionality -//! \~\russian Тесты базовой функциональности конструктора, set, get и оператора= -TEST(PIProtectedVariable_Basic, ConstructorCreatesValidObject) { - PIProtectedVariable pv; - // Note: PIProtectedVariable does not value-initialize its internal var, - // so we just verify that the object is constructible and we can set/get values - pv.set(42); - EXPECT_EQ(pv.get(), 42); -} - -TEST(PIProtectedVariable_Basic, SetCorrectlySetsValue) { - PIProtectedVariable pv; - pv.set(42); - EXPECT_EQ(pv.get(), 42); -} - -TEST(PIProtectedVariable_Basic, SetWithDifferentTypes) { +// Basic functionality tests +TEST(PIProtectedVariable_Basic, BasicFunctionality) { + // Test basic set/get with different types PIProtectedVariable pvInt; PIProtectedVariable pvDouble; - PIProtectedVariable pvString; + PIProtectedVariable pvString; pvInt.set(123); pvDouble.set(3.14159); - pvString.set("Hello, World!"); + pvString.set(PIString("Hello, World!")); EXPECT_EQ(pvInt.get(), 123); EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159); - EXPECT_EQ(pvString.get(), "Hello, World!"); -} + EXPECT_EQ(pvString.get(), PIString("Hello, World!")); -TEST(PIProtectedVariable_Basic, GetReturnsCopy) { - PIProtectedVariable pv; - pv.set(100); + // Test operator= + pvInt = 999; + EXPECT_EQ(pvInt.get(), 999); - int val1 = pv.get(); - int val2 = pv.get(); - - // Modifying copies should not affect the protected variable - val1 = 200; - val2 = 300; - - EXPECT_EQ(pv.get(), 100); - EXPECT_EQ(val1, 200); - EXPECT_EQ(val2, 300); -} - -TEST(PIProtectedVariable_Basic, GetReturnsCopyForObjectTypes) { - struct TestStruct { - int x; - int y; - TestStruct(): x(0), y(0) {} - TestStruct(int a, int b): x(a), y(b) {} - }; - - PIProtectedVariable pv; - pv.set(TestStruct(10, 20)); - - TestStruct val = pv.get(); - val.x = 100; - val.y = 200; - - // Original should be unchanged - TestStruct original = pv.get(); - EXPECT_EQ(original.x, 10); - EXPECT_EQ(original.y, 20); - - // Verify the copy was modified - EXPECT_EQ(val.x, 100); - EXPECT_EQ(val.y, 200); -} - -TEST(PIProtectedVariable_Basic, OperatorAssignmentWorks) { - PIProtectedVariable pv; - pv = 999; - EXPECT_EQ(pv.get(), 999); -} - -TEST(PIProtectedVariable_Basic, OperatorAssignmentReturnsReference) { - PIProtectedVariable pv; - PIProtectedVariable & ref = (pv = 42); - EXPECT_EQ(&ref, &pv); - EXPECT_EQ(pv.get(), 42); -} - -TEST(PIProtectedVariable_Basic, EmptyDefaultConstructedVariable) { - // Note: PIProtectedVariable does not value-initialize its internal var - // We can only test that it's constructible and works after setting values - PIProtectedVariable pv; - pv.set(0); // Set to zero explicitly - EXPECT_EQ(pv.get(), 0); - - PIProtectedVariable pvStr; - pvStr.set(""); // Set to empty explicitly - EXPECT_TRUE(pvStr.get().empty()); -} - -TEST(PIProtectedVariable_Basic, SetWithMoveSemantics) { - PIProtectedVariable pv; - string movable = "This is a movable string"; - pv.set(std::move(movable)); - EXPECT_EQ(pv.get(), "This is a movable string"); -} - -//! \~\brief Thread safety tests -//! \~\english Tests for concurrent access safety -//! \~\russian Тесты потокобезопасности при конкурентном доступе -TEST(PIProtectedVariable_ThreadSafety, ConcurrentSetOperations) { - PIProtectedVariable pv; - atomic start(false); - - vector threads; - for (int i = 0; i < THREAD_COUNT; ++i) { - threads.emplace_back([&pv, &start, i]() { - while (!start.load()) { - this_thread::yield(); - } - for (int j = 0; j < ITERATIONS_PER_THREAD; ++j) { - pv.set(i * ITERATIONS_PER_THREAD + j); - } - }); - } - - start.store(true); - for (auto & t: threads) { - t.join(); - } - - // The final value should be one of the values set by the threads - // We can't predict which one, but it should be valid - int finalVal = pv.get(); - EXPECT_GE(finalVal, 0); - EXPECT_LT(finalVal, THREAD_COUNT * ITERATIONS_PER_THREAD); -} - -TEST(PIProtectedVariable_ThreadSafety, ConcurrentGetOperations) { - PIProtectedVariable pv; - pv.set(42); - atomic readCount(0); - atomic wrongCount(0); - atomic start(false); - - vector threads; - for (int i = 0; i < THREAD_COUNT; ++i) { - threads.emplace_back([&pv, &readCount, &wrongCount, &start]() { - while (!start.load()) { - this_thread::yield(); - } - for (int j = 0; j < ITERATIONS_PER_THREAD; ++j) { - int val = pv.get(); - readCount++; - if (val != 42) { - wrongCount++; - } - } - }); - } - - start.store(true); - for (auto & t: threads) { - t.join(); - } - - EXPECT_EQ(readCount, THREAD_COUNT * ITERATIONS_PER_THREAD); - EXPECT_EQ(wrongCount, 0); -} - -TEST(PIProtectedVariable_ThreadSafety, MixedReadWriteOperations) { - PIProtectedVariable pv; - pv.set(0); // Initialize with a valid value - atomic counter(0); - atomic readCount(0); - atomic invalidReads(0); - atomic start(false); - const int NUM_WRITER_ITERATIONS = 1000; - - // Writer thread - thread writer([&pv, &counter, &start]() { - while (!start.load()) { - this_thread::yield(); - } - for (int i = 0; i < NUM_WRITER_ITERATIONS; ++i) { - int val = counter.fetch_add(1) + 1; - pv.set(val); - } - }); - - // Reader threads - vector readers; - for (int i = 0; i < THREAD_COUNT; ++i) { - readers.emplace_back([&pv, &readCount, &invalidReads, &start]() { - while (!start.load()) { - this_thread::yield(); - } - for (int j = 0; j < NUM_WRITER_ITERATIONS; ++j) { - int val = pv.get(); - readCount++; - // Verify value is within expected range (0 to NUM_WRITER_ITERATIONS) - if (val < 0 || val > NUM_WRITER_ITERATIONS) { - invalidReads++; - } - } - }); - } - - start.store(true); - writer.join(); - for (auto & t: readers) { - t.join(); - } - - // Verify that counter reached expected value - EXPECT_EQ(counter, NUM_WRITER_ITERATIONS); - - // Verify that all reads were valid - EXPECT_EQ(readCount, THREAD_COUNT * NUM_WRITER_ITERATIONS); - EXPECT_EQ(invalidReads, 0); -} - -TEST(PIProtectedVariable_ThreadSafety, HeavyConcurrentAccess) { - PIProtectedVariable pv; - pv.set(0); // Initialize with a valid value - atomic writeCount(0); - atomic readCount(0); - atomic done(false); - atomic start(false); - - // Multiple writers - vector writers; - for (int i = 0; i < THREAD_COUNT / 2; ++i) { - writers.emplace_back([&pv, &writeCount, &done, &start, i]() { - while (!start.load()) { - this_thread::yield(); - } - while (!done.load()) { - pv.set(i * 1000 + writeCount.fetch_add(1)); - } - }); - } - - // Multiple readers - vector readers; - for (int i = 0; i < THREAD_COUNT / 2; ++i) { - readers.emplace_back([&pv, &readCount, &done, &start]() { - while (!start.load()) { - this_thread::yield(); - } - while (!done.load()) { - int val = pv.get(); - readCount++; - // Verify value is non-negative (writers only write positive values) - if (val < 0) { - FAIL() << "Invalid value read: " << val; - } - } - }); - } - - start.store(true); - - // Run for a short time - this_thread::sleep_for(chrono::milliseconds(500)); - done.store(true); - - for (auto & t: writers) { - t.join(); - } - for (auto & t: readers) { - t.join(); - } - - EXPECT_GT(writeCount, 0); - EXPECT_GT(readCount, 0); -} - -//! \~\brief Pointer wrapper tests -//! \~\english Tests for getRef() and Pointer wrapper functionality -//! \~\russian Тесты обертки getRef() и Pointer -TEST(PIProtectedVariable_Pointer, GetRefReturnsValidPointer) { - PIProtectedVariable pv; - pv.set(42); - - auto ptr = pv.getRef(); - EXPECT_NE(&(*ptr), nullptr); - EXPECT_EQ(*ptr, 42); -} - -TEST(PIProtectedVariable_Pointer, OperatorArrowWorks) { + // Test getRef() with Pointer struct TestStruct { int x = 10; int y = 20; int getValue() const { return x + y; } }; - PIProtectedVariable pv; - pv.set(TestStruct()); + PIProtectedVariable pvStruct; - auto ptr = pv.getRef(); + auto ptr = pvStruct.getRef(); EXPECT_EQ(ptr->x, 10); EXPECT_EQ(ptr->y, 20); EXPECT_EQ(ptr->getValue(), 30); -} -TEST(PIProtectedVariable_Pointer, OperatorDerefWorks) { - PIProtectedVariable pv; - pv.set("original"); + // Modify through pointer + *ptr = TestStruct(); + ptr->x = 100; + EXPECT_EQ(pvStruct.get().x, 100); - auto ptr = pv.getRef(); - *ptr = "modified"; - - EXPECT_EQ(pv.get(), "modified"); -} - -TEST(PIProtectedVariable_Pointer, OperatorDerefAllowsModification) { - PIProtectedVariable pv; - pv.set(100); - - auto ptr = pv.getRef(); - (*ptr) += 50; - - EXPECT_EQ(pv.get(), 150); -} - -TEST(PIProtectedVariable_Pointer, MutexLockedWhenPointerCreated) { - PIProtectedVariable pv; - pv.set(42); - - atomic gotRef(false); - atomic start(false); - - // Start blocker thread but don't let it run yet - thread blocker([&pv, &gotRef, &start]() { - while (!start.load()) { - this_thread::yield(); - } - auto p = pv.getRef(); - gotRef.store(true); - }); - - // Get the lock in main thread - auto ptr = pv.getRef(); - - // Now let blocker try to acquire - start.store(true); - - // Give the thread time to try to acquire the lock - this_thread::sleep_for(chrono::milliseconds(100)); - - // The blocker should NOT have acquired the lock yet - EXPECT_FALSE(gotRef.load()); - - // Release the first pointer by letting it go out of scope - ptr.~Pointer(); - - blocker.join(); - EXPECT_TRUE(gotRef.load()); -} - -TEST(PIProtectedVariable_Pointer, MutexUnlockedWhenPointerDestroyed) { - PIProtectedVariable pv; - pv.set(42); - - { - auto ptr = pv.getRef(); - // ptr holds the lock - (*ptr) = 100; - } // ptr destroyed here, lock released - - // Now we should be able to get another reference immediately - auto ptr2 = pv.getRef(); - EXPECT_EQ(*ptr2, 100); -} - -TEST(PIProtectedVariable_Pointer, PointerModifiesProtectedValue) { - PIProtectedVariable pv; - pv.set("Hello"); - - { - auto ptr = pv.getRef(); - *ptr += " World"; - } - - EXPECT_EQ(pv.get(), "Hello World"); -} - -TEST(PIProtectedVariable_Pointer, PointerWithComplexTypes) { - struct ComplexType { - vector data; - map mapping; - - ComplexType() { - data = {1, 2, 3, 4, 5}; - mapping["one"] = 1; - mapping["two"] = 2; - } - }; - - PIProtectedVariable pv; - pv.set(ComplexType()); - - auto ptr = pv.getRef(); - ptr->data.push_back(6); - ptr->mapping["three"] = 3; - - EXPECT_EQ(pv.get().data.size(), 6u); - EXPECT_EQ(pv.get().mapping.size(), 3u); - EXPECT_EQ(pv.get().mapping["three"], 3); -} - -//! \~\brief Move semantics tests -//! \~\english Tests for move constructor and lock ownership transfer -//! \~\russian Тесты конструктора перемещения и передачи владения блокировкой -TEST(PIProtectedVariable_Move, MoveConstructorWorks) { - PIProtectedVariable pv; - pv.set(42); - - auto ptr1 = pv.getRef(); + // Test move semantics for Pointer + pvInt.set(42); + auto ptr1 = pvInt.getRef(); auto ptr2 = std::move(ptr1); - - // ptr2 should have access EXPECT_EQ(*ptr2, 42); *ptr2 = 100; - EXPECT_EQ(pv.get(), 100); + EXPECT_EQ(pvInt.get(), 100); } -TEST(PIProtectedVariable_Move, MoveConstructorTransfersLockOwnership) { - PIProtectedVariable pv; - pv.set(42); - - // We can't directly test unlock count without modifying the class, - // but we can test the behavior: after move, only the target should unlock - - auto ptr1 = pv.getRef(); - - // ptr1 holds the lock - bool canAcquire = false; - thread tryAcquire([&pv, &canAcquire]() { - auto p = pv.getRef(); - canAcquire = true; - }); - - this_thread::sleep_for(chrono::milliseconds(50)); - EXPECT_FALSE(canAcquire); // Lock still held by ptr1 - - // Move ptr1 to ptr2 - auto ptr2 = std::move(ptr1); - - // ptr2 should still hold the lock - this_thread::sleep_for(chrono::milliseconds(50)); - EXPECT_FALSE(canAcquire); // Lock still held (now by ptr2) - - // Destroy ptr2 - ptr2.~Pointer(); - - tryAcquire.join(); - EXPECT_TRUE(canAcquire); // Now lock is free -} - -TEST(PIProtectedVariable_Move, SourceDoesNotUnlockAfterMove) { - PIProtectedVariable pv; - pv.set(42); - - // If source unlocked after move, we would get a double-unlock error - // or undefined behavior. This test verifies that moving works correctly. - - auto ptr1 = pv.getRef(); - (*ptr1) = 100; - - // Move ptr1 - auto ptr2 = std::move(ptr1); - - // ptr2 should work correctly - (*ptr2) = 200; - EXPECT_EQ(pv.get(), 200); - - // When ptr1 is destroyed, it should NOT unlock (ownsLock should be false) - // When ptr2 is destroyed, it SHOULD unlock - // This should complete without any errors -} - -TEST(PIProtectedVariable_Move, TargetUnlocksInDestructor) { - PIProtectedVariable pv; - pv.set(42); - - bool lockReleased = false; - - { - auto ptr1 = pv.getRef(); - auto ptr2 = std::move(ptr1); - // ptr2 holds the lock, ptr1 does not - } // ptr2 destroyed here, should release lock - - // If we get here without deadlock, the lock was properly released - auto ptr3 = pv.getRef(); - lockReleased = true; - - EXPECT_TRUE(lockReleased); -} - -TEST(PIProtectedVariable_Move, UnlockCalledExactlyOnceAfterMove) { - PIProtectedVariable pv; - pv.set(42); - - // Test that after move: - // 1. Source Pointer's destructor does NOT call unlock() - // 2. Target Pointer's destructor DOES call unlock() - // 3. Total unlock() calls = 1 (not 2!) - - { - auto ptr1 = pv.getRef(); - auto ptr2 = std::move(ptr1); - // ptr1.ownsLock should be false - // ptr2.ownsLock should be true - - // Both go out of scope - // Only ptr2 should unlock - } - - // If we get here without issues, exactly one unlock happened - EXPECT_EQ(pv.get(), 42); -} - -TEST(PIProtectedVariable_Move, MoveWithRVODisabled) { - // This test should be compiled with -fno-elide-constructors - // to ensure move constructor is actually called - - PIProtectedVariable pv(42); - - auto ptr_src = pv.getRef(); - *ptr_src = 99; - auto ptr = std::move(ptr_src); - *ptr = 100; - - EXPECT_EQ(pv.get(), 100); -} - -TEST(PIProtectedVariable_Move, MoveConstructorWithComplexTypes) { - struct ComplexType { - vector data; - string name; - - ComplexType(): data{1, 2, 3}, name("test") {} - }; - - PIProtectedVariable pv; - pv.set(ComplexType()); - - auto ptr1 = pv.getRef(); - ptr1->data.push_back(4); - ptr1->name = "modified"; - - auto ptr2 = std::move(ptr1); - ptr2->data.push_back(5); - - EXPECT_EQ(pv.get().data.size(), 5u); - EXPECT_EQ(pv.get().name, "modified"); -} - -//! \~\brief Copy prevention tests -//! \~\english Tests that verify copy operations are deleted -//! \~\russian Тесты проверки что операции копирования удалены -TEST(PIProtectedVariable_CopyPrevention, CopyConstructorDeleted) { - PIProtectedVariable pv; - pv.set(42); - - auto ptr = pv.getRef(); - - // This should not compile: - // auto ptr2 = ptr; // Copy constructor is deleted - - // We verify this at compile time by ensuring the code compiles - // without attempting to copy - EXPECT_EQ(*ptr, 42); -} - -TEST(PIProtectedVariable_CopyPrevention, CopyAssignmentDeleted) { - PIProtectedVariable pv; - pv.set(42); - - auto ptr1 = pv.getRef(); - auto ptr2 = pv.getRef(); // This will block until ptr1 is released - - // This should not compile: - // ptr1 = ptr2; // Copy assignment is deleted - - (*ptr2) = 100; - EXPECT_EQ(pv.get(), 100); -} - -TEST(PIProtectedVariable_CopyPrevention, MoveAssignmentDeleted) { - PIProtectedVariable pv; - pv.set(42); - - auto ptr1 = pv.getRef(); - auto ptr2 = pv.getRef(); // This will block until ptr1 is released - - // This should not compile: - // ptr1 = std::move(ptr2); // Move assignment is deleted - - (*ptr2) = 100; - EXPECT_EQ(pv.get(), 100); -} - -TEST(PIProtectedVariable_CopyPrevention, CompileTimeCheck) { - // Compile-time verification that copy operations are deleted - // These static_assert statements will fail to compile if copy operations exist - +// Thread safety tests +TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { PIProtectedVariable pv; - // The fact that this file compiles proves that: - // 1. Pointer copy constructor is deleted - // 2. Pointer copy assignment is deleted - // 3. Pointer move assignment is deleted + atomic writeCount(0); + atomic readCount(0); + atomic invalidReads(0); + const int NUM_ITERATIONS = 1000; + const int NUM_WRITERS = 10; + const int NUM_READERS = 20; + const int TOTAL_WRITES = NUM_WRITERS * NUM_ITERATIONS; - EXPECT_TRUE(true); // Placeholder assertion -} + // Collect thread handles for joining + PIVector threads; -//! \~\brief Edge case tests -//! \~\english Tests for edge cases and exception safety -//! \~\russian Тесты граничных случаев и исключительной безопасности -TEST(PIProtectedVariable_EdgeCases, MultipleGetRefCallsSequential) { - PIProtectedVariable pv; - pv.set(0); - - for (int i = 0; i < 100; ++i) { - auto ptr = pv.getRef(); - (*ptr)++; - } - - EXPECT_EQ(pv.get(), 100); -} - -TEST(PIProtectedVariable_EdgeCases, NestedGetRefInSeparateScopes) { - PIProtectedVariable pv; - pv.set(0); - - { - auto ptr1 = pv.getRef(); - (*ptr1) += 10; - } - - { - auto ptr2 = pv.getRef(); - (*ptr2) += 20; - } - - { - auto ptr3 = pv.getRef(); - (*ptr3) += 30; - } - - EXPECT_EQ(pv.get(), 60); -} - -TEST(PIProtectedVariable_EdgeCases, LargeNumberOfThreads) { - PIProtectedVariable pv; - atomic counter(0); - atomic start(false); - const int NUM_THREADS = 50; - - vector threads; - for (int i = 0; i < NUM_THREADS; ++i) { - threads.emplace_back([&pv, &counter, &start]() { - while (!start.load()) { - this_thread::yield(); + // Create writer threads + for (int i = 0; i < NUM_WRITERS; ++i) { + threads.push_back(new PIThread([&pv, &writeCount]() { + for (int j = 0; j < NUM_ITERATIONS; ++j) { + auto val = pv.getRef(); + (*val)++; + writeCount++; } - for (int j = 0; j < 100; ++j) { - int val = counter.fetch_add(1); - pv.set(val); - int readVal = pv.get(); - EXPECT_GE(readVal, 0); - } - }); + })); } - start.store(true); - for (auto & t: threads) { - t.join(); - } - - EXPECT_EQ(counter, NUM_THREADS * 100); -} - -TEST(PIProtectedVariable_EdgeCases, ValueInitializationOfDifferentTypes) { - // Note: PIProtectedVariable does not value-initialize its internal var - // We test that setting and getting works for different types - PIProtectedVariable pvInt; - PIProtectedVariable pvDouble; - PIProtectedVariable pvBool; - PIProtectedVariable pvString; - PIProtectedVariable pvVoidPtr; - - pvInt.set(0); - pvDouble.set(0.0); - pvBool.set(false); - pvString.set(""); - pvVoidPtr.set(nullptr); - - EXPECT_EQ(pvInt.get(), 0); - EXPECT_DOUBLE_EQ(pvDouble.get(), 0.0); - EXPECT_FALSE(pvBool.get()); - EXPECT_TRUE(pvString.get().empty()); - EXPECT_EQ(pvVoidPtr.get(), nullptr); -} - -TEST(PIProtectedVariable_EdgeCases, NegativeAndExtremeValues) { - PIProtectedVariable pv; - - pv.set(INT_MIN); - EXPECT_EQ(pv.get(), INT_MIN); - - pv.set(INT_MAX); - EXPECT_EQ(pv.get(), INT_MAX); - - pv.set(-1); - EXPECT_EQ(pv.get(), -1); - - pv.set(0); - EXPECT_EQ(pv.get(), 0); -} - -TEST(PIProtectedVariable_EdgeCases, EmptyStringAndContainer) { - PIProtectedVariable pvStr; - PIProtectedVariable> pvVec; - - pvStr.set(""); - EXPECT_TRUE(pvStr.get().empty()); - - pvVec.set(vector()); - EXPECT_TRUE(pvVec.get().empty()); -} - -TEST(PIProtectedVariable_EdgeCases, PointerLifetime) { - PIProtectedVariable pv; - pv.set(42); - - int * rawPtr = nullptr; - { - auto ptr = pv.getRef(); - rawPtr = &(*ptr); - // ptr destroyed here - } - - // rawPtr still points to valid memory (pv.var) - // but we shouldn't modify through it without holding the lock - EXPECT_EQ(*rawPtr, 42); -} - -//! \~\brief Exception safety tests -//! \~\english Tests for exception safety -//! \~\russian Тесты исключительной безопасности -TEST(PIProtectedVariable_ExceptionSafety, SetWithMoveDoesNotLeaveInBadState) { - struct MovesOnly { - int value; - MovesOnly(): value(0) {} - MovesOnly(int v): value(v) {} - MovesOnly(const MovesOnly &) = delete; // Copy is deleted - MovesOnly(MovesOnly &&) noexcept = default; - MovesOnly & operator=(const MovesOnly &) = delete; - MovesOnly & operator=(MovesOnly &&) noexcept = default; - }; - - PIProtectedVariable pv; - pv.set(MovesOnly(42)); - - // Use getRef to access the value since get() requires copy - auto ptr = pv.getRef(); - EXPECT_EQ(ptr->value, 42); -} - -TEST(PIProtectedVariable_ExceptionSafety, GetRefDoesNotLeakLockOnException) { - PIProtectedVariable pv; - pv.set(0); - - bool exceptionThrown = false; - try { - auto ptr = pv.getRef(); - (*ptr) = 100; - // ptr goes out of scope normally, lock released - } catch (...) { - exceptionThrown = true; - } - - EXPECT_FALSE(exceptionThrown); - EXPECT_EQ(pv.get(), 100); - - // If lock was leaked, this would deadlock - auto ptr2 = pv.getRef(); - EXPECT_EQ(*ptr2, 100); -} - -//! \~\brief Integration tests -//! \~\english Integration tests combining multiple features -//! \~\russian Интеграционные тесты, сочетающие несколько функций -TEST(PIProtectedVariable_Integration, ProducerConsumerPattern) { - PIProtectedVariable buffer; - atomic done(false); - atomic produced(0); - atomic consumed(0); - atomic start(false); - const int NUM_ITEMS = 1000; - - // Producer - thread producer([&]() { - while (!start.load()) { - this_thread::yield(); - } - for (int i = 0; i < NUM_ITEMS; ++i) { - auto ptr = buffer.getRef(); - *ptr = i; - produced++; - } - done.store(true); - }); - - // Consumer - consumes exactly NUM_ITEMS - thread consumer([&]() { - while (!start.load()) { - this_thread::yield(); - } - for (int i = 0; i < NUM_ITEMS; ++i) { - // Wait for producer to produce something - while (produced.load() <= consumed.load() && !done.load()) { - this_thread::yield(); - } - auto ptr = buffer.getRef(); - int val = *ptr; - consumed++; - (void)val; // Suppress unused warning - } - }); - - start.store(true); - producer.join(); - consumer.join(); - - EXPECT_EQ(produced, NUM_ITEMS); - EXPECT_EQ(consumed, NUM_ITEMS); -} - -TEST(PIProtectedVariable_Integration, ReadMostlyPattern) { - PIProtectedVariable config; - config.set(100); - - atomic totalReads(0); - atomic wrongReads(0); - atomic start(false); - atomic done(false); - - // Readers - vector readers; - for (int i = 0; i < THREAD_COUNT; ++i) { - readers.emplace_back([&]() { - while (!start.load()) { - this_thread::yield(); - } - while (!done.load()) { - int val = config.get(); - totalReads++; - if (val < 100 || val > 200) { - wrongReads++; + // Create reader threads + for (int i = 0; i < NUM_READERS; ++i) { + threads.push_back(new PIThread([&pv, &invalidReads, &readCount]() { + for (int j = 0; j < NUM_ITERATIONS; ++j) { + auto val = pv.get(); + readCount++; + // Value should always be in valid range [0, TOTAL_WRITES] + if (val < 0 || val > TOTAL_WRITES) { + invalidReads++; } } - }); + })); } - // Writer - thread writer([&]() { - while (!start.load()) { - this_thread::yield(); - } - for (int i = 0; i < 100; ++i) { - config.set(100 + i); - this_thread::sleep_for(chrono::milliseconds(10)); - } - done.store(true); - }); - - start.store(true); - writer.join(); - for (auto & t: readers) { - t.join(); + // Start all threads + for (auto * t: threads) { + t->startOnce(); } - EXPECT_GT(totalReads, 0); - EXPECT_EQ(wrongReads, 0); + // Wait for all threads to finish + for (auto * t: threads) { + t->waitForFinish(PISystemTime::fromSeconds(5)); + delete t; + } + + // Verify results + EXPECT_EQ(writeCount, TOTAL_WRITES); + EXPECT_EQ(readCount, NUM_READERS * NUM_ITERATIONS); + EXPECT_EQ(invalidReads, 0) << "All reads should return valid values in range [0, " << TOTAL_WRITES << "]"; + + // Final value should be TOTAL_WRITES + int finalVal = pv.get(); + EXPECT_EQ(finalVal, TOTAL_WRITES); } From 449978bda013e6d926b8d91b1225e464f28fa6c0 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 19:07:01 +0300 Subject: [PATCH 06/26] revert main and more tests --- main.cpp | 21 --------- tests/thread/piprotectedvariable_test.cpp | 56 ++++++++--------------- 2 files changed, 18 insertions(+), 59 deletions(-) diff --git a/main.cpp b/main.cpp index 7dda6513..cf2f6235 100644 --- a/main.cpp +++ b/main.cpp @@ -20,27 +20,6 @@ inline PIByteArray SMBusTypeInfo_genHash(PIString n) { int main(int argc, char * argv[]) { - PIProtectedVariable pv(3.0); - piCout << pv.get(); - { - auto ref = pv.getRef(); - piCout << *ref; - *ref = 11.; - piCout << *ref; - } - piCout << pv.get(); - { - auto ref = pv.getRef(); - piCout << *ref; - *ref = 12.; - piCout << *ref; - auto ref2 = std::move(ref); - piCout << *ref2; - } - piCout << pv.get(); - return 0; - - PICrypt _crypt; // auto ba = PIFile::readAll("logo.png"); PIString str = "hello!"_a; diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index 09f52d05..77c43c05 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -1,32 +1,11 @@ -//! \~\file piprotectedvariable_test.cpp -//! \~\brief Unit tests for PIProtectedVariable class -/* - PIP - Platform Independent Primitives - Unit tests for PIProtectedVariable - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . -*/ - #include "piprotectedvariable.h" #include "pistring.h" #include "pithread.h" +#include "piliterals_time.h" #include "gtest/gtest.h" #include -using namespace std; - // Basic functionality tests TEST(PIProtectedVariable_Basic, BasicFunctionality) { // Test basic set/get with different types @@ -65,22 +44,30 @@ TEST(PIProtectedVariable_Basic, BasicFunctionality) { ptr->x = 100; EXPECT_EQ(pvStruct.get().x, 100); - // Test move semantics for Pointer + // Test for Pointer pvInt.set(42); auto ptr1 = pvInt.getRef(); + EXPECT_EQ(*ptr1, 42); + *ptr1 = 55; + EXPECT_EQ(*ptr1, 55); auto ptr2 = std::move(ptr1); - EXPECT_EQ(*ptr2, 42); + EXPECT_EQ(*ptr2, 55); *ptr2 = 100; - EXPECT_EQ(pvInt.get(), 100); + EXPECT_EQ(*ptr2, 100); + auto ptr3 = pvInt.getRef(); + EXPECT_EQ(*ptr3, 100); + *ptr3 = 333; + EXPECT_EQ(*ptr3, 333); + EXPECT_EQ(pvInt.get(), 333); } // Thread safety tests TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { PIProtectedVariable pv; - atomic writeCount(0); - atomic readCount(0); - atomic invalidReads(0); + std::atomic writeCount(0); + std::atomic readCount(0); + std::atomic invalidReads(0); const int NUM_ITERATIONS = 1000; const int NUM_WRITERS = 10; const int NUM_READERS = 20; @@ -114,16 +101,9 @@ TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { })); } - // Start all threads - for (auto * t: threads) { - t->startOnce(); - } - - // Wait for all threads to finish - for (auto * t: threads) { - t->waitForFinish(PISystemTime::fromSeconds(5)); - delete t; - } + threads.forEach([](PIThread * & t) {t->startOnce();}); + threads.forEach([](PIThread * & t) {t->waitForFinish(2_s);}); + piDeleteAll(threads); // Verify results EXPECT_EQ(writeCount, TOTAL_WRITES); From e761625eab90641cdfee280528665322d481f1c4 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 19:14:14 +0300 Subject: [PATCH 07/26] using recursive mutex --- libs/main/thread/piprotectedvariable.h | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 754eb4e3..077d2192 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -49,15 +49,11 @@ public: public: //! \~english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. - Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; + Pointer(Pointer && other): pv(other.pv) { pv.mutex.lock(); }; //! \~english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. - ~Pointer() { - if (can_unlock) { - pv.mutex.unlock(); - } - } + ~Pointer() { pv.mutex.unlock(); } //! \~english Returns pointer access to the protected value. //! \~russian Возвращает указательный доступ к защищенному значению. @@ -72,7 +68,6 @@ public: explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); } PIProtectedVariable & pv; - bool can_unlock = true; }; //! \~english Replaces the protected value with \a v. From 9dc1af921c100ebbba50feafd2f59903fec7eafc Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 19:18:44 +0300 Subject: [PATCH 08/26] more simplify Pointer --- tests/thread/piprotectedvariable_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp index 77c43c05..e7facc40 100644 --- a/tests/thread/piprotectedvariable_test.cpp +++ b/tests/thread/piprotectedvariable_test.cpp @@ -82,7 +82,9 @@ TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) { for (int j = 0; j < NUM_ITERATIONS; ++j) { auto val = pv.getRef(); (*val)++; + auto val2 = pv.getRef(); writeCount++; + ASSERT_EQ(writeCount, *val2); } })); } From c02b627d470e47a7435bace0accbeb4158ee8bae Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Mar 2026 20:14:22 +0300 Subject: [PATCH 09/26] some fixes PIEthernet::listen PIClientServer::Server::~Server PISystemTime const sleep ClientServer tests fast and stable --- libs/client_server/piclientserver_server.cpp | 6 +---- libs/main/io_devices/piethernet.cpp | 1 + libs/main/types/pisystemtime.cpp | 4 ++-- libs/main/types/pisystemtime.h | 4 ++-- tests/client_server/client_server_test.cpp | 24 +++++++------------- 5 files changed, 14 insertions(+), 25 deletions(-) diff --git a/libs/client_server/piclientserver_server.cpp b/libs/client_server/piclientserver_server.cpp index f1700b01..edfd2083 100644 --- a/libs/client_server/piclientserver_server.cpp +++ b/libs/client_server/piclientserver_server.cpp @@ -72,11 +72,7 @@ PIClientServer::Server::~Server() { clean_thread->waitForFinish(); piDeleteSafety(clean_thread); stopServer(); - for (auto c: clients) { - c->aboutDelete(); - c->destroy(); - delete c; - } + closeAll(); piDeleteSafety(tcp_server); } diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp index 19eddf28..7cb7a1dc 100644 --- a/libs/main/io_devices/piethernet.cpp +++ b/libs/main/io_devices/piethernet.cpp @@ -527,6 +527,7 @@ bool PIEthernet::listen(bool threaded) { listen_threaded = true; server_bounded = false; server_thread_.start(server_func); + server_thread_.waitForStart(); return true; } listen_threaded = server_bounded = false; diff --git a/libs/main/types/pisystemtime.cpp b/libs/main/types/pisystemtime.cpp index 4d218968..f26b7bd6 100644 --- a/libs/main/types/pisystemtime.cpp +++ b/libs/main/types/pisystemtime.cpp @@ -105,7 +105,7 @@ extern clock_serv_t __pi_mac_clock; //! Используйте этот метод для ожидания разниц системных времен или своего времени. //! Если метод будет вызван для системного времени \a PISystemTime::current(), то //! ожидание будет почти бесконечным -void PISystemTime::sleep() { +void PISystemTime::sleep() const { piUSleep(piFloord(toMicroseconds())); } @@ -118,7 +118,7 @@ void PISystemTime::toTimespec(void * ts) { } -PISystemTime::Frequency PISystemTime::toFrequency() { +PISystemTime::Frequency PISystemTime::toFrequency() const { return PISystemTime::Frequency::fromSystemTime(*this); } diff --git a/libs/main/types/pisystemtime.h b/libs/main/types/pisystemtime.h index bb197377..c7438c7e 100644 --- a/libs/main/types/pisystemtime.h +++ b/libs/main/types/pisystemtime.h @@ -253,7 +253,7 @@ public: //! \~english Sleep for this time //! \~russian Ожидать это время - void sleep(); + void sleep() const; //! \~english On *nix system assign current value to timespec struct //! \~russian На *nix системах присваивает время к timespec структуре @@ -261,7 +261,7 @@ public: //! \~english Returns \a Frequency that corresponds this time interval //! \~russian Возвращает \a Frequency соответствующую этому временному интервалу - PISystemTime::Frequency toFrequency(); + PISystemTime::Frequency toFrequency() const; //! \~english Returns "yyyy-MM-dd hh:mm:ss.zzz" for absolute time and " ..." for relative //! \~russian Возвращает "yyyy-MM-dd hh:mm:ss.zzz" для абсолютного времени и " ..." для относительного diff --git a/tests/client_server/client_server_test.cpp b/tests/client_server/client_server_test.cpp index bbe639c5..e0980649 100644 --- a/tests/client_server/client_server_test.cpp +++ b/tests/client_server/client_server_test.cpp @@ -32,6 +32,7 @@ Client * createAndConnectClient() { TEST(ClientServer, OneClient) { auto const loop_timeout = 1000_ms; auto s = createServer(); + piMinSleep(); auto c = createAndConnectClient>(); waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout); @@ -106,6 +107,7 @@ TEST(ClientServer, ManyClients) { constexpr int clients_count = 20; PIVector clients; auto s = createServer(); + piMinSleep(); piForTimes(clients_count) { clients.append(new ClientSendThread()); @@ -137,7 +139,7 @@ TEST(ClientServer, ManyClients) { for (const auto c: clients) { c->startSend(); } - (100_ms).sleep(); + loop_timeout.sleep(); EXPECT_TRUE(getClientsPings(clients) > clients_count * 2); EXPECT_TRUE(getServerPongs(s) > clients_count * 2); piDeleteAllAndClear(clients); @@ -147,7 +149,7 @@ TEST(ClientServer, ManyClients) { } TEST(ClientServer, DynamicClients) { - auto const loop_timeout = 100_ms; + auto const loop_timeout = 10_ms; constexpr int clients_count = 20; PIVector clients; PIMutex clients_mutex; @@ -160,7 +162,6 @@ TEST(ClientServer, DynamicClients) { clients_mutex.lock(); clients << c; clients_mutex.unlock(); - piCout << "new client" << clients.size(); }; piForTimes(clients_count) { @@ -178,9 +179,8 @@ TEST(ClientServer, DynamicClients) { piForTimes(new_cnt) { spawnClient(); } - piCout << "+++++++"; }, - 12_Hz); + 120_Hz); deleteThread.start( [&clients, &clients_mutex]() { @@ -194,28 +194,20 @@ TEST(ClientServer, DynamicClients) { clients_mutex.unlock(); if (c) { delete c; - piCout << "remove client" << clients.size(); } } - piCout << "----------"; }, - 13_Hz); + 130_Hz); - (2_s).sleep(); + (loop_timeout * clients_count).sleep(); EXPECT_GE(s->clientsCount(), 10); - piCout << "now clients" << clients.size(); - - deleteThread.stopAndWait(); spawnThread.stopAndWait(); - - piCout << "total clients" << clients.size(); - piDeleteAllAndClear(clients); - waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout * clients_count); EXPECT_EQ(0, s->clientsCount()); delete s; From ac415ebbb6a06b137b1a903f4fc58333d9eac01b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 18 Mar 2026 08:54:06 +0300 Subject: [PATCH 10/26] revert can_unlock flag --- libs/main/thread/piprotectedvariable.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h index 077d2192..754eb4e3 100644 --- a/libs/main/thread/piprotectedvariable.h +++ b/libs/main/thread/piprotectedvariable.h @@ -49,11 +49,15 @@ public: public: //! \~english Move constructor - transfers ownership of the lock. //! \~russian Конструктор перемещения - передает владение блокировкой. - Pointer(Pointer && other): pv(other.pv) { pv.mutex.lock(); }; + Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; }; //! \~english Destroys wrapper and releases the mutex. //! \~russian Уничтожает обертку и освобождает мьютекс. - ~Pointer() { pv.mutex.unlock(); } + ~Pointer() { + if (can_unlock) { + pv.mutex.unlock(); + } + } //! \~english Returns pointer access to the protected value. //! \~russian Возвращает указательный доступ к защищенному значению. @@ -68,6 +72,7 @@ public: explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); } PIProtectedVariable & pv; + bool can_unlock = true; }; //! \~english Replaces the protected value with \a v. From ccbf86f781902dbe96c1133933054919610bf8d6 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 18 Mar 2026 09:30:28 +0300 Subject: [PATCH 11/26] disable exeptions in cmake --- 3rd/LuaBridge/detail/LuaException.h | 9 ++++++--- 3rd/LuaBridge/detail/Namespace.h | 9 ++++++--- 3rd/LuaBridge/detail/Userdata.h | 7 +++++-- CMakeLists.txt | 2 +- libs/main/core/pibase.h | 15 +++++++-------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/3rd/LuaBridge/detail/LuaException.h b/3rd/LuaBridge/detail/LuaException.h index 836cb852..2fe6d608 100644 --- a/3rd/LuaBridge/detail/LuaException.h +++ b/3rd/LuaBridge/detail/LuaException.h @@ -97,8 +97,10 @@ public: { int code = lua_pcall (L, nargs, nresults, msgh); - if (code != LUABRIDGE_LUA_OK) - Throw (LuaException (L, code)); + if (code != LUABRIDGE_LUA_OK) { + // Throw (LuaException (L, code)); + assert(true); + } } //---------------------------------------------------------------------------- @@ -128,7 +130,8 @@ protected: private: static int throwAtPanic (lua_State* L) { - throw LuaException (L, -1); + // throw LuaException (L, -1); + return -1; } }; diff --git a/3rd/LuaBridge/detail/Namespace.h b/3rd/LuaBridge/detail/Namespace.h index 68ae21fa..8a4dd20a 100644 --- a/3rd/LuaBridge/detail/Namespace.h +++ b/3rd/LuaBridge/detail/Namespace.h @@ -101,7 +101,8 @@ protected: { if (m_stackSize == 0) { - throw std::logic_error ("Unable to continue registration"); + std::cerr << ("Unable to continue registration"); + assert(true); } } }; @@ -1054,7 +1055,8 @@ public: { if (m_stackSize == 1) { - throw std::logic_error ("endNamespace () called on global namespace"); + std::cerr << ("endNamespace () called on global namespace"); + assert(true); } assert (m_stackSize > 1); @@ -1150,7 +1152,8 @@ public: { if (m_stackSize == 1) { - throw std::logic_error ("addProperty () called on global namespace"); + std::cerr << ("addProperty () called on global namespace"); + assert(true); } assert (lua_istable (L, -1)); // Stack: namespace table (ns) diff --git a/3rd/LuaBridge/detail/Userdata.h b/3rd/LuaBridge/detail/Userdata.h index ef13d9fc..3993e176 100644 --- a/3rd/LuaBridge/detail/Userdata.h +++ b/3rd/LuaBridge/detail/Userdata.h @@ -33,6 +33,7 @@ #include #include +#include namespace luabridge { @@ -320,7 +321,8 @@ public: lua_rawgetp (L, LUA_REGISTRYINDEX, ClassInfo ::getClassKey ()); if (!lua_istable (L, -1)) { - throw std::logic_error ("The class is not registered in LuaBridge"); + std::cerr << ("The class is not registered in LuaBridge"); + assert(true); } lua_setmetatable (L, -2); return ud; @@ -375,7 +377,8 @@ private: lua_rawgetp (L, LUA_REGISTRYINDEX, key); if (!lua_istable (L, -1)) { - throw std::logic_error ("The class is not registered in LuaBridge"); + std::cerr << ("The class is not registered in LuaBridge"); + assert(true); } lua_setmetatable (L, -2); } diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e4740bd..8478147f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,7 +363,7 @@ if(WIN32) set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0") endif() else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-exceptions") if(DEFINED ENV{QNX_HOST} OR PIP_FREERTOS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-32") endif() diff --git a/libs/main/core/pibase.h b/libs/main/core/pibase.h index 8f6223c8..b379dea4 100644 --- a/libs/main/core/pibase.h +++ b/libs/main/core/pibase.h @@ -726,6 +726,7 @@ inline bool piDeleteSafety(T *& pointer) { //! \~english In this example "Error!" will be printed on every \b false function return. //! \~russian В данном примере будет выведен "Error!" при каждом \b false возврате из функции. class PIP_EXPORT PIScopeExitCall { + NO_COPY_CLASS(PIScopeExitCall) public: //! \~\brief //! \~english Constructor that takes a function to execute @@ -758,8 +759,6 @@ public: } private: - NO_COPY_CLASS(PIScopeExitCall) - std::function func; }; @@ -768,14 +767,14 @@ private: //! \~english Inherit from this class to make your class non-trivially copyable. //! \~russian Наследуйтесь от этого класса чтобы сделать свой класс нетривиально копируемым. struct PIP_EXPORT PINonTriviallyCopyable { - PINonTriviallyCopyable() noexcept = default; - PINonTriviallyCopyable(const PINonTriviallyCopyable &) noexcept = default; - PINonTriviallyCopyable(PINonTriviallyCopyable &&) noexcept; - PINonTriviallyCopyable & operator=(const PINonTriviallyCopyable &) noexcept = default; - PINonTriviallyCopyable & operator=(PINonTriviallyCopyable &&) noexcept = default; + PINonTriviallyCopyable() = default; + PINonTriviallyCopyable(const PINonTriviallyCopyable &) = default; + PINonTriviallyCopyable(PINonTriviallyCopyable &&) ; + PINonTriviallyCopyable & operator=(const PINonTriviallyCopyable &) = default; + PINonTriviallyCopyable & operator=(PINonTriviallyCopyable &&) = default; ~PINonTriviallyCopyable() = default; }; -inline PINonTriviallyCopyable::PINonTriviallyCopyable(PINonTriviallyCopyable &&) noexcept = default; +inline PINonTriviallyCopyable::PINonTriviallyCopyable(PINonTriviallyCopyable &&) = default; //! \~\brief From ba57aa0144522959166bb3bf92a47162d575b88e Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 18 Mar 2026 09:40:32 +0300 Subject: [PATCH 12/26] disable exceptions on win --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8478147f..19d29932 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -360,7 +360,7 @@ endif() if(WIN32) add_definitions(-DPSAPI_VERSION=1) if(${C_COMPILER} STREQUAL "cl.exe") - set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0") + set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0 /EH-") endif() else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-exceptions") From 79fa5492017dc278696fe9f61cdeadf0957361c8 Mon Sep 17 00:00:00 2001 From: peri4 Date: Wed, 18 Mar 2026 11:46:08 +0300 Subject: [PATCH 13/26] fix matrix test --- tests/math/testpimathmatrixt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/math/testpimathmatrixt.cpp b/tests/math/testpimathmatrixt.cpp index 0245599c..243431ec 100644 --- a/tests/math/testpimathmatrixt.cpp +++ b/tests/math/testpimathmatrixt.cpp @@ -376,7 +376,7 @@ TEST(PIMathMatrixT_Test, invert) { matrix3 = matrix1; matrix1.invert(); EXPECT_EQ(matrix1, matrix3); - EXPECT_DOUBLE_EQ(std::abs(d1 - (1. / d2)), 0.); + EXPECT_NEAR(std::abs(d1 - (1. / d2)), 0., 1e-12); } TEST(PIMathMatrixT_Test, inverted) { From 99c99c39c258b663cb103152eb3099e58790ac06 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 18 Mar 2026 13:39:01 +0300 Subject: [PATCH 14/26] separate cmake opts for tests --- CMakeLists.txt | 6 +++++- tests/CMakeLists.txt | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19d29932..eaec1c29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,8 @@ set(PIP_DLL_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE STRING "") option(ICU "ICU support for convert codepages" ${_ICU_DEFAULT}) option(STD_IOSTREAM "Building with std iostream operators support" OFF) option(INTROSPECTION "Build with introspection" OFF) -option(TESTS "Build tests and perform their before install step" OFF) +option(TESTS "Build tests" OFF) +option(TESTS_RUN "Run tests before install step" OFF) option(COVERAGE "Build project with coverage info" OFF) option(PIP_FFTW_F "Support fftw module for float" ON) option(PIP_FFTW_L "Support fftw module for long double" ON) @@ -823,6 +824,9 @@ if (PIP_TESTS_LIST) foreach(_test ${PIP_TESTS_LIST}) message(" * ${_test}") endforeach() + if (TESTS_RUN) + message("TESTS_RUN ON -> Run tests before install step") + endif() else() message(" Tests: skip (tests off)") endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 67f7d5f5..20503839 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,8 +24,10 @@ macro(pip_test NAME) file(GLOB _HDRS "${NAME}/*.h") set(_target pip_${NAME}_test) add_executable(${_target} ${_CPPS} ${_HDRS}) - target_link_libraries(${_target} pip ${ARGN} gtest_main) - gtest_discover_tests(${_target}) + target_link_libraries(${_target} pip ${ARGN} gtest_main) + if (TESTS_RUN) + gtest_discover_tests(${_target}) + endif() list(APPEND PIP_TESTS_LIST "${NAME}") set(PIP_TESTS_LIST ${PIP_TESTS_LIST} PARENT_SCOPE) endmacro() From a1be5be5a1ae7b2c2944e860f1d00a532403afe3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 18 Mar 2026 15:01:17 +0300 Subject: [PATCH 15/26] increase timeout and remove sleep --- tests/client_server/client_server_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/client_server/client_server_test.cpp b/tests/client_server/client_server_test.cpp index e0980649..cb6fc68c 100644 --- a/tests/client_server/client_server_test.cpp +++ b/tests/client_server/client_server_test.cpp @@ -33,7 +33,7 @@ TEST(ClientServer, OneClient) { auto const loop_timeout = 1000_ms; auto s = createServer(); piMinSleep(); - auto c = createAndConnectClient>(); + auto c = createAndConnectClient>(); waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout); EXPECT_EQ(1, s->clientsCount()); @@ -103,7 +103,7 @@ int getClientsPings(const PIVector & clients) { TEST(ClientServer, ManyClients) { - auto const loop_timeout = 100_ms; + auto const loop_timeout = 1_s; constexpr int clients_count = 20; PIVector clients; auto s = createServer(); @@ -139,8 +139,9 @@ TEST(ClientServer, ManyClients) { for (const auto c: clients) { c->startSend(); } - loop_timeout.sleep(); + waitLoop([&clients]() { return getClientsPings(clients) > clients_count * 2; }, loop_timeout); EXPECT_TRUE(getClientsPings(clients) > clients_count * 2); + waitLoop([s]() { return getServerPongs(s) > clients_count * 2; }, loop_timeout); EXPECT_TRUE(getServerPongs(s) > clients_count * 2); piDeleteAllAndClear(clients); waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); @@ -149,7 +150,7 @@ TEST(ClientServer, ManyClients) { } TEST(ClientServer, DynamicClients) { - auto const loop_timeout = 10_ms; + auto const loop_timeout = 3_s; constexpr int clients_count = 20; PIVector clients; PIMutex clients_mutex; @@ -199,15 +200,14 @@ TEST(ClientServer, DynamicClients) { }, 130_Hz); - (loop_timeout * clients_count).sleep(); - + waitLoop([s]() { return s->clientsCount() >= 10; }, loop_timeout); EXPECT_GE(s->clientsCount(), 10); deleteThread.stopAndWait(); spawnThread.stopAndWait(); piDeleteAllAndClear(clients); - waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout * clients_count); + waitLoop([s]() { return s->clientsCount() == 0; }, loop_timeout); EXPECT_EQ(0, s->clientsCount()); delete s; From fe3b30bd49d21f6ffa9f93c63983b78e0c062f8f Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 20 Mar 2026 13:00:24 +0300 Subject: [PATCH 16/26] version 5.6.1 patch deploy_tool: procDpkg now prioritize "non-cross" and "non-dev" packages, then only "non-cross" add PIP_MANUAL_TEST CMake option --- CMakeLists.txt | 17 ++++++++++------- utils/deploy_tool/main.cpp | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaec1c29..de404754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ endif() project(PIP) set(PIP_MAJOR 5) set(PIP_MINOR 6) -set(PIP_REVISION 0) +set(PIP_REVISION 1) set(PIP_SUFFIX ) set(PIP_COMPANY SHS) set(PIP_DOMAIN org.SHS) @@ -73,6 +73,7 @@ option(COVERAGE "Build project with coverage info" OFF) option(PIP_FFTW_F "Support fftw module for float" ON) option(PIP_FFTW_L "Support fftw module for long double" ON) option(PIP_FFTW_Q "Support fftw module for quad double" OFF) +option(PIP_MANUAL_TEST "Build dev test (main.cpp)" OFF) set(PIP_UTILS 1) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_STANDARD 11) @@ -593,12 +594,14 @@ if (NOT CROSSTOOLS) #target_link_libraries(pip_plugin pip) if (NOT DEFINED ANDROID_PLATFORM) - if(microhttpd_FOUND AND curl_FOUND) - add_executable(pip_test "main.cpp") - target_link_libraries(pip_test pip pip_io_utils pip_client_server pip_http_server pip_http_client) - if(sodium_FOUND) - add_executable(pip_cloud_test "main_picloud_test.cpp") - target_link_libraries(pip_cloud_test pip_cloud) + if (PIP_MANUAL_TEST) + if(microhttpd_FOUND AND curl_FOUND) + add_executable(pip_test "main.cpp") + target_link_libraries(pip_test pip pip_io_utils pip_client_server pip_http_server pip_http_client) + if(sodium_FOUND) + add_executable(pip_cloud_test "main_picloud_test.cpp") + target_link_libraries(pip_cloud_test pip_cloud) + endif() endif() endif() endif() diff --git a/utils/deploy_tool/main.cpp b/utils/deploy_tool/main.cpp index 899c9a46..69b15942 100644 --- a/utils/deploy_tool/main.cpp +++ b/utils/deploy_tool/main.cpp @@ -467,11 +467,20 @@ bool procDpkg(const PIString & l) { if (!vs.isEmpty()) { PIStringList lines = vs.split('\n').reverse(); for (auto l: lines) { - auto sl = l; - l = l.left(l.find(":")); - if (!l.isEmpty() && !l.endsWith("-cross") && !l.contains(' ')) all_deps << l; - // PICout(true) << "** found \"" << l << "\" in \"" << sl << "\""; - return true; + l = l.left(l.find(":")); + if (!l.isEmpty() && !l.contains(' ') && !l.endsWith("-cross") && !l.endsWith("-dev")) { + // PICout(true) << "** found \"" << l << "\" in \"" << sl << "\""; + all_deps << l; + return true; + } + } + for (auto l: lines) { + l = l.left(l.find(":")); + if (!l.isEmpty() && !l.contains(' ') && !l.endsWith("-cross")) { + // PICout(true) << "** found \"" << l << "\" in \"" << sl << "\""; + all_deps << l; + return true; + } } } // piCout << "No dep on" << l; From 6cfc4524f07971fb84db979e33c5e75d3873a05b Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Fri, 20 Mar 2026 13:19:55 +0300 Subject: [PATCH 17/26] PITimer slot optimize --- libs/main/thread/pitimer.cpp | 11 ++++++----- libs/main/thread/pitimer.h | 9 ++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libs/main/thread/pitimer.cpp b/libs/main/thread/pitimer.cpp index 84a72e6f..71c1ccc0 100644 --- a/libs/main/thread/pitimer.cpp +++ b/libs/main/thread/pitimer.cpp @@ -122,13 +122,13 @@ PITimer::PITimer(): PIObject() { PITimer::PITimer(std::function func) { initFirst(); - ret_func = func; + ret_func_delim = std::move(func); } PITimer::PITimer(std::function func) { initFirst(); - ret_func = [func](int) { func(); }; + ret_func = std::move(func); } @@ -224,7 +224,8 @@ void PITimer::adjustTimes() { void PITimer::execTick() { if (!isRunning()) return; if (lockRun) lock(); - if (ret_func) ret_func(1); + if (ret_func) ret_func(); + if (ret_func_delim) ret_func_delim(1); tick(1); tickEvent(1); if (callEvents) maybeCallQueuedEvents(); @@ -233,8 +234,8 @@ void PITimer::execTick() { i.tick = 0; if (i.func) i.func(i.delim); - else if (ret_func) - ret_func(i.delim); + else if (ret_func_delim) + ret_func_delim(i.delim); tick(i.delim); tickEvent(i.delim); } diff --git a/libs/main/thread/pitimer.h b/libs/main/thread/pitimer.h index d3c1c4d4..5780a998 100644 --- a/libs/main/thread/pitimer.h +++ b/libs/main/thread/pitimer.h @@ -126,13 +126,11 @@ public: //! \~english Sets a tick callback that ignores the delimiter value. //! \~russian Устанавливает обратный вызов тика, игнорирующий значение делителя. - void setSlot(std::function func) { - ret_func = [func](int) { func(); }; - } + void setSlot(std::function func) { ret_func = std::move(func); } //! \~english Sets a tick callback that receives the current delimiter value. //! \~russian Устанавливает обратный вызов тика, принимающий текущее значение делителя. - void setSlot(std::function func) { ret_func = func; } + void setSlot(std::function func) { ret_func_delim = std::move(func); } //! \~english Enables locking of the internal mutex around tick processing. //! \~russian Включает блокировку внутреннего мьютекса вокруг обработки тиков. @@ -245,7 +243,8 @@ protected: PIMutex mutex_; PISystemTime m_interval, m_interval_x5; PISystemTime m_time_next; - std::function ret_func = nullptr; + std::function ret_func = nullptr; + std::function ret_func_delim = nullptr; PIVector delims; PIConditionVariable event; }; From 6efe77a395652bc8866ce7d04547d89cc95e41c0 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Fri, 20 Mar 2026 13:46:31 +0300 Subject: [PATCH 18/26] add move --- libs/main/thread/pitimer.cpp | 4 ++-- libs/main/thread/pitimer.h | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libs/main/thread/pitimer.cpp b/libs/main/thread/pitimer.cpp index 71c1ccc0..611e9852 100644 --- a/libs/main/thread/pitimer.cpp +++ b/libs/main/thread/pitimer.cpp @@ -263,7 +263,7 @@ bool PITimer::start(PISystemTime interval) { bool PITimer::start(PISystemTime interval, std::function func) { if (isRunning()) stopAndWait(); setInterval(interval); - setSlot(func); + setSlot(std::move(func)); return start(); } @@ -275,7 +275,7 @@ void PITimer::stopAndWait(PISystemTime timeout) { void PITimer::addDelimiter(int delim, std::function func) { - delims << Delimiter(func, delim); + delims << Delimiter(std::move(func), delim); } diff --git a/libs/main/thread/pitimer.h b/libs/main/thread/pitimer.h index 5780a998..4ed9f089 100644 --- a/libs/main/thread/pitimer.h +++ b/libs/main/thread/pitimer.h @@ -126,11 +126,17 @@ public: //! \~english Sets a tick callback that ignores the delimiter value. //! \~russian Устанавливает обратный вызов тика, игнорирующий значение делителя. - void setSlot(std::function func) { ret_func = std::move(func); } + void setSlot(std::function func) { + ret_func_delim = nullptr; + ret_func = std::move(func); + } //! \~english Sets a tick callback that receives the current delimiter value. //! \~russian Устанавливает обратный вызов тика, принимающий текущее значение делителя. - void setSlot(std::function func) { ret_func_delim = std::move(func); } + void setSlot(std::function func) { + ret_func = nullptr; + ret_func_delim = std::move(func); + } //! \~english Enables locking of the internal mutex around tick processing. //! \~russian Включает блокировку внутреннего мьютекса вокруг обработки тиков. @@ -139,7 +145,8 @@ public: EVENT_HANDLER0(void, unlock) { mutex_.unlock(); } //! \~english Returns whether the timer drains queued delivery for itself as performer on each main tick. By default \b true. - //! \~russian Возвращает, должен ли таймер обрабатывать отложенную доставку для себя как исполнителя на каждом основном тике. По умолчанию \b true. + //! \~russian Возвращает, должен ли таймер обрабатывать отложенную доставку для себя как исполнителя на каждом основном тике. По + //! умолчанию \b true. bool isCallQueuedEvents() const { return callEvents; } //! \~english Enables or disables queued-delivery draining through \a maybeCallQueuedEvents() on each main tick. @@ -219,7 +226,7 @@ public: protected: struct PIP_EXPORT Delimiter { Delimiter(std::function func_ = nullptr, int delim_ = 1) { - func = func_; + func = std::move(func_); delim = delim_; } std::function func; @@ -243,7 +250,7 @@ protected: PIMutex mutex_; PISystemTime m_interval, m_interval_x5; PISystemTime m_time_next; - std::function ret_func = nullptr; + std::function ret_func = nullptr; std::function ret_func_delim = nullptr; PIVector delims; PIConditionVariable event; From 96c22e118483f031186291dd87821b4d1c4ab569 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Fri, 20 Mar 2026 16:31:30 +0300 Subject: [PATCH 19/26] move std function --- libs/http_client/pihttpclient.cpp | 6 +- libs/http_server/pihttpserver.cpp | 2 +- .../client_server/piclientserver_server.h | 2 +- libs/main/console/pikbdlistener.h | 2 +- libs/main/containers/pideque.h | 80 ++++++++++--------- libs/main/containers/pivector.h | 18 +++-- libs/main/containers/pivector2d.h | 28 ++++--- libs/main/core/pibase.h | 2 +- libs/main/http_server/microhttpd_server.h | 4 +- libs/main/io_devices/pibinarylog.h | 2 +- libs/main/io_devices/piiodevice.cpp | 2 +- libs/main/io_utils/pipacketextractor.h | 6 +- libs/main/io_utils/piparsehelper.h | 4 +- libs/main/math/pievaluator.h | 2 +- libs/main/math/pimathvector.h | 7 +- libs/main/state_machine/pistatemachine.h | 2 +- libs/main/state_machine/pistatemachine_base.h | 2 +- .../main/state_machine/pistatemachine_state.h | 15 ++-- .../pistatemachine_transition.cpp | 2 +- libs/main/system/pisignals.h | 2 +- libs/main/thread/piconditionvar.cpp | 12 +-- libs/main/thread/piconditionvar.h | 4 +- libs/main/thread/pithread.cpp | 30 +++---- libs/main/thread/pithreadpoolexecutor.cpp | 4 +- libs/main/thread/pithreadpoolexecutor.h | 2 +- libs/main/thread/pithreadpoolloop.cpp | 4 +- libs/main/types/pibytearray.h | 28 ++++--- libs/main/types/pivaluetree.cpp | 2 +- 28 files changed, 145 insertions(+), 131 deletions(-) diff --git a/libs/http_client/pihttpclient.cpp b/libs/http_client/pihttpclient.cpp index b6d3aa52..9dbea1e3 100644 --- a/libs/http_client/pihttpclient.cpp +++ b/libs/http_client/pihttpclient.cpp @@ -247,7 +247,7 @@ PIHTTPClient * PIHTTPClient::onFinish(std::function f) { PIHTTPClient * PIHTTPClient::onFinish(std::function f) { - on_finish = f; + on_finish = std::move(f); return this; } @@ -258,7 +258,7 @@ PIHTTPClient * PIHTTPClient::onError(std::function f) { PIHTTPClient * PIHTTPClient::onError(std::function f) { - on_error = f; + on_error = std::move(f); return this; } @@ -269,7 +269,7 @@ PIHTTPClient * PIHTTPClient::onAbort(std::function f) { PIHTTPClient * PIHTTPClient::onAbort(std::function f) { - on_abort = f; + on_abort = std::move(f); return this; } diff --git a/libs/http_server/pihttpserver.cpp b/libs/http_server/pihttpserver.cpp index b80dfd7b..796f88c4 100644 --- a/libs/http_server/pihttpserver.cpp +++ b/libs/http_server/pihttpserver.cpp @@ -45,7 +45,7 @@ bool PIHTTPServer::registerPath(const PIString & path, PIHTTP::Method method, Re Endpoint ep; if (!ep.create(path)) return false; ep.method = method; - ep.function = functor; + ep.function = std::move(functor); endpoints[ep.priority] << ep; return true; } diff --git a/libs/main/client_server/piclientserver_server.h b/libs/main/client_server/piclientserver_server.h index 14bf68bb..138d1189 100644 --- a/libs/main/client_server/piclientserver_server.h +++ b/libs/main/client_server/piclientserver_server.h @@ -103,7 +103,7 @@ public: //! \~english Sets the factory used to create accepted client objects. //! \~russian Устанавливает фабрику, создающую объекты принятых клиентов. - void setClientFactory(std::function f) { client_factory = f; } + void setClientFactory(std::function f) { client_factory = std::move(f); } private: void newClient(ServerClient * c); diff --git a/libs/main/console/pikbdlistener.h b/libs/main/console/pikbdlistener.h index b79fb85e..d74e112b 100644 --- a/libs/main/console/pikbdlistener.h +++ b/libs/main/console/pikbdlistener.h @@ -204,7 +204,7 @@ public: //! \~english Sets the callback receiving both key event and user data. //! \~russian Устанавливает обратный вызов, получающий событие клавиши и пользовательские данные. - void setSlot(KBFunc slot) { ret_func = slot; } + void setSlot(KBFunc slot) { ret_func = std::move(slot); } //! \~english Sets the callback that only receives the key event and ignores user data. //! \~russian Устанавливает обратный вызов, получающий только событие клавиши и игнорирующий пользовательские данные. diff --git a/libs/main/containers/pideque.h b/libs/main/containers/pideque.h index fe5c53be..047bf410 100644 --- a/libs/main/containers/pideque.h +++ b/libs/main/containers/pideque.h @@ -184,7 +184,7 @@ public: //! \endcode inline PIDeque(size_t piv_size, std::function f) { PIINTROSPECTION_CONTAINER_NEW(T, sizeof(T)) - expand(piv_size, f); + expand(piv_size, std::move(f)); } //! \~english Move constructor. @@ -729,7 +729,7 @@ public: //! заданному в передаваемой функции `test`, или `def` если такого элемента нет. //! \~\sa \a indexWhere() inline const T & atWhere(std::function test, ssize_t start = 0, const T & def = T()) const { - const ssize_t i = indexWhere(test, start); + const ssize_t i = indexWhere(std::move(test), start); if (i < 0) return def; else @@ -743,7 +743,7 @@ public: //! заданному в передаваемой функции `test`, или `def` если такого элемента нет. //! \~\sa \a lastIndexWhere() inline const T & lastAtWhere(std::function test, ssize_t start = -1, const T & def = T()) const { - const ssize_t i = lastIndexWhere(test, start); + const ssize_t i = lastIndexWhere(std::move(test), start); if (i < 0) return def; else @@ -1265,7 +1265,7 @@ public: deleteT(pid_data + pid_start + new_size, pid_size - new_size); pid_size = new_size; } else if (new_size > pid_size) { - expand(new_size, f); + expand(new_size, std::move(f)); } return *this; } @@ -1328,15 +1328,15 @@ public: if (index < pid_size - 1) { const size_t os = pid_size - index - 1; memmove(reinterpret_cast(pid_data + pid_start + index + 1), - reinterpret_cast(pid_data + pid_start + index), - os * sizeof(T)); + reinterpret_cast(pid_data + pid_start + index), + os * sizeof(T)); } } else { alloc_backward(pid_size + 1, -1); if (index > 0) { memmove(reinterpret_cast(pid_data + pid_start), - reinterpret_cast(pid_data + pid_start + 1), - index * sizeof(T)); + reinterpret_cast(pid_data + pid_start + 1), + index * sizeof(T)); } } elementNew(pid_data + pid_start + index, e); @@ -1358,15 +1358,15 @@ public: if (index < pid_size - 1) { const size_t os = pid_size - index - 1; memmove(reinterpret_cast(pid_data + pid_start + index + 1), - reinterpret_cast(pid_data + pid_start + index), - os * sizeof(T)); + reinterpret_cast(pid_data + pid_start + index), + os * sizeof(T)); } } else { alloc_backward(pid_size + 1, -1); if (index > 0) { memmove(reinterpret_cast(pid_data + pid_start), - reinterpret_cast(pid_data + pid_start + 1), - index * sizeof(T)); + reinterpret_cast(pid_data + pid_start + 1), + index * sizeof(T)); } } elementNew(pid_data + pid_start + index, std::move(e)); @@ -1393,15 +1393,15 @@ public: alloc_forward(pid_size + v.pid_size); if (os > 0) { memmove(reinterpret_cast(pid_data + pid_start + index + v.pid_size), - reinterpret_cast(pid_data + pid_start + index), - os * sizeof(T)); + reinterpret_cast(pid_data + pid_start + index), + os * sizeof(T)); } } else { alloc_backward(pid_size + v.pid_size, -v.pid_size); if (index > 0) { memmove(reinterpret_cast(pid_data + pid_start), - reinterpret_cast(pid_data + pid_start + v.pid_size), - index * sizeof(T)); + reinterpret_cast(pid_data + pid_start + v.pid_size), + index * sizeof(T)); } } newT(pid_data + pid_start + index, v.pid_data + v.pid_start, v.pid_size); @@ -1426,15 +1426,15 @@ public: alloc_forward(pid_size + init_list.size()); if (os > 0) { memmove(reinterpret_cast(pid_data + pid_start + index + init_list.size()), - reinterpret_cast(pid_data + pid_start + index), + reinterpret_cast(pid_data + pid_start + index), os * sizeof(T)); } } else { alloc_backward(pid_size + init_list.size(), -init_list.size()); if (index > 0) { memmove(reinterpret_cast(pid_data + pid_start), - reinterpret_cast(pid_data + pid_start + init_list.size()), - index * sizeof(T)); + reinterpret_cast(pid_data + pid_start + init_list.size()), + index * sizeof(T)); } } newT(pid_data + pid_start + index, init_list.begin(), init_list.size()); @@ -1462,13 +1462,13 @@ public: deleteT(pid_data + pid_start + index, count); if (os <= index) { memmove(reinterpret_cast(pid_data + pid_start + index), - reinterpret_cast(pid_data + pid_start + index + count), - os * sizeof(T)); + reinterpret_cast(pid_data + pid_start + index + count), + os * sizeof(T)); } else { if (index > 0) { memmove(reinterpret_cast(pid_data + pid_start + count), - reinterpret_cast(pid_data + pid_start), - index * sizeof(T)); + reinterpret_cast(pid_data + pid_start), + index * sizeof(T)); } pid_start += count; } @@ -1540,7 +1540,7 @@ public: //! \endcode //! \~\sa \a sort() inline PIDeque & sort(std::function comp) { - std::stable_sort(begin(), end(), comp); + std::stable_sort(begin(), end(), std::move(comp)); return *this; } @@ -1654,7 +1654,13 @@ public: //! \endcode //! \~\sa \a remove(), \a removeOne(), \a removeWhere() inline PIDeque & removeWhere(std::function test) { - ssize_t j = indexWhere(test); + ssize_t j = -1; + for (size_t i = pid_start; i < pid_start + pid_size; ++i) { + if (test(pid_data[i])) { + j = ssize_t(i) - pid_start; + break; + } + } if (j != -1) { for (size_t i = j + 1; i < pid_size; ++i) { if (!test(pid_data[i + pid_start])) { @@ -2545,21 +2551,21 @@ public: if (index + count > pid_size) count = pid_size - index; ret.alloc_forward(count); memcpy(reinterpret_cast(ret.pid_data + ret.pid_start), - reinterpret_cast(pid_data + pid_start + index), - count * sizeof(T)); + reinterpret_cast(pid_data + pid_start + index), + count * sizeof(T)); const size_t os = pid_size - index - count; if (os <= index) { if (os > 0) { memmove(reinterpret_cast(pid_data + pid_start + index), - reinterpret_cast(pid_data + pid_start + index + count), - os * sizeof(T)); + reinterpret_cast(pid_data + pid_start + index + count), + os * sizeof(T)); } } else { if (index > 0) { memmove(reinterpret_cast(pid_data + pid_start + count), - reinterpret_cast(pid_data + pid_start), - index * sizeof(T)); + reinterpret_cast(pid_data + pid_start), + index * sizeof(T)); } pid_start += count; } @@ -2646,8 +2652,8 @@ private: size_t ns = (pid_rsize - pid_size) / 2; if (pid_start != ns) { memmove(reinterpret_cast(pid_data + ns), - reinterpret_cast(pid_data + pid_start), - pid_size * sizeof(T)); + reinterpret_cast(pid_data + pid_start), + pid_size * sizeof(T)); pid_start = ns; } } @@ -2656,8 +2662,8 @@ private: const size_t ns = (pid_rsize - pid_size) / 2; if (pid_start != ns) { memmove(reinterpret_cast(pid_data + ns), - reinterpret_cast(pid_data + pid_start), - pid_size * sizeof(T)); + reinterpret_cast(pid_data + pid_start), + pid_size * sizeof(T)); pid_start = ns; } } @@ -2694,8 +2700,8 @@ private: PIINTROSPECTION_CONTAINER_ALLOC(T, (as - pid_rsize)) if (pid_rsize > 0 && pid_data) { memcpy(reinterpret_cast(tmp_data + new_start), - reinterpret_cast(pid_data + pid_start), - pid_size * sizeof(T)); + reinterpret_cast(pid_data + pid_start), + pid_size * sizeof(T)); dealloc(); } pid_data = tmp_data; diff --git a/libs/main/containers/pivector.h b/libs/main/containers/pivector.h index da13d844..bdd2a700 100644 --- a/libs/main/containers/pivector.h +++ b/libs/main/containers/pivector.h @@ -184,7 +184,7 @@ public: //! \endcode inline PIVector(size_t size, std::function f) { PIINTROSPECTION_CONTAINER_NEW(T, sizeof(T)) - expand(size, f); + expand(size, std::move(f)); } //! \~english Move constructor. @@ -725,7 +725,7 @@ public: //! заданному в передаваемой функции `test`, или `def` если такого элемента нет. //! \~\sa \a indexWhere() inline const T & atWhere(std::function test, ssize_t start = 0, const T & def = T()) const { - const ssize_t i = indexWhere(test, start); + const ssize_t i = indexWhere(std::move(test), start); if (i < 0) return def; else @@ -739,7 +739,7 @@ public: //! заданному в передаваемой функции `test`, или `def` если такого элемента нет. //! \~\sa \a lastIndexWhere() inline const T & lastAtWhere(std::function test, ssize_t start = -1, const T & def = T()) const { - const ssize_t i = lastIndexWhere(test, start); + const ssize_t i = lastIndexWhere(std::move(test), start); if (i < 0) return def; else @@ -1267,7 +1267,7 @@ public: deleteT(piv_data + new_size, piv_size - new_size); piv_size = new_size; } else if (new_size > piv_size) { - expand(new_size, f); + expand(new_size, std::move(f)); } return *this; } @@ -1486,7 +1486,7 @@ public: //! \endcode //! \~\sa \a sort() inline PIVector & sort(std::function comp) { - std::stable_sort(begin(), end(), comp); + std::stable_sort(begin(), end(), std::move(comp)); return *this; } @@ -1599,7 +1599,13 @@ public: //! \endcode //! \~\sa \a remove(), \a removeOne(), \a removeWhere() inline PIVector & removeWhere(std::function test) { - ssize_t j = indexWhere(test); + ssize_t j = -1; + for (size_t i = 0; i < piv_size; ++i) { + if (test(piv_data[i])) { + j = i; + break; + } + } if (j != -1) { for (size_t i = j + 1; i < piv_size; ++i) { if (!test(piv_data[i])) { diff --git a/libs/main/containers/pivector2d.h b/libs/main/containers/pivector2d.h index dd38c6f9..e60f5ed0 100644 --- a/libs/main/containers/pivector2d.h +++ b/libs/main/containers/pivector2d.h @@ -1070,7 +1070,7 @@ public: //! \~english Counts elements in the flat vector that pass the `test`. //! \~russian Подсчитывает элементы в плоском векторе, проходящие `test`. //! \~\sa PIVector::entries(std::function) - inline int entries(std::function test) const { return mat.entries(test); } + inline int entries(std::function test) const { return mat.entries(std::move(test)); } //! \~english Returns the first index (row, col) of `e` in the 2D array. @@ -1086,7 +1086,7 @@ public: //! \~russian Возвращает первый индекс (строка, столбец) в двумерном массиве, проходящий `test`. //! \~\sa PIVector::indexWhere() inline Index indexWhere(std::function test, ssize_t start = 0) const { - ssize_t flat = mat.indexWhere(test, start); + ssize_t flat = mat.indexWhere(std::move(test), start); if (flat < 0 || cols_ == 0) return Index{-1, -1}; return Index{flat / static_cast(cols_), flat % static_cast(cols_)}; } @@ -1104,7 +1104,7 @@ public: //! \~russian Возвращает последний индекс (строка, столбец) в двумерном массиве, проходящий `test`. //! \~\sa PIVector::lastIndexWhere() inline Index lastIndexWhere(std::function test, ssize_t start = -1) const { - ssize_t flat = mat.lastIndexWhere(test, start); + ssize_t flat = mat.lastIndexWhere(std::move(test), start); if (flat < 0 || cols_ == 0) return Index{-1, -1}; return Index{flat / static_cast(cols_), flat % static_cast(cols_)}; } @@ -1113,12 +1113,12 @@ public: //! \~english Tests if any element in the flat vector passes the `test`. //! \~russian Проверяет, проходит ли какой-либо элемент в плоском векторе `test`. //! \~\sa PIVector::any() - inline bool any(std::function test) const { return mat.any(test); } + inline bool any(std::function test) const { return mat.any(std::move(test)); } //! \~english Tests if all elements in the flat vector pass the `test`. //! \~russian Проверяет, проходят ли все элементы в плоском векторе `test`. //! \~\sa PIVector::every() - inline bool every(std::function test) const { return mat.every(test); } + inline bool every(std::function test) const { return mat.every(std::move(test)); } //! \~english Fills the entire 2D array with copies of `e`. //! \~russian Заполняет весь двумерный массив копиями `e`. @@ -1132,7 +1132,7 @@ public: //! \~russian Заполняет весь двумерный массив, используя функцию-генератор `f` на основе плоского индекса. //! \~\sa PIVector::fill(std::function) inline PIVector2D & fill(std::function f) { - mat.fill(f); + mat.fill(std::move(f)); return *this; } @@ -1228,7 +1228,7 @@ public: //! \~\sa PIVector::map() template inline PIVector2D map(std::function f) const { - return PIVector2D(rows_, cols_, mat.template map(f)); + return PIVector2D(rows_, cols_, mat.template map(std::move(f))); } //! \~english Applies a function (with row and col indices) to each element and returns a new 2D array. @@ -1250,23 +1250,26 @@ public: //! \~russian Применяет функцию к каждой строке (с возможностью изменения). //! \~\sa forEachRow() const, PIVector::forEach() inline PIVector2D & forEachRow(std::function f) { - for (size_t r = 0; r < rows_; ++r) + for (size_t r = 0; r < rows_; ++r) { f(row(r)); + } return *this; } //! \~english Applies a function to each row (read-only). //! \~russian Применяет функцию к каждой строке (только чтение). inline void forEachRow(std::function f) const { - for (size_t r = 0; r < rows_; ++r) + for (size_t r = 0; r < rows_; ++r) { f(row(r)); + } } //! \~english Applies a function to each column (modifiable). //! \~russian Применяет функцию к каждому столбцу (с возможностью изменения). inline PIVector2D & forEachColumn(std::function f) { - for (size_t c = 0; c < cols_; ++c) + for (size_t c = 0; c < cols_; ++c) { f(col(c)); + } return *this; } @@ -1274,8 +1277,9 @@ public: //! \~russian Применяет функцию к каждому столбцу (только чтение). //! \param f Function taking a \a ColConst. inline void forEachColumn(std::function f) const { - for (size_t c = 0; c < cols_; ++c) + for (size_t c = 0; c < cols_; ++c) { f(col(c)); + } } //! \~english Accumulates a value across all elements. @@ -1283,7 +1287,7 @@ public: //! \~\sa PIVector::reduce() template inline ST reduce(std::function f, const ST & initial = ST()) const { - return mat.template reduce(f, initial); + return mat.template reduce(std::move(f), initial); } //! \~english Accumulates a value across all elements with indices. diff --git a/libs/main/core/pibase.h b/libs/main/core/pibase.h index b379dea4..babe62ac 100644 --- a/libs/main/core/pibase.h +++ b/libs/main/core/pibase.h @@ -731,7 +731,7 @@ public: //! \~\brief //! \~english Constructor that takes a function to execute //! \~russian Конструктор, который принимает функцию для выполнения - explicit PIScopeExitCall(std::function f): func(f) {} + explicit PIScopeExitCall(std::function f): func(std::move(f)) {} //! \~\brief //! \~english Destructor that executes the function if it exists diff --git a/libs/main/http_server/microhttpd_server.h b/libs/main/http_server/microhttpd_server.h index e706743f..fc90b321 100644 --- a/libs/main/http_server/microhttpd_server.h +++ b/libs/main/http_server/microhttpd_server.h @@ -113,11 +113,11 @@ public: //! \~english Sets the callback that receives parsed requests and returns replies. //! \~russian Устанавливает callback, который получает разобранные запросы и возвращает ответы. - void setRequestCallback(std::function c) { callback = c; } + void setRequestCallback(std::function c) { callback = std::move(c); } //! \~english Sets the credential validator used when HTTP Basic authentication is enabled. //! \~russian Устанавливает валидатор учетных данных, используемый при включенной HTTP Basic-аутентификации. - void setBasicAuthCallback(std::function c) { callback_auth = c; } + void setBasicAuthCallback(std::function c) { callback_auth = std::move(c); } private: static void addFixedHeaders(PIHTTP::MessageMutable & msg); diff --git a/libs/main/io_devices/pibinarylog.h b/libs/main/io_devices/pibinarylog.h index f26fe5e4..38b3d21c 100644 --- a/libs/main/io_devices/pibinarylog.h +++ b/libs/main/io_devices/pibinarylog.h @@ -419,7 +419,7 @@ public: //! \~\param f //! \~english The callback function returning the next file path, or nullptr to use the internal generator. //! \~russian Функция обратного вызова, возвращающая путь к следующему файлу, или nullptr для использования внутреннего генератора. - void setFuncGetNewFilePath(std::function f) { f_new_path = f; } + void setFuncGetNewFilePath(std::function f) { f_new_path = std::move(f); } //! \~english Writes one record with explicit ID and payload. //! \~russian Записывает одну запись с явным идентификатором и данными. diff --git a/libs/main/io_devices/piiodevice.cpp b/libs/main/io_devices/piiodevice.cpp index 45344dd4..c3fb8b73 100644 --- a/libs/main/io_devices/piiodevice.cpp +++ b/libs/main/io_devices/piiodevice.cpp @@ -177,7 +177,7 @@ void PIIODevice::setReopenTimeout(PISystemTime timeout) { //! после каждого успешного потокового чтения. Метод должен быть //! в формате "bool func(void * data, uchar * readed, int size)" void PIIODevice::setThreadedReadSlot(ReadRetFunc func) { - func_read = func; + func_read = std::move(func); } diff --git a/libs/main/io_utils/pipacketextractor.h b/libs/main/io_utils/pipacketextractor.h index bb831a3a..7b41e0da 100644 --- a/libs/main/io_utils/pipacketextractor.h +++ b/libs/main/io_utils/pipacketextractor.h @@ -103,15 +103,15 @@ public: //! \~english Sets custom header validation callback. //! \~russian Устанавливает пользовательский callback проверки заголовка. - void setHeaderCheckSlot(PacketExtractorHeaderFunc f) { func_header = f; } + void setHeaderCheckSlot(PacketExtractorHeaderFunc f) { func_header = std::move(f); } //! \~english Sets custom payload validation callback. //! \~russian Устанавливает пользовательский callback проверки полезной нагрузки. - void setPayloadCheckSlot(PacketExtractorPayloadFunc f) { func_payload = f; } + void setPayloadCheckSlot(PacketExtractorPayloadFunc f) { func_payload = std::move(f); } //! \~english Sets custom footer validation callback. //! \~russian Устанавливает пользовательский callback проверки окончания пакета. - void setFooterCheckSlot(PacketExtractorFooterFunc f) { func_footer = f; } + void setFooterCheckSlot(PacketExtractorFooterFunc f) { func_footer = std::move(f); } //! \~english Switches packet extraction to mode "mode". diff --git a/libs/main/io_utils/piparsehelper.h b/libs/main/io_utils/piparsehelper.h index fe8e9433..2fb13c39 100644 --- a/libs/main/io_utils/piparsehelper.h +++ b/libs/main/io_utils/piparsehelper.h @@ -62,7 +62,7 @@ public: //! \~russian Связывает ключ "key" с callback-функцией "func" без аргументов полезной нагрузки. void assign(Key key, std::function func) { auto lf = [func](PIByteArray) { func(); }; - functions[key] << lf; + functions[key] << std::move(lf); } @@ -77,7 +77,7 @@ public: func(v); } }; - functions[key] << lf; + functions[key] << std::move(lf); } diff --git a/libs/main/math/pievaluator.h b/libs/main/math/pievaluator.h index c492cb7a..69fb9b75 100644 --- a/libs/main/math/pievaluator.h +++ b/libs/main/math/pievaluator.h @@ -175,7 +175,7 @@ struct PIP_EXPORT Function { identifier = name; arguments = args; type = bfCustom; - handler = h; + handler = std::move(h); } PIString identifier; diff --git a/libs/main/math/pimathvector.h b/libs/main/math/pimathvector.h index 2deaa403..d7974bdf 100644 --- a/libs/main/math/pimathvector.h +++ b/libs/main/math/pimathvector.h @@ -5,7 +5,7 @@ //! \~russian Математический вектор /* PIP - Platform Independent Primitives - Math vector + Math vector Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru This program is free software: you can redistribute it and/or modify @@ -805,11 +805,12 @@ public: //! \~english Applies \a f to every coordinate without modifying the vector. //! \~russian Применяет \a f к каждой координате без изменения вектора. - void forEach(std::function f) const { c.forEach(f); } + void forEach(std::function f) const { c.forEach(std::move(f)); } + //! \~english Applies \a f to every coordinate and returns this vector. //! \~russian Применяет \a f к каждой координате и возвращает этот вектор. _CVector & forEach(std::function f) { - c.forEach(f); + c.forEach(std::move(f)); return *this; } diff --git a/libs/main/state_machine/pistatemachine.h b/libs/main/state_machine/pistatemachine.h index d391bf68..a2db754e 100644 --- a/libs/main/state_machine/pistatemachine.h +++ b/libs/main/state_machine/pistatemachine.h @@ -57,7 +57,7 @@ public: //! \~english Sets a callback invoked when the machine finishes. //! \~russian Задает callback-функцию, вызываемую при завершении машины. - void setOnFinish(std::function f) { on_finish = f; } + void setOnFinish(std::function f) { on_finish = std::move(f); } //! \~english Posts an event to active states and triggers the first matching transition. diff --git a/libs/main/state_machine/pistatemachine_base.h b/libs/main/state_machine/pistatemachine_base.h index 60afe49c..661538d3 100644 --- a/libs/main/state_machine/pistatemachine_base.h +++ b/libs/main/state_machine/pistatemachine_base.h @@ -51,7 +51,7 @@ public: template FunctionBase * makeFunction(std::function func) { auto * ret = new Function(); - ret->func = func; + ret->func = std::move(func); return ret; } diff --git a/libs/main/state_machine/pistatemachine_state.h b/libs/main/state_machine/pistatemachine_state.h index 388c94ce..b79049ca 100644 --- a/libs/main/state_machine/pistatemachine_state.h +++ b/libs/main/state_machine/pistatemachine_state.h @@ -5,8 +5,8 @@ //! \~russian Объявляет состояния, используемые в PIStateMachine /* PIP - Platform Independent Primitives - State machine node - Ivan Pelipenko peri4ko@yandex.ru + State machine node + Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -196,10 +196,10 @@ class PIP_EXPORT PIStateLambda: public PIStateBase { public: //! \~english Creates a state backed by enter and exit callbacks. //! \~russian Создает состояние с callback-функциями входа и выхода. - PIStateLambda(std::function on_enter, std::function on_exit = nullptr, const PIString & n = {}): PIStateBase(n) { - enter = on_enter; - exit = on_exit; - } + PIStateLambda(std::function on_enter, std::function on_exit = nullptr, const PIString & n = {}) + : PIStateBase(n) + , enter(std::move(on_enter)) + , exit(std::move(on_exit)) {} //! \~english Executes the enter callback. @@ -228,9 +228,8 @@ class PIP_EXPORT PIStateFinal: public PIStateBase { public: //! \~english Creates a final state with an optional callback executed on entry. //! \~russian Создает финальное состояние с необязательной callback-функцией, выполняемой при входе. - PIStateFinal(std::function on_finish = nullptr, const PIString & n = {}): PIStateBase(n) { + PIStateFinal(std::function on_finish = nullptr, const PIString & n = {}): PIStateBase(n), enter(std::move(on_finish)) { is_final = true; - enter = on_finish; } diff --git a/libs/main/state_machine/pistatemachine_transition.cpp b/libs/main/state_machine/pistatemachine_transition.cpp index b59150b6..849681fb 100644 --- a/libs/main/state_machine/pistatemachine_transition.cpp +++ b/libs/main/state_machine/pistatemachine_transition.cpp @@ -35,7 +35,7 @@ PITransitionBase::~PITransitionBase() { PITransitionBase * PITransitionBase::addAction(std::function a) { - action = a; + action = std::move(a); return this; } diff --git a/libs/main/system/pisignals.h b/libs/main/system/pisignals.h index e308001c..55e920fb 100644 --- a/libs/main/system/pisignals.h +++ b/libs/main/system/pisignals.h @@ -68,7 +68,7 @@ public: //! \~english Installs callback that receives grabbed signals. //! \~russian Устанавливает обратный вызов, получающий перехваченные сигналы. - static void setSlot(SignalEvent slot) { ret_func = slot; } + static void setSlot(SignalEvent slot) { ret_func = std::move(slot); } //! \~english Redirects selected signals to the slot set by \a setSlot(). //! \~russian Перенаправляет выбранные сигналы в обработчик, заданный через \a setSlot(). diff --git a/libs/main/thread/piconditionvar.cpp b/libs/main/thread/piconditionvar.cpp index 0c6412c4..753b2cfc 100644 --- a/libs/main/thread/piconditionvar.cpp +++ b/libs/main/thread/piconditionvar.cpp @@ -86,11 +86,9 @@ void PIConditionVariable::wait(PIMutex & lk) { } -void PIConditionVariable::wait(PIMutex & lk, const std::function & condition) { - bool isCondition; +void PIConditionVariable::wait(PIMutex & lk, std::function condition) { while (true) { - isCondition = condition(); - if (isCondition) break; + if (condition()) break; #if defined(WINDOWS) SleepConditionVariableCS(&PRIVATE->nativeHandle, (PCRITICAL_SECTION)lk.handle(), INFINITE); #elif defined(FREERTOS) @@ -122,8 +120,7 @@ bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout) { } -bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout, const std::function & condition) { - bool isCondition; +bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout, std::function condition) { #if defined(WINDOWS) || defined(FREERTOS) PITimeMeasurer measurer; #else @@ -135,8 +132,7 @@ bool PIConditionVariable::waitFor(PIMutex & lk, PISystemTime timeout, const std: xEventGroupClearBits(PRIVATE->nativeHandle, 1); #endif while (true) { - isCondition = condition(); - if (isCondition) break; + if (condition()) break; bool isTimeout; #if defined(WINDOWS) isTimeout = SleepConditionVariableCS(&PRIVATE->nativeHandle, diff --git a/libs/main/thread/piconditionvar.h b/libs/main/thread/piconditionvar.h index 4487edea..25626a09 100644 --- a/libs/main/thread/piconditionvar.h +++ b/libs/main/thread/piconditionvar.h @@ -106,7 +106,7 @@ public: //! \param condition вызываемый объект или функция, не принимающая аргументов и возвращающая значение, которое может быть оценено как //! bool. Вызывается повторно, пока не примет значение true //! - virtual void wait(PIMutex & lk, const std::function & condition); + virtual void wait(PIMutex & lk, std::function condition); //! \~english Waits for at most \a timeout and returns \c true if awakened before it expires. @@ -167,7 +167,7 @@ public: //! bool. Вызывается повторно, пока не примет значение true //! \return false если достигнут таймаут, или true если условие пробуждения истинно //! - virtual bool waitFor(PIMutex & lk, PISystemTime timeout, const std::function & condition); + virtual bool waitFor(PIMutex & lk, PISystemTime timeout, std::function condition); private: PRIVATE_DECLARATION(PIP_EXPORT) diff --git a/libs/main/thread/pithread.cpp b/libs/main/thread/pithread.cpp index 4e03b73a..0f9aa389 100644 --- a/libs/main/thread/pithread.cpp +++ b/libs/main/thread/pithread.cpp @@ -540,7 +540,7 @@ PRIVATE_DEFINITION_END(PIThread) PIThread::PIThread(void * data, ThreadFunc func, bool startNow, PISystemTime loop_delay): PIObject() { PIINTROSPECTION_THREAD_NEW(this); data_ = data; - ret_func = func; + ret_func = std::move(func); terminating = running_ = lockRun = false; priority_ = piNormal; delay_ = loop_delay; @@ -609,13 +609,13 @@ bool PIThread::start(PISystemTime loop_delay) { bool PIThread::start(ThreadFunc func) { - ret_func = func; + ret_func = std::move(func); return start(); } bool PIThread::start(ThreadFunc func, PISystemTime loop_delay) { - ret_func = func; + ret_func = std::move(func); delay_ = loop_delay; return start(); } @@ -641,7 +641,7 @@ bool PIThread::startOnce() { bool PIThread::startOnce(ThreadFunc func) { - ret_func = func; + ret_func = std::move(func); return startOnce(); } @@ -738,12 +738,12 @@ bool PIThread::_startThread(void * func) { #ifdef FREERTOS auto name_ba = createThreadName(); - if (xTaskCreate((__THREAD_FUNC_RET__ (*)(void *))func, - (const char *)name_ba.data(), // A name just for humans - 128, // This stack size can be checked & adjusted by reading the Stack Highwater - this, - priority_, - &PRIVATE->thread) == pdPASS) { + if (xTaskCreate((__THREAD_FUNC_RET__(*)(void *))func, + (const char *)name_ba.data(), // A name just for humans + 128, // This stack size can be checked & adjusted by reading the Stack Highwater + this, + priority_, + &PRIVATE->thread) == pdPASS) { tid_ = (llong)PRIVATE->thread; return true; } @@ -752,7 +752,7 @@ bool PIThread::_startThread(void * func) { if (PRIVATE->thread) CloseHandle(PRIVATE->thread); # ifdef CC_GCC - PRIVATE->thread = (void *)_beginthreadex(0, 0, (__THREAD_FUNC_RET__ (*)(void *))func, this, CREATE_SUSPENDED, 0); + PRIVATE->thread = (void *)_beginthreadex(0, 0, (__THREAD_FUNC_RET__(*)(void *))func, this, CREATE_SUSPENDED, 0); # else PRIVATE->thread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)func, this, CREATE_SUSPENDED, 0); # endif @@ -766,7 +766,7 @@ bool PIThread::_startThread(void * func) { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - int ret = pthread_create(&PRIVATE->thread, &attr, (__THREAD_FUNC_RET__ (*)(void *))func, this); + int ret = pthread_create(&PRIVATE->thread, &attr, (__THREAD_FUNC_RET__(*)(void *))func, this); pthread_attr_destroy(&attr); // PICout(PICoutManipulators::DefaultControls) << "pthread_create" << PRIVATE->thread; // piCout << "started" << PRIVATE->thread; @@ -884,8 +884,8 @@ void PIThread::_runThread() { PIINTROSPECTION_THREAD_RUN(this); // PICout(PICoutManipulators::DefaultControls) << "thread" << this << "lock" << "..."; if (lockRun) thread_mutex.lock(); - // PICout(PICoutManipulators::DefaultControls) << "thread" << this << "lock" << "ok"; - // PICout(PICoutManipulators::DefaultControls) << "thread" << this << "run" << "..."; + // PICout(PICoutManipulators::DefaultControls) << "thread" << this << "lock" << "ok"; + // PICout(PICoutManipulators::DefaultControls) << "thread" << this << "run" << "..."; #ifdef PIP_INTROSPECTION PITimeMeasurer _tm; #endif @@ -1042,7 +1042,7 @@ void PIThread::runOnce(PIObject * object, const char * handler, const PIString & void PIThread::runOnce(std::function func, const PIString & name) { PIThread * t = new PIThread(); t->setName(name); - t->setSlot(func); + t->setSlot(std::move(func)); #ifndef MICRO_PIP __PIThreadCollection::instance()->startedAuto(t); CONNECT0(void, t, stopped, __PIThreadCollection::instance(), stoppedAuto); diff --git a/libs/main/thread/pithreadpoolexecutor.cpp b/libs/main/thread/pithreadpoolexecutor.cpp index 55d3ae29..1973b6bd 100644 --- a/libs/main/thread/pithreadpoolexecutor.cpp +++ b/libs/main/thread/pithreadpoolexecutor.cpp @@ -68,8 +68,8 @@ PIThreadPoolExecutor::~PIThreadPoolExecutor() { } -void PIThreadPoolExecutor::execute(const std::function & runnable) { - if (!isShutdown_) taskQueue.offer(runnable); +void PIThreadPoolExecutor::execute(std::function runnable) { + if (!isShutdown_) taskQueue.offer(std::move(runnable)); } diff --git a/libs/main/thread/pithreadpoolexecutor.h b/libs/main/thread/pithreadpoolexecutor.h index a94cd4e5..c302dbd2 100644 --- a/libs/main/thread/pithreadpoolexecutor.h +++ b/libs/main/thread/pithreadpoolexecutor.h @@ -59,7 +59,7 @@ public: //! \~russian //! Это вызов по принципу best-effort без ожидания результата и без сообщения о том, была ли задача принята. //! \После запроса на завершение новые задачи игнорируются. - void execute(const std::function & runnable); + void execute(std::function runnable); //! \~english Requests immediate shutdown and stops worker threads without waiting for queued tasks to finish. //! \~russian Запрашивает немедленное завершение и останавливает рабочие потоки без ожидания завершения задач в очереди. diff --git a/libs/main/thread/pithreadpoolloop.cpp b/libs/main/thread/pithreadpoolloop.cpp index b8e1494f..41367adc 100644 --- a/libs/main/thread/pithreadpoolloop.cpp +++ b/libs/main/thread/pithreadpoolloop.cpp @@ -136,7 +136,7 @@ PIThreadPoolLoop::~PIThreadPoolLoop() { void PIThreadPoolLoop::setFunction(std::function f) { - func = f; + func = std::move(f); } @@ -163,6 +163,6 @@ void PIThreadPoolLoop::exec(int index_start, int index_count) { void PIThreadPoolLoop::exec(int index_start, int index_count, std::function f) { - setFunction(f); + setFunction(std::move(f)); exec(index_start, index_count); } diff --git a/libs/main/types/pibytearray.h b/libs/main/types/pibytearray.h index c519b152..14fd688f 100644 --- a/libs/main/types/pibytearray.h +++ b/libs/main/types/pibytearray.h @@ -198,7 +198,7 @@ public: //! Метод возвращает **false** при любом условии для пустого массива. //! \~\details //! \~\sa \a every(), \a contains(), \a entries(), \a forEach() - inline bool any(std::function test) const { return d.any(test); } + inline bool any(std::function test) const { return d.any(std::move(test)); } //! \~english Tests whether all elements in the array passes the test //! implemented by the provided function `test`. @@ -213,7 +213,7 @@ public: //! Метод возвращает **true** при любом условии для пустого массива. //! \~\details //! \~\sa \a any(), \a contains(), \a entries(), \a forEach() - inline bool every(std::function test) const { return d.every(test); } + inline bool every(std::function test) const { return d.every(std::move(test)); } //! \~english Full access to element by `index`. //! \~russian Полный доступ к элементу по индексу `index`. @@ -340,7 +340,7 @@ public: //! Обратите внимание: если индекс отрицателен, массив всё равно просматривается от начала к концу. //! Значение по умолчанию равно 0, что означает, что просматривается весь массив. //! \~\sa \a every(), \a any(), \a contains(), \a indexWhere() - inline int entries(std::function test, ssize_t start = 0) const { return d.entries(test, start); } + inline int entries(std::function test, ssize_t start = 0) const { return d.entries(std::move(test), start); } bool startsWith(const PIByteArray & o) const; @@ -405,7 +405,9 @@ public: //! piCout << v.indexWhere([](const uchar & s){return s > 10;}); // -1 //! \endcode //! \~\sa \a indexOf(), \a lastIndexOf(), \a lastIndexWhere(), \a contains() - inline ssize_t indexWhere(std::function test, ssize_t start = 0) const { return d.indexWhere(test, start); } + inline ssize_t indexWhere(std::function test, ssize_t start = 0) const { + return d.indexWhere(std::move(test), start); + } //! \~english Returns the last index at which a given element `e` //! can be found in the array, or `-1` if it is not present. @@ -469,7 +471,7 @@ public: //! и означает, что просматривается весь массив. //! \~\sa \a indexOf(), \a lastIndexOf(), \a indexWhere(), \a contains() inline ssize_t lastIndexWhere(std::function test, ssize_t start = -1) const { - return d.lastIndexWhere(test, start); + return d.lastIndexWhere(std::move(test), start); } //! \~english Pointer to array @@ -525,7 +527,7 @@ public: //! \~\details //! \~\sa \a resize() inline PIByteArray & fill(std::function f) { - d.fill(f); + d.fill(std::move(f)); return *this; } @@ -570,7 +572,7 @@ public: //! лишние элементы удаляются с конца массива. //! \~\sa \a size(), \a clear() inline PIByteArray & resize(size_t new_size, std::function f) { - d.resize(new_size, f); + d.resize(new_size, std::move(f)); return *this; } @@ -738,7 +740,7 @@ public: //! \~\details //! \~\sa \a remove(), \a removeOne(), \a removeWhere() inline PIByteArray & removeWhere(std::function test) { - d.removeWhere(test); + d.removeWhere(std::move(test)); return *this; } @@ -950,7 +952,7 @@ public: //! piCout << v2; // {3, 5, 7} //! \endcode //! \~\sa \a map(), \a any(), \a every() - inline PIByteArray filter(std::function test) const { return PIByteArray(d.filter(test)); } + inline PIByteArray filter(std::function test) const { return PIByteArray(d.filter(std::move(test))); } //! \~english Execute function `void f(const uchar & e)` for every element in array. //! \~russian Выполняет функцию `void f(const uchar & e)` для каждого элемента массива. @@ -966,7 +968,7 @@ public: //! piCout << s; // 15 //! \endcode //! \~\sa \a filter(), \a map(), \a reduce(), \a any(), \a every() - inline void forEach(std::function f) const { d.forEach(f); } + inline void forEach(std::function f) const { d.forEach(std::move(f)); } //! \~english Execute function `void f(uchar & e)` for every element in array. //! \~russian Выполняет функцию `void f(uchar & e)` для каждого элемента массива. @@ -982,7 +984,7 @@ public: //! \endcode //! \~\sa \a filter(), \a map(), \a reduce(), \a any(), \a every() inline PIByteArray & forEach(std::function f) { - d.forEach(f); + d.forEach(std::move(f)); return *this; } @@ -1005,7 +1007,7 @@ public: //! \~\sa \a forEach(), \a reduce() template inline PIDeque map(std::function f) const { - return d.map(f); + return d.map(std::move(f)); } //! \~english Applies the function `ST f(const uchar & e, const ST & acc)` @@ -1051,7 +1053,7 @@ public: //! \~\sa \a forEach(), \a map() template inline ST reduce(std::function f, const ST & initial = ST()) const { - return d.reduce(f, initial); + return d.reduce(std::move(f), initial); } //! \~english Convert data to Base 64 and return this byte array diff --git a/libs/main/types/pivaluetree.cpp b/libs/main/types/pivaluetree.cpp index b21f6279..a02ce13c 100644 --- a/libs/main/types/pivaluetree.cpp +++ b/libs/main/types/pivaluetree.cpp @@ -262,7 +262,7 @@ PIValueTree & PIValueTree::remove(const PIString & name) { void PIValueTree::forEachRecursive(std::function func) { - forEachRecursiveInternal(func); + forEachRecursiveInternal(std::move(func)); } From dc16a0c903e1a948167624db5ce9374e61a5690d Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 20 Mar 2026 17:26:58 +0300 Subject: [PATCH 20/26] CMakeLists.txt --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de404754..3b6453a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -365,7 +365,10 @@ if(WIN32) set(CMAKE_CXX_FLAGS "/O2 /Ob2 /Ot /W0 /EH-") endif() else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-exceptions") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + if (NOT DEFINED ANDROID_PLATFORM) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") + endif() if(DEFINED ENV{QNX_HOST} OR PIP_FREERTOS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-32") endif() From edb7189013c6eab45178d203c8fe3d60c3bb0996 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Sat, 21 Mar 2026 13:31:29 +0300 Subject: [PATCH 21/26] disable gmock build and remove obsolete tests --- tests/CMakeLists.txt | 2 +- tests/concurrent/BlockingDequeueUnitTest.cpp | 262 ------------------ .../ConditionLockIntegrationTest.cpp | 57 ---- .../ConditionVariableIntegrationTest.cpp | 209 -------------- tests/concurrent/ExecutorIntegrationTest.cpp | 51 ---- tests/concurrent/pithreadnotifier_test.cpp | 57 ---- tests/concurrent/testutil.h | 62 ----- 7 files changed, 1 insertion(+), 699 deletions(-) delete mode 100644 tests/concurrent/BlockingDequeueUnitTest.cpp delete mode 100644 tests/concurrent/ConditionLockIntegrationTest.cpp delete mode 100644 tests/concurrent/ConditionVariableIntegrationTest.cpp delete mode 100644 tests/concurrent/ExecutorIntegrationTest.cpp delete mode 100644 tests/concurrent/pithreadnotifier_test.cpp delete mode 100644 tests/concurrent/testutil.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 20503839..4a1a80ef 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,6 +13,7 @@ FetchContent_Declare( ) # For Windows: Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(BUILD_GMOCK OFF CACHE BOOL "Build Google Mock" FORCE) FetchContent_MakeAvailable(googletest) enable_testing() @@ -32,7 +33,6 @@ macro(pip_test NAME) set(PIP_TESTS_LIST ${PIP_TESTS_LIST} PARENT_SCOPE) endmacro() -#pip_test(concurrent) pip_test(math) pip_test(core) pip_test(piobject) diff --git a/tests/concurrent/BlockingDequeueUnitTest.cpp b/tests/concurrent/BlockingDequeueUnitTest.cpp deleted file mode 100644 index c0b42b9d..00000000 --- a/tests/concurrent/BlockingDequeueUnitTest.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "piblockingqueue.h" - -#include "gtest/gtest.h" - -class MockConditionVar: public PIConditionVariable { -public: - bool isWaitCalled = false; - bool isWaitForCalled = false; - bool isTrueCondition = false; - int timeout = -1; - - void wait(PIMutex & lk) override { isWaitCalled = true; } - - void wait(PIMutex & lk, const std::function & condition) override { - isWaitCalled = true; - isTrueCondition = condition(); - } - - bool waitFor(PIMutex & lk, int timeoutMs) override { - isWaitForCalled = true; - timeout = timeoutMs; - return false; - } - - bool waitFor(PIMutex & lk, int timeoutMs, const std::function & condition) override { - isWaitForCalled = true; - isTrueCondition = condition(); - timeout = timeoutMs; - return isTrueCondition; - } -}; - -TEST(BlockingDequeueUnitTest, put_is_block_when_capacity_reach) { - size_t capacity = 0; - auto conditionVarAdd = new MockConditionVar(); - auto conditionVarRem = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVarAdd, conditionVarRem); - dequeue.put(11); - ASSERT_TRUE(conditionVarRem->isWaitCalled); - ASSERT_FALSE(conditionVarRem->isTrueCondition); -} - -TEST(BlockingDequeueUnitTest, offer_timedout_is_false_when_capacity_reach) { - size_t capacity = 0; - int timeout = 11; - auto conditionVarAdd = new MockConditionVar(); - auto conditionVarRem = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVarAdd, conditionVarRem); - ASSERT_FALSE(dequeue.offer(11, timeout)); -} - -TEST(BlockingDequeueUnitTest, offer_timedout_is_block_when_capacity_reach) { - size_t capacity = 0; - int timeout = 11; - auto conditionVarAdd = new MockConditionVar(); - auto conditionVarRem = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVarAdd, conditionVarRem); - dequeue.offer(11, timeout); - EXPECT_TRUE(conditionVarRem->isWaitForCalled); - EXPECT_EQ(timeout, conditionVarRem->timeout); - ASSERT_FALSE(conditionVarRem->isTrueCondition); -} - -TEST(BlockingDequeueUnitTest, offer_is_true_before_capacity_reach) { - size_t capacity = 1; - PIBlockingQueue dequeue(capacity); - ASSERT_TRUE(dequeue.offer(10)); -} - -TEST(BlockingDequeueUnitTest, offer_is_false_when_capacity_reach) { - size_t capacity = 1; - PIBlockingQueue dequeue(capacity); - dequeue.offer(11); - ASSERT_FALSE(dequeue.offer(10)); -} - -// TODO change take_is_block_when_empty to prevent segfault -TEST(DISABLED_BlockingDequeueUnitTest, take_is_block_when_empty) { - size_t capacity = 1; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - // May cause segfault because take front of empty queue - dequeue.take(); - EXPECT_TRUE(conditionVar->isWaitCalled); - ASSERT_FALSE(conditionVar->isTrueCondition); -} - -TEST(BlockingDequeueUnitTest, take_is_not_block_when_not_empty) { - size_t capacity = 1; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.offer(111); - dequeue.take(); - - EXPECT_TRUE(conditionVar->isWaitCalled); - ASSERT_TRUE(conditionVar->isTrueCondition); -} - -TEST(BlockingDequeueUnitTest, take_is_value_eq_to_offer_value) { - size_t capacity = 1; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - - dequeue.offer(111); - ASSERT_EQ(dequeue.take(), 111); -} - -TEST(BlockingDequeueUnitTest, take_is_last) { - size_t capacity = 10; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - EXPECT_TRUE(dequeue.offer(111)); - EXPECT_TRUE(dequeue.offer(222)); - ASSERT_EQ(dequeue.take(), 111); - ASSERT_EQ(dequeue.take(), 222); -} - -TEST(BlockingDequeueUnitTest, poll_is_not_block_when_empty) { - size_t capacity = 1; - bool isOk; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.poll(0, 111, &isOk); - EXPECT_FALSE(conditionVar->isWaitForCalled); -} - -TEST(BlockingDequeueUnitTest, poll_is_default_value_when_empty) { - size_t capacity = 1; - bool isOk; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - ASSERT_EQ(dequeue.poll(0, 111, &isOk), 111); -} - -TEST(BlockingDequeueUnitTest, poll_is_offer_value_when_not_empty) { - size_t capacity = 1; - bool isOk; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.offer(111); - ASSERT_EQ(dequeue.poll(0, -1, &isOk), 111); -} - -TEST(BlockingDequeueUnitTest, poll_timeouted_is_block_when_empty) { - size_t capacity = 1; - int timeout = 11; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.poll(timeout, 111); - EXPECT_TRUE(conditionVar->isWaitForCalled); - EXPECT_EQ(timeout, conditionVar->timeout); - ASSERT_FALSE(conditionVar->isTrueCondition); -} - -TEST(BlockingDequeueUnitTest, poll_timeouted_is_default_value_when_empty) { - size_t capacity = 1; - int timeout = 11; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - ASSERT_EQ(dequeue.poll(timeout, 111), 111); -} - -TEST(BlockingDequeueUnitTest, poll_timeouted_is_not_block_when_not_empty) { - size_t capacity = 1; - int timeout = 11; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.offer(111); - dequeue.poll(timeout, -1); - - EXPECT_TRUE(conditionVar->isWaitForCalled); - ASSERT_TRUE(conditionVar->isTrueCondition); -} - -TEST(BlockingDequeueUnitTest, poll_timeouted_is_offer_value_when_not_empty) { - size_t capacity = 1; - int timeout = 11; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.offer(111); - ASSERT_EQ(dequeue.poll(timeout, -1), 111); -} - -TEST(BlockingDequeueUnitTest, poll_timeouted_is_last) { - size_t capacity = 10; - auto conditionVar = new MockConditionVar(); - PIBlockingQueue dequeue(capacity, conditionVar); - dequeue.offer(111); - dequeue.offer(222); - ASSERT_EQ(dequeue.poll(10, -1), 111); - ASSERT_EQ(dequeue.poll(10, -1), 222); -} - -TEST(BlockingDequeueUnitTest, capacity_is_eq_constructor_capacity) { - size_t capacity = 10; - PIBlockingQueue dequeue(capacity); - ASSERT_EQ(dequeue.capacity(), capacity); -} - -TEST(BlockingDequeueUnitTest, remainingCapacity_is_dif_of_capacity_and_size) { - size_t capacity = 2; - PIBlockingQueue dequeue(capacity); - ASSERT_EQ(dequeue.remainingCapacity(), capacity); - dequeue.offer(111); - ASSERT_EQ(dequeue.remainingCapacity(), capacity - 1); -} - -TEST(BlockingDequeueUnitTest, remainingCapacity_is_zero_when_capacity_reach) { - size_t capacity = 1; - PIBlockingQueue dequeue(capacity); - dequeue.offer(111); - dequeue.offer(111); - ASSERT_EQ(dequeue.remainingCapacity(), 0); -} - -TEST(BlockingDequeueUnitTest, size_is_eq_to_num_of_elements) { - size_t capacity = 1; - PIBlockingQueue dequeue(capacity); - ASSERT_EQ(dequeue.size(), 0); - dequeue.offer(111); - ASSERT_EQ(dequeue.size(), 1); -} - -TEST(BlockingDequeueUnitTest, size_is_eq_to_capacity_when_capacity_reach) { - size_t capacity = 1; - PIBlockingQueue dequeue(capacity); - dequeue.offer(111); - dequeue.offer(111); - ASSERT_EQ(dequeue.size(), capacity); -} - -TEST(BlockingDequeueUnitTest, drainTo_is_elements_moved) { - size_t capacity = 10; - PIDeque refDeque; - for (size_t i = 0; i < capacity / 2; ++i) - refDeque.push_back(i * 10); - PIBlockingQueue blockingDequeue(refDeque); - PIDeque deque; - blockingDequeue.drainTo(deque); - ASSERT_EQ(blockingDequeue.size(), 0); - ASSERT_TRUE(deque == refDeque); -} - -TEST(BlockingDequeueUnitTest, drainTo_is_ret_eq_to_size_when_all_moved) { - size_t capacity = 10; - PIDeque refDeque; - for (size_t i = 0; i < capacity / 2; ++i) - refDeque.push_back(i * 10); - PIBlockingQueue blockingDequeue(refDeque); - PIDeque deque; - ASSERT_EQ(blockingDequeue.drainTo(deque), refDeque.size()); -} - -TEST(BlockingDequeueUnitTest, drainTo_is_ret_eq_to_maxCount) { - size_t capacity = 10; - PIDeque refDeque; - for (size_t i = 0; i < capacity / 2; ++i) - refDeque.push_back(i * 10); - PIBlockingQueue blockingDequeue(refDeque); - PIDeque deque; - ASSERT_EQ(blockingDequeue.drainTo(deque, refDeque.size() - 1), refDeque.size() - 1); -} diff --git a/tests/concurrent/ConditionLockIntegrationTest.cpp b/tests/concurrent/ConditionLockIntegrationTest.cpp deleted file mode 100644 index 0e1e22f0..00000000 --- a/tests/concurrent/ConditionLockIntegrationTest.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "piconditionvar.h" -#include "pithread.h" -#include "testutil.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -class ConditionLock - : public ::testing::Test - , public TestUtil { -public: - PIMutex * m = new PIMutex(); - bool isProtect; - bool isReleased; -}; - - -TEST_F(ConditionLock, DISABLED_lock_is_protect) { - m->lock(); - isProtect = true; - createThread([&]() { - m->lock(); - isProtect = false; - }); - EXPECT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); - ASSERT_TRUE(isProtect); -} - -TEST_F(ConditionLock, DISABLED_unlock_is_release) { - m->lock(); - isReleased = false; - m->unlock(); - - createThread([&]() { - m->lock(); - isReleased = true; - m->unlock(); - }); - ASSERT_TRUE(isReleased); -} - -TEST_F(ConditionLock, tryLock_is_false_when_locked) { - createThread([&]() { - m->lock(); - piMSleep(WAIT_THREAD_TIME_MS); - }); - ASSERT_FALSE(m->tryLock()); -} - -TEST_F(ConditionLock, tryLock_is_true_when_unlocked) { - ASSERT_TRUE(m->tryLock()); -} - -TEST_F(ConditionLock, tryLock_is_recursive_lock_enable) { - m->lock(); - ASSERT_TRUE(m->tryLock()); -} diff --git a/tests/concurrent/ConditionVariableIntegrationTest.cpp b/tests/concurrent/ConditionVariableIntegrationTest.cpp deleted file mode 100644 index 4b514a94..00000000 --- a/tests/concurrent/ConditionVariableIntegrationTest.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "piconditionvar.h" -#include "pithread.h" -#include "testutil.h" - -#include "gtest/gtest.h" - -class ConditionVariable - : public ::testing::Test - , public TestUtil { -public: - ~ConditionVariable() { delete variable; } - PIMutex m; - PIConditionVariable * variable; - -protected: - void SetUp() override { - variable = new PIConditionVariable(); - adapterFunctionDefault = [&]() { - m.lock(); - variable->wait(m); - m.unlock(); - }; - } -}; - -TEST_F(ConditionVariable, wait_is_block) { - createThread(); - ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); -} - -TEST_F(ConditionVariable, wait_is_block_when_notifyOne_before_wait) { - variable->notifyOne(); - createThread(); - ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); -} - -TEST_F(ConditionVariable, wait_is_block_when_notifyAll_before_wait) { - variable->notifyAll(); - createThread(); - ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); -} - -TEST_F(ConditionVariable, wait_is_unblock_when_notifyOne_after_wait) { - createThread(); - variable->notifyOne(); - ASSERT_TRUE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); -} - -TEST_F(ConditionVariable, wait_is_unblock_when_notifyAll_after_wait) { - PIVector threads; - - for (int i = 0; i < THREAD_COUNT; ++i) { - threads.push_back(new PIThread([=]() { adapterFunctionDefault(); })); - } - - piForeach(PIThread * thread, threads) - thread->startOnce(); - piMSleep(WAIT_THREAD_TIME_MS * THREAD_COUNT); - variable->notifyAll(); - PITimeMeasurer measurer; - piForeach(PIThread * thread, threads) { - int timeout = WAIT_THREAD_TIME_MS * THREAD_COUNT - (int)measurer.elapsed_m(); - thread->waitForFinish(timeout > 0 ? timeout : 0); - } - for (size_t i = 0; i < threads.size(); ++i) - EXPECT_FALSE(threads[i]->isRunning()) << "Thread " << i << " still running"; - piForeach(PIThread * thread, threads) - delete thread; -} - -TEST_F(ConditionVariable, wait_is_one_unblock_when_notifyOne) { - PIVector threads; - - for (int i = 0; i < THREAD_COUNT; ++i) { - threads.push_back(new PIThread(adapterFunctionDefault)); - } - - piForeach(PIThread * thread, threads) - thread->startOnce(); - piMSleep(WAIT_THREAD_TIME_MS * THREAD_COUNT); - variable->notifyOne(); - piMSleep(WAIT_THREAD_TIME_MS * THREAD_COUNT); - int runningThreadCount = 0; - piForeach(PIThread * thread, threads) - if (thread->isRunning()) runningThreadCount++; - ASSERT_EQ(runningThreadCount, THREAD_COUNT - 1); -} - -TEST_F(ConditionVariable, wait_is_protected_unblock_when_notifyOne) { - createThread([&]() { - m.lock(); - variable->wait(m); - piMSleep(2 * WAIT_THREAD_TIME_MS); - // Missing unlock - }); - variable->notifyOne(); - piMSleep(WAIT_THREAD_TIME_MS); - ASSERT_FALSE(m.tryLock()); -} - -TEST_F(ConditionVariable, wait_condition_is_block) { - createThread([&]() { - m.lock(); - variable->wait(m, []() { return false; }); - m.unlock(); - }); - ASSERT_FALSE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); -} - -TEST_F(ConditionVariable, wait_condition_is_check_condition_before_block) { - bool isConditionChecked = false; - createThread([&]() { - m.lock(); - variable->wait(m, [&]() { - isConditionChecked = true; - return false; - }); - m.unlock(); - }); - m.lock(); - ASSERT_TRUE(isConditionChecked); - m.unlock(); -} - -TEST_F(ConditionVariable, wait_condition_is_check_condition_when_notifyOne) { - bool isConditionChecked; - createThread([&]() { - m.lock(); - variable->wait(m, [&]() { - isConditionChecked = true; - return false; - }); - m.unlock(); - }); - m.lock(); - isConditionChecked = false; - m.unlock(); - variable->notifyOne(); - piMSleep(threadStartTime + 1); - m.lock(); - ASSERT_TRUE(isConditionChecked); - m.unlock(); -} - -TEST_F(ConditionVariable, wait_condition_is_unblock_when_condition_and_notifyOne) { - bool condition = false; - createThread([&]() { - m.lock(); - variable->wait(m, [&]() { return condition; }); - m.unlock(); - }); - m.lock(); - condition = true; - m.unlock(); - variable->notifyOne(); - ASSERT_TRUE(thread->waitForFinish(WAIT_THREAD_TIME_MS)); -} - -TEST_F(ConditionVariable, DISABLED_waitFor_is_block_before_timeout) { - createThread([&]() { - PITimeMeasurer measurer; - m.lock(); - variable->waitFor(m, WAIT_THREAD_TIME_MS * 2); - m.unlock(); - // Not reliable because spurious wakeup may happen - ASSERT_GE(measurer.elapsed_m(), WAIT_THREAD_TIME_MS); - }); - EXPECT_TRUE(thread->waitForFinish(WAIT_THREAD_TIME_MS * 3)); -} - -TEST_F(ConditionVariable, waitFor_is_unblock_when_timeout) { - std::atomic_bool isUnblock(false); - createThread([&]() { - m.lock(); - variable->waitFor(m, WAIT_THREAD_TIME_MS); - isUnblock = true; - m.unlock(); - }); - // Test failed if suspend forever - EXPECT_TRUE(thread->waitForFinish(2 * WAIT_THREAD_TIME_MS)); - ASSERT_TRUE(isUnblock); -} - -TEST_F(ConditionVariable, waitFor_is_false_when_timeout) { - bool waitRet = true; - createThread([&]() { - m.lock(); - waitRet = variable->waitFor(m, WAIT_THREAD_TIME_MS); - m.unlock(); - }); - EXPECT_TRUE(thread->waitForFinish(2 * WAIT_THREAD_TIME_MS)); - ASSERT_FALSE(waitRet); -} - -TEST_F(ConditionVariable, waitFor_is_unblock_when_condition_and_notifyOne) { - bool condition = false; - createThread([&]() { - m.lock(); - variable->waitFor(m, 3 * WAIT_THREAD_TIME_MS, [&]() { return condition; }); - m.unlock(); - }); - EXPECT_TRUE(thread->isRunning()); - m.lock(); - condition = true; - m.unlock(); - variable->notifyOne(); - piMSleep(WAIT_THREAD_TIME_MS); - ASSERT_FALSE(thread->isRunning()); -} diff --git a/tests/concurrent/ExecutorIntegrationTest.cpp b/tests/concurrent/ExecutorIntegrationTest.cpp deleted file mode 100644 index 885fa9c6..00000000 --- a/tests/concurrent/ExecutorIntegrationTest.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "pimutex.h" -#include "pithreadpoolexecutor.h" - -#include "gtest/gtest.h" - -const int WAIT_THREAD_TIME_MS = 30; - -TEST(ExcutorIntegrationTest, execute_is_runnable_invoke) { - PIMutex m; - int invokedRunnables = 0; - PIThreadPoolExecutor executorService(1); - executorService.execute([&]() { - m.lock(); - invokedRunnables++; - m.unlock(); - }); - piMSleep(WAIT_THREAD_TIME_MS); - ASSERT_EQ(invokedRunnables, 1); -} - -TEST(ExcutorIntegrationTest, execute_is_not_execute_after_shutdown) { - bool isRunnableInvoke = false; - PIThreadPoolExecutor executorService(1); - executorService.shutdown(); - executorService.execute([&]() { isRunnableInvoke = true; }); - piMSleep(WAIT_THREAD_TIME_MS); - ASSERT_FALSE(isRunnableInvoke); -} - -TEST(ExcutorIntegrationTest, execute_is_execute_before_shutdown) { - bool isRunnableInvoke = false; - PIThreadPoolExecutor executorService(1); - executorService.execute([&]() { - piMSleep(WAIT_THREAD_TIME_MS); - isRunnableInvoke = true; - }); - executorService.shutdown(); - piMSleep(2 * WAIT_THREAD_TIME_MS); - ASSERT_TRUE(isRunnableInvoke); -} - -TEST(ExcutorIntegrationTest, execute_is_awaitTermination_wait) { - PIThreadPoolExecutor executorService(1); - executorService.execute([&]() { piMSleep(2 * WAIT_THREAD_TIME_MS); }); - executorService.shutdown(); - PITimeMeasurer measurer; - ASSERT_TRUE(executorService.awaitTermination(3 * WAIT_THREAD_TIME_MS)); - double waitTime = measurer.elapsed_m(); - ASSERT_GE(waitTime, WAIT_THREAD_TIME_MS); - ASSERT_LE(waitTime, 4 * WAIT_THREAD_TIME_MS); -} diff --git a/tests/concurrent/pithreadnotifier_test.cpp b/tests/concurrent/pithreadnotifier_test.cpp deleted file mode 100644 index 4b9994e3..00000000 --- a/tests/concurrent/pithreadnotifier_test.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "pithreadnotifier.h" - -#include "gtest/gtest.h" - - -TEST(PIThreadNotifierTest, One) { - PIThreadNotifier n; - int cnt = 0; - PIThread t1( - [&n, &cnt]() { - n.wait(); - cnt++; - }, - true); - piMSleep(10); - n.notifyOnce(); - piMSleep(10); - ASSERT_EQ(cnt, 1); - n.notifyOnce(); - piMSleep(10); - ASSERT_EQ(cnt, 2); -} - - -TEST(PIThreadNotifierTest, Two) { - PIThreadNotifier n; - int cnt1 = 0; - int cnt2 = 0; - int cnt3 = 0; - PIThread t1( - [&n, &cnt1]() { - n.wait(); - cnt1++; - piMSleep(2); - }, - true); - PIThread t2( - [&n, &cnt2]() { - n.wait(); - cnt2++; - piMSleep(2); - }, - true); - PIThread t3( - [&n, &cnt3]() { - n.notifyOnce(); - cnt3++; - piMSleep(1); - }, - true); - piMSleep(20); - t3.stop(true); - piMSleep(100); - t1.stop(); - t2.stop(); - ASSERT_EQ(cnt1 + cnt2, cnt3); -} diff --git a/tests/concurrent/testutil.h b/tests/concurrent/testutil.h deleted file mode 100644 index 21f597a0..00000000 --- a/tests/concurrent/testutil.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef AWRCANFLASHER_TESTUTIL_H -#define AWRCANFLASHER_TESTUTIL_H - -#include "pithread.h" - -#include - -/** - * Minimum wait thread start, switch context or another interthread communication action time. Increase it if tests - * write "Start thread timeout reach!" message. You can reduce it if you want increase test performance. - */ -const int WAIT_THREAD_TIME_MS = 400; - -const int THREAD_COUNT = 5; - -class TestUtil: public PIObject { - PIOBJECT(TestUtil) - -public: - double threadStartTime; - PIThread * thread = new PIThread(); - std::atomic_bool isRunning; - std::function adapterFunctionDefault; - - TestUtil(): isRunning(false) {} - - bool createThread(const std::function & fun = nullptr, PIThread * thread_ = nullptr) { - std::function actualFun = fun == nullptr ? adapterFunctionDefault : fun; - if (thread_ == nullptr) thread_ = thread; - thread_->startOnce([=](void *) { - isRunning = true; - actualFun(); - }); - return waitThread(thread_); - } - - bool waitThread(PIThread * thread_, bool runningStatus = true) { - PITimeMeasurer measurer; - bool isTimeout = !thread_->waitForStart(WAIT_THREAD_TIME_MS); - while (!isRunning) { - isTimeout = WAIT_THREAD_TIME_MS <= measurer.elapsed_m(); - if (isTimeout) break; - piUSleep(100); - } - - threadStartTime = measurer.elapsed_m(); - - if (isTimeout) piCout << "Start thread timeout reach!"; - - if (threadStartTime > 1) { - piCout << "Start time" << threadStartTime << "ms"; - } else if (threadStartTime > 0.001) { - piCout << "Start time" << threadStartTime * 1000 << "mcs"; - } else { - piCout << "Start time" << threadStartTime * 1000 * 1000 << "ns"; - } - - return !isTimeout; - } -}; - -#endif // AWRCANFLASHER_TESTUTIL_H From 3102b985d5515b2bebdf7dea89f92cd4c9753e6a Mon Sep 17 00:00:00 2001 From: peri4 Date: Tue, 24 Mar 2026 14:09:41 +0300 Subject: [PATCH 22/26] add new PIThreadPoolWorker - rework of PIThreadPoolExecutor --- libs/main/thread/pithread.h | 2 + libs/main/thread/pithreadmodule.h | 2 +- libs/main/thread/pithreadpoolexecutor.cpp | 83 ------------ libs/main/thread/pithreadpoolexecutor.h | 99 +------------- libs/main/thread/pithreadpoolworker.cpp | 157 ++++++++++++++++++++++ libs/main/thread/pithreadpoolworker.h | 150 +++++++++++++++++++++ libs/main/thread/pitimer.cpp | 12 ++ libs/main/thread/pitimer.h | 28 ++-- main.cpp | 147 +++----------------- 9 files changed, 353 insertions(+), 327 deletions(-) delete mode 100644 libs/main/thread/pithreadpoolexecutor.cpp create mode 100644 libs/main/thread/pithreadpoolworker.cpp create mode 100644 libs/main/thread/pithreadpoolworker.h diff --git a/libs/main/thread/pithread.h b/libs/main/thread/pithread.h index 2c80e367..102b790c 100644 --- a/libs/main/thread/pithread.h +++ b/libs/main/thread/pithread.h @@ -217,6 +217,7 @@ public: //! \~english Waits until the thread starts. Returns \b false if the timeout expires first. //! \~russian Ожидает запуска потока. Возвращает \b false, если таймаут истек раньше. bool waitForStart(PISystemTime timeout = {}); + //! \~english Deprecated overload of \a waitForStart() that accepts milliseconds. //! \~russian Устаревшая перегрузка \a waitForStart(), принимающая миллисекунды. bool waitForStart(int timeout_msecs) DEPRECATEDM("use waitForStart(PISystemTime)") { @@ -226,6 +227,7 @@ public: //! \~english Waits for thread completion. Returns \b false if the timeout expires first. //! \~russian Ожидает завершения потока. Возвращает \b false, если таймаут истек раньше. bool waitForFinish(PISystemTime timeout = {}); + //! \~english Deprecated overload of \a waitForFinish() that accepts milliseconds. //! \~russian Устаревшая перегрузка \a waitForFinish(), принимающая миллисекунды. bool waitForFinish(int timeout_msecs) DEPRECATEDM("use waitForFinish(PISystemTime)") { diff --git a/libs/main/thread/pithreadmodule.h b/libs/main/thread/pithreadmodule.h index 55ce08ab..15557d1d 100644 --- a/libs/main/thread/pithreadmodule.h +++ b/libs/main/thread/pithreadmodule.h @@ -71,8 +71,8 @@ #include "pispinlock.h" #include "pithread.h" #include "pithreadnotifier.h" -#include "pithreadpoolexecutor.h" #include "pithreadpoolloop.h" +#include "pithreadpoolworker.h" #include "pitimer.h" #endif // PITHREADMODULE_H diff --git a/libs/main/thread/pithreadpoolexecutor.cpp b/libs/main/thread/pithreadpoolexecutor.cpp deleted file mode 100644 index 1973b6bd..00000000 --- a/libs/main/thread/pithreadpoolexecutor.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - PIP - Platform Independent Primitives - - Stephan Fomenko - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . -*/ - -#include "pithreadpoolexecutor.h" - -#include "piliterals_time.h" - -/*! \class PIThreadPoolExecutor - * \brief Thread pools address two different problems: they usually provide improved performance when executing large - * numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and - * managing the resources, including threads, consumed when executing a collection of tasks. - */ - - -PIThreadPoolExecutor::PIThreadPoolExecutor(int corePoolSize): isShutdown_(false) { - for (int i = 0; i < corePoolSize; ++i) { - PIThread * thread = new PIThread([&, i]() { - auto runnable = taskQueue.poll(100_ms, std::function()); - if (runnable) { - runnable(); - } - if (isShutdown_ && taskQueue.size() == 0) threadPool[i]->stop(); - }); - threadPool.push_back(thread); - thread->start(); - } -} - - -bool PIThreadPoolExecutor::awaitTermination(PISystemTime timeout) { - PITimeMeasurer measurer; - for (size_t i = 0; i < threadPool.size(); ++i) { - auto dif = timeout - measurer.elapsed(); - if (dif.isNegative()) return false; - if (!threadPool[i]->waitForFinish(dif)) return false; - } - return true; -} - - -void PIThreadPoolExecutor::shutdownNow() { - isShutdown_ = true; - for (size_t i = 0; i < threadPool.size(); ++i) - threadPool[i]->stop(); -} - - -PIThreadPoolExecutor::~PIThreadPoolExecutor() { - shutdownNow(); - while (threadPool.size() > 0) - delete threadPool.take_back(); -} - - -void PIThreadPoolExecutor::execute(std::function runnable) { - if (!isShutdown_) taskQueue.offer(std::move(runnable)); -} - - -bool PIThreadPoolExecutor::isShutdown() const { - return isShutdown_; -} - - -void PIThreadPoolExecutor::shutdown() { - isShutdown_ = true; -} diff --git a/libs/main/thread/pithreadpoolexecutor.h b/libs/main/thread/pithreadpoolexecutor.h index c302dbd2..135cd5db 100644 --- a/libs/main/thread/pithreadpoolexecutor.h +++ b/libs/main/thread/pithreadpoolexecutor.h @@ -1,95 +1,6 @@ -//! \~\file pithreadpoolexecutor.h -//! \~\ingroup Thread -//! \brief -//! \~english Thread pool executor -//! \~russian Исполнитель пула потоков -//! -//! \details -//! \~english Executes tasks in a pool of worker threads. -//! \~russian Выполняет задачи в пуле рабочих потоков. -/* - PIP - Platform Independent Primitives +#include "pithreadpoolworker.h" - Stephan Fomenko - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . -*/ - -#ifndef PITHREADPOOLEXECUTOR_H -#define PITHREADPOOLEXECUTOR_H - -#include "piblockingqueue.h" -#include "pithread.h" - -#include - - -//! \~\ingroup Thread -//! \~\brief -//! \~english Fixed-size pool of worker threads for fire-and-forget tasks. -//! \~russian Фиксированный пул рабочих потоков для задач без ожидания результата. -class PIP_EXPORT PIThreadPoolExecutor { -public: - //! \~english Constructs executor with \a corePoolSize worker threads. - //! \~russian Создает исполнитель с \a corePoolSize рабочими потоками. - explicit PIThreadPoolExecutor(int corePoolSize); - - //! \~english Stops worker threads and destroys executor resources. - //! \~russian Останавливает рабочие потоки и уничтожает ресурсы исполнителя. - virtual ~PIThreadPoolExecutor(); - - //! \~\brief - //! \~english Submits \a runnable for asynchronous execution by a worker thread. - //! \~russian Передает \a runnable на асинхронное выполнение рабочим потоком. - //! \details - //! \~english - //! This is a best-effort fire-and-forget call and does not report whether the task was accepted. - //! \After shutdown requests new tasks are ignored. - //! \~russian - //! Это вызов по принципу best-effort без ожидания результата и без сообщения о том, была ли задача принята. - //! \После запроса на завершение новые задачи игнорируются. - void execute(std::function runnable); - - //! \~english Requests immediate shutdown and stops worker threads without waiting for queued tasks to finish. - //! \~russian Запрашивает немедленное завершение и останавливает рабочие потоки без ожидания завершения задач в очереди. - void shutdownNow(); - - //! \~\brief - //! \~english Requests orderly shutdown: new tasks are rejected and workers stop after the current queue is drained. - //! \~russian Запрашивает упорядоченное завершение: новые задачи отклоняются, а рабочие потоки останавливаются после опустошения текущей - //! очереди. - //! \details - //! \~english This method does not wait for worker termination. - //! \~russian Этот метод не ожидает завершения рабочих потоков. - void shutdown(); - - //! \~english Returns \c true after \a shutdown() or \a shutdownNow() has been requested. - //! \~russian Возвращает \c true после запроса \a shutdown() или \a shutdownNow(). - bool isShutdown() const; - - //! \~\brief - //! \~english Waits up to \a timeout for all worker threads to finish. - //! \~russian Ожидает до \a timeout завершения всех рабочих потоков. - //! \return - //! \~english \c false if the timeout expires first. - //! \~russian \c false, если таймаут истек раньше. - bool awaitTermination(PISystemTime timeout); - -private: - std::atomic_bool isShutdown_; - PIBlockingQueue> taskQueue; - PIVector threadPool; -}; - -#endif // PITHREADPOOLEXECUTOR_H +namespace { +// [[deprecated("DeprecatedHeader.h is deprecated. Use NewHeader.h instead.")]] +DEPRECATEDM("Use pithreadpoolworker.h") const bool deprecated_header_is_used = true; +} // namespace diff --git a/libs/main/thread/pithreadpoolworker.cpp b/libs/main/thread/pithreadpoolworker.cpp new file mode 100644 index 00000000..03b49b32 --- /dev/null +++ b/libs/main/thread/pithreadpoolworker.cpp @@ -0,0 +1,157 @@ +/* + PIP - Platform Independent Primitives + + Stephan Fomenko, Ivan Pelipenko + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include "pithreadpoolworker.h" + +#include "piliterals_time.h" +#include "pisysteminfo.h" + +//! \class PIThreadPoolWorker +//! \brief +//! Thread pools address two different problems: they usually provide improved performance when executing large +//! numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and +//! managing the resources, including threads, consumed when executing a collection of tasks. +//! + + +PIThreadPoolWorker::PIThreadPoolWorker(int threads_count) { + if (threads_count < 0) threads_count = PISystemInfo::instance()->processorsCount; + assertm(threads_count > 0, "Invalid threads count!"); + for (int i = 0; i < threads_count; ++i) { + Worker * w = new Worker(); + w->thread.setSlot([this, w]() { threadFunc(w); }); + workers << w; + } +} + + +PIThreadPoolWorker::~PIThreadPoolWorker() { + stop(); + for (auto * w: workers) { + if (!w->thread.waitForFinish(1_s)) w->thread.terminate(); + } + piDeleteAllAndClear(workers); +} + + +void PIThreadPoolWorker::start() { + for (auto w: workers) + w->thread.start(); + m_running = true; +} + + +void PIThreadPoolWorker::stop() { + m_running = false; + for (auto w: workers) { + w->thread.stop(); + w->notifier.notify(); + } +} + + +bool PIThreadPoolWorker::stopAndWait(PISystemTime timeout) { + stop(); + return waitForFinish(timeout); +} + + +bool PIThreadPoolWorker::waitForStart(PISystemTime timeout) { + PITimeMeasurer tm; + for (auto w: workers) { + if (timeout.isNull()) + w->thread.waitForStart(); + else { + auto remains = timeout - tm.elapsed(); + if (remains.isNegative()) return false; + if (!w->thread.waitForStart(remains)) return false; + } + } + return true; +} + + +bool PIThreadPoolWorker::waitForFinish(PISystemTime timeout) { + PITimeMeasurer tm; + for (auto w: workers) { + if (timeout.isNull()) + w->thread.waitForFinish(); + else { + auto remains = timeout - tm.elapsed(); + if (remains.isNegative()) return false; + if (!w->thread.waitForFinish(remains)) return false; + } + } + return true; +} + + +int64_t PIThreadPoolWorker::enqueueTask(std::function func, PIObject * context) { + int64_t id = ++next_task_id; + Task task; + task.id = id; + task.func = std::move(func); + task.context = context; + tasks_queue.getRef()->append(std::move(task)); + for (auto * w: workers) + w->notifier.notify(); + return id; +} + + +bool PIThreadPoolWorker::removeTask(int64_t id) { + auto qref = tasks_queue.getRef(); + auto prev_size = qref->size(); + qref->removeWhere([id](const Task & t) { return t.id == id; }); + return prev_size != qref->size(); +} + + +void PIThreadPoolWorker::clearTasks() { + tasks_queue.getRef()->clear(); +} + + +PIThreadPoolWorker::TaskStatus PIThreadPoolWorker::taskStatus(int64_t id) const { + if (id <= 0) return TaskStatus::Unknown; + auto qref = tasks_queue.getRef(); + for (auto w: workers) + if (w->in_work == id) return TaskStatus::InProgress; + for (const auto & t: *qref) + if (t.id == id) return TaskStatus::Enqueued; + return id <= next_task_id ? TaskStatus::Done : TaskStatus::Unknown; +} + + +void PIThreadPoolWorker::threadFunc(Worker * w) { + w->notifier.wait(); + if (w->thread.isStopping()) return; + if (!m_running) return; + Task task; + { + auto ref = tasks_queue.getRef(); + if (ref->isEmpty()) return; + task = ref->dequeue(); + } + if (!task.isValid()) return; + w->in_work = task.id; + task.func(task.id); + w->in_work = -1; + w->notifier.notify(); +} diff --git a/libs/main/thread/pithreadpoolworker.h b/libs/main/thread/pithreadpoolworker.h new file mode 100644 index 00000000..04ff1888 --- /dev/null +++ b/libs/main/thread/pithreadpoolworker.h @@ -0,0 +1,150 @@ +//! \~\file pithreadpoolworker.h +//! \~\ingroup Thread +//! \brief +//! \~english Thread pool worker +//! \~russian Исполнитель пула потоков +//! +//! \details +//! \~english Executes tasks in a pool of worker threads. +//! \~russian Выполняет задачи в пуле рабочих потоков. +/* + PIP - Platform Independent Primitives + + Stephan Fomenko, Ivan Pelipenko + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef PITHREADPOOLWORKER_H +#define PITHREADPOOLWORKER_H + +#include "piblockingqueue.h" +#include "piprotectedvariable.h" +#include "pithread.h" + + +//! \~\ingroup Thread +//! \~\brief +//! \~english Fixed-size pool of worker threads for fire-and-forget tasks. +//! \~russian Фиксированный пул рабочих потоков для задач без ожидания результата. +class PIP_EXPORT PIThreadPoolWorker { +public: + //! \~english Constructs executor with \a threads_count worker threads. + //! \~russian Создает исполнитель с \a threads_count рабочими потоками. + explicit PIThreadPoolWorker(int threads_count = -1); + + //! \~english Stops worker threads and destroys executor resources. + //! \~russian Останавливает рабочие потоки и уничтожает ресурсы исполнителя. + virtual ~PIThreadPoolWorker(); + + enum class TaskStatus { + Unknown, + Enqueued, + InProgress, + Done + }; + + + //! \~english Starts the threads. + //! \~russian Запускает потоки. + void start(); + + //! \~english Requests graceful threads shutdown. + //! \~russian Запрашивает корректное завершение потоков. + void stop(); + + //! \~english Requests stop and waits for threads completion. Returns \b false if the timeout expires. + //! \~russian Запрашивает остановку и ожидает завершения потоков. Возвращает \b false, если таймаут истек. + bool stopAndWait(PISystemTime timeout = {}); + + //! \~english Waits until the threads starts. Returns \b false if the timeout expires first. + //! \~russian Ожидает запуска потоков. Возвращает \b false, если таймаут истек раньше. + bool waitForStart(PISystemTime timeout = {}); + + //! \~english Waits for threads completion. Returns \b false if the timeout expires first. + //! \~russian Ожидает завершения потоков. Возвращает \b false, если таймаут истек раньше. + bool waitForFinish(PISystemTime timeout = {}); + + //! \~english Returns whether the threads are currently running. + //! \~russian Возвращает, выполняются ли потоки в данный момент. + bool isRunning() const { return m_running; } + + + int64_t enqueueTask(std::function func, PIObject * context = nullptr); + + int64_t enqueueTask(std::function func, PIObject * context = nullptr) { + return enqueueTask([func](int64_t) { func(); }, context); + } + + template + int64_t enqueueTask(O * obj, void (O::*member_func)(int64_t)) { + return enqueueTask([obj, member_func](int64_t id) { (obj->*member_func)(id); }); + } + + template + int64_t enqueueTask(O * obj, void (O::*member_func)()) { + return enqueueTask([obj, member_func](int64_t) { (obj->*member_func)(); }); + } + + bool removeTask(int64_t id); + + TaskStatus taskStatus(int64_t id) const; + + + // DEPRECATED + + //! \~\brief + //! \~english Submits \a runnable for asynchronous execution by a worker thread. + //! \~russian Передает \a runnable на асинхронное выполнение рабочим потоком. + //! \details + //! \~english + //! This is a best-effort fire-and-forget call and does not report whether the task was accepted. + //! \After shutdown requests new tasks are ignored. + //! \~russian + //! Это вызов по принципу best-effort без ожидания результата и без сообщения о том, была ли задача принята. + //! \После запроса на завершение новые задачи игнорируются. + void execute(std::function runnable); + + void shutdownNow() DEPRECATEDM("Use stopAndWait()") { stopAndWait(); } + void shutdown() DEPRECATEDM("Use stop()") { stop(); } + bool isShutdown() const DEPRECATEDM("Use !isRunning()") { return !isRunning(); } + bool awaitTermination(PISystemTime timeout) DEPRECATEDM("Use waitForFinish()") { return waitForFinish(timeout); } + +private: + struct Worker { + PIThread thread; + PIThreadNotifier notifier; + std::atomic_int64_t in_work = {-1}; + }; + struct Task { + bool isValid() const { return func != nullptr; } + PIObject * context = nullptr; + std::function func = nullptr; + int64_t id = -1; + }; + + void threadFunc(Worker * w); + + mutable PIVector workers; + mutable PIProtectedVariable> tasks_queue; + + std::atomic_bool m_running = {false}; + std::atomic_int64_t next_task_id = {0}; +}; + + +typedef PIThreadPoolWorker PIThreadPoolExecutor DEPRECATEDM("Use PIThreadPoolWorker"); + + +#endif // PITHREADPOOLWORKER_H diff --git a/libs/main/thread/pitimer.cpp b/libs/main/thread/pitimer.cpp index 611e9852..8cb72a22 100644 --- a/libs/main/thread/pitimer.cpp +++ b/libs/main/thread/pitimer.cpp @@ -274,6 +274,18 @@ void PITimer::stopAndWait(PISystemTime timeout) { } +void PITimer::setSlot(std::function func) { + ret_func_delim = nullptr; + ret_func = std::move(func); +} + + +void PITimer::setSlot(std::function func) { + ret_func = nullptr; + ret_func_delim = std::move(func); +} + + void PITimer::addDelimiter(int delim, std::function func) { delims << Delimiter(std::move(func), delim); } diff --git a/libs/main/thread/pitimer.h b/libs/main/thread/pitimer.h index 4ed9f089..3ff367a8 100644 --- a/libs/main/thread/pitimer.h +++ b/libs/main/thread/pitimer.h @@ -110,11 +110,6 @@ public: //! \~english Deprecated overload of \a start() that accepts milliseconds. //! \~russian Устаревшая перегрузка \a start(), принимающая миллисекунды. bool start(double interval_ms) DEPRECATEDM("use start(PISystemTime)") { return start(PISystemTime::fromMilliseconds(interval_ms)); } - EVENT_HANDLER0(bool, start); - - EVENT_HANDLER0(bool, restart); - - EVENT_HANDLER0(void, stop); //! \~english Deprecated overload of \a stopAndWait() that accepts milliseconds. //! \~russian Устаревшая перегрузка \a stopAndWait(), принимающая миллисекунды. @@ -126,23 +121,15 @@ public: //! \~english Sets a tick callback that ignores the delimiter value. //! \~russian Устанавливает обратный вызов тика, игнорирующий значение делителя. - void setSlot(std::function func) { - ret_func_delim = nullptr; - ret_func = std::move(func); - } + void setSlot(std::function func); //! \~english Sets a tick callback that receives the current delimiter value. //! \~russian Устанавливает обратный вызов тика, принимающий текущее значение делителя. - void setSlot(std::function func) { - ret_func = nullptr; - ret_func_delim = std::move(func); - } + void setSlot(std::function func); //! \~english Enables locking of the internal mutex around tick processing. //! \~russian Включает блокировку внутреннего мьютекса вокруг обработки тиков. void needLockRun(bool need) { lockRun = need; } - EVENT_HANDLER0(void, lock) { mutex_.lock(); } - EVENT_HANDLER0(void, unlock) { mutex_.unlock(); } //! \~english Returns whether the timer drains queued delivery for itself as performer on each main tick. By default \b true. //! \~russian Возвращает, должен ли таймер обрабатывать отложенную доставку для себя как исполнителя на каждом основном тике. По @@ -169,9 +156,6 @@ public: //! \~russian Удаляет все делители со значением \a delim. void removeDelimiter(int delim); - EVENT_HANDLER0(void, clearDelimiters) { delims.clear(); } - - EVENT1(tickEvent, int, delimiter); //! \handlers //! \{ @@ -180,31 +164,37 @@ public: //! \brief //! \~english Starts the timer with the current \a interval(). //! \~russian Запускает таймер с текущим значением \a interval(). + EVENT_HANDLER0(bool, start); //! \fn bool restart() //! \brief //! \~english Stops the timer, then starts it again with the current \a interval(). //! \~russian Останавливает таймер, затем снова запускает его с текущим значением \a interval(). + EVENT_HANDLER0(bool, restart); //! \fn bool stop() //! \brief //! \~english Requests stop and wakes the timer thread, but does not wait for completion. //! \~russian Запрашивает остановку и пробуждает поток таймера, но не ожидает завершения. + EVENT_HANDLER0(void, stop); //! \fn void clearDelimiters() //! \brief //! \~english Remove all frequency delimiters //! \~russian Удаляет все делители частоты + EVENT_HANDLER0(void, clearDelimiters) { delims.clear(); } //! \fn void lock() //! \brief //! \~english Locks the internal timer mutex. //! \~russian Блокирует внутренний мьютекс таймера. + EVENT_HANDLER0(void, lock) { mutex_.lock(); } //! \fn void unlock() //! \brief //! \~english Unlocks the internal timer mutex. //! \~russian Разблокирует внутренний мьютекс таймера. + EVENT_HANDLER0(void, unlock) { mutex_.unlock(); } //! \} //! \events @@ -219,7 +209,7 @@ public: //! "delimiter" is the frequency delimiter, 1 for the main loop. //! \~russian //! "delimiter" - делитель частоты, 1 для основного цикла. - + EVENT1(tickEvent, int, delimiter); //! \} diff --git a/main.cpp b/main.cpp index cf2f6235..23928d89 100644 --- a/main.cpp +++ b/main.cpp @@ -1,143 +1,30 @@ -#include "libs/http_client/curl_thread_pool_p.h" -#include "picodeparser.h" -#include "pidigest.h" -#include "pihttpclient.h" -#include "piliterals.h" #include "pip.h" -#include "piunits.h" -#include "pivaluetree_conversions.h" - -using namespace PICoutManipulators; -using namespace PIHTTP; -using namespace PIUnits::Class; - -int rcnt = 0, scnt = 0; - -inline PIByteArray SMBusTypeInfo_genHash(PIString n) { - PICrypt c; - return piSerialize(c.shorthash(n.removeAll(" "), PIString("SMBusDataHashKey").toByteArray())); -} int main(int argc, char * argv[]) { - PICrypt _crypt; - // auto ba = PIFile::readAll("logo.png"); - PIString str = "hello!"_a; - PIByteArray ba = str.toAscii(); - PIByteArray key = PIString("SMBusDataHashKey").toByteArray(); + PITimer status_timer; + PIThreadPoolWorker pool(1); - const int times = 1000000; - PITimeMeasurer tm; - PISystemTime el; + int64_t id = -1; - tm.reset(); - piForTimes(times) { - PIDigest::calculateWithKey(ba, key, PIDigest::Type::SipHash_2_4_128); - } - el = tm.elapsed(); - piCout << "PIDigest" << el.toString(); + status_timer.start(10_Hz, [&id, &pool] { piCout << "[timer] status" << (int)pool.taskStatus(id); }); - tm.reset(); - piForTimes(times) { - _crypt.shorthash(str, key); - } - el = tm.elapsed(); - piCout << " sodium" << el.toString(); - - tm.reset(); - piForTimes(times) { - PIDigest::calculateWithKey(ba, key, PIDigest::Type::BLAKE2b_128); - } - el = tm.elapsed(); - piCout << " blake" << el.toString(); - - return 0; - - - PIEthernet *eth_r, *eth_s; - eth_r = PIIODevice::createFromFullPath("eth://udp: 192.168.1.25 :10000")->cast(); - eth_s = PIIODevice::createFromFullPath("eth://udp: : : 192.168.1.25:10000")->cast(); - - eth_r->setReadBufferSize(1_MiB); - CONNECTL(eth_r, threadedReadEvent, [](const uchar * readed, ssize_t size) { - // piCout << "rec"; - piMSleep(1); - ++rcnt; + 200_ms .sleep(); + id = pool.enqueueTask([](int64_t id) { + piCout << "[task ] start, id" << id; + 300_ms .sleep(); + piCout << "[task ] done"; }); - eth_r->startThreadedRead(); + piCout << "[main ]" << "enqueued, id" << id; - PIByteArray _ba(1400); - for (int i = 0; i < 100; ++i) { - eth_s->write(_ba); - ++scnt; - } + 200_ms .sleep(); + piCout << pool.removeTask(id); + piCout << pool.removeTask(id); + piCout << "[main ]" << "start"; + pool.start(); - 0.2_s .sleep(); + 1000_ms .sleep(); - piCout << "snd" << scnt; - piCout << "rec" << rcnt; - - piDeleteSafety(eth_r); - piDeleteSafety(eth_s); - return 0; - - PITranslator::loadLang("ru"); - /*auto ucl = PIUnits::allClasses(); - for (auto c: ucl) { - piCout << (c->className() + ":"); - for (auto t: c->allTypes()) { - piCout << " " << c->name(t) << "->" << c->unit(t); - } - }*/ - - // PIUnits::Value(1); - // piCout << PIUnits::name(PIUnits::Class::Information::Bit); - // piCout << PIUnits::name(PIUnits::Class::Information::Byte); - // piCout << PIUnits::name(PIUnits::Class::Information::_LastType); - // piCout << PIUnits::name((int)PIUnits::Class::Angle::Degree); - - // piCout << PIUnits::unit(PIUnits::Class::Information::Bit); - // piCout << PIUnits::unit(PIUnits::Class::Information::Byte); - // piCout << PIUnits::unit(PIUnits::Class::Information::_LastType); - // piCout << PIUnits::unit((int)PIUnits::Class::Angle::Degree); - - // for (int i = -10; i < 10; ++i) - // piCout << PIUnits::Value(pow10(i * 0.99), PIUnits::Class::Distance::Meter).toString(); - - auto v = PIUnits::Value(M_PI, Angle::Radian); - piCout << v << "=" << v.converted(Angle::Degree); - - v = PIUnits::Value(45, Angle::Degree); - piCout << v << "=" << v.converted(Angle::Radian); - - piCout << PIUnits::Value(5E-5, Time::Second); - piCout << PIUnits::Value(3E-3, Time::Second); - piCout << PIUnits::Value(0.8, Time::Second); - piCout << PIUnits::Value(1.2, Time::Second); - piCout << PIUnits::Value(1001, Time::Second); - piCout << PIUnits::Value(1000001, Time::Second); - - piCout << PIUnits::Value(1_KB, Information::Byte); - piCout << PIUnits::Value(1_MB, Information::Byte); - piCout << PIUnits::Value(1_MiB, Information::Byte); - piCout << PIUnits::Value(1_MB, Information::Byte).converted(Information::Bit); - piCout << PIUnits::Value(1_MiB, Information::Byte).converted(Information::Bit); - - piCout << PIUnits::Value(0., Temperature::Celsius).converted(Temperature::Kelvin); - piCout << PIUnits::Value(0., Temperature::Celsius).converted(Temperature::Fahrenheit); - piCout << PIUnits::Value(100., Temperature::Celsius).converted(Temperature::Fahrenheit); - - piCout << PIUnits::Value(1., Pressure::Atmosphere).converted(Pressure::Pascal); - piCout << PIUnits::Value(1., Pressure::Atmosphere).converted(Pressure::MillimetreOfMercury); - piCout << PIUnits::Value(766., Pressure::MillimetreOfMercury).converted(Pressure::Atmosphere); - - piCout << PIUnits::Value(5E-5, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(3E-3, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(0.8, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(1.2, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(1001, Time::Second).converted(Time::Hertz); - piCout << PIUnits::Value(1000001, Time::Second).converted(Time::Hertz); - // piCout << PIUnits::Value(0.2, Time::Second).converted(Time::Hertz); - // piCout << PIUnits::Value(5E-5, Time::Second).converted(Time::Hertz); + status_timer.stopAndWait(); return 0; } From 5868e0ec9d9a5bbd32800a3de6bee38301101d2b Mon Sep 17 00:00:00 2001 From: peri4 Date: Tue, 24 Mar 2026 19:56:43 +0300 Subject: [PATCH 23/26] work with PIThreadPoolWorker --- libs/main/thread/pithreadpoolexecutor.h | 8 ++- libs/main/thread/pithreadpoolworker.cpp | 89 ++++++++++++++++++++++--- libs/main/thread/pithreadpoolworker.h | 43 ++++++++++-- 3 files changed, 123 insertions(+), 17 deletions(-) diff --git a/libs/main/thread/pithreadpoolexecutor.h b/libs/main/thread/pithreadpoolexecutor.h index 135cd5db..264d7168 100644 --- a/libs/main/thread/pithreadpoolexecutor.h +++ b/libs/main/thread/pithreadpoolexecutor.h @@ -1,6 +1,10 @@ +#ifndef PITHREADPOOLEXECUTOR_H +#define PITHREADPOOLEXECUTOR_H + #include "pithreadpoolworker.h" namespace { -// [[deprecated("DeprecatedHeader.h is deprecated. Use NewHeader.h instead.")]] DEPRECATEDM("Use pithreadpoolworker.h") const bool deprecated_header_is_used = true; -} // namespace +} + +#endif diff --git a/libs/main/thread/pithreadpoolworker.cpp b/libs/main/thread/pithreadpoolworker.cpp index 03b49b32..c3505fe7 100644 --- a/libs/main/thread/pithreadpoolworker.cpp +++ b/libs/main/thread/pithreadpoolworker.cpp @@ -42,23 +42,19 @@ PIThreadPoolWorker::PIThreadPoolWorker(int threads_count) { PIThreadPoolWorker::~PIThreadPoolWorker() { - stop(); - for (auto * w: workers) { - if (!w->thread.waitForFinish(1_s)) w->thread.terminate(); - } piDeleteAllAndClear(workers); } void PIThreadPoolWorker::start() { - for (auto w: workers) + for (auto w: workers) { w->thread.start(); - m_running = true; + w->notifier.notify(); + } } void PIThreadPoolWorker::stop() { - m_running = false; for (auto w: workers) { w->thread.stop(); w->notifier.notify(); @@ -102,13 +98,87 @@ bool PIThreadPoolWorker::waitForFinish(PISystemTime timeout) { } +bool PIThreadPoolWorker::isRunning() const { + return workers.every([](Worker * w) { return w->thread.isRunning(); }); +} + + +bool PIThreadPoolWorker::waitForTasks(PISystemTime timeout) { + if (!isRunning()) return tasks_queue.getRef()->isEmpty(); + auto checkWorking = [this] { + if (tasks_queue.getRef()->isNotEmpty()) return true; + for (auto * w: workers) + if (w->in_work > 0) return true; + return false; + }; + if (timeout.isNull()) { + for (;;) { + if (!checkWorking()) break; + piMinSleep(); + } + return true; + } + PITimeMeasurer tm; + while (tm.elapsed() < timeout) { + if (!checkWorking()) return true; + piMinSleep(); + } + return tm.elapsed() < timeout; +} + + +bool PIThreadPoolWorker::waitForTask(int64_t id, PISystemTime timeout) { + if (!isRunning()) return tasks_queue.getRef()->every([id](const Task & t) { return t.id != id; }); + auto checkWorking = [this, id] { + if (tasks_queue.getRef()->any([id](const Task & t) { return t.id == id; })) return true; + for (auto * w: workers) + if (w->in_work == id) return true; + return false; + }; + if (timeout.isNull()) { + for (;;) { + if (!checkWorking()) break; + piMinSleep(); + } + return true; + } + PITimeMeasurer tm; + while (tm.elapsed() < timeout) { + if (!checkWorking()) return true; + piMinSleep(); + } + return tm.elapsed() < timeout; +} + + +void PIThreadPoolWorker::exec() { + start(); + waitForStart(); + waitForTasks(); + stopAndWait(); +} + + int64_t PIThreadPoolWorker::enqueueTask(std::function func, PIObject * context) { + if (context) { + if (!contexts[context]) { + contexts << context; + CONNECTL(context, deleted, ([this, context](PIObject *) { + contexts.remove(context); + auto qref = tasks_queue.getRef(); + // auto prev_size = qref->size(); + // piCout << "deleted" << (void *)context << qref->map([](const Task & t) { return t.context; }); + qref->removeWhere([context](const Task & t) { return t.context == context; }); + // piCout << prev_size << qref->size() << qref->map([](const Task & t) { return t.context; }); + })); + } + } int64_t id = ++next_task_id; Task task; task.id = id; task.func = std::move(func); task.context = context; - tasks_queue.getRef()->append(std::move(task)); + tasks_queue.getRef()->enqueue(std::move(task)); for (auto * w: workers) w->notifier.notify(); return id; @@ -142,7 +212,6 @@ PIThreadPoolWorker::TaskStatus PIThreadPoolWorker::taskStatus(int64_t id) const void PIThreadPoolWorker::threadFunc(Worker * w) { w->notifier.wait(); if (w->thread.isStopping()) return; - if (!m_running) return; Task task; { auto ref = tasks_queue.getRef(); @@ -151,7 +220,9 @@ void PIThreadPoolWorker::threadFunc(Worker * w) { } if (!task.isValid()) return; w->in_work = task.id; + taskStarted(task.id); task.func(task.id); w->in_work = -1; + taskFinished(task.id); w->notifier.notify(); } diff --git a/libs/main/thread/pithreadpoolworker.h b/libs/main/thread/pithreadpoolworker.h index 04ff1888..aa89823f 100644 --- a/libs/main/thread/pithreadpoolworker.h +++ b/libs/main/thread/pithreadpoolworker.h @@ -38,7 +38,9 @@ //! \~\brief //! \~english Fixed-size pool of worker threads for fire-and-forget tasks. //! \~russian Фиксированный пул рабочих потоков для задач без ожидания результата. -class PIP_EXPORT PIThreadPoolWorker { +class PIP_EXPORT PIThreadPoolWorker: public PIObject { + PIOBJECT(PIThreadPoolWorker) + public: //! \~english Constructs executor with \a threads_count worker threads. //! \~russian Создает исполнитель с \a threads_count рабочими потоками. @@ -78,8 +80,17 @@ public: //! \~english Returns whether the threads are currently running. //! \~russian Возвращает, выполняются ли потоки в данный момент. - bool isRunning() const { return m_running; } + bool isRunning() const; + //! \~english Waits for all tasks completion. Returns \b false if the timeout expires first. + //! \~russian Ожидает завершения всех задач. Возвращает \b false, если таймаут истек раньше. + bool waitForTasks(PISystemTime timeout = {}); + + //! \~english Waits for task with id \a id completion. Returns \b false if the timeout expires first. + //! \~russian Ожидает завершения задачи с id \a id. Возвращает \b false, если таймаут истек раньше. + bool waitForTask(int64_t id, PISystemTime timeout = {}); + + void exec(); int64_t enqueueTask(std::function func, PIObject * context = nullptr); @@ -89,18 +100,38 @@ public: template int64_t enqueueTask(O * obj, void (O::*member_func)(int64_t)) { - return enqueueTask([obj, member_func](int64_t id) { (obj->*member_func)(id); }); + return enqueueTask([obj, member_func](int64_t id) { (obj->*member_func)(id); }, + PIObject::isPIObject(obj) ? dynamic_cast(obj) : nullptr); } template int64_t enqueueTask(O * obj, void (O::*member_func)()) { - return enqueueTask([obj, member_func](int64_t) { (obj->*member_func)(); }); + return enqueueTask([obj, member_func](int64_t) { (obj->*member_func)(); }, + PIObject::isPIObject(obj) ? dynamic_cast(obj) : nullptr); } bool removeTask(int64_t id); + void clearTasks(); TaskStatus taskStatus(int64_t id) const; + //! \events + //! \{ + + //! \fn void taskStarted(int64_t id) + //! \brief + //! \~english Raised on start execution task with id \a id. + //! \~russian Вызывается при старте выполнения задачи с id \a id. + EVENT1(taskStarted, int64_t, id); + + //! \fn void taskFinished(int64_t id) + //! \brief + //! \~english Raised on finish execution task with id \a id. + //! \~russian Вызывается при завершении выполнения задачи с id \a id. + EVENT1(taskFinished, int64_t, id); + + //! \} + // DEPRECATED @@ -114,7 +145,7 @@ public: //! \~russian //! Это вызов по принципу best-effort без ожидания результата и без сообщения о том, была ли задача принята. //! \После запроса на завершение новые задачи игнорируются. - void execute(std::function runnable); + void execute(std::function runnable) { enqueueTask(runnable); } void shutdownNow() DEPRECATEDM("Use stopAndWait()") { stopAndWait(); } void shutdown() DEPRECATEDM("Use stop()") { stop(); } @@ -139,7 +170,7 @@ private: mutable PIVector workers; mutable PIProtectedVariable> tasks_queue; - std::atomic_bool m_running = {false}; + PISet contexts; std::atomic_int64_t next_task_id = {0}; }; From a16f629dc5f2572df873949db5aab71c03ed9c26 Mon Sep 17 00:00:00 2001 From: peri4 Date: Wed, 25 Mar 2026 10:59:32 +0300 Subject: [PATCH 24/26] PIThreadPoolWorker ready to use --- libs/main/thread/pithreadpoolexecutor.h | 13 +++++ libs/main/thread/pithreadpoolworker.cpp | 26 ++++++--- libs/main/thread/pithreadpoolworker.h | 72 ++++++++++++------------- main.cpp | 59 +++++++++++++------- 4 files changed, 106 insertions(+), 64 deletions(-) diff --git a/libs/main/thread/pithreadpoolexecutor.h b/libs/main/thread/pithreadpoolexecutor.h index 264d7168..c5e78142 100644 --- a/libs/main/thread/pithreadpoolexecutor.h +++ b/libs/main/thread/pithreadpoolexecutor.h @@ -7,4 +7,17 @@ namespace { DEPRECATEDM("Use pithreadpoolworker.h") const bool deprecated_header_is_used = true; } +class DEPRECATEDM("Use PIThreadPoolWorker") PIThreadPoolExecutor: public PIThreadPoolWorker { +public: + PIThreadPoolExecutor(int threads_count): PIThreadPoolWorker(threads_count) { start(); } + ~PIThreadPoolExecutor() { stopAndWait(); } + + void execute(std::function runnable) DEPRECATEDM("Use enqueueTask()") { enqueueTask(runnable); } + void shutdownNow() DEPRECATEDM("Use stopAndWait()") { stopAndWait(); } + void shutdown() DEPRECATEDM("Use stop()") { stop(); } + bool isShutdown() const DEPRECATEDM("Use !isRunning()") { return !isRunning(); } + bool awaitTermination(PISystemTime timeout) DEPRECATEDM("Use waitForFinish()") { return waitForFinish(timeout); } +}; + + #endif diff --git a/libs/main/thread/pithreadpoolworker.cpp b/libs/main/thread/pithreadpoolworker.cpp index c3505fe7..26032f12 100644 --- a/libs/main/thread/pithreadpoolworker.cpp +++ b/libs/main/thread/pithreadpoolworker.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives - Stephan Fomenko, Ivan Pelipenko + Ivan Pelipenko, Stephan Fomenko This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -19,15 +19,25 @@ #include "pithreadpoolworker.h" -#include "piliterals_time.h" #include "pisysteminfo.h" -//! \class PIThreadPoolWorker -//! \brief -//! Thread pools address two different problems: they usually provide improved performance when executing large -//! numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and -//! managing the resources, including threads, consumed when executing a collection of tasks. +//! \addtogroup Thread +//! \{ +//! \class PIThreadPoolWorker pithreadpoolworker.h +//! \~\details +//! \~english +//! \PIThreadPoolWorker is a class that implements a fixed-size pool of worker threads for general-purpose task execution. It +//! allows starting threads, enqueuing tasks as functors or class member methods, and managing their execution. The class provides methods +//! to wait for all tasks or specific tasks by ID to complete, as well as to gracefully stop the pool with timeouts. The lifecycle of each +//! task can be monitored using the taskStarted and taskFinished events. It is a versatile tool for multithreaded asynchronous operations. +//! \~russian +//! PIThreadPoolWorker — это класс, реализующий фиксированный пул рабочих потоков для выполнения задач общего назначения. Он +//! позволяет запускать потоки, добавлять в очередь задачи в виде функторов или методов класса, а также управлять их выполнением. Класс +//! предоставляет методы для ожидания завершения всех задач или отдельных задач по их идентификатору, а также для корректной остановки пула +//! с таймаутами. С помощью событий taskStarted и taskFinished можно отслеживать жизненный цикл каждой задачи. Это универсальный инструмент +//! для многопоточного асинхронного выполнения операций. //! +//! \} PIThreadPoolWorker::PIThreadPoolWorker(int threads_count) { @@ -205,7 +215,7 @@ PIThreadPoolWorker::TaskStatus PIThreadPoolWorker::taskStatus(int64_t id) const if (w->in_work == id) return TaskStatus::InProgress; for (const auto & t: *qref) if (t.id == id) return TaskStatus::Enqueued; - return id <= next_task_id ? TaskStatus::Done : TaskStatus::Unknown; + return id <= next_task_id ? TaskStatus::DoneOrCancelled : TaskStatus::Unknown; } diff --git a/libs/main/thread/pithreadpoolworker.h b/libs/main/thread/pithreadpoolworker.h index aa89823f..295ea6ea 100644 --- a/libs/main/thread/pithreadpoolworker.h +++ b/libs/main/thread/pithreadpoolworker.h @@ -3,14 +3,10 @@ //! \brief //! \~english Thread pool worker //! \~russian Исполнитель пула потоков -//! -//! \details -//! \~english Executes tasks in a pool of worker threads. -//! \~russian Выполняет задачи в пуле рабочих потоков. /* PIP - Platform Independent Primitives - Stephan Fomenko, Ivan Pelipenko + Ivan Pelipenko, Stephan Fomenko This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -29,32 +25,35 @@ #ifndef PITHREADPOOLWORKER_H #define PITHREADPOOLWORKER_H -#include "piblockingqueue.h" #include "piprotectedvariable.h" #include "pithread.h" //! \~\ingroup Thread //! \~\brief -//! \~english Fixed-size pool of worker threads for fire-and-forget tasks. -//! \~russian Фиксированный пул рабочих потоков для задач без ожидания результата. +//! \~english Fixed-size pool of worker threads for generic-purpose tasks. +//! \~russian Фиксированный пул рабочих потоков для задач общего назначения. class PIP_EXPORT PIThreadPoolWorker: public PIObject { PIOBJECT(PIThreadPoolWorker) public: - //! \~english Constructs executor with \a threads_count worker threads. - //! \~russian Создает исполнитель с \a threads_count рабочими потоками. + //! \~english Constructs executor with \a threads_count worker threads. If \a threads_count < 0 processor threads count used. + //! \~russian Создает исполнитель с \a threads_count рабочими потоками. Если \a threads_count < 0 используется количество потоков + //! процессора. explicit PIThreadPoolWorker(int threads_count = -1); - //! \~english Stops worker threads and destroys executor resources. - //! \~russian Останавливает рабочие потоки и уничтожает ресурсы исполнителя. + //! \~english Destroy worker threads. Call \a stopAndWait() before. + //! \~russian Уничтожает рабочие потоки. Вызывайте перед этим \a stopAndWait(). virtual ~PIThreadPoolWorker(); + + //! \~english Task status. + //! \~russian Статус задачи. enum class TaskStatus { - Unknown, - Enqueued, - InProgress, - Done + Unknown /** \~english ID <= 0 or not queued yet \~russian ID <= 0 или не поставлена в очередь */, + Enqueued /** \~english Wait for execution \~russian Ожидает выполнения */, + InProgress /** \~english In execution now \~russian В процессе выполнения */, + DoneOrCancelled /** \~english Done or cancelled \~russian Выполнена или отменена */ }; @@ -90,31 +89,50 @@ public: //! \~russian Ожидает завершения задачи с id \a id. Возвращает \b false, если таймаут истек раньше. bool waitForTask(int64_t id, PISystemTime timeout = {}); + //! \~english Starts threads, wait for all tasks complete and threads stop. + //! \~russian Запускает потоки, ожидает завершения всех задач и остановки потоков. void exec(); + + //! \~english Queue functor to execution. Pass task ID in functor. Returns task ID. + //! \~russian Запланировать функтор на выполнение. В функтор передастся ID задачи. Возвращает ID задачи. int64_t enqueueTask(std::function func, PIObject * context = nullptr); + //! \~english Queue functor to execution. Returns task ID. + //! \~russian Запланировать функтор на выполнение. Возвращает ID задачи. int64_t enqueueTask(std::function func, PIObject * context = nullptr) { return enqueueTask([func](int64_t) { func(); }, context); } + //! \~english Queue class member method to execution. Pass task ID in method. Returns task ID. + //! \~russian Запланировать член-метод класса на выполнение. В метод передастся ID задачи. Возвращает ID задачи. template int64_t enqueueTask(O * obj, void (O::*member_func)(int64_t)) { return enqueueTask([obj, member_func](int64_t id) { (obj->*member_func)(id); }, PIObject::isPIObject(obj) ? dynamic_cast(obj) : nullptr); } + //! \~english Queue class member method to execution. Returns task ID. + //! \~russian Запланировать член-метод класса на выполнение. Возвращает ID задачи. template int64_t enqueueTask(O * obj, void (O::*member_func)()) { return enqueueTask([obj, member_func](int64_t) { (obj->*member_func)(); }, PIObject::isPIObject(obj) ? dynamic_cast(obj) : nullptr); } + //! \~english Remove task with id \a id from queue. Returns if task delete. + //! \~russian Удаляет задачу с id \a id из очереди. Возвращиает была ли задача удалена. bool removeTask(int64_t id); + + //! \~english Remove all queued tasks. + //! \~russian Удаляет все задачи из очереди. void clearTasks(); + //! \~english Returns task status with id \a id. + //! \~russian Возвращиает статус задачи с id \a id. TaskStatus taskStatus(int64_t id) const; + //! \events //! \{ @@ -133,25 +151,6 @@ public: //! \} - // DEPRECATED - - //! \~\brief - //! \~english Submits \a runnable for asynchronous execution by a worker thread. - //! \~russian Передает \a runnable на асинхронное выполнение рабочим потоком. - //! \details - //! \~english - //! This is a best-effort fire-and-forget call and does not report whether the task was accepted. - //! \After shutdown requests new tasks are ignored. - //! \~russian - //! Это вызов по принципу best-effort без ожидания результата и без сообщения о том, была ли задача принята. - //! \После запроса на завершение новые задачи игнорируются. - void execute(std::function runnable) { enqueueTask(runnable); } - - void shutdownNow() DEPRECATEDM("Use stopAndWait()") { stopAndWait(); } - void shutdown() DEPRECATEDM("Use stop()") { stop(); } - bool isShutdown() const DEPRECATEDM("Use !isRunning()") { return !isRunning(); } - bool awaitTermination(PISystemTime timeout) DEPRECATEDM("Use waitForFinish()") { return waitForFinish(timeout); } - private: struct Worker { PIThread thread; @@ -175,7 +174,4 @@ private: }; -typedef PIThreadPoolWorker PIThreadPoolExecutor DEPRECATEDM("Use PIThreadPoolWorker"); - - #endif // PITHREADPOOLWORKER_H diff --git a/main.cpp b/main.cpp index 23928d89..61a7b945 100644 --- a/main.cpp +++ b/main.cpp @@ -1,30 +1,53 @@ #include "pip.h" +#include "pithreadpoolexecutor.h" +class A: public PIObject { + PIOBJECT(A) + +public: + A(PIString n = {}) { setName(n); } + void foo() { + 100_ms .sleep(); + piCoutObj << "foo!"; + 100_ms .sleep(); + } +}; int main(int argc, char * argv[]) { + PIVector objects; + objects << new A("1") << new A("2") << new A("3"); + PITimer status_timer; - PIThreadPoolWorker pool(1); - - int64_t id = -1; - - status_timer.start(10_Hz, [&id, &pool] { piCout << "[timer] status" << (int)pool.taskStatus(id); }); - - 200_ms .sleep(); - id = pool.enqueueTask([](int64_t id) { - piCout << "[task ] start, id" << id; - 300_ms .sleep(); - piCout << "[task ] done"; - }); - piCout << "[main ]" << "enqueued, id" << id; - - 200_ms .sleep(); - piCout << pool.removeTask(id); - piCout << pool.removeTask(id); - piCout << "[main ]" << "start"; + PIThreadPoolWorker pool(2); pool.start(); + // int64_t id = -1; + // status_timer.start(10_Hz, [&id, &pool] { piCout << "[timer] status" << (int)pool.taskStatus(id); }); + + 100_ms .sleep(); + // pool.enqueueTask([](int64_t id) { + // piCout << "[task ] start, id" << id; + // // 500_ms .sleep(); + // piCout << "[task ] done"; + // }); + pool.enqueueTask(objects[0], &A::foo); + pool.enqueueTask(objects[1], &A::foo); + pool.enqueueTask(objects[1], &A::foo); + pool.enqueueTask(objects[2], &A::foo); + + 10_ms .sleep(); + delete objects[1]; + objects.remove(1); + // piCout << "[main ]" << "enqueued, id" << id; + + // 200_ms .sleep(); + piCout << "[main ]" << "wait ..."; + piCout << "[main ]" << "wait done"; + 1000_ms .sleep(); + pool.stopAndWait(); status_timer.stopAndWait(); + piDeleteAll(objects); return 0; } From 88d4a8e74d5ef8251946707273c98a0cc921b402 Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 26 Mar 2026 09:32:30 +0300 Subject: [PATCH 25/26] PIByteArray gcc one annoying warning off --- libs/main/types/pibytearray.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libs/main/types/pibytearray.h b/libs/main/types/pibytearray.h index 14fd688f..5d0ada7f 100644 --- a/libs/main/types/pibytearray.h +++ b/libs/main/types/pibytearray.h @@ -1105,9 +1105,18 @@ public: //! \~english Add to the end byte array "data" //! \~russian Добавляет в конец массива содержимое массива "data" PIByteArray & append(const PIByteArray & data_) { +#ifdef CC_GCC +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstringop-overflow" +# pragma GCC diagnostic ignored "-Warray-bounds" +# pragma GCC diagnostic ignored "-Wrestrict" +#endif const size_t ps = size(); enlarge(data_.size_s()); memcpy(data(ps), data_.data(), data_.size()); +#ifdef CC_GCC +# pragma GCC diagnostic pop +#endif return *this; } From f52fd45936062e2da12351c5bdfeab2ee3f6385f Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 26 Mar 2026 09:33:23 +0300 Subject: [PATCH 26/26] version --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b6453a6..c35a866c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,9 @@ if (POLICY CMP0177) endif() project(PIP) set(PIP_MAJOR 5) -set(PIP_MINOR 6) -set(PIP_REVISION 1) -set(PIP_SUFFIX ) +set(PIP_MINOR 7) +set(PIP_REVISION 0) +set(PIP_SUFFIX _alpha) set(PIP_COMPANY SHS) set(PIP_DOMAIN org.SHS)