|
16 | 16 |
|
17 | 17 | #include <chrono> |
18 | 18 | #include <memory> |
| 19 | +#include <thread> |
19 | 20 |
|
20 | 21 | #include <gmock/gmock.h> |
21 | 22 | #include <gtest/gtest.h> |
|
26 | 27 | #include <folly/coro/Sleep.h> |
27 | 28 | #include <folly/coro/Task.h> |
28 | 29 | #include <thrift/lib/cpp2/async/RocketClientChannel.h> |
| 30 | +#include <thrift/lib/cpp2/server/Cpp2Worker.h> |
29 | 31 | #include <thrift/lib/cpp2/server/ParallelConcurrencyController.h> |
30 | 32 | #include <thrift/lib/cpp2/server/RoundRobinRequestPile.h> |
31 | 33 | #include <thrift/lib/cpp2/server/ThriftServer.h> |
32 | 34 | #include <thrift/lib/cpp2/test/gen-cpp2/Calculator.h> |
33 | 35 | #include <thrift/lib/cpp2/test/gen-cpp2/Streamer.h> |
| 36 | +#include <thrift/lib/cpp2/transport/core/ManagedConnectionIf.h> |
34 | 37 | #include <thrift/lib/cpp2/util/ScopedServerInterfaceThread.h> |
35 | 38 |
|
36 | 39 | using namespace ::testing; |
@@ -1649,3 +1652,111 @@ TEST(InteractionTest, InteractionSnapshots) { |
1649 | 1652 | EXPECT_TRUE(foundInteraction) |
1650 | 1653 | << "Expected to find at least one connection with an active interaction"; |
1651 | 1654 | } |
| 1655 | + |
| 1656 | +TEST(InteractionTest, InteractionSnapshotLastActivityTime) { |
| 1657 | + // Test that lastActivityTime is tracked and updated on each call |
| 1658 | + ScopedServerInterfaceThread runner{std::make_shared<SemiCalculatorHandler>()}; |
| 1659 | + auto* thriftServer = dynamic_cast<ThriftServer*>(&runner.getThriftServer()); |
| 1660 | + ASSERT_NE(thriftServer, nullptr); |
| 1661 | + |
| 1662 | + folly::EventBase eb; |
| 1663 | + Client<Calculator> client( |
| 1664 | + RocketClientChannel::newChannel( |
| 1665 | + folly::AsyncSocket::UniquePtr( |
| 1666 | + new folly::AsyncSocket(&eb, runner.getAddress())))); |
| 1667 | + |
| 1668 | + // Create an interaction and make a call to establish it |
| 1669 | + auto adder = client.createAddition(); |
| 1670 | + adder.semifuture_accumulatePrimitive(1).via(&eb).getVia(&eb); |
| 1671 | + |
| 1672 | + // Get snapshot — lastActivityTime should be close to now |
| 1673 | + auto snapshot1 = thriftServer->getServerSnapshot().get(); |
| 1674 | + |
| 1675 | + std::chrono::steady_clock::time_point firstLastActivity{}; |
| 1676 | + for (const auto& [addr, connSnapshot] : snapshot1.connections) { |
| 1677 | + if (!connSnapshot.interactions.empty()) { |
| 1678 | + const auto& interaction = connSnapshot.interactions[0]; |
| 1679 | + firstLastActivity = interaction.lastActivityTime; |
| 1680 | + // lastActivityTime should be set (non-zero) |
| 1681 | + EXPECT_GT(firstLastActivity.time_since_epoch().count(), 0); |
| 1682 | + // lastActivityTime should be >= creationTime |
| 1683 | + EXPECT_GE(firstLastActivity, interaction.creationTime); |
| 1684 | + } |
| 1685 | + } |
| 1686 | + |
| 1687 | + // Sleep briefly and make another call to update lastActivityTime |
| 1688 | + /* sleep override */ |
| 1689 | + std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| 1690 | + adder.semifuture_accumulatePrimitive(2).via(&eb).getVia(&eb); |
| 1691 | + |
| 1692 | + // Get snapshot again — lastActivityTime should have advanced |
| 1693 | + auto snapshot2 = thriftServer->getServerSnapshot().get(); |
| 1694 | + for (const auto& [addr, connSnapshot] : snapshot2.connections) { |
| 1695 | + if (!connSnapshot.interactions.empty()) { |
| 1696 | + const auto& interaction = connSnapshot.interactions[0]; |
| 1697 | + EXPECT_GT(interaction.lastActivityTime, firstLastActivity) |
| 1698 | + << "lastActivityTime should advance after a new call"; |
| 1699 | + } |
| 1700 | + } |
| 1701 | +} |
| 1702 | + |
| 1703 | +TEST(InteractionTest, TerminateInteraction) { |
| 1704 | + // Test that terminateInteraction removes an interaction from snapshots |
| 1705 | + ScopedServerInterfaceThread runner{std::make_shared<SemiCalculatorHandler>()}; |
| 1706 | + auto* thriftServer = dynamic_cast<ThriftServer*>(&runner.getThriftServer()); |
| 1707 | + ASSERT_NE(thriftServer, nullptr); |
| 1708 | + |
| 1709 | + folly::EventBase eb; |
| 1710 | + Client<Calculator> client( |
| 1711 | + RocketClientChannel::newChannel( |
| 1712 | + folly::AsyncSocket::UniquePtr( |
| 1713 | + new folly::AsyncSocket(&eb, runner.getAddress())))); |
| 1714 | + |
| 1715 | + // Create an interaction and make a call to establish it |
| 1716 | + auto adder = client.createAddition(); |
| 1717 | + adder.semifuture_accumulatePrimitive(1).via(&eb).getVia(&eb); |
| 1718 | + |
| 1719 | + // Get snapshot to find the interaction ID |
| 1720 | + auto snapshot1 = thriftServer->getServerSnapshot().get(); |
| 1721 | + int64_t interactionId = 0; |
| 1722 | + for (const auto& [addr, connSnapshot] : snapshot1.connections) { |
| 1723 | + if (!connSnapshot.interactions.empty()) { |
| 1724 | + interactionId = connSnapshot.interactions[0].interactionId; |
| 1725 | + break; |
| 1726 | + } |
| 1727 | + } |
| 1728 | + ASSERT_GT(interactionId, 0) << "Expected to find an active interaction"; |
| 1729 | + |
| 1730 | + // Terminate the interaction via the connection's terminateInteraction API. |
| 1731 | + // This exercises the cross-thread scheduling path since forEachWorker |
| 1732 | + // runs on the calling thread, not the worker's event base. |
| 1733 | + bool terminated = false; |
| 1734 | + thriftServer->forEachWorker([&](wangle::Acceptor* acceptor) { |
| 1735 | + auto* worker = dynamic_cast<Cpp2Worker*>(acceptor); |
| 1736 | + if (!worker || terminated) { |
| 1737 | + return; |
| 1738 | + } |
| 1739 | + worker->getEventBase()->runInEventBaseThreadAndWait([&] { |
| 1740 | + if (auto* connectionManager = worker->getConnectionManager()) { |
| 1741 | + connectionManager->forEachConnection( |
| 1742 | + [&](wangle::ManagedConnection* wangleConn) { |
| 1743 | + if (auto* managedConn = |
| 1744 | + dynamic_cast<ManagedConnectionIf*>(wangleConn)) { |
| 1745 | + managedConn->terminateInteraction(interactionId); |
| 1746 | + terminated = true; |
| 1747 | + } |
| 1748 | + }); |
| 1749 | + } |
| 1750 | + }); |
| 1751 | + }); |
| 1752 | + ASSERT_TRUE(terminated) << "Failed to find connection to terminate on"; |
| 1753 | + |
| 1754 | + // Verify the interaction is gone from snapshots |
| 1755 | + auto snapshot2 = thriftServer->getServerSnapshot().get(); |
| 1756 | + for (const auto& [addr, connSnapshot] : snapshot2.connections) { |
| 1757 | + for (const auto& interaction : connSnapshot.interactions) { |
| 1758 | + EXPECT_NE(interaction.interactionId, interactionId) |
| 1759 | + << "Interaction should have been terminated"; |
| 1760 | + } |
| 1761 | + } |
| 1762 | +} |
0 commit comments