mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
The two active users of DeadlineTimer, NetworkOPs and Application, now use asio::steady_timers rather than DeadlineTimer. DeadlineTimer is removed since it is no longer used. To assure that all in-flight closures on timers are done before Stoppables call stopped(), the JobCounter is made more generic. It's now a ClosureCounter. The ClosureCounter is currently used to count closures in flight for the JobQueue, NetworkOPs, and the Application.
330 lines
12 KiB
C++
330 lines
12 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2017 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <BeastConfig.h>
|
|
#include <ripple/core/ClosureCounter.h>
|
|
#include <ripple/beast/unit_test.h>
|
|
#include <test/jtx/Env.h>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
namespace ripple {
|
|
namespace test {
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
class ClosureCounter_test : public beast::unit_test::suite
|
|
{
|
|
// We're only using Env for its Journal.
|
|
jtx::Env env {*this};
|
|
beast::Journal j {env.app().journal ("ClosureCounter_test")};
|
|
|
|
void testConstruction()
|
|
{
|
|
// Build different kinds of ClosureCounters.
|
|
{
|
|
// Count closures that return void and take no arguments.
|
|
ClosureCounter<void> voidCounter;
|
|
BEAST_EXPECT (voidCounter.count() == 0);
|
|
|
|
int evidence = 0;
|
|
// Make sure voidCounter.wrap works with an rvalue closure.
|
|
auto wrapped = voidCounter.wrap ([&evidence] () { ++evidence; });
|
|
BEAST_EXPECT (voidCounter.count() == 1);
|
|
BEAST_EXPECT (evidence == 0);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
// wrapped() should be callable with no arguments.
|
|
(*wrapped)();
|
|
BEAST_EXPECT (evidence == 1);
|
|
(*wrapped)();
|
|
BEAST_EXPECT (evidence == 2);
|
|
|
|
// Destroying the contents of wrapped should decrement voidCounter.
|
|
wrapped = boost::none;
|
|
BEAST_EXPECT (voidCounter.count() == 0);
|
|
}
|
|
{
|
|
// Count closures that return void and take one int argument.
|
|
ClosureCounter<void, int> setCounter;
|
|
BEAST_EXPECT (setCounter.count() == 0);
|
|
|
|
int evidence = 0;
|
|
// Make sure setCounter.wrap works with a non-const lvalue closure.
|
|
auto setInt = [&evidence] (int i) { evidence = i; };
|
|
auto wrapped = setCounter.wrap (setInt);
|
|
|
|
BEAST_EXPECT (setCounter.count() == 1);
|
|
BEAST_EXPECT (evidence == 0);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
// wrapped() should be callable with one integer argument.
|
|
(*wrapped)(5);
|
|
BEAST_EXPECT (evidence == 5);
|
|
(*wrapped)(11);
|
|
BEAST_EXPECT (evidence == 11);
|
|
|
|
// Destroying the contents of wrapped should decrement setCounter.
|
|
wrapped = boost::none;
|
|
BEAST_EXPECT (setCounter.count() == 0);
|
|
}
|
|
{
|
|
// Count closures that return int and take two int arguments.
|
|
ClosureCounter<int, int, int> sumCounter;
|
|
BEAST_EXPECT (sumCounter.count() == 0);
|
|
|
|
// Make sure sumCounter.wrap works with a const lvalue closure.
|
|
auto const sum = [] (int i, int j) { return i + j; };
|
|
auto wrapped = sumCounter.wrap (sum);
|
|
|
|
BEAST_EXPECT (sumCounter.count() == 1);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
// wrapped() should be callable with two integers.
|
|
BEAST_EXPECT ((*wrapped)(5, 2) == 7);
|
|
BEAST_EXPECT ((*wrapped)(2, -8) == -6);
|
|
|
|
// Destroying the contents of wrapped should decrement sumCounter.
|
|
wrapped = boost::none;
|
|
BEAST_EXPECT (sumCounter.count() == 0);
|
|
}
|
|
}
|
|
|
|
// A class used to test argument passing.
|
|
class TrackedString
|
|
{
|
|
public:
|
|
int copies = {0};
|
|
int moves = {0};
|
|
std::string str;
|
|
|
|
TrackedString() = delete;
|
|
|
|
explicit TrackedString(char const* rhs)
|
|
: str (rhs) {}
|
|
|
|
// Copy constructor
|
|
TrackedString (TrackedString const& rhs)
|
|
: copies (rhs.copies + 1)
|
|
, moves (rhs.moves)
|
|
, str (rhs.str) {}
|
|
|
|
// Move constructor
|
|
TrackedString (TrackedString&& rhs)
|
|
: copies (rhs.copies)
|
|
, moves (rhs.moves + 1)
|
|
, str (std::move(rhs.str)) {}
|
|
|
|
// Delete copy and move assignment.
|
|
TrackedString& operator=(TrackedString const& rhs) = delete;
|
|
|
|
// String concatenation
|
|
TrackedString& operator+=(char const* rhs)
|
|
{
|
|
str += rhs;
|
|
return *this;
|
|
}
|
|
|
|
friend
|
|
TrackedString operator+(TrackedString const& str, char const* rhs)
|
|
{
|
|
TrackedString ret {str};
|
|
ret.str += rhs;
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
void testArgs()
|
|
{
|
|
// Make sure a wrapped closure handles rvalue reference arguments
|
|
// correctly.
|
|
{
|
|
// Pass by value.
|
|
ClosureCounter<TrackedString, TrackedString> strCounter;
|
|
BEAST_EXPECT (strCounter.count() == 0);
|
|
|
|
auto wrapped = strCounter.wrap (
|
|
[] (TrackedString in) { return in += "!"; });
|
|
|
|
BEAST_EXPECT (strCounter.count() == 1);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
TrackedString const strValue ("value");
|
|
TrackedString const result = (*wrapped)(strValue);
|
|
BEAST_EXPECT (result.copies == 2);
|
|
BEAST_EXPECT (result.moves == 1);
|
|
BEAST_EXPECT (result.str == "value!");
|
|
BEAST_EXPECT (strValue.str.size() == 5);
|
|
}
|
|
{
|
|
// Use a const lvalue argument.
|
|
ClosureCounter<TrackedString, TrackedString const&> strCounter;
|
|
BEAST_EXPECT (strCounter.count() == 0);
|
|
|
|
auto wrapped = strCounter.wrap (
|
|
[] (TrackedString const& in) { return in + "!"; });
|
|
|
|
BEAST_EXPECT (strCounter.count() == 1);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
TrackedString const strConstLValue ("const lvalue");
|
|
TrackedString const result = (*wrapped)(strConstLValue);
|
|
BEAST_EXPECT (result.copies == 1);
|
|
// BEAST_EXPECT (result.moves == ?); // moves VS == 1, gcc == 0
|
|
BEAST_EXPECT (result.str == "const lvalue!");
|
|
BEAST_EXPECT (strConstLValue.str.size() == 12);
|
|
}
|
|
{
|
|
// Use a non-const lvalue argument.
|
|
ClosureCounter<TrackedString, TrackedString&> strCounter;
|
|
BEAST_EXPECT (strCounter.count() == 0);
|
|
|
|
auto wrapped = strCounter.wrap (
|
|
[] (TrackedString& in) { return in += "!"; });
|
|
|
|
BEAST_EXPECT (strCounter.count() == 1);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
TrackedString strLValue ("lvalue");
|
|
TrackedString const result = (*wrapped)(strLValue);
|
|
BEAST_EXPECT (result.copies == 1);
|
|
BEAST_EXPECT (result.moves == 0);
|
|
BEAST_EXPECT (result.str == "lvalue!");
|
|
BEAST_EXPECT (strLValue.str == result.str);
|
|
}
|
|
{
|
|
// Use an rvalue argument.
|
|
ClosureCounter<TrackedString, TrackedString&&> strCounter;
|
|
BEAST_EXPECT (strCounter.count() == 0);
|
|
|
|
auto wrapped = strCounter.wrap (
|
|
[] (TrackedString&& in) {
|
|
// Note that none of the compilers noticed that in was
|
|
// leaving scope. So, without intervention, they would
|
|
// do a copy for the return (June 2017). An explicit
|
|
// std::move() was required.
|
|
return std::move(in += "!");
|
|
});
|
|
|
|
BEAST_EXPECT (strCounter.count() == 1);
|
|
BEAST_EXPECT (wrapped);
|
|
|
|
// Make the string big enough to (probably) avoid the small string
|
|
// optimization.
|
|
TrackedString strRValue ("rvalue abcdefghijklmnopqrstuvwxyz");
|
|
TrackedString const result = (*wrapped)(std::move(strRValue));
|
|
BEAST_EXPECT (result.copies == 0);
|
|
BEAST_EXPECT (result.moves == 1);
|
|
BEAST_EXPECT (result.str == "rvalue abcdefghijklmnopqrstuvwxyz!");
|
|
BEAST_EXPECT (strRValue.str.size() == 0);
|
|
}
|
|
}
|
|
|
|
void testWrap()
|
|
{
|
|
// Verify reference counting.
|
|
ClosureCounter<void> voidCounter;
|
|
BEAST_EXPECT (voidCounter.count() == 0);
|
|
{
|
|
auto wrapped1 = voidCounter.wrap ([] () {});
|
|
BEAST_EXPECT (voidCounter.count() == 1);
|
|
{
|
|
// Copy should increase reference count.
|
|
auto wrapped2 (wrapped1);
|
|
BEAST_EXPECT (voidCounter.count() == 2);
|
|
{
|
|
// Move should increase reference count.
|
|
auto wrapped3 (std::move(wrapped2));
|
|
BEAST_EXPECT (voidCounter.count() == 3);
|
|
{
|
|
// An additional closure also increases count.
|
|
auto wrapped4 = voidCounter.wrap ([] () {});
|
|
BEAST_EXPECT (voidCounter.count() == 4);
|
|
}
|
|
BEAST_EXPECT (voidCounter.count() == 3);
|
|
}
|
|
BEAST_EXPECT (voidCounter.count() == 2);
|
|
}
|
|
BEAST_EXPECT (voidCounter.count() == 1);
|
|
}
|
|
BEAST_EXPECT (voidCounter.count() == 0);
|
|
|
|
// Join with 0 count should not stall.
|
|
using namespace std::chrono_literals;
|
|
voidCounter.join("testWrap", 1ms, j);
|
|
|
|
// Wrapping a closure after join() should return boost::none.
|
|
BEAST_EXPECT (voidCounter.wrap ([] () {}) == boost::none);
|
|
}
|
|
|
|
void testWaitOnJoin()
|
|
{
|
|
// Verify reference counting.
|
|
ClosureCounter<void> voidCounter;
|
|
BEAST_EXPECT (voidCounter.count() == 0);
|
|
|
|
auto wrapped = (voidCounter.wrap ([] () {}));
|
|
BEAST_EXPECT (voidCounter.count() == 1);
|
|
|
|
// Calling join() now should stall, so do it on a different thread.
|
|
std::atomic<bool> threadExited {false};
|
|
std::thread localThread ([&voidCounter, &threadExited, this] ()
|
|
{
|
|
// Should stall after calling join.
|
|
using namespace std::chrono_literals;
|
|
voidCounter.join("testWaitOnJoin", 1ms, j);
|
|
threadExited.store (true);
|
|
});
|
|
|
|
// Wait for the thread to call voidCounter.join().
|
|
while (! voidCounter.joined());
|
|
|
|
// The thread should still be active after waiting 5 milliseconds.
|
|
// This is not a guarantee that join() stalled the thread, but it
|
|
// improves confidence.
|
|
using namespace std::chrono_literals;
|
|
std::this_thread::sleep_for (5ms);
|
|
BEAST_EXPECT (threadExited == false);
|
|
|
|
// Destroy the contents of wrapped and expect the thread to exit
|
|
// (asynchronously).
|
|
wrapped = boost::none;
|
|
BEAST_EXPECT (voidCounter.count() == 0);
|
|
|
|
// Wait for the thread to exit.
|
|
while (threadExited == false);
|
|
localThread.join();
|
|
}
|
|
|
|
public:
|
|
void run()
|
|
{
|
|
testConstruction();
|
|
testArgs();
|
|
testWrap();
|
|
testWaitOnJoin();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(ClosureCounter, core, ripple);
|
|
|
|
} // test
|
|
} // ripple
|