diff --git a/doc/modules/ROOT/pages/8.examples/8n.custom-executor.adoc b/doc/modules/ROOT/pages/8.examples/8n.custom-executor.adoc index 719772f61..76b0be567 100644 --- a/doc/modules/ROOT/pages/8.examples/8n.custom-executor.adoc +++ b/doc/modules/ROOT/pages/8.examples/8n.custom-executor.adoc @@ -33,7 +33,7 @@ target_link_libraries(custom_executor PRIVATE Boost::capy) include::example$custom-executor/custom_executor.cpp[tag=inherit] ---- -The destructor must call `shutdown()`, give up any work still queued with cpp:abandon[], then call `destroy()`. Destroying a service can post more work, and unwinding an abandoned chain can create a service, so the destructor repeats `destroy()` and the drain until a drain finds nothing. The queue holds copies of each cpp:continuation[], so the owner that cpp:abandon[] needs travels with the handle. +The destructor must call `shutdown()`, give up any work still queued with cpp:abandon[], then call `destroy()`. The drain also takes work posted while it runs. That includes the work of a service an unwound chain creates, which is shut down as it is created. Services give up their work in `shutdown()`, so destroying them posts nothing. The destructor drains once more for stray work a destructor posts anyway. No service may be created after `destroy()`. The queue holds copies of each cpp:continuation[], so the owner that cpp:abandon[] needs travels with the handle. === The Executor Concept diff --git a/example/custom-executor/custom_executor.cpp b/example/custom-executor/custom_executor.cpp index 957bcdfc8..af5a1247c 100644 --- a/example/custom-executor/custom_executor.cpp +++ b/example/custom-executor/custom_executor.cpp @@ -52,25 +52,19 @@ class run_loop : public capy::execution_context // are unwound. An unwind posted here is given up in turn. auto drain = [this] { - bool any = false; while (!queue_.empty()) { auto c = queue_.front(); queue_.pop(); - any = true; capy::abandon(c); } - return any; }; shutdown(); drain(); - // A service's destructor may post too: repeat until nothing - // is left of either. - do - { - destroy(); - } - while (drain()); + destroy(); + // Services give up their work in shutdown(), but anything a + // destructor posts anyway is still given up. + drain(); } run_loop(run_loop const&) = delete; diff --git a/example/gui-integration/gui_integration.cpp b/example/gui-integration/gui_integration.cpp index bb5cc1f3e..d52fed003 100644 --- a/example/gui-integration/gui_integration.cpp +++ b/example/gui-integration/gui_integration.cpp @@ -89,30 +89,25 @@ class gui_app : public capy::execution_context // are unwound. An unwind posted here is given up in turn. auto drain = [this] { - bool any = false; for(;;) { capy::continuation c; { std::lock_guard lock(m_); if(queue_.empty()) - return any; + return; c = queue_.front(); queue_.pop_front(); } - any = true; capy::abandon(c); } }; shutdown(); drain(); - // A service's destructor may post too: repeat until nothing - // is left of either. - do - { - destroy(); - } - while(drain()); + destroy(); + // Services give up their work in shutdown(), but anything a + // destructor posts anyway is still given up. + drain(); } gui_app(gui_app const&) = delete; diff --git a/include/boost/capy/ex/execution_context.hpp b/include/boost/capy/ex/execution_context.hpp index b36dc78c0..3cca6cea3 100644 --- a/include/boost/capy/ex/execution_context.hpp +++ b/include/boost/capy/ex/execution_context.hpp @@ -38,10 +38,12 @@ namespace capy { execution facilities like event loops and thread pools. Derived class destructors must call `shutdown()` and `destroy()` to ensure proper service cleanup before member destruction. Between the two, - a context that queues work abandons what is still queued. Since a - service's destructor can post and an unwound chain can create a - service, it then repeats `destroy()` and that drain until the - drain finds nothing. + a context that queues work abandons what is still queued, including + work posted while it does so. A service created by an unwound chain + is shut down as it is created, so its work is taken too. Services + do not post from their destructors. A context may drain once more + after `destroy()`, so that stray work is still destroyed, but no + service may be created after `destroy()` returns. @par Service Lifecycle Services are created on first use via `use_service()` or explicitly @@ -106,8 +108,14 @@ class BOOST_CAPY_DECL Derived classes must implement the pure virtual `shutdown()` member function, which is called when the owning execution context is - being destroyed. The `shutdown()` function should release resources - and cancel outstanding operations without blocking. + being destroyed. The `shutdown()` function gives up all of the + service's work, releases resources and cancels outstanding + operations. It does not block, except to wait for work the + service already started, such as its own threads or operations + it submitted to the system, so that nothing that work posts + arrives later. The + destructor must not post: by the time it runs, the context has + already drained its queue. @par Deriving from service @li Implement `shutdown()` to perform cleanup. @@ -133,10 +141,13 @@ class BOOST_CAPY_DECL /** Called when the owning execution context shuts down. - Implementations should release resources and cancel any - outstanding asynchronous operations. This function must - not block and must not throw exceptions. Services are - shut down in reverse order of creation. + Implementations give up all of the service's work here, + release resources and cancel any outstanding asynchronous + operations, rather than in the destructor. This function + must not throw exceptions, and must not block except to + wait for work the service already started, such as threads + it owns. Services are shut down in + reverse order of creation. @par Exception Safety No-throw guarantee. @@ -472,8 +483,9 @@ class BOOST_CAPY_DECL /** Destroy all services. Deletes all services in reverse order of creation. Derived - classes should call this as the final step of destruction. - This function is idempotent; subsequent calls have no effect. + classes call this after giving up the work still queued, so + that work is destroyed while every service exists. This + function is idempotent; subsequent calls have no effect. @par Preconditions @li `shutdown()` was called. @@ -496,6 +508,11 @@ class BOOST_CAPY_DECL @par Thread Safety Not thread-safe. Must not be called concurrently with other operations on this execution_context. + + @note No service may be created after this returns. It would + outlive the derived context's members, which its destructor + may still reach. Debug builds check this when the context is + destroyed. */ void destroy() noexcept; @@ -536,6 +553,7 @@ class BOOST_CAPY_DECL std::pmr::memory_resource* frame_alloc_ = nullptr; service* head_ = nullptr; bool shutdown_ = false; + bool destroyed_ = false; }; template< typename Derived > diff --git a/include/boost/capy/ex/thread_pool.hpp b/include/boost/capy/ex/thread_pool.hpp index e5a905caf..4dc84b317 100644 --- a/include/boost/capy/ex/thread_pool.hpp +++ b/include/boost/capy/ex/thread_pool.hpp @@ -61,7 +61,9 @@ class BOOST_CAPY_DECL (or to a strand built on it) concurrently with, or after, destruction. Doing so is undefined behavior. Submit such work through @ref run_async or @ref run and call @ref join before - the pool is destroyed, so it has completed first. + the pool is destroyed, so it has completed first. A thread + owned by one of this pool's services may post or dispatch + until that service's `shutdown()` returns. */ ~thread_pool(); diff --git a/src/ex/execution_context.cpp b/src/ex/execution_context.cpp index 2a67b8fe8..bed8f02d2 100644 --- a/src/ex/execution_context.cpp +++ b/src/ex/execution_context.cpp @@ -23,6 +23,10 @@ execution_context() execution_context:: ~execution_context() { + // A service created after a derived destructor destroyed the + // services would be destroyed here, after the derived members it + // may still reach. + BOOST_CAPY_ASSERT(!destroyed_ || !head_); shutdown(); destroy(); } @@ -54,6 +58,7 @@ destroy() noexcept head_ = p->next_; delete p; } + destroyed_ = true; } execution_context::service* diff --git a/src/ex/thread_pool.cpp b/src/ex/thread_pool.cpp index 68cdf1df7..4528c7d19 100644 --- a/src/ex/thread_pool.cpp +++ b/src/ex/thread_pool.cpp @@ -115,16 +115,12 @@ class thread_pool::impl return current_.get() == this; } - // Abandon queued coroutines. Called after the services shut down, - // since their shutdown can post more, and again after they are - // destroyed, for anything their destructors post. Each is taken - // under the lock, as other threads may still post, and abandoned - // outside it, as abandoning can post. Returns whether there was - // anything to abandon. - bool + // Abandon queued coroutines. Each is taken under the lock, as other + // threads may still post, and abandoned outside it, as abandoning + // can post. + void drain_abandoned() noexcept { - bool any = false; for(;;) { continuation* c; @@ -133,8 +129,7 @@ class thread_pool::impl c = pop(); } if(!c) - return any; - any = true; + return; if(c->h && c->h != std::noop_coroutine()) capy::abandon(*c); } @@ -302,14 +297,13 @@ thread_pool:: impl_->stop(); impl_->join(); shutdown(); + // Takes what the unwinds post too, and the work of any service they + // create, which is shut down as it is created. + impl_->drain_abandoned(); + destroy(); + // Services give up their work in shutdown(), but anything a + // destructor posts anyway is still destroyed. impl_->drain_abandoned(); - // A frame the drain unwinds can create a service, whose destructor - // can post again: repeat until nothing is left of either. - do - { - destroy(); - } - while(impl_->drain_abandoned()); delete impl_; } diff --git a/src/test/run_blocking.cpp b/src/test/run_blocking.cpp index fda50d140..88d03713a 100644 --- a/src/test/run_blocking.cpp +++ b/src/test/run_blocking.cpp @@ -46,36 +46,32 @@ blocking_context::blocking_context() blocking_context::~blocking_context() { - // Nothing will run these now; abandon them as their holder. The - // services may post while they shut down or are destroyed, so the - // queue outlives both. Each is taken under the lock, as other - // threads may still post, and abandoned outside it. + // Nothing will run these now; abandon them as their holder. Each is + // taken under the lock and abandoned outside it, since abandoning + // one may post another. auto drain = [this] { - bool any = false; for(;;) { continuation c; { std::lock_guard lock(impl_->mtx); if(impl_->queue.empty()) - return any; + return; c = impl_->queue.front(); impl_->queue.pop(); } - any = true; capy::abandon(c); } }; shutdown(); + // Takes what the unwinds post too, and the work of any service they + // create, which is shut down as it is created. + drain(); + destroy(); + // Services give up their work in shutdown(), but anything a + // destructor posts anyway is still destroyed. drain(); - // A frame the drain unwinds can create a service, whose destructor - // can post again: repeat until nothing is left of either. - do - { - destroy(); - } - while(drain()); delete impl_; } diff --git a/test/unit/death_test.hpp b/test/unit/death_test.hpp index bbb4a2244..c59ea3bbd 100644 --- a/test/unit/death_test.hpp +++ b/test/unit/death_test.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -70,6 +71,15 @@ terminates(F fn) return WIFEXITED(status) && WEXITSTATUS(status) == terminated_status; } +/// Return true when `fn` aborts, as a failed assertion does. +template +bool +aborts(F fn) +{ + int status = run_in_child(fn); + return WIFSIGNALED(status) && WTERMSIG(status) == SIGABRT; +} + /// Return true when `fn` returns, and returns true if it returns a bool. template bool diff --git a/test/unit/ex/execution_context.cpp b/test/unit/ex/execution_context.cpp index 040ff74b6..adeef0ab4 100644 --- a/test/unit/ex/execution_context.cpp +++ b/test/unit/ex/execution_context.cpp @@ -11,6 +11,7 @@ #include #include +#include "death_test.hpp" #include "test_helpers.hpp" #include @@ -519,6 +520,28 @@ struct execution_context_test BOOST_TEST_NE(new_mr, default_mr); } +#if !defined(_WIN32) && !defined(NDEBUG) + // Once a context has destroyed its services, a service created + // later would outlive the derived context's members, so a debug + // build stops the program. + void + testServiceCreatedAfterDestroyAsserts() + { + struct late_creating_context : execution_context + { + ~late_creating_context() + { + shutdown(); + destroy(); + make_service(); + } + }; + BOOST_TEST(aborts([] { + late_creating_context ctx; + })); + } +#endif + void run() { @@ -542,6 +565,9 @@ struct execution_context_test testGetFrameAllocator(); testSetFrameAllocatorRawPointer(); testSetFrameAllocatorTemplate(); +#if !defined(_WIN32) && !defined(NDEBUG) + testServiceCreatedAfterDestroyAsserts(); +#endif } }; diff --git a/test/unit/ex/run_blocking.cpp b/test/unit/ex/run_blocking.cpp index b72975ba7..3aee026d3 100644 --- a/test/unit/ex/run_blocking.cpp +++ b/test/unit/ex/run_blocking.cpp @@ -105,13 +105,12 @@ struct dtor_poster : execution_context::service void shutdown() override {} }; -// Starts posting from another thread when the context's services shut -// down, while the context drains, and joins that thread when destroyed. +// Posts from a thread of its own while the context's services shut down, +// and joins that thread before its shutdown() returns. struct shutdown_poster : execution_context::service { blocking_executor ex_; std::vector* cs_; - std::thread t_; shutdown_poster( execution_context&, @@ -122,18 +121,14 @@ struct shutdown_poster : execution_context::service { } - ~shutdown_poster() - { - t_.join(); - } - void shutdown() override { - t_ = std::thread([this] + std::thread t([this] { for(auto& c : *cs_) ex_.post(c); }); + t.join(); } }; @@ -158,13 +153,54 @@ struct shutdown_dispatcher : execution_context::service } }; -// A second dtor_poster, so one can be created while the first exists. -struct late_dtor_poster : dtor_poster +// Posts when the context's services shut down, as a service giving up +// its work would. +struct late_poster : execution_context::service { - using dtor_poster::dtor_poster; + blocking_executor ex_; + continuation* c_; + + late_poster(execution_context&, blocking_executor ex, continuation& c) + : ex_(ex) + , c_(&c) + { + } + + void shutdown() override + { + ex_.post(*c_); + } +}; + +// A second late_poster, so one can be created while the first exists. +struct later_poster : late_poster +{ + using late_poster::late_poster; +}; + +// Records, when the context destroys it, how many probes were already +// destroyed. Made after the services the test creates, it is destroyed +// before them. +struct teardown_witness : execution_context::service +{ + int const* down_; + int* seen_; + + teardown_witness(execution_context&, int const& down, int& seen) + : down_(&down) + , seen_(&seen) + { + } + + ~teardown_witness() + { + *seen_ = *down_; + } + + void shutdown() override {} }; -// Creates a late_dtor_poster on the context when destroyed. +// Creates a later_poster on the context when destroyed. struct poster_maker { blocking_context* ctx; @@ -185,7 +221,7 @@ struct poster_maker ~poster_maker() { if(ctx) - ctx->make_service(ctx->get_executor(), *c); + ctx->make_service(ctx->get_executor(), *c); } }; @@ -470,12 +506,12 @@ struct run_blocking_test BOOST_TEST_EQ(down, 1); } - // Work posted from another thread while the context drains is - // neither lost nor torn from the queue. + // Work a service's own thread posts while the service shuts down is + // destroyed with the context, none of it lost. void - testPostDuringDrain() + testPostFromServiceThreadAtShutdown() { - constexpr int n = 2000; + constexpr int n = 16; int down = 0; auto probe = [](destroy_count_t) -> task { co_return; }; std::vector cs; @@ -541,7 +577,8 @@ struct run_blocking_test BOOST_TEST_EQ(down, 1); } - // Work posted while the services are destroyed is destroyed too. + // A service should not post from its destructor, but work one posts + // anyway is still destroyed with the context. void testPostFromServiceDestructor() { @@ -556,12 +593,15 @@ struct run_blocking_test BOOST_TEST_EQ(down, 1); } - // A frame destroyed by the context's last drain may create a service - // whose destructor posts again; that work is destroyed too. + // A frame destroyed while the context shuts down may create a service. + // That service is shut down as it is created, and the drain destroys + // the work it gives up before the services made before the teardown + // are destroyed. void - testServiceCreatedByLastDrainDestroyed() + testServiceCreatedDuringDrain() { int down = 0; + int seen = -1; auto maker = [](poster_maker) -> task { co_return; }; auto t = plain_probe(destroy_count_t(down)); continuation probe_c{t.handle()}; @@ -572,8 +612,10 @@ struct run_blocking_test auto m = maker(poster_maker(ctx, probe_c)); maker_c.h = m.handle(); m.release(); - ctx.make_service(ctx.get_executor(), maker_c); + ctx.make_service(ctx.get_executor(), maker_c); + ctx.make_service(down, seen); } + BOOST_TEST_EQ(seen, 1); BOOST_TEST_EQ(down, 1); } @@ -643,10 +685,10 @@ struct run_blocking_test testDestroyedContextDestroysQueued(); testDispatchDuringShutdown(); testAbandonedChainThrows(); - testPostDuringDrain(); + testPostFromServiceThreadAtShutdown(); testPostDuringServiceShutdown(); testPostFromServiceDestructor(); - testServiceCreatedByLastDrainDestroyed(); + testServiceCreatedDuringDrain(); } }; diff --git a/test/unit/ex/strand.cpp b/test/unit/ex/strand.cpp index e81381195..b0e3d0cab 100644 --- a/test/unit/ex/strand.cpp +++ b/test/unit/ex/strand.cpp @@ -1067,6 +1067,44 @@ struct strand_test BOOST_TEST_EQ(d, 1); } + // The strand service gives up every strand's queued work in its + // shutdown(), so a strand dropped afterwards has nothing left to + // give up, and destroying the services posts nothing. + void + testShutdownLeavesNothingForStrandDestructor() + { + int down[2] = {}; + continuation conts[2] = {}; + std::queue q; + auto probe = [](destroy_count_t) -> task { co_return; }; + for(std::size_t i = 0; i < 2; ++i) + { + auto t = probe(destroy_count_t(down[i])); + conts[i].h = t.handle(); + t.release(); + } + auto ctx = std::make_unique(); + queuing_executor qe(q); + qe.ctx_ = ctx.get(); + auto s = std::make_unique>(qe); + s->post(conts[0]); + s->post(conts[1]); + BOOST_TEST_EQ(q.size(), 1u); + capy::abandon(q.front()); + q.pop(); + for(int d : down) + BOOST_TEST_EQ(d, 0); + + ctx->shutdown(); + for(int d : down) + BOOST_TEST_EQ(d, 1); + s.reset(); + ctx.reset(); + for(int d : down) + BOOST_TEST_EQ(d, 1); + BOOST_TEST(q.empty()); + } + void testShutdownReleasesLiveStrand() { @@ -1571,6 +1609,7 @@ struct strand_test run() { testShutdownDestroysQueuedAcrossStrands(); + testShutdownLeavesNothingForStrandDestructor(); testShutdownDestroysQueuedParentedChain(); testShutdownReleasesLiveStrand(); testConstruct(); diff --git a/test/unit/ex/thread_pool.cpp b/test/unit/ex/thread_pool.cpp index 1a9b4659e..8c573f681 100644 --- a/test/unit/ex/thread_pool.cpp +++ b/test/unit/ex/thread_pool.cpp @@ -18,6 +18,7 @@ #include #include +#include "death_test.hpp" #include "test_helpers.hpp" #include @@ -189,13 +190,12 @@ struct dtor_poster : execution_context::service void shutdown() override {} }; -// Starts posting from another thread when the pool's services shut -// down, while the pool drains, and joins that thread when destroyed. +// Posts from a thread of its own while the pool's services shut down, +// and joins that thread before its shutdown() returns. struct shutdown_poster : execution_context::service { thread_pool::executor_type ex_; std::vector* cs_; - std::thread t_; shutdown_poster( execution_context&, @@ -206,28 +206,46 @@ struct shutdown_poster : execution_context::service { } - ~shutdown_poster() - { - t_.join(); - } - void shutdown() override { - t_ = std::thread([this] + std::thread t([this] { for(auto& c : *cs_) ex_.post(c); }); + t.join(); } }; -// A second dtor_poster, so one can be created while the first exists. -struct late_dtor_poster : dtor_poster +// A second late_poster, so one can be created while the first exists. +struct later_poster : late_poster { - using dtor_poster::dtor_poster; + using late_poster::late_poster; }; -// Creates a late_dtor_poster on the pool when destroyed. +// Records, when the pool destroys it, how many probes were already +// destroyed. Made after the services the test creates, it is destroyed +// before them. +struct teardown_witness : execution_context::service +{ + int const* down_; + int* seen_; + + teardown_witness(execution_context&, int const& down, int& seen) + : down_(&down) + , seen_(&seen) + { + } + + ~teardown_witness() + { + *seen_ = *down_; + } + + void shutdown() override {} +}; + +// Creates a later_poster on the pool when destroyed. struct poster_maker { thread_pool* pool; @@ -248,10 +266,37 @@ struct poster_maker ~poster_maker() { if(pool) - pool->make_service(pool->get_executor(), *c); + pool->make_service(pool->get_executor(), *c); } }; +#if !defined(_WIN32) && !defined(NDEBUG) +// Creates a dtor_poster on the pool when destroyed. +struct dtor_poster_maker +{ + thread_pool* pool; + continuation* c; + + dtor_poster_maker(thread_pool& p, continuation& cont) noexcept + : pool(&p) + , c(&cont) + { + } + + dtor_poster_maker(dtor_poster_maker&& o) noexcept + : pool(std::exchange(o.pool, nullptr)) + , c(o.c) + { + } + + ~dtor_poster_maker() + { + if(pool) + pool->make_service(pool->get_executor(), *c); + } +}; +#endif + // Parks the awaiting coroutine in a slot and signals that it did. struct signal_park { @@ -1026,12 +1071,15 @@ struct thread_pool_test BOOST_TEST(st.balanced()); } - // A frame destroyed by the pool's last drain may create a service - // whose destructor posts again; that work is destroyed too. + // A frame destroyed while the pool shuts down may create a service. + // That service is shut down as it is created, and the drain destroys + // the work it gives up before the services made before the teardown + // are destroyed. void - testServiceCreatedByLastDrainDestroyed() + testServiceCreatedDuringDrain() { int down = 0; + int seen = -1; auto probe = [](destroy_count_t) -> task { co_return; }; auto maker = [](poster_maker) -> task { co_return; }; auto p = probe(destroy_count_t(down)); @@ -1043,11 +1091,38 @@ struct thread_pool_test auto m = maker(poster_maker(pool, probe_c)); maker_c.h = m.handle(); m.release(); - pool.make_service(pool.get_executor(), maker_c); + pool.make_service(pool.get_executor(), maker_c); + pool.make_service(down, seen); } + BOOST_TEST_EQ(seen, 1); BOOST_TEST_EQ(down, 1); } +#if !defined(_WIN32) && !defined(NDEBUG) + // A service whose destructor posts breaks the contract, and the work + // it posts is still destroyed. If destroying that work creates a + // service whose destructor posts too, that service would outlive the + // pool's queue, so a debug build stops the program instead. + void + testServiceCreatedAfterDestroyAsserts() + { + BOOST_TEST(aborts([] { + auto probe = [](destroy_count_t) -> task { co_return; }; + auto maker = [](dtor_poster_maker) -> task { co_return; }; + int down = 0; + auto p = probe(destroy_count_t(down)); + continuation probe_c{p.handle()}; + p.release(); + continuation maker_c; + thread_pool pool(1); + auto m = maker(dtor_poster_maker(pool, probe_c)); + maker_c.h = m.handle(); + m.release(); + pool.make_service(pool.get_executor(), maker_c); + })); + } +#endif + // A started chain whose leaf is queued when the pool is destroyed // comes down there, on another thread than its child was made on, // with every frame freed. @@ -1103,8 +1178,8 @@ struct thread_pool_test BOOST_TEST_EQ(down, 1); } - // Work posted while the pool's services are destroyed is destroyed - // with the pool. + // A service should not post from its destructor, but work one posts + // anyway is still destroyed with the pool. void testPostFromServiceDestructorDestroyed() { @@ -1120,12 +1195,12 @@ struct thread_pool_test BOOST_TEST_EQ(down, 1); } - // Work posted from another thread while the pool drains is neither - // lost nor torn from the queue. + // Work a service's own thread posts while the service shuts down is + // destroyed with the pool, none of it lost. void - testPostDuringDrain() + testPostFromServiceThreadAtShutdown() { - constexpr int n = 2000; + constexpr int n = 16; int down = 0; auto probe = [](destroy_count_t) -> task { co_return; }; std::vector cs; @@ -1175,9 +1250,12 @@ struct thread_pool_test testForeignPostAfterJoin(); testPostDuringShutdownDestroyed(); testPostFromServiceDestructorDestroyed(); - testPostDuringDrain(); + testPostFromServiceThreadAtShutdown(); testDestroyDrainsParentedChain(); - testServiceCreatedByLastDrainDestroyed(); + testServiceCreatedDuringDrain(); +#if !defined(_WIN32) && !defined(NDEBUG) + testServiceCreatedAfterDestroyAsserts(); +#endif testTeardownFromAnotherThreadWithLiveRoot(); testJoinRacesFirstPost(); testJoinWaitingThenFirstPost();