int main(){ AutoCurrentContext ctxt; // A context must be initiated before events can be received. Similar to CoreThread ctxt->Initiate(); // This creates an proxy object that can fire MyEvent::* events AutoFired<MyEvent> eventFirer; // Inject receiver types into current context AutoRequired<FooEvent> foo; AutoRequired<BarEvent> bar; std::cout << "Foo should be 0: " << foo->getSecret() << std::endl; std::cout << "Bar Should be 0: " << bar->getSecret() << std::endl; // Fire event, this should set m_secret on both foo and bar eventFirer(&MyEvent::myFunction)(42); std::cout << "Foo should be 42: " << foo->getSecret() << std::endl; std::cout << "Bar should be 42: " << bar->getSecret() << std::endl; // You can also manually fire events on a context with `Invoke` // Since the function pointer is to `BarEvent`, `FooEvent` won't receive the event ctxt->Invoke(&BarEvent::myFunction)(77); std::cout << "Foo should be 42: " << foo->getSecret() << std::endl; std::cout << "Bar should be 77: " << bar->getSecret() << std::endl; }
TEST_F(BasicThreadTest, ValidateThreadTimes) { AutoCurrentContext ctxt; ctxt->Initiate(); static const size_t spinCount = 10000000; auto spinsThenQuits = ctxt->Construct<SpinsAndThenQuits>(spinCount); // Instantaneous benchmark on the time it takes to decrement the counter value: std::chrono::nanoseconds benchmark; { auto startTime = std::chrono::high_resolution_clock::now(); for(volatile size_t i = spinCount; i--;); benchmark = std::chrono::high_resolution_clock::now() - startTime; } // By this point, not much should have happened: std::chrono::milliseconds kernelTime; std::chrono::milliseconds userTime; spinsThenQuits->GetThreadTimes(kernelTime, userTime); // Kick off the thread and wait for it to exit: spinsThenQuits->Continue(); ASSERT_TRUE(spinsThenQuits->WaitFor(std::chrono::seconds(10))) << "Spin-then-quit test took too long to execute"; // Thread should not have been able to complete in less time than we completed, by a factor of ten or so at least ASSERT_LE(benchmark, spinsThenQuits->m_userTime * 10) << "Reported execution time could not possibly be correct, spin operation took less time to execute than should have been possible with the CPU"; }
int main(){ // The 2 main thread classes in Autowiring are the BasicThread and CoreThread. // Classes that inherit from these types will have thread capabilities // Both start when their enclosing context is 'initiated'. Threads injected // after the context is initiated will start immediatly AutoRequired<MyBasicThread> myBasic; AutoCurrentContext ctxt; ctxt->Initiate(); // myBasic->Run() starts now in its own thread std::this_thread::sleep_for(std::chrono::milliseconds(250)); std::cout << "injecting a CoreThread" << std::endl; // Types inheriting from CoreThread implement a dispatch queue in their 'run()' // function. Lambdas can be appended with operator+= AutoRequired<MyCoreThread> myCore; myCore->AddToQueue(42); myCore->AddToQueue(1337); *myCore += []{ std::cout << "This gets run after '1337'" << std::endl; }; // This should be run before 'myCore' is finished std::cout << "This thread is faster\n"; // This will wait for all outstanding threads to finish before terminating the context ctxt->SignalShutdown(true); }
TEST_F(CoreContextTest, TerminatedContextHarmless) { AutoCurrentContext ctxt; ctxt->Initiate(); AutoRequired<TriesToCreateChild>{}; ctxt->SignalShutdown(); ASSERT_THROW(ctxt->Create<void>(), dispatch_aborted_exception) << "An exception should have been thrown when attempting to create a child from a terminated context"; }
TEST_F(AutoConfig_SliderTest, ConcurrentModification) { AutoCurrentContext ctxt; auto mgr = ctxt->Inject<SliderManager>(); auto mic = ctxt->Inject<MonotonicIncreaseChecker>(); ctxt->Initiate(); ASSERT_EQ(2U, mgr->all_sliders.size()) << "Slider manager did not find all sliders as expected"; // Set all integer sliders: for (int i = 0; i < 100; i++) { for (auto& slider : mgr->all_sliders) if(sizeof(int) == slider->size) slider->assigner(i); } // Now set boolean sliders, this should cause our class to exit because // the only boolean slider is there to cause the thread to exit for (auto& slider : mgr->all_sliders) if (sizeof(bool) == slider->size) slider->assigner(1); ASSERT_TRUE(mic->WaitFor(std::chrono::seconds{ 5 })) << "Counter class did not exit in time"; ASSERT_TRUE(mic->succeeded) << "Values received out-of-order in the sequence checker"; }
TEST_F(AutoPacketFactoryTest, AddSubscriberTest) { AutoCurrentContext ctxt; AutoRequired<AutoPacketFactory> factory; ctxt->Initiate(); bool first_called = false; bool second_called = false; factory->AddSubscriber(AutoFilterDescriptor([&first_called](int) {first_called = true; })); { std::vector<AutoFilterDescriptor> descs; factory->AppendAutoFiltersTo(descs); ASSERT_EQ(1UL, descs.size()) << "Expected exactly one AutoFilters after call to AddSubscriber"; } *factory += [&second_called] (int v) { second_called = true; ASSERT_EQ(101, v) << "Decoration value mismatch"; }; { std::vector<AutoFilterDescriptor> descs; factory->AppendAutoFiltersTo(descs); ASSERT_EQ(2UL, descs.size()) << "Expected exactly two AutoFilters on this packet"; } auto packet = factory->NewPacket(); ASSERT_FALSE(first_called) << "Normal subscriber called too early"; ASSERT_FALSE(second_called) << "Subscriber added with operator+= called too early"; packet->DecorateImmediate(int(101)); ASSERT_TRUE(first_called) << "Normal subscriber never called"; ASSERT_TRUE(second_called) << "Subscriber added with operator+= never called"; }
TEST_F(AutoPacketFactoryTest, AutoPacketStatistics) { // Create a context, fill it up, kick it off: AutoCurrentContext ctxt; AutoRequired<DelaysAutoPacketsOneMS> dapoms; AutoRequired<AutoPacketFactory> factory; ctxt->Initiate(); int numPackets = 20; // Send 20 packets which should all be delayed 1ms for (int i = 0; i < numPackets; ++i) { auto packet = factory->NewPacket(); packet->Decorate(i); } // Shutdown our context, and rundown our factory ctxt->SignalShutdown(); factory->Wait(); // Ensure that the statistics are not too wrong // We delayed each packet by one ms, and our statistics are given in nanoseconds double packetDelay = (double) std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(1)).count(); ASSERT_EQ(numPackets, factory->GetTotalPacketCount()) << "The factory did not get enough packets"; ASSERT_LE(packetDelay, factory->GetMeanPacketLifetime()) << "The mean packet lifetime was less than the delay on each packet"; }
TEST_F(AutoPacketFactoryTest, IsRunningWhilePacketIssued) { AutoCurrentContext ctxt; ctxt->Initiate(); AutoRequired<AutoPacketFactory> factory; auto packet = factory->NewPacket(); ctxt->SignalShutdown(); ASSERT_TRUE(factory->IsRunning()) << "Factory should be considered to be running as long as packets are outstanding"; }
TEST_F(CoreContextTest, InitiateOrder) { AutoCurrentContext testCtxt; testCtxt->Initiate(); // Initiate inner to outer { auto outerCtxt = testCtxt->Create<void>(); auto middleCtxt = outerCtxt->Create<void>(); auto innerCtxt = middleCtxt->Create<void>(); innerCtxt->Initiate(); middleCtxt->Initiate(); outerCtxt->Initiate(); ASSERT_TRUE(outerCtxt->IsRunning()) << "Context not running after begin initiated"; ASSERT_TRUE(middleCtxt->IsRunning()) << "Context not running after begin initiated"; ASSERT_TRUE(innerCtxt->IsRunning()) << "Context not running after begin initiated"; outerCtxt->SignalShutdown(true); } // Initiate outer to inner { auto outerCtxt = testCtxt->Create<void>(); auto middleCtxt = outerCtxt->Create<void>(); auto innerCtxt = middleCtxt->Create<void>(); outerCtxt->Initiate(); middleCtxt->Initiate(); innerCtxt->Initiate(); ASSERT_TRUE(outerCtxt->IsRunning()) << "Context not running after begin initiated"; ASSERT_TRUE(middleCtxt->IsRunning()) << "Context not running after begin initiated"; ASSERT_TRUE(innerCtxt->IsRunning()) << "Context not running after begin initiated"; outerCtxt->SignalShutdown(true); } // Initiate middle, inner, then outer { auto outerCtxt = testCtxt->Create<void>(); auto middleCtxt = outerCtxt->Create<void>(); auto innerCtxt = middleCtxt->Create<void>(); middleCtxt->Initiate(); innerCtxt->Initiate(); outerCtxt->Initiate(); ASSERT_TRUE(outerCtxt->IsRunning()) << "Context not running after begin initiated"; ASSERT_TRUE(middleCtxt->IsRunning()) << "Context not running after begin initiated"; ASSERT_TRUE(innerCtxt->IsRunning()) << "Context not running after begin initiated"; outerCtxt->SignalShutdown(true); } }
Benchmark PriorityBoost::CanBoostPriority(void) { AutoCurrentContext ctxt; // Create two spinners and kick them off at the same time: AutoRequired<JustIncrementsANumber<ThreadPriority::BelowNormal>> lower; AutoRequired<JustIncrementsANumber<ThreadPriority::Normal>> higher; ctxt->Initiate(); #ifdef _MSC_VER // We want all of our threads to run on ONE cpu for awhile, and then we want to put it back at exit DWORD_PTR originalAffinity, systemAffinity; GetProcessAffinityMask(GetCurrentProcess(), &originalAffinity, &systemAffinity); SetProcessAffinityMask(GetCurrentProcess(), 1); auto onreturn = MakeAtExit([originalAffinity] { SetProcessAffinityMask(GetCurrentProcess(), originalAffinity); }); #else // TODO: Implement on Unix so that this benchmark is trustworthy #endif // Poke the conditional variable a lot: AutoRequired<std::mutex> contended; for(size_t i = 100; i--;) { // We sleep while holding contention lock to force waiting threads into the sleep queue. The reason we have to do // this is due to the way that mutex is implemented under the hood. The STL mutex uses a high-frequency variable // and attempts to perform a CAS (check-and-set) on this variable. If it succeeds, the lock is obtained; if it // fails, it will put the thread into a non-ready state by calling WaitForSingleObject on Windows or one of the // mutex_lock methods on Unix. // // When a thread can't be run, it's moved from the OS's ready queue to the sleep queue. The scheduler knows that // the thread can be moved back to the ready queue if a particular object is signalled, but in the case of a lock, // only one of the threads waiting on the object can actually be moved to the ready queue. It's at THIS POINT that // the operating system consults the thread priority--if only thread can be moved over, then the highest priority // thread will wind up in the ready queue every time. // // Thread priority does _not_ necessarily influence the amount of time the scheduler allocates allocated to a ready // thread with respect to other threads of the same process. This is why we hold the lock for a full millisecond, // in order to force the thread over to the sleep queue and ensure that the priority resolution mechanism is // directly tested. std::lock_guard<std::mutex> lk(*contended); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // Need to terminate before we try running a comparison. ctxt->SignalTerminate(); return Benchmark { {"Low priority CPU time", std::chrono::nanoseconds{lower->val}}, {"High priority CPU time", std::chrono::nanoseconds{higher->val}}, }; }
TEST_F(CoreContextTest, ChildContextSignalOrder) { AutoCurrentContext ctxt; AutoCreateContext childCtxt; AutoRequired<ChildListener> cl(childCtxt); childCtxt->Initiate(); // Now verify correct ordering: bool gotStartObservation = false; childCtxt->onRunning += [&] { gotStartObservation = cl->gotStart; }; ctxt->Initiate(); ASSERT_TRUE(gotStartObservation) << "Start observation obtained"; }
TEST_F(AutoPacketFactoryTest, MultipleInstanceAddition) { AutoCurrentContext ctxt; AutoRequired<AutoPacketFactory> factory; ctxt->Initiate(); bool ary[2] = {}; for (size_t i = 0; i < 2; i++) *factory += [i, &ary] (int) { ary[i] = true; }; auto packet = factory->NewPacket(); packet->Decorate(101); ASSERT_TRUE(ary[0]) << "First of two identically typed AutoFilter lambdas was not called"; ASSERT_TRUE(ary[1]) << "Second of two identically typed AutoFilter lambdas was not called"; }
TEST_F(CoreContextTest, InitiateAssertsSignals) { AutoCurrentContext outer; auto teardown = std::make_shared<bool>(false); { AutoCreateContext ctxt; auto initiated = std::make_shared<bool>(false); auto running = std::make_shared<bool>(false); auto shutdown = std::make_shared<bool>(false); ctxt->onInitiated += [initiated] { *initiated = true; }; ctxt->onRunning += [running] { *running = true; }; ctxt->onShutdown += [shutdown] { *shutdown = true; }; ctxt->onTeardown += [teardown] (const CoreContext&) { *teardown = true; }; ctxt->Initiate(); ASSERT_TRUE(*initiated) << "Initiation signal not asserted on context startup"; ASSERT_FALSE(*running) << "Running signal asserted before the outer context was started"; ASSERT_FALSE(*shutdown) << "Termination signal asserted prematurely"; *initiated = false; outer->Initiate(); ASSERT_FALSE(*initiated) << "Initiation signal was redundantly asserted"; ASSERT_TRUE(*running) << "Running signal not asserted when the outer context was started"; ASSERT_FALSE(*shutdown) << "Termination signal asserted prematurely"; *running = false; ctxt->Initiate(); ASSERT_FALSE(*initiated) << "Initiation signal redundantly asserted"; ASSERT_FALSE(*running) << "Running signal redundantly asserted"; ASSERT_FALSE(*shutdown) << "Termination signal asserted unexpectedly"; ctxt->SignalShutdown(); ASSERT_FALSE(*initiated) << "Initiation signal not asserted during teardown"; ASSERT_FALSE(*running) << "Running signal asserted improperly on teardown"; ASSERT_TRUE(*shutdown) << "Termination signal not asserted as expected"; ASSERT_FALSE(*teardown) << "Teardown handler notified prematurely"; } ASSERT_TRUE(*teardown) << "Teardown handler not correctly notified on context teardown"; }
TEST_F(CoreContextTest, AppropriateShutdownInterleave) { // Need both an outer and an inner context AutoCurrentContext ctxtOuter; AutoCreateContext ctxtInner; // Need to inject types at both scopes AutoRequired<ExplicitlyHoldsOutstandingCount> outer(ctxtOuter); AutoRequired<ExplicitlyHoldsOutstandingCount> inner(ctxtInner); // Start both contexts up ctxtOuter->Initiate(); ctxtInner->Initiate(); // Now shut down the outer context. Hand off to an async, we want this to block. std::thread holder{ [ctxtOuter] { ctxtOuter->SignalShutdown(true); } }; auto holderClean = MakeAtExit([&holder] { holder.join(); }); // Need to ensure that both outstanding counters are reset at some point: { auto cleanup = MakeAtExit([&] { outer->Proceed(); inner->Proceed(); }); // Outer entry should have called "stop": auto future = outer->calledStop.get_future(); ASSERT_EQ( std::future_status::ready, future.wait_for(std::chrono::seconds(5)) ) << "Outer scope's OnStop method was incorrectly blocked by a child context member taking a long time to shut down"; } // Both contexts should be stopped now: ASSERT_TRUE(ctxtOuter->Wait(std::chrono::seconds(5))) << "Outer context did not tear down in a timely fashion"; ASSERT_TRUE(ctxtOuter->IsQuiescent()) << "Quiescence not achieved by outer context after shutdown"; ASSERT_TRUE(ctxtInner->Wait(std::chrono::seconds(5))) << "Inner context did not tear down in a timely fashion"; }
TEST_F(CoreContextTest, InitiateMultipleChildren) { AutoCurrentContext testCtxt; testCtxt->Initiate(); // Initiate all children { auto outerCtxt = testCtxt->Create<void>(); auto child1 = outerCtxt->Create<void>(); auto child2 = outerCtxt->Create<void>(); auto child3 = outerCtxt->Create<void>(); child1->Initiate(); child2->Initiate(); child3->Initiate(); outerCtxt->Initiate(); ASSERT_TRUE(child1->IsRunning()); ASSERT_TRUE(child2->IsRunning()); ASSERT_TRUE(child3->IsRunning()); outerCtxt->SignalShutdown(true); } // Don't initiate middle child { auto outerCtxt = testCtxt->Create<void>(); auto child1 = outerCtxt->Create<void>(); auto child2 = outerCtxt->Create<void>(); auto child3 = outerCtxt->Create<void>(); child1->Initiate(); child3->Initiate(); outerCtxt->Initiate(); ASSERT_TRUE(child1->IsRunning()); ASSERT_FALSE(child2->IsInitiated()); ASSERT_TRUE(child3->IsRunning()); outerCtxt->SignalShutdown(true); } // Don't initiate middle child and initiate parent first { auto outerCtxt = testCtxt->Create<void>(); auto child1 = outerCtxt->Create<void>(); auto child2 = outerCtxt->Create<void>(); auto child3 = outerCtxt->Create<void>(); outerCtxt->Initiate(); child1->Initiate(); child3->Initiate(); ASSERT_TRUE(child1->IsRunning()); ASSERT_FALSE(child2->IsInitiated()); ASSERT_TRUE(child3->IsRunning()); outerCtxt->SignalShutdown(true); } }