| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a | 5 // NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a |
| 6 // heavily-loaded system). Sorry. |test::EpsilonTimeout()| may be increased to | 6 // heavily-loaded system). Sorry. |test::EpsilonTimeout()| may be increased to |
| 7 // increase tolerance and reduce observed flakiness (though doing so reduces the | 7 // increase tolerance and reduce observed flakiness (though doing so reduces the |
| 8 // meaningfulness of the test). | 8 // meaningfulness of the test). |
| 9 | 9 |
| 10 #include "mojo/edk/system/wait_set_dispatcher.h" | 10 #include "mojo/edk/system/wait_set_dispatcher.h" |
| 11 | 11 |
| 12 #include <map> |
| 12 #include <thread> | 13 #include <thread> |
| 14 #include <utility> |
| 13 #include <vector> | 15 #include <vector> |
| 14 | 16 |
| 17 #include "base/logging.h" |
| 15 #include "mojo/edk/platform/test_stopwatch.h" | 18 #include "mojo/edk/platform/test_stopwatch.h" |
| 16 #include "mojo/edk/platform/thread_utils.h" | 19 #include "mojo/edk/platform/thread_utils.h" |
| 17 #include "mojo/edk/system/mock_simple_dispatcher.h" | 20 #include "mojo/edk/system/mock_simple_dispatcher.h" |
| 21 #include "mojo/edk/system/test/random.h" |
| 18 #include "mojo/edk/system/test/timeouts.h" | 22 #include "mojo/edk/system/test/timeouts.h" |
| 23 #include "mojo/edk/util/mutex.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" | 24 #include "testing/gtest/include/gtest/gtest.h" |
| 20 | 25 |
| 21 using mojo::platform::test::Stopwatch; | 26 using mojo::platform::test::Stopwatch; |
| 22 using mojo::platform::ThreadSleep; | 27 using mojo::platform::ThreadSleep; |
| 23 using mojo::util::MakeRefCounted; | 28 using mojo::util::MakeRefCounted; |
| 29 using mojo::util::Mutex; |
| 30 using mojo::util::MutexLocker; |
| 31 using mojo::util::RefPtr; |
| 24 | 32 |
| 25 namespace mojo { | 33 namespace mojo { |
| 26 namespace system { | 34 namespace system { |
| 27 namespace { | 35 namespace { |
| 28 | 36 |
| 29 // Helper to check if an array of |MojoWaitSetResult|s has a result |r| for the | 37 // Helper to check if an array of |MojoWaitSetResult|s has a result |r| for the |
| 30 // given cookie, in which case: | 38 // given cookie, in which case: |
| 31 // - |r.wait_result| must equal |wait_result|. | 39 // - |r.wait_result| must equal |wait_result|. |
| 32 // - If |wait_result| is |MOJO_RESULT_OK| or | 40 // - If |wait_result| is |MOJO_RESULT_OK| or |
| 33 // |MOJO_RESULT_FAILED_PRECONDITION|, then | 41 // |MOJO_RESULT_FAILED_PRECONDITION|, then |
| (...skipping 598 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 632 d_member->SetSatisfiedSignals(kR); | 640 d_member->SetSatisfiedSignals(kR); |
| 633 | 641 |
| 634 for (auto& t : threads) | 642 for (auto& t : threads) |
| 635 t.join(); | 643 t.join(); |
| 636 } | 644 } |
| 637 | 645 |
| 638 EXPECT_EQ(MOJO_RESULT_OK, d_member->Close()); | 646 EXPECT_EQ(MOJO_RESULT_OK, d_member->Close()); |
| 639 EXPECT_EQ(MOJO_RESULT_OK, d->Close()); | 647 EXPECT_EQ(MOJO_RESULT_OK, d->Close()); |
| 640 } | 648 } |
| 641 | 649 |
| 642 // TODO(vtl): Stress tests. | 650 // The set-up for this test is as follows: |
| 651 // - We'll just use the "readable" handle signal everywhere. |
| 652 // - There's one wait set. |
| 653 // - It contains a single "quit" entry for a "quit" dispatcher ("owned" by |
| 654 // the main thread). |
| 655 // - There are a number of waiter threads waiting on it. |
| 656 // - Upon being awoken, a waiter thread looks at the results. |
| 657 // - If one of them was for "quit", the waiter thread ends. |
| 658 // - Otherwise, it resets the signal for the things it was awoken for. |
| 659 // - There are a bunch of "worker" threads. |
| 660 // - Each worker thread operates in a tight loop. |
| 661 // - In each iteration, it checks if the quit dispatcher is signaled; if |
| 662 // it is, the worker thread ends. |
| 663 // - Otherwise, it might create a dispatcher (which it owns) and add entry |
| 664 // for it. |
| 665 // - It might also signal an entry that it previously added. |
| 666 // - It might also remove an entry that it previously added. |
| 667 // - The main thread just sleeps for some desired amount of time, and then |
| 668 // signals the quit dispatcher and joins all of the above threads. |
| 669 TEST(WaitSetDispatcherTest, ThreadedStress) { |
| 670 static constexpr auto kTestRunTime = static_cast<MojoDeadline>(1000 * 1000u); |
| 671 static constexpr size_t kNumWaiters = 4; |
| 672 static constexpr size_t kNumWorkers = 8; |
| 673 static constexpr size_t kMaxEntriesPerWorker = 100; |
| 674 |
| 675 static constexpr auto kNone = MOJO_HANDLE_SIGNAL_NONE; |
| 676 static constexpr auto kSignal = MOJO_HANDLE_SIGNAL_READABLE; |
| 677 static constexpr uint64_t kQuitCookie = 0; |
| 678 |
| 679 Mutex mu; |
| 680 // The next cookie to "allocate". Guarded by |mu|. |
| 681 uint64_t next_cookie = kQuitCookie + 1; |
| 682 // Cookie -> dispatcher map. Guarded by |mu|. |
| 683 std::map<uint64_t, RefPtr<test::MockSimpleDispatcher>> |
| 684 cookie_to_dispatcher_map; |
| 685 |
| 686 auto wait_set = |
| 687 WaitSetDispatcher::Create(WaitSetDispatcher::kDefaultCreateOptions); |
| 688 // The quit dispatcher and entry. |
| 689 auto quit = MakeRefCounted<test::MockSimpleDispatcher>(kNone, kSignal); |
| 690 EXPECT_EQ(MOJO_RESULT_OK, |
| 691 wait_set->WaitSetAdd(NullUserPointer(), quit.Clone(), kSignal, |
| 692 kQuitCookie)); |
| 693 |
| 694 std::vector<std::thread> threads; |
| 695 |
| 696 // Add waiter threads. |
| 697 for (size_t i = 0; i < kNumWaiters; i++) { |
| 698 threads.push_back(std::thread([&mu, &cookie_to_dispatcher_map, &wait_set, |
| 699 i]() { |
| 700 uint64_t total_wakeups = 0; |
| 701 |
| 702 for (;;) { |
| 703 uint32_t num_results = 10u; |
| 704 MojoWaitSetResult results[10] = {}; |
| 705 EXPECT_EQ(MOJO_RESULT_OK, |
| 706 wait_set->WaitSetWait( |
| 707 MOJO_DEADLINE_INDEFINITE, MakeUserPointer(&num_results), |
| 708 MakeUserPointer(results), NullUserPointer())); |
| 709 EXPECT_GE(num_results, 1u); |
| 710 total_wakeups++; |
| 711 |
| 712 // First, see if we were woken up for a quit cookie. |
| 713 for (uint32_t j = 0; j < num_results; j++) { |
| 714 if (results[j].cookie == kQuitCookie) { |
| 715 VLOG(1) << "Waiter thread #" << i |
| 716 << ": total_wakeups = " << total_wakeups; |
| 717 return; |
| 718 } |
| 719 } |
| 720 |
| 721 // Otherwise, get the dispatcher for each cookie and reset its signals. |
| 722 MutexLocker locker(&mu); |
| 723 for (uint32_t j = 0; j < num_results; j++) { |
| 724 auto it = cookie_to_dispatcher_map.find(results[j].cookie); |
| 725 if (it == cookie_to_dispatcher_map.end()) { |
| 726 // This is not an error, since it may have been removed/destroyed by |
| 727 // a worker thread. |
| 728 continue; |
| 729 } |
| 730 it->second->SetSatisfiedSignals(kNone); |
| 731 } |
| 732 } |
| 733 })); |
| 734 } |
| 735 |
| 736 // Add worker threads. |
| 737 for (size_t i = 0; i < kNumWorkers; i++) { |
| 738 threads.push_back(std::thread([&mu, &next_cookie, &cookie_to_dispatcher_map, |
| 739 &wait_set, &quit, i]() { |
| 740 uint64_t total_adds = 0; |
| 741 uint64_t total_triggers = 0; |
| 742 uint64_t total_removes = 0; |
| 743 |
| 744 // These are parallel vectors. |
| 745 std::vector<RefPtr<test::MockSimpleDispatcher>> dispatchers; |
| 746 std::vector<uint64_t> cookies; |
| 747 |
| 748 for (;;) { |
| 749 // If |quit| is signaled, quit. |
| 750 if ((quit->GetHandleSignalsState().satisfied_signals & kSignal)) |
| 751 break; |
| 752 |
| 753 // Should we add an entry (i.e., a dispatcher)? Make the probability be |
| 754 // 1 - (current number) / (maximum number). |
| 755 if (test::RandomInt(1, static_cast<int>(kMaxEntriesPerWorker)) > |
| 756 static_cast<int>(dispatchers.size())) { |
| 757 total_adds++; |
| 758 |
| 759 auto new_dispatcher = |
| 760 MakeRefCounted<test::MockSimpleDispatcher>(kNone, kSignal); |
| 761 uint64_t new_cookie; |
| 762 { |
| 763 MutexLocker locker(&mu); |
| 764 new_cookie = next_cookie++; |
| 765 cookie_to_dispatcher_map[new_cookie] = new_dispatcher; |
| 766 dispatchers.push_back(new_dispatcher); |
| 767 cookies.push_back(new_cookie); |
| 768 } |
| 769 EXPECT_NE(new_cookie, kQuitCookie); |
| 770 EXPECT_EQ( |
| 771 MOJO_RESULT_OK, |
| 772 wait_set->WaitSetAdd(NullUserPointer(), std::move(new_dispatcher), |
| 773 kSignal, new_cookie)); |
| 774 } |
| 775 |
| 776 // Should we trigger an entry? Make the probability be (current number) |
| 777 // / (maximum number). |
| 778 int j = test::RandomInt(0, static_cast<int>(kMaxEntriesPerWorker - 1)); |
| 779 if (j < static_cast<int>(dispatchers.size())) { |
| 780 total_triggers++; |
| 781 |
| 782 // Just use |j| as an index into |dispatchers|/|cookies|. |
| 783 dispatchers[j]->SetSatisfiedSignals(kSignal); |
| 784 } |
| 785 |
| 786 // Should we remove an entry? Make the probability be (current number) / |
| 787 // (maximum number). |
| 788 j = test::RandomInt(0, static_cast<int>(kMaxEntriesPerWorker - 1)); |
| 789 if (j < static_cast<int>(dispatchers.size())) { |
| 790 total_removes++; |
| 791 |
| 792 EXPECT_NE(cookies[j], kQuitCookie); |
| 793 EXPECT_EQ(MOJO_RESULT_OK, wait_set->WaitSetRemove(cookies[j])); |
| 794 { |
| 795 MutexLocker locker(&mu); |
| 796 cookie_to_dispatcher_map.erase(cookies[j]); |
| 797 } |
| 798 EXPECT_EQ(MOJO_RESULT_OK, dispatchers[j]->Close()); |
| 799 dispatchers.erase(dispatchers.begin() + j); |
| 800 cookies.erase(cookies.begin() + j); |
| 801 } |
| 802 } |
| 803 |
| 804 // Remove remaining entries. |
| 805 for (auto cookie : cookies) |
| 806 EXPECT_EQ(MOJO_RESULT_OK, wait_set->WaitSetRemove(cookie)); |
| 807 |
| 808 // Close all our dispatchers. |
| 809 for (auto& dispatcher : dispatchers) |
| 810 EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); |
| 811 |
| 812 VLOG(1) << "Worker thread #" << i << ": total_adds = " << total_adds |
| 813 << ", total_triggers = " << total_triggers |
| 814 << ", total_removes = " << total_removes; |
| 815 })); |
| 816 } |
| 817 |
| 818 // Main thread work: just sleep and then signal |quit|. |
| 819 ThreadSleep(kTestRunTime); |
| 820 quit->SetSatisfiedSignals(kSignal); |
| 821 |
| 822 for (auto& t : threads) |
| 823 t.join(); |
| 824 |
| 825 EXPECT_EQ(MOJO_RESULT_OK, quit->Close()); |
| 826 EXPECT_EQ(MOJO_RESULT_OK, wait_set->Close()); |
| 827 } |
| 643 | 828 |
| 644 // TODO(vtl): Test options validation for "create" and "add" (not that there's | 829 // TODO(vtl): Test options validation for "create" and "add" (not that there's |
| 645 // much to test). | 830 // much to test). |
| 646 | 831 |
| 647 } // namespace | 832 } // namespace |
| 648 } // namespace system | 833 } // namespace system |
| 649 } // namespace mojo | 834 } // namespace mojo |
| OLD | NEW |