Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(430)

Side by Side Diff: mojo/edk/system/wait_set_dispatcher_unittest.cc

Issue 2099863002: Add a threaded stress test for WaitSetDispatcher. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698