This is an automated email from the ASF dual-hosted git repository.

chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new 8ef336ea Stabilize timing-sensitive bthread tests and fix the butex 
interruption race (#3545)
8ef336ea is described below

commit 8ef336ea6cefcf16d4b1264f1f70af2cc677846b
Author: Bright Chen <[email protected]>
AuthorDate: Thu Sep 24 17:53:40 2026 +0800

    Stabilize timing-sensitive bthread tests and fix the butex interruption 
race (#3545)
    
    * Stabilize timing-sensitive bthread tests and fix the butex interruption 
race
    
    * Fix pthread_kill get ESRCH
    
    * Use a non-fatal assertion
    
    * Fix some issues
    
    * Check and set task->interrupted under task->version_lock
    
    * Fix butex_requeue UT
---
 src/bthread/butex.cpp                         |  12 +-
 test/brpc_socket_unittest.cpp                 |  19 +-
 test/bthread_butex_multi_tag_unittest.cpp     |  63 ++++-
 test/bthread_butex_unittest.cpp               | 387 +++++++++++++++++---------
 test/bthread_cond_unittest.cpp                |   2 +-
 test/bthread_fd_unittest.cpp                  | 250 ++++++++++-------
 test/bthread_futex_unittest.cpp               | 117 ++++++--
 test/bthread_mutex_unittest.cpp               |  10 +-
 test/bthread_rwlock_unittest.cpp              |  83 +++---
 test/bthread_timer_thread_unittest.cpp        | 128 ++++++---
 test/bthread_unittest.cpp                     |  26 +-
 test/bthread_work_stealing_queue_unittest.cpp |   3 +-
 12 files changed, 735 insertions(+), 365 deletions(-)

diff --git a/src/bthread/butex.cpp b/src/bthread/butex.cpp
index 790da145..df0562bd 100644
--- a/src/bthread/butex.cpp
+++ b/src/bthread/butex.cpp
@@ -729,9 +729,19 @@ static int butex_wait_from_pthread(TaskGroup* g, Butex* b, 
int expected_value,
         BT_LOOP_WHEN(task->current_waiter.exchange(
                          nullptr, butil::memory_order_acquire) == nullptr,
                      30/*nops before sched_yield*/);
+        // TaskGroup::interrupt() updates `interrupted` under `version_lock`.
+        // Keep the check, clear, and signal classification together so this
+        // epilogue neither races with nor consumes a concurrent interruption.
+        BAIDU_SCOPED_LOCK(task->version_lock);
         if (task->interrupted) {
             task->interrupted = false;
-            if (rc == 0) {
+            // If interrupted after enqueueing but before futex_wait_private,
+            // pw.sig is already signalled and futex_wait_private may report
+            // EWOULDBLOCK. This is an interruption, not a value mismatch on
+            // the user's butex. Preserve other errors (notably ETIMEDOUT).
+            if (rc == 0 || (errno == EWOULDBLOCK &&
+                            pw.sig.load(butil::memory_order_acquire) ==
+                            PTHREAD_SIGNALLED)) {
                 errno = EINTR;
                 return -1;
             }
diff --git a/test/brpc_socket_unittest.cpp b/test/brpc_socket_unittest.cpp
index 30e4e763..6c3211c1 100644
--- a/test/brpc_socket_unittest.cpp
+++ b/test/brpc_socket_unittest.cpp
@@ -1819,15 +1819,20 @@ TEST_F(SocketTest, socket_buffer_options_before_accept) 
{
     brpc::SocketId id = brpc::INVALID_SOCKET_ID;
     ASSERT_EQ(0, brpc::Socket::Create(options, &id));
 
-    const int64_t start_time = butil::cpuwide_time_us();
-    while (messenger->ConnectionCount() < 1) {
+    // ConnectionCount includes reserved slots before the accepted sockets
+    // have been inserted into the connection map. Wait for publication, not
+    // just for the connection slot to be acquired.
+    std::vector<brpc::SocketId> connections;
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    for (;;) {
+        messenger->ListConnections(&connections);
+        if (!connections.empty()) {
+            break;
+        }
+        ASSERT_LT(butil::cpuwide_time_us(), deadline)
+            << "Timed out waiting for the accepted socket to be published";
         bthread_usleep(1000);
-        ASSERT_LT(butil::cpuwide_time_us(), start_time + 1000000L)
-            << "Too long!";
     }
-
-    std::vector<brpc::SocketId> connections;
-    messenger->ListConnections(&connections);
     ASSERT_EQ(1ul, connections.size());
 
     {
diff --git a/test/bthread_butex_multi_tag_unittest.cpp 
b/test/bthread_butex_multi_tag_unittest.cpp
index e41fefdd..31be2afe 100644
--- a/test/bthread_butex_multi_tag_unittest.cpp
+++ b/test/bthread_butex_multi_tag_unittest.cpp
@@ -21,6 +21,8 @@
 #include "bthread/condition_variable.h"
 #include "bthread/countdown_event.h"
 #include "bthread/mutex.h"
+#include "bthread/task_group.h"
+#include "bthread/task_meta.h"
 
 DECLARE_int32(task_group_ntags);
 
@@ -33,7 +35,18 @@ int main(int argc, char* argv[]) {
 
 namespace {
 
-std::vector<bthread_tag_t> butex_wake_return(2, 0);
+// Observe registration, not just arrival immediately before a blocking call.
+void WaitForWaiter(bthread_t tid) {
+    auto* meta = bthread::TaskGroup::address_meta(tid);
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    while (meta->current_waiter.load(butil::memory_order_acquire) == nullptr &&
+           butil::cpuwide_time_us() < deadline) {
+        bthread_usleep(1000);
+    }
+    ASSERT_NE(nullptr, meta->current_waiter.load(butil::memory_order_acquire));
+}
+
+std::vector<bthread_tag_t> butex_wake_return;
 
 void* butex_wake_func(void* arg) {
     auto mutex = static_cast<bthread::Mutex*>(arg);
@@ -45,19 +58,23 @@ void* butex_wake_func(void* arg) {
 }
 
 TEST(BthreadButexMultiTest, butex_wake) {
+    butex_wake_return.clear();
     bthread::Mutex mutex;
     mutex.lock();
     bthread_t tid1;
     bthread_attr_t attr = BTHREAD_ATTR_NORMAL;
     attr.tag = 1;
-    bthread_start_urgent(&tid1, &attr, butex_wake_func, &mutex);
+    ASSERT_EQ(0, bthread_start_urgent(&tid1, &attr, butex_wake_func, &mutex));
+    ASSERT_NO_FATAL_FAILURE(WaitForWaiter(tid1));
     mutex.unlock();
     bthread_join(tid1, nullptr);
-    ASSERT_EQ(butex_wake_return[0], butex_wake_return[1]);
+    ASSERT_EQ(2ul, butex_wake_return.size());
+    ASSERT_EQ(1, butex_wake_return[0]);
+    ASSERT_EQ(1, butex_wake_return[1]);
 }
 
-std::vector<bthread_tag_t> butex_wake_all_return1(2, 0);
-std::vector<bthread_tag_t> butex_wake_all_return2(2, 0);
+std::vector<bthread_tag_t> butex_wake_all_return1;
+std::vector<bthread_tag_t> butex_wake_all_return2;
 
 struct ButexWakeAllArgs {
     bthread::CountdownEvent* ev;
@@ -87,6 +104,8 @@ void* butex_wake_all_func2(void* arg) {
 }
 
 TEST(BthreadButexMultiTest, butex_wake_all) {
+    butex_wake_all_return1.clear();
+    butex_wake_all_return2.clear();
     bthread::CountdownEvent ev(2);
     bthread::CountdownEvent ack(2);
     ButexWakeAllArgs args{&ev, &ack};
@@ -98,15 +117,21 @@ TEST(BthreadButexMultiTest, butex_wake_all) {
     attr2.tag = 2;
     bthread_start_background(&tid2, &attr2, butex_wake_all_func2, &args);
     ack.wait();
+    ASSERT_NO_FATAL_FAILURE(WaitForWaiter(tid1));
+    ASSERT_NO_FATAL_FAILURE(WaitForWaiter(tid2));
     ev.signal(2);
     bthread_join(tid1, nullptr);
     bthread_join(tid2, nullptr);
-    ASSERT_EQ(butex_wake_all_return1[0], butex_wake_all_return1[1]);
-    ASSERT_EQ(butex_wake_all_return2[0], butex_wake_all_return2[1]);
+    ASSERT_EQ(2ul, butex_wake_all_return1.size());
+    ASSERT_EQ(2ul, butex_wake_all_return2.size());
+    ASSERT_EQ(1, butex_wake_all_return1[0]);
+    ASSERT_EQ(1, butex_wake_all_return1[1]);
+    ASSERT_EQ(2, butex_wake_all_return2[0]);
+    ASSERT_EQ(2, butex_wake_all_return2[1]);
 }
 
-std::vector<bthread_tag_t> butex_requeue_return1(2, 0);
-std::vector<bthread_tag_t> butex_requeue_return2(2, 0);
+std::vector<bthread_tag_t> butex_requeue_return1;
+std::vector<bthread_tag_t> butex_requeue_return2;
 
 struct ButexRequeueArgs {
     bthread::Mutex* mutex;
@@ -119,11 +144,11 @@ void* butex_requeue_func1(void* arg) {
     auto mutex = p->mutex;
     auto cond = p->cond;
     auto ack = p->ack;
-    butex_wake_all_return1.push_back(bthread_self_tag());
+    butex_requeue_return1.push_back(bthread_self_tag());
     std::unique_lock<bthread::Mutex> lk(*mutex);
     ack->signal();
     cond->wait(lk);
-    butex_wake_all_return1.push_back(bthread_self_tag());
+    butex_requeue_return1.push_back(bthread_self_tag());
     return nullptr;
 }
 
@@ -132,15 +157,17 @@ void* butex_requeue_func2(void* arg) {
     auto mutex = p->mutex;
     auto cond = p->cond;
     auto ack = p->ack;
-    butex_wake_all_return2.push_back(bthread_self_tag());
+    butex_requeue_return2.push_back(bthread_self_tag());
     std::unique_lock<bthread::Mutex> lk(*mutex);
     ack->signal();
     cond->wait(lk);
-    butex_wake_all_return2.push_back(bthread_self_tag());
+    butex_requeue_return2.push_back(bthread_self_tag());
     return nullptr;
 }
 
 TEST(BthreadButexMultiTest, butex_requeue) {
+    butex_requeue_return1.clear();
+    butex_requeue_return2.clear();
     bthread::Mutex mutex;
     bthread::ConditionVariable cond;
     bthread::CountdownEvent ack(2);
@@ -154,6 +181,8 @@ TEST(BthreadButexMultiTest, butex_requeue) {
     attr2.tag = 2;
     bthread_start_background(&tid2, &attr2, butex_requeue_func2, &args);
     ack.wait();
+    ASSERT_NO_FATAL_FAILURE(WaitForWaiter(tid1));
+    ASSERT_NO_FATAL_FAILURE(WaitForWaiter(tid2));
     {
         std::unique_lock<bthread::Mutex> lk(mutex);
         cond.notify_all();
@@ -164,8 +193,12 @@ TEST(BthreadButexMultiTest, butex_requeue) {
     }
     bthread_join(tid1, nullptr);
     bthread_join(tid2, nullptr);
-    ASSERT_EQ(butex_wake_all_return1[0], butex_wake_all_return1[1]);
-    ASSERT_EQ(butex_wake_all_return2[0], butex_wake_all_return2[1]);
+    ASSERT_EQ(2ul, butex_requeue_return1.size());
+    ASSERT_EQ(2ul, butex_requeue_return2.size());
+    ASSERT_EQ(1, butex_requeue_return1[0]);
+    ASSERT_EQ(1, butex_requeue_return1[1]);
+    ASSERT_EQ(2, butex_requeue_return2[0]);
+    ASSERT_EQ(2, butex_requeue_return2[1]);
 }
 
 }  // namespace
diff --git a/test/bthread_butex_unittest.cpp b/test/bthread_butex_unittest.cpp
index 924f30cb..a626e3b1 100644
--- a/test/bthread_butex_unittest.cpp
+++ b/test/bthread_butex_unittest.cpp
@@ -15,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <condition_variable>
+#include <mutex>
 #include <gtest/gtest.h>
 #include "butil/atomicops.h"
 #include "butil/time.h"
@@ -35,6 +37,7 @@ inline TaskControl* get_task_control() {
 } // namespace bthread
 
 namespace {
+
 TEST(ButexTest, wait_on_already_timedout_butex) {
     uint32_t* butex = bthread::butex_create_checked<uint32_t>();
     ASSERT_TRUE(butex);
@@ -43,6 +46,7 @@ TEST(ButexTest, wait_on_already_timedout_butex) {
     *butex = 1;
     ASSERT_EQ(-1, bthread::butex_wait(butex, 1, &now));
     ASSERT_EQ(ETIMEDOUT, errno);
+    bthread::butex_destroy(butex);
 }
 
 struct JoinSleepArg {
@@ -52,7 +56,14 @@ struct JoinSleepArg {
 };
 
 void* sleeper(void* arg) {
-    bthread_usleep((uint64_t)arg);
+    JoinSleepArg* a = static_cast<JoinSleepArg*>(arg);
+    butil::Timer tm;
+    tm.start();
+    EXPECT_EQ(0, bthread_usleep(a->sleep_us));
+    tm.stop();
+    // Sleep may finish late under load, but must not finish early.
+    EXPECT_GE(tm.u_elapsed(), static_cast<int64_t>(a->sleep_us));
+    a->finished.store(true, butil::memory_order_release);
     return nullptr;
 }
 
@@ -133,14 +144,17 @@ struct WaiterArg {
 
 void* waiter(void* arg) {
     WaiterArg * wa = (WaiterArg*)arg;
-    const long t1 = butil::gettimeofday_us();
-    const int rc = bthread::butex_wait(
-        wa->butex, wa->expected_value, wa->ptimeout);
-    const long t2 = butil::gettimeofday_us();
+    long t1 = butil::gettimeofday_us();
+    int rc = bthread::butex_wait(wa->butex, wa->expected_value, wa->ptimeout);
+    int saved_errno = errno;
+    long t2 = butil::gettimeofday_us();
     if (rc == 0) {
         EXPECT_EQ(wa->expected_result, 0) << bthread_self();
     } else {
-        EXPECT_EQ(wa->expected_result, errno) << bthread_self();
+        EXPECT_EQ(wa->expected_result, saved_errno) << bthread_self();
+        if (saved_errno == ETIMEDOUT && wa->ptimeout) {
+            EXPECT_GE(t2, butil::timespec_to_microseconds(*wa->ptimeout));
+        }
     }
     LOG(INFO) << "after wait, time=" << (t2-t1) << "us";
     return nullptr;
@@ -149,9 +163,9 @@ void* waiter(void* arg) {
 TEST(ButexTest, sanity) {
     const size_t N = 5;
     WaiterArg args[N * 4];
-    pthread_t t1, t2;
-    butil::atomic<int>* b1 =
-        bthread::butex_create_checked<butil::atomic<int> >();
+    pthread_t pthreads[2 * N];
+    bthread_t bthreads[2 * N];
+    butil::atomic<int>* b1 = bthread::butex_create_checked<butil::atomic<int> 
>();
     ASSERT_TRUE(b1);
     bthread::butex_destroy(b1);
     
@@ -159,17 +173,17 @@ TEST(ButexTest, sanity) {
     *b1 = 1;
     ASSERT_EQ(0, bthread::butex_wake(b1));
 
-    WaiterArg *unmatched_arg = new WaiterArg;
-    unmatched_arg->expected_value = *b1 + 1;
-    unmatched_arg->expected_result = EWOULDBLOCK;
-    unmatched_arg->butex = b1;
-    unmatched_arg->ptimeout = nullptr;
-    pthread_create(&t2, nullptr, waiter, unmatched_arg);
-    bthread_t th;
-    ASSERT_EQ(0, bthread_start_urgent(&th, nullptr, waiter, unmatched_arg));
-
-    const timespec abstime = butil::seconds_from_now(1);
-    for (size_t i = 0; i < 4*N; ++i) {
+    WaiterArg unmatched_arg = { EWOULDBLOCK, *b1 + 1, b1, nullptr };
+    pthread_t unmatched_pthread;
+    bthread_t unmatched_bthread;
+    ASSERT_EQ(0, pthread_create(&unmatched_pthread, nullptr, waiter, 
&unmatched_arg));
+    ASSERT_EQ(0, bthread_start_urgent(
+        &unmatched_bthread, nullptr, waiter, &unmatched_arg));
+    ASSERT_EQ(0, pthread_join(unmatched_pthread, nullptr));
+    ASSERT_EQ(0, bthread_join(unmatched_bthread, nullptr));
+
+    timespec abstime = butil::seconds_from_now(1);
+    for (size_t i = 0; i < 4 * N; ++i) {
         args[i].expected_value = *b1;
         args[i].butex = b1;
         if ((i % 2) == 0) {
@@ -179,34 +193,81 @@ TEST(ButexTest, sanity) {
             args[i].expected_result = ETIMEDOUT;
             args[i].ptimeout = &abstime;
         }
-        if (i < 2*N) { 
-            pthread_create(&t1, nullptr, waiter, &args[i]);
+        if (i < 2*N) {
+            ASSERT_EQ(0, pthread_create(&pthreads[i], nullptr, waiter, 
&args[i]));
         } else {
-            ASSERT_EQ(0, bthread_start_urgent(&th, nullptr, waiter, &args[i]));
+            ASSERT_EQ(0, bthread_start_urgent(&bthreads[i - 2*N], nullptr, 
waiter, &args[i]));
         }
     }
-    
-    sleep(2);
-    for (size_t i = 0; i < 2*N; ++i) {
-        ASSERT_EQ(1, bthread::butex_wake(b1));
+
+    // Join timed waiters before waking anyone, rather than assuming all
+    // timeout callbacks have completed after a fixed sleep.
+    for (size_t i = 1; i < 2 * N; i += 2) {
+        ASSERT_EQ(0, pthread_join(pthreads[i], nullptr));
+        ASSERT_EQ(0, bthread_join(bthreads[i], nullptr));
+    }
+    size_t nwoken = 0;
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    while (nwoken < 2 * N) {
+        int rc = bthread::butex_wake(b1);
+        ASSERT_GE(rc, 0);
+        ASSERT_LE(rc, 1);
+        nwoken += rc;
+        if (rc == 0) {
+            ASSERT_LT(butil::cpuwide_time_us(), deadline)
+                << "Timed out waiting for untimed waiters to register";
+            bthread_usleep(1000);
+        }
     }
     ASSERT_EQ(0, bthread::butex_wake(b1));
-    sleep(1);
+    for (size_t i = 0; i < 2 * N; i += 2) {
+        ASSERT_EQ(0, pthread_join(pthreads[i], nullptr));
+        ASSERT_EQ(0, bthread_join(bthreads[i], nullptr));
+    }
     bthread::butex_destroy(b1);
 }
 
+// A gate deliberately using pthread-backed synchronization: stopping a bthread
+// must not consume its pending interruption while it waits at the test gate.
+class TestGate {
+public:
+    void wait() {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _cond.wait(lock, [this] { return _open; });
+    }
+
+    void signal() {
+        std::lock_guard<std::mutex> lock(_mutex);
+        _open = true;
+        _cond.notify_all();
+    }
+
+private:
+    std::mutex _mutex;
+    std::condition_variable _cond;
+    bool _open = false;
+};
 
 struct ButexWaitArg {
     int* butex;
     int expected_val;
     long wait_msec;
     int error_code;
+    TestGate* before_wait;
+    TestGate* entering_wait;
 };
 
 void* wait_butex(void* void_arg) {
     ButexWaitArg* arg = static_cast<ButexWaitArg*>(void_arg);
-    const timespec ts = butil::milliseconds_from_now(arg->wait_msec);
-    int rc = bthread::butex_wait(arg->butex, arg->expected_val, &ts);
+    if (arg->before_wait) {
+        arg->before_wait->wait();
+    }
+    if (arg->entering_wait) {
+        arg->entering_wait->signal();
+    }
+    timespec ts = butil::milliseconds_from_now(arg->wait_msec);
+    int rc = bthread::butex_wait(arg->butex, arg->expected_val,
+                               arg->wait_msec < 0 ? nullptr : &ts);
     int saved_errno = errno;
     if (arg->error_code) {
         EXPECT_EQ(-1, rc);
@@ -221,11 +282,11 @@ TEST(ButexTest, wait_without_stop) {
     int* butex = bthread::butex_create_checked<int>();
     *butex = 7;
     butil::Timer tm;
-    const long WAIT_MSEC = 500;
+    long WAIT_MSEC = 500;
     for (int i = 0; i < 2; ++i) {
-        const bthread_attr_t attr =
-            (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL);
-        ButexWaitArg arg = { butex, *butex, WAIT_MSEC, ETIMEDOUT };
+        bthread_attr_t attr = (i == 0 ? BTHREAD_ATTR_PTHREAD : 
BTHREAD_ATTR_NORMAL);
+        ButexWaitArg arg = { butex, *butex, WAIT_MSEC, ETIMEDOUT,
+                            nullptr, nullptr };
         bthread_t th;
         
         tm.start();
@@ -233,7 +294,8 @@ TEST(ButexTest, wait_without_stop) {
         ASSERT_EQ(0, bthread_join(th, nullptr));
         tm.stop();
         
-        ASSERT_LT(labs(tm.m_elapsed() - WAIT_MSEC), 250);
+        // Timer delivery and rescheduling may be arbitrarily delayed by load.
+        ASSERT_GE(tm.m_elapsed(), WAIT_MSEC);
     }
     bthread::butex_destroy(butex);
 }
@@ -241,23 +303,24 @@ TEST(ButexTest, wait_without_stop) {
 TEST(ButexTest, stop_after_running) {
     int* butex = bthread::butex_create_checked<int>();
     *butex = 7;
-    const long WAIT_MSEC = 500;
-    const long SLEEP_MSEC = 10;
     for (int i = 0; i < 2; ++i) {
         const bthread_attr_t attr =
             (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL);
         bthread_t th;
-        ButexWaitArg arg = { butex, *butex, WAIT_MSEC, EINTR };
+        TestGate entering_wait;
+        // No timeout may race with stop. The waiter must report EINTR.
+        ButexWaitArg arg = { butex, *butex, -1, EINTR,
+                            nullptr, &entering_wait };
 
         ASSERT_EQ(0, bthread_start_urgent(&th, &attr, wait_butex, &arg));
-        ASSERT_EQ(0, bthread_usleep(SLEEP_MSEC * 1000L));
-        ASSERT_EQ(0, bthread_stop(th));
+        entering_wait.wait();
+        EXPECT_EQ(0, bthread_stop(th));
         ASSERT_EQ(0, bthread_join(th, nullptr));
 
         // ASSERT_TRUE(bthread::get_task_control()->
         //             timer_thread()._idset.empty());
         ASSERT_EQ(EINVAL, bthread_stop(th));
-    }    
+    }
     bthread::butex_destroy(butex);
 }
 
@@ -267,13 +330,18 @@ TEST(ButexTest, stop_before_running) {
     const long WAIT_MSEC = 500;
 
     for (int i = 0; i < 2; ++i) {
-        const bthread_attr_t attr =
+        bthread_attr_t attr =
             (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL) | 
BTHREAD_NOSIGNAL;
         bthread_t th;
-        ButexWaitArg arg = { butex, *butex, WAIT_MSEC, EINTR };
+        TestGate before_wait;
+        ButexWaitArg arg = { butex, *butex, -1, EINTR,
+                             &before_wait, nullptr };
 
         ASSERT_EQ(0, bthread_start_background(&th, &attr, wait_butex, &arg));
-        ASSERT_EQ(0, bthread_stop(th));
+        EXPECT_EQ(0, bthread_stop(th));
+        // NOSIGNAL suppresses notification, but does not prevent a worker
+        // from picking up the task. The gate enforces stop-before-wait.
+        before_wait.signal();
         bthread_flush();
         ASSERT_EQ(0, bthread_join(th, nullptr));
         ASSERT_EQ(EINVAL, bthread_stop(th));
@@ -281,132 +349,168 @@ TEST(ButexTest, stop_before_running) {
     bthread::butex_destroy(butex);
 }
 
+struct JoinWaiterArg {
+    bthread_t tid;
+    TestGate entering_join;
+    butil::atomic<bool> finished{false};
+};
+
 void* join_the_waiter(void* arg) {
-    EXPECT_EQ(0, bthread_join((bthread_t)arg, nullptr));
+    JoinWaiterArg* a = static_cast<JoinWaiterArg*>(arg);
+    a->entering_join.signal();
+    EXPECT_EQ(0, bthread_join(a->tid, nullptr));
+    a->finished.store(true, butil::memory_order_release);
     return nullptr;
 }
 
 TEST(ButexTest, join_cant_be_wakeup) {
-    const long WAIT_MSEC = 100;
     int* butex = bthread::butex_create_checked<int>();
     *butex = 7;
-    butil::Timer tm;
-    ButexWaitArg arg = { butex, *butex, 1000, EINTR };
-
     for (int i = 0; i < 2; ++i) {
-        const bthread_attr_t attr =
+        bthread_attr_t attr =
             (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL);
-        tm.start();
         bthread_t th, th2;
+        TestGate entering_wait;
+        ButexWaitArg arg = { butex, *butex, -1, EINTR,
+                            nullptr, &entering_wait };
         ASSERT_EQ(0, bthread_start_urgent(&th, nullptr, wait_butex, &arg));
-        ASSERT_EQ(0, bthread_start_urgent(&th2, &attr, join_the_waiter, 
(void*)th));
-        ASSERT_EQ(0, bthread_stop(th2));
-        ASSERT_EQ(0, bthread_usleep(WAIT_MSEC / 2 * 1000L));
-        ASSERT_TRUE(bthread::TaskGroup::exists(th));
-        ASSERT_TRUE(bthread::TaskGroup::exists(th2));
-        ASSERT_EQ(0, bthread_usleep(WAIT_MSEC / 2 * 1000L));
-        ASSERT_EQ(0, bthread_stop(th));
+        // Start the target before a pthread-stack joiner can block its worker.
+        entering_wait.wait();
+        JoinWaiterArg join_arg;
+        join_arg.tid = th;
+        ASSERT_EQ(0, bthread_start_urgent(&th2, &attr, join_the_waiter, 
&join_arg));
+        join_arg.entering_join.wait();
+        EXPECT_EQ(0, bthread_stop(th2));
+        // Give the interrupted joiner a chance to run. There is no deadline
+        // on the target waiter, so scheduler delays cannot make it exit.
+        EXPECT_EQ(0, bthread_usleep(50000));
+        EXPECT_FALSE(join_arg.finished.load(butil::memory_order_acquire));
+        EXPECT_TRUE(bthread::TaskGroup::exists(th));
+        EXPECT_TRUE(bthread::TaskGroup::exists(th2));
+        EXPECT_EQ(0, bthread_stop(th));
         ASSERT_EQ(0, bthread_join(th2, nullptr));
         ASSERT_EQ(0, bthread_join(th, nullptr));
-        tm.stop();
-        ASSERT_LT(tm.m_elapsed(), WAIT_MSEC + 15);
+        EXPECT_TRUE(join_arg.finished.load(butil::memory_order_acquire));
         ASSERT_EQ(EINVAL, bthread_stop(th));
         ASSERT_EQ(EINVAL, bthread_stop(th2));
     }
     bthread::butex_destroy(butex);
 }
 
-TEST(ButexTest, stop_after_slept) {
+struct StopSleepArg {
+    bool pthread_task;
+    TestGate* before_sleep;
+    TestGate entering_sleep;
+    TestGate allow_exit;
+};
+
+void* stoppable_sleeper(void* arg) {
+    StopSleepArg* a = static_cast<StopSleepArg*>(arg);
+    if (a->before_sleep) {
+        a->before_sleep->wait();
+    }
+    // Pthread-stack tasks use native usleep, which bthread_stop cannot
+    // interrupt. Normal bthreads must return ESTOP rather than time out.
+    int64_t sleep_us = a->pthread_task ? 100000L : 60000000L;
+    a->entering_sleep.signal();
     butil::Timer tm;
-    const long SLEEP_MSEC = 100;
-    const long WAIT_MSEC = 10;
-    
-    for (int i = 0; i < 2; ++i) {
-        const bthread_attr_t attr =
-            (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL);
-        tm.start();
-        bthread_t th;
-        ASSERT_EQ(0, bthread_start_urgent(
-                      &th, &attr, sleeper, (void*)(SLEEP_MSEC*1000L)));
-        ASSERT_EQ(0, bthread_usleep(WAIT_MSEC * 1000L));
-        ASSERT_EQ(0, bthread_stop(th));
-        ASSERT_EQ(0, bthread_join(th, nullptr));
-        tm.stop();
-        if (attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) {
-            ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 15);
-        } else {
-            ASSERT_LT(labs(tm.m_elapsed() - WAIT_MSEC), 15);
-        }
-        // ASSERT_TRUE(bthread::get_task_control()->
-        //             timer_thread()._idset.empty());
-        ASSERT_EQ(EINVAL, bthread_stop(th));
+    tm.start();
+    int rc = bthread_usleep(sleep_us);
+    int saved_errno = errno;
+    tm.stop();
+    if (a->pthread_task) {
+        EXPECT_EQ(0, rc);
+        EXPECT_GE(tm.u_elapsed(), sleep_us);
+    } else {
+        EXPECT_EQ(-1, rc);
+        EXPECT_EQ(ESTOP, saved_errno);
     }
+    // Keep the task alive even if the controller is descheduled longer than
+    // the native sleep, so stop() cannot race with task destruction.
+    a->allow_exit.wait();
+    return nullptr;
 }
 
-TEST(ButexTest, stop_just_when_sleeping) {
-    butil::Timer tm;
-    const long SLEEP_MSEC = 100;
-    
+void TestStopSleep(bool stop_before_sleep, bool wait_until_sleeping) {
     for (int i = 0; i < 2; ++i) {
-        const bthread_attr_t attr =
-            (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL);
-        tm.start();
+        bthread_attr_t attr = (i == 0 ? BTHREAD_ATTR_PTHREAD : 
BTHREAD_ATTR_NORMAL);
+        StopSleepArg arg;
+        arg.pthread_task = (i == 0);
+        TestGate before_sleep;
+        arg.before_sleep = stop_before_sleep ? &before_sleep : nullptr;
         bthread_t th;
-        ASSERT_EQ(0, bthread_start_urgent(
-                      &th, &attr, sleeper, (void*)(SLEEP_MSEC*1000L)));
-        ASSERT_EQ(0, bthread_stop(th));
-        ASSERT_EQ(0, bthread_join(th, nullptr));
-        tm.stop();
-        if (attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) {
-            ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 15);
+        if (stop_before_sleep) {
+            attr = attr | BTHREAD_NOSIGNAL;
+            ASSERT_EQ(0, bthread_start_background(
+                &th, &attr, stoppable_sleeper, &arg));
         } else {
-            ASSERT_LT(tm.m_elapsed(), 15);
+            ASSERT_EQ(0, bthread_start_urgent(&th, &attr, stoppable_sleeper, 
&arg));
+            arg.entering_sleep.wait();
+            if (wait_until_sleeping && !arg.pthread_task) {
+                // Observe timer registration instead of assuming a fixed
+                // delay is enough for the worker to enter sleep.
+                bthread::TaskMeta* meta = bthread::TaskGroup::address_meta(th);
+                int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+                bool sleeping = false;
+                do {
+                    pthread_spin_lock(&meta->version_lock);
+                    sleeping = (meta->current_sleep != 0);
+                    pthread_spin_unlock(&meta->version_lock);
+                    if (sleeping) {
+                        break;
+                    }
+                    bthread_usleep(1000);
+                } while (butil::cpuwide_time_us() < deadline);
+                EXPECT_TRUE(sleeping) << "Timed out waiting for sleep 
registration";
+            }
         }
-        // ASSERT_TRUE(bthread::get_task_control()->
-        //             timer_thread()._idset.empty());
+        EXPECT_EQ(0, bthread_stop(th));
+        before_sleep.signal();
+        arg.allow_exit.signal();
+        if (stop_before_sleep) {
+            bthread_flush();
+        }
+        ASSERT_EQ(0, bthread_join(th, nullptr));
         ASSERT_EQ(EINVAL, bthread_stop(th));
     }
 }
 
+TEST(ButexTest, stop_after_slept) {
+    TestStopSleep(false, true);
+}
+
+TEST(ButexTest, stop_just_when_sleeping) {
+    TestStopSleep(false, false);
+}
+
 TEST(ButexTest, stop_before_sleeping) {
-    butil::Timer tm;
-    const long SLEEP_MSEC = 100;
+    TestStopSleep(true, false);
+}
 
-    for (int i = 0; i < 2; ++i) {
-        bthread_t th;
-        const bthread_attr_t attr =
-            (i == 0 ? BTHREAD_ATTR_PTHREAD : BTHREAD_ATTR_NORMAL) | 
BTHREAD_NOSIGNAL;
-        
-        tm.start();
-        ASSERT_EQ(0, bthread_start_background(&th, &attr, sleeper,
-                                              (void*)(SLEEP_MSEC*1000L)));
-        ASSERT_EQ(0, bthread_stop(th));
-        bthread_flush();
-        ASSERT_EQ(0, bthread_join(th, nullptr));
-        tm.stop();
+struct SignalArg {
+    pthread_t waiter;
+    WaiterArg* wait_arg;
+    butil::atomic<bool> stop{false};
+};
 
-        if (attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) {
-            ASSERT_LT(labs(tm.m_elapsed() - SLEEP_MSEC), 10);
-        } else {
-            ASSERT_LT(tm.m_elapsed(), 10);
-        }
-        // ASSERT_TRUE(bthread::get_task_control()->
-        //             timer_thread()._idset.empty());
-        ASSERT_EQ(EINVAL, bthread_stop(th));
-    }
+void* signal_waiter(void* arg) {
+    SignalArg* a = static_cast<SignalArg*>(arg);
+    waiter(a->wait_arg);
+    a->stop.store(true);
+    return nullptr;
 }
 
 void* trigger_signal(void* arg) {
-    pthread_t * th = (pthread_t*)arg;
-    const long t1 = butil::gettimeofday_us();
-    for (size_t i = 0; i < 50; ++i) {
+    SignalArg* a = static_cast<SignalArg*>(arg);
+    long t1 = butil::gettimeofday_us();
+    for (size_t i = 0; i < 50 && !a->stop.load(); ++i) {
       usleep(100000);
-      if (bthread::interrupt_pthread(*th) == ESRCH) {
+      if (a->stop.load() || bthread::interrupt_pthread(a->waiter) == ESRCH) {
         LOG(INFO) << "waiter thread end, trigger count=" << i;
         break;
       }
     }
-    const long t2 = butil::gettimeofday_us();
+    long t2 = butil::gettimeofday_us();
     LOG(INFO) << "trigger signal thread end, elapsed=" << (t2-t1) << "us";
     return nullptr;
 }
@@ -414,7 +518,7 @@ void* trigger_signal(void* arg) {
 TEST(ButexTest, wait_with_signal_triggered) {
     butil::Timer tm;
 
-    const int64_t WAIT_MSEC = 500;
+    int64_t WAIT_MSEC = 500;
     WaiterArg waiter_args;
     pthread_t waiter_th, tigger_th;
     butil::atomic<int>* butex =
@@ -423,23 +527,34 @@ TEST(ButexTest, wait_with_signal_triggered) {
     *butex = 1;
     ASSERT_EQ(0, bthread::butex_wake(butex));
 
-    const timespec abstime = butil::milliseconds_from_now(WAIT_MSEC);
+    timespec abstime = butil::milliseconds_from_now(WAIT_MSEC);
     waiter_args.expected_value = *butex;
     waiter_args.butex = butex;
     waiter_args.expected_result = ETIMEDOUT;
     waiter_args.ptimeout = &abstime;
     tm.start();
-    pthread_create(&waiter_th, nullptr, waiter, &waiter_args);
-    pthread_create(&tigger_th, nullptr, trigger_signal, &waiter_th);
-    
+    SignalArg signal_arg;
+    signal_arg.wait_arg = &waiter_args;
+    ASSERT_EQ(0, pthread_create(&waiter_th, nullptr, signal_waiter, 
&signal_arg));
+    signal_arg.waiter = waiter_th;
+    int signal_rc = pthread_create(
+        &tigger_th, nullptr, trigger_signal, &signal_arg);
+    EXPECT_EQ(0, signal_rc);
+    // Keep the target joinable until the signalling thread has stopped.
+    // pthread_kill on a pthread_t whose lifetime ended at join is undefined.
+    if (signal_rc == 0) {
+        EXPECT_EQ(0, pthread_join(tigger_th, nullptr));
+    }
     ASSERT_EQ(0, pthread_join(waiter_th, nullptr));
     tm.stop();
-    auto wait_elapsed_ms = tm.m_elapsed();;
+    auto wait_elapsed_ms = tm.m_elapsed();
     LOG(INFO) << "waiter thread end, elapsed " << wait_elapsed_ms << " ms";
 
-    ASSERT_LT(labs(wait_elapsed_ms - WAIT_MSEC), 250);
+    // The timeout is absolute and starts before the worker is scheduled.
+    // Check the deadline itself rather than a narrow elapsed-time window.
+    EXPECT_GE(butil::gettimeofday_us(),
+              butil::timespec_to_microseconds(abstime));
 
-    ASSERT_EQ(0, pthread_join(tigger_th, nullptr));
     bthread::butex_destroy(butex);
 }
 
diff --git a/test/bthread_cond_unittest.cpp b/test/bthread_cond_unittest.cpp
index 85181691..5fd0a70d 100644
--- a/test/bthread_cond_unittest.cpp
+++ b/test/bthread_cond_unittest.cpp
@@ -145,7 +145,6 @@ std::atomic<int> WrapperArg::wake_time{0};
 
 void* cv_signaler(void* void_arg) {
     WrapperArg* a = (WrapperArg*)void_arg;
-    signal_start_time = butil::gettimeofday_us();
     while (!stop) {
         bthread_usleep(SIGNAL_INTERVAL_US);
         a->cond.notify_one();
@@ -224,6 +223,7 @@ TEST(CondTest, cpp_wrapper) {
 
 TEST(CondTest, cpp_wrapper2) {
     stop = false;
+    WrapperArg::wake_time = 0;
     bthread::ConditionVariable cond;
     pthread_t bmutex_waiter_threads[8];
     pthread_t mutex_waiter_threads[8];
diff --git a/test/bthread_fd_unittest.cpp b/test/bthread_fd_unittest.cpp
index 02c579c1..a730f2d7 100644
--- a/test/bthread_fd_unittest.cpp
+++ b/test/bthread_fd_unittest.cpp
@@ -411,39 +411,69 @@ TEST(FDTest, add_existing_fd) {
 #endif
 }
 
+struct EpollWaitArg {
+    int epfd;
+    butil::atomic<bool> done{false};
+    int result = 0;
+    int error = 0;
+};
+
 void* epoll_waiter(void* arg) {
+    EpollWaitArg* a = static_cast<EpollWaitArg*>(arg);
 #if defined(OS_LINUX)
     epoll_event e;
-    if (1 == epoll_wait((int)(intptr_t)arg, &e, 1, -1)) {
-        std::cout << e.events << std::endl;
-    }
+    a->result = epoll_wait(a->epfd, &e, 1, 10000);
 #elif defined(OS_MACOSX)
     struct kevent e;
-    if (1 == kevent((int)(intptr_t)arg, nullptr, 0, &e, 1, nullptr)) {
-        std::cout << e.flags << std::endl;
-    }
+    timespec timeout = {10, 0};
+    a->result = kevent(a->epfd, nullptr, 0, &e, 1, &timeout);
 #endif
-    std::cout << pthread_self() << " quits" << std::endl;
+    a->error = errno;
+    a->done.store(true, butil::memory_order_release);
     return nullptr;
 }
 
 TEST(FDTest, interrupt_pthread) {
 #if defined(OS_LINUX)
-    const int epfd = epoll_create(1024);
+    butil::fd_guard epfd(epoll_create(1024));
 #elif defined(OS_MACOSX)
-    const int epfd = kqueue();
+    butil::fd_guard epfd(kqueue());
 #endif
-    pthread_t th, th2;
-    ASSERT_EQ(0, pthread_create(&th, nullptr, epoll_waiter, 
(void*)(intptr_t)epfd));
-    ASSERT_EQ(0, pthread_create(&th2, nullptr, epoll_waiter, 
(void*)(intptr_t)epfd));
-    bthread_usleep(100000L);
-    std::cout << "wake up " << th << std::endl;
-    bthread::interrupt_pthread(th);
-    bthread_usleep(100000L);
-    std::cout << "wake up " << th2 << std::endl;
-    bthread::interrupt_pthread(th2);
-    pthread_join(th, nullptr);
-    pthread_join(th2, nullptr);
+    ASSERT_GE(epfd, 0);
+    EpollWaitArg args[2];
+    pthread_t threads[2];
+    size_t started = 0;
+    for (; started < ARRAY_SIZE(threads); ++started) {
+        args[started].epfd = epfd;
+        int rc = pthread_create(&threads[started], nullptr,
+                                      epoll_waiter, &args[started]);
+        EXPECT_EQ(0, rc);
+        if (rc != 0) {
+            break;
+        }
+    }
+    int64_t deadline = butil::cpuwide_time_us() + 15000000L;
+    for (size_t i = 0; i < started; ++i) {
+        // Signals are not persistent. Retry until the syscall observes one;
+        // keep the pthread joinable until all signalling is finished.
+        while (!args[i].done.load(butil::memory_order_acquire) &&
+               butil::cpuwide_time_us() < deadline) {
+            int rc = bthread::interrupt_pthread(threads[i]);
+            if (rc != 0) {
+                // The waiter may finish between the check above and the
+                // signal, in which case interruption is no longer needed.
+                // Why it stopped waiting is checked on args[i] below.
+                EXPECT_EQ(ESRCH, rc) << berror(rc);
+                break;
+            }
+            bthread_usleep(1000);
+        }
+        EXPECT_EQ(0, pthread_join(threads[i], nullptr));
+    }
+    for (size_t i = 0; i < started; ++i) {
+        ASSERT_EQ(-1, args[i].result);
+        ASSERT_EQ(EINTR, args[i].error);
+    }
 }
 
 void* close_the_fd(void* arg) {
@@ -484,49 +514,74 @@ TEST(FDTest, invalid_epoll_events) {
     ASSERT_EQ(0, bthread_fd_wait(fds[0], EVFILT_READ));
 #endif
     tm.stop();
-    ASSERT_LT(tm.m_elapsed(), 20);
+    // Successful readiness, not scheduler latency, is the contract.
     ASSERT_EQ(0, bthread_join(th, nullptr));
     ASSERT_EQ(0, bthread_close(fds[0]));
 }
 
+struct FDWaitArg {
+    int fd;
+    int timeout_ms;
+    int result = 0;
+    int error = 0;
+};
+
 void* wait_for_the_fd(void* arg) {
-    timespec ts = butil::milliseconds_from_now(50);
+    FDWaitArg* a = static_cast<FDWaitArg*>(arg);
+    timespec ts = butil::milliseconds_from_now(a->timeout_ms);
 #if defined(OS_LINUX)
-    bthread_fd_timedwait(*(int*)arg, EPOLLIN, &ts);
+    a->result = bthread_fd_timedwait(a->fd, EPOLLIN, &ts);
 #elif defined(OS_MACOSX)
-    bthread_fd_timedwait(*(int*)arg, EVFILT_READ, &ts);
+    a->result = bthread_fd_timedwait(a->fd, EVFILT_READ, &ts);
 #endif
+    a->error = errno;
+    if (a->result == -1 && a->error == ETIMEDOUT) {
+        EXPECT_GE(butil::gettimeofday_us(), 
butil::timespec_to_microseconds(ts));
+    }
     return nullptr;
 }
 
 TEST(FDTest, timeout) {
     int fds[2];
     ASSERT_EQ(0, pipe(fds));
+    FDWaitArg args[2];
+    for (auto& arg : args) {
+        arg.fd = fds[0];
+        arg.timeout_ms = 50;
+    }
     pthread_t th;
-    ASSERT_EQ(0, pthread_create(&th, nullptr, wait_for_the_fd, &fds[0]));
+    ASSERT_EQ(0, pthread_create(&th, nullptr, wait_for_the_fd, &args[0]));
     bthread_t bth;
-    ASSERT_EQ(0, bthread_start_urgent(&bth, nullptr, wait_for_the_fd, 
&fds[0]));
-    butil::Timer tm;
-    tm.start();
+    ASSERT_EQ(0, bthread_start_urgent(&bth, nullptr, wait_for_the_fd, 
&args[1]));
     ASSERT_EQ(0, pthread_join(th, nullptr));
     ASSERT_EQ(0, bthread_join(bth, nullptr));
-    tm.stop();
-    ASSERT_LT(tm.m_elapsed(), 80);
     ASSERT_EQ(0, bthread_close(fds[0]));
     ASSERT_EQ(0, bthread_close(fds[1]));
+    for (auto& arg : args) {
+        ASSERT_EQ(-1, arg.result);
+        ASSERT_EQ(ETIMEDOUT, arg.error);
+    }
 }
 
 TEST(FDTest, close_should_wakeup_waiter) {
     int fds[2];
     ASSERT_EQ(0, pipe(fds));
+    FDWaitArg arg;
+    arg.fd = fds[0];
+    arg.timeout_ms = 10000;
     bthread_t bth;
-    ASSERT_EQ(0, bthread_start_urgent(&bth, nullptr, wait_for_the_fd, 
&fds[0]));
-    butil::Timer tm;
-    tm.start();
+    ASSERT_EQ(0, bthread_start_urgent(&bth, nullptr, wait_for_the_fd, &arg));
+    auto* meta = bthread::TaskGroup::address_meta(bth);
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    while (meta->current_waiter.load(butil::memory_order_acquire) == nullptr &&
+           butil::cpuwide_time_us() < deadline) {
+        bthread_usleep(1000);
+    }
+    // Keep cleanup reachable even if the waiter did not register in time.
+    EXPECT_NE(nullptr, meta->current_waiter.load(butil::memory_order_acquire));
     ASSERT_EQ(0, bthread_close(fds[0]));
     ASSERT_EQ(0, bthread_join(bth, nullptr));
-    tm.stop();
-    ASSERT_LT(tm.m_elapsed(), 5);
+    ASSERT_EQ(0, arg.result) << "errno=" << arg.error;
 
     // Launch again, should quit soon due to EBADF
 #if defined(OS_LINUX)
@@ -566,73 +621,76 @@ TEST(FDTest, double_close) {
     ASSERT_EQ(ec, errno);
 }
 
-const char* g_hostname1 = "github.com";
-const char* g_hostname2 = "baidu.com";
-TEST(FDTest, bthread_connect) {
-    butil::EndPoint ep1;
-    butil::EndPoint ep2;
-    ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep1));
-    ASSERT_EQ(0, butil::hostname2endpoint(g_hostname2, 80, &ep2));
-
-    {
-        struct sockaddr_storage serv_addr{};
-        socklen_t serv_addr_size = 0;
-        ASSERT_EQ(0, endpoint2sockaddr(ep1, &serv_addr, &serv_addr_size));
-        butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0));
-        ASSERT_LE(0, sockfd);
-        bool is_blocking = butil::is_blocking(sockfd);
-        ASSERT_LE(0, sockfd);
-        ASSERT_EQ(0, bthread_connect(sockfd, (struct sockaddr*) &serv_addr, 
serv_addr_size));
-        ASSERT_EQ(is_blocking, butil::is_blocking(sockfd));
+// Local listeners keep connect tests independent of DNS, Internet latency,
+// and the assumption that a connection cannot complete within one millisecond.
+void TestLocalConnect(bool timed) {
+    butil::EndPoint endpoint;
+    ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:0", &endpoint));
+    butil::fd_guard listener(butil::tcp_listen(endpoint));
+    ASSERT_GE(listener, 0);
+    ASSERT_EQ(0, butil::get_local_side(listener, &endpoint));
+    struct sockaddr_storage address{};
+    socklen_t length = 0;
+    ASSERT_EQ(0, endpoint2sockaddr(endpoint, &address, &length));
+    butil::fd_guard client(socket(address.ss_family, SOCK_STREAM, 0));
+    ASSERT_GE(client, 0);
+    bool was_blocking = butil::is_blocking(client);
+    timespec deadline = butil::seconds_from_now(10);
+    int rc = bthread_timed_connect(
+        client, reinterpret_cast<sockaddr*>(&address), length,
+        timed ? &deadline : nullptr);
+    ASSERT_EQ(0, rc) << "errno=" << errno;
+    ASSERT_EQ(was_blocking, butil::is_blocking(client));
+    ASSERT_EQ(0, butil::is_connected(client));
+    // The handshake does not require a concurrent accept thread.
+    butil::fd_guard accepted(accept(listener, nullptr, nullptr));
+    ASSERT_GE(accepted, 0);
+}
 
-    }
+TEST(FDTest, bthread_connect) {
+    TestLocalConnect(false);
+    TestLocalConnect(true);
+}
 
-    {
-        struct sockaddr_storage serv_addr{};
-        socklen_t serv_addr_size = 0;
-        ASSERT_EQ(0, endpoint2sockaddr(ep2, &serv_addr, &serv_addr_size));
-        butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0));
-        ASSERT_LE(0, sockfd);
-        bool is_blocking = butil::is_blocking(sockfd);
-        // In most cases, 1 millisecond will result in a connection timeout.
-        timespec abstime = butil::milliseconds_from_now(1);
-        const int rc = bthread_timed_connect(
-            sockfd, (struct sockaddr*) &serv_addr,
-            serv_addr_size, &abstime);
-        ASSERT_EQ(-1, rc);
-        ASSERT_EQ(ETIMEDOUT, errno);
-        ASSERT_EQ(is_blocking, butil::is_blocking(sockfd));
-    }
+#if defined(OS_LINUX)
+TEST(FDTest, connect_timeout_with_full_accept_queue) {
+    butil::EndPoint endpoint;
+    ASSERT_EQ(0, butil::str2endpoint("127.0.0.1:0", &endpoint));
+    butil::fd_guard listener(butil::tcp_listen(endpoint));
+    ASSERT_GE(listener, 0);
+    // Linux permits one queued connection for backlog=0. Leave it unaccepted
+    // so the next handshake cannot complete; no external network is needed.
+    ASSERT_EQ(0, listen(listener, 0));
+    ASSERT_EQ(0, butil::get_local_side(listener, &endpoint));
+    sockaddr_storage address{};
+    socklen_t length = 0;
+    ASSERT_EQ(0, endpoint2sockaddr(endpoint, &address, &length));
+    butil::fd_guard queued(socket(AF_INET, SOCK_STREAM, 0));
+    ASSERT_GE(queued, 0);
+    timespec setup_deadline = butil::seconds_from_now(5);
+    ASSERT_EQ(0, bthread_timed_connect(queued,
+        reinterpret_cast<sockaddr*>(&address), length, &setup_deadline));
+    butil::fd_guard client(socket(AF_INET, SOCK_STREAM, 0));
+    ASSERT_GE(client, 0);
+    timespec deadline = butil::milliseconds_from_now(50);
+    int rc = bthread_timed_connect(client,
+        reinterpret_cast<sockaddr*>(&address), length, &deadline);
+    int error = errno;
+    ASSERT_EQ(-1, rc);
+    // tcp_abort_on_overflow may turn an accept-queue overflow into an RST.
+    ASSERT_TRUE(error == ETIMEDOUT || error == ECONNREFUSED)
+        << "errno=" << error;
+    EXPECT_TRUE(butil::is_blocking(client));
 }
+#endif
 
 void TestConnectInterruptImpl(bool timed) {
-    butil::EndPoint ep;
-    ASSERT_EQ(0, butil::hostname2endpoint(g_hostname1, 80, &ep));
-    struct sockaddr_storage serv_addr{};
-    socklen_t serv_addr_size = 0;
-    ASSERT_EQ(0, endpoint2sockaddr(ep, &serv_addr, &serv_addr_size));
-    butil::fd_guard sockfd(socket(serv_addr.ss_family, SOCK_STREAM, 0));
-    ASSERT_GE(sockfd, 0);
-
-    int rc;
-    if (timed) {
-        int64_t start_ms = butil::cpuwide_time_ms();
-        butil::tcp_connect(ep, nullptr);
-        int64_t connect_ms = butil::cpuwide_time_ms() - start_ms;
-        LOG(INFO) << "Connect to " << ep << ", cost " << connect_ms << "ms";
-
-        timespec abstime = butil::milliseconds_from_now(connect_ms * 10);
-        rc = bthread_timed_connect(
-            sockfd, (struct sockaddr*) &serv_addr,
-            serv_addr_size, &abstime);
-    } else {
-        rc = bthread_timed_connect(
-            sockfd, (struct sockaddr*) &serv_addr,
-            serv_addr_size, nullptr);
+    // Stop must precede connect even if the task starts on another worker
+    // immediately. Yield does not consume the pending interruption.
+    while (!bthread_stopped(bthread_self())) {
+        bthread_yield();
     }
-    ASSERT_EQ(0, rc) << "errno=" << errno;
-    ASSERT_EQ(0, butil::is_connected(sockfd));
-
+    TestLocalConnect(timed);
 }
 
 void* ConnectThread(void* arg) {
diff --git a/test/bthread_futex_unittest.cpp b/test/bthread_futex_unittest.cpp
index a9742755..ce32356e 100644
--- a/test/bthread_futex_unittest.cpp
+++ b/test/bthread_futex_unittest.cpp
@@ -20,6 +20,7 @@
 #include <stdio.h>
 #include <signal.h>
 #include <gtest/gtest.h>
+#include <vector>
 #include "butil/time.h"
 #include "butil/macros.h"
 #include "butil/errno.h"
@@ -30,7 +31,7 @@
 #include <bthread/processor.h>
 
 namespace {
-volatile bool stop = false;
+butil::atomic<bool> stop(false);
 
 butil::atomic<int> nthread(0);
 
@@ -57,21 +58,26 @@ void* read_thread(void* arg) {
         }
 
         ++nthread;
-        bthread::futex_wait_private(m/*lock1*/, 0/*consumed_njob*/, nullptr);
+        // A stop between the loop condition and futex_wait must not leave
+        // this worker asleep forever. Periodically recheck the stop flag.
+        timespec timeout = butil::milliseconds_to_timespec(100);
+        bthread::futex_wait_private(m/*lock1*/, 0/*consumed_njob*/, &timeout);
         --nthread;
     }
     return new int(njob);
 }
 
 TEST(FutexTest, rdlock_performance) {
-    const size_t N = 100000;
+    stop = false;
+    nthread = 0;
+    size_t N = 100000;
     butil::atomic<int> lock1(0);
     pthread_t rth[8];
     for (size_t i = 0; i < ARRAY_SIZE(rth); ++i) {
         ASSERT_EQ(0, pthread_create(&rth[i], nullptr, read_thread, &lock1));
     }
 
-    const int64_t t1 = butil::cpuwide_time_ns();
+    int64_t t1 = butil::cpuwide_time_ns();
     for (size_t i = 0; i < N; ++i) {
         if (nthread) {
             lock1.fetch_add(1);
@@ -83,14 +89,11 @@ TEST(FutexTest, rdlock_performance) {
             }
         }
     }
-    const int64_t t2 = butil::cpuwide_time_ns();
+    int64_t t2 = butil::cpuwide_time_ns();
 
     bthread_usleep(3000000);
     stop = true;
-    for (int i = 0; i < 10; ++i) {
-        bthread::futex_wake_private(&lock1, INT_MAX);
-        sched_yield();
-    }
+    bthread::futex_wake_private(&lock1, INT_MAX);
 
     int njob = 0;
     int* res;
@@ -112,31 +115,91 @@ TEST(FutexTest, futex_wake_before_wait) {
     ASSERT_EQ(ETIMEDOUT, errno);
 }
 
-void* dummy_waiter(void* lock) {
-    bthread::futex_wait_private(lock, 0, nullptr);
+struct DummyWaiterArg {
+    butil::atomic<int>* lock;
+    butil::atomic<int>* registered;
+    butil::atomic<bool>* cleaning_up;
+};
+
+void* dummy_waiter(void* void_arg) {
+    DummyWaiterArg* arg = static_cast<DummyWaiterArg*>(void_arg);
+    timespec timeout = butil::seconds_to_timespec(10);
+    // Publish readiness before entering the wait so the controller does not
+    // start measuring wakeups while threads are still being created.
+    arg->registered->fetch_add(1, butil::memory_order_release);
+    int rc;
+    do {
+        rc = bthread::futex_wait_private(arg->lock, 0, &timeout);
+    } while (rc != 0 && errno == EINTR);
+    if (arg->cleaning_up->load(butil::memory_order_acquire) &&
+        rc == -1 && errno == EWOULDBLOCK) {
+        return nullptr;
+    }
+    EXPECT_EQ(0, rc);
     return nullptr;
 }
 
 TEST(FutexTest, futex_wake_many_waiters_perf) {
-    
-    int lock1 = 0;
-    size_t N = 0;
-    pthread_t th;
-    for (; N < 1000 && !pthread_create(&th, nullptr, dummy_waiter, &lock1); 
++N) {}
-    
-    sleep(1);
+    butil::atomic<int> lock1(0);
+    butil::atomic<int> registered(0);
+    butil::atomic<bool> cleaning_up(false);
+    DummyWaiterArg arg = { &lock1, &registered, &cleaning_up };
+    std::vector<pthread_t> threads;
+    for (size_t i = 0; i < 1000; ++i) {
+        pthread_t th;
+        if (pthread_create(&th, nullptr, dummy_waiter, &arg) != 0) {
+            break;
+        }
+        threads.push_back(th);
+    }
+    ASSERT_FALSE(threads.empty());
+    size_t N = threads.size();
+    const int64_t registration_deadline =
+        butil::cpuwide_time_us() + 10000000L;
+    while (registered.load(butil::memory_order_acquire) !=
+               static_cast<int>(N) &&
+           butil::cpuwide_time_us() < registration_deadline) {
+        usleep(1000);
+    }
+    const bool all_registered =
+        registered.load(butil::memory_order_acquire) == static_cast<int>(N);
+
     int nwakeup = 0;
+    int64_t wake_ns = 0;
+    const int64_t wake_deadline = butil::cpuwide_time_us() + 10000000L;
     butil::Timer tm;
-    tm.start();
-    for (size_t i = 0; i < N; ++i) {
-        nwakeup += bthread::futex_wake_private(&lock1, 1);
+    if (all_registered) {
+        while (static_cast<size_t>(nwakeup) < N &&
+               butil::cpuwide_time_us() < wake_deadline) {
+            tm.start();
+            int rc = bthread::futex_wake_private(&lock1, 1);
+            tm.stop();
+            EXPECT_GE(rc, 0);
+            if (rc > 0) {
+                nwakeup += rc;
+                wake_ns += tm.n_elapsed();
+            } else {
+                usleep(1000);
+            }
+        }
+    }
+    // Also release waiters if a wake assertion fails; a wake alone is not
+    // persistent, and a worker that races with this store gets EWOULDBLOCK.
+    cleaning_up.store(true, butil::memory_order_release);
+    lock1.store(1);
+    bthread::futex_wake_private(&lock1, INT_MAX);
+    for (pthread_t th : threads) {
+        EXPECT_EQ(0, pthread_join(th, nullptr));
+    }
+    EXPECT_TRUE(all_registered)
+        << "Timed out waiting for all futex waiters to register";
+    EXPECT_EQ(N, static_cast<size_t>(nwakeup));
+    if (nwakeup != 0) {
+        printf("N=%lu, futex_wake a thread = %" PRId64 "ns\n", N,
+               wake_ns / nwakeup);
     }
-    tm.stop();
-    printf("N=%lu, futex_wake a thread = %" PRId64 "ns\n", N, tm.n_elapsed() / 
N);
-    ASSERT_EQ(N, (size_t)nwakeup);
 
-    sleep(2);
-    const size_t REP = 10000;
+    size_t REP = 10000;
     nwakeup = 0;
     tm.start();
     for (size_t i = 0; i < REP; ++i) {
@@ -191,7 +254,7 @@ void* batch_waker(void* lock) {
 
 TEST(FutexTest, many_futex_wake_nop_perf) {
     pthread_t th[8];
-    int lock1;
+    int lock1 = 0;
     std::cout << "[Direct wake]" << std::endl;
     for (size_t i = 0; i < ARRAY_SIZE(th); ++i) {
         ASSERT_EQ(0, pthread_create(&th[i], nullptr, waker, &lock1));
diff --git a/test/bthread_mutex_unittest.cpp b/test/bthread_mutex_unittest.cpp
index 98494421..eca329e0 100644
--- a/test/bthread_mutex_unittest.cpp
+++ b/test/bthread_mutex_unittest.cpp
@@ -52,8 +52,14 @@ TEST(MutexTest, sanity) {
     ASSERT_EQ(1u, *get_butex(m));
     bthread_t th1;
     ASSERT_EQ(0, bthread_start_urgent(&th1, nullptr, locker, &m));
-    usleep(5000); // wait for locker to run.
-    ASSERT_EQ(257u, *get_butex(m)); // contention
+    auto* state = reinterpret_cast<butil::atomic<unsigned>*>(get_butex(m));
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    while (state->load(butil::memory_order_relaxed) != 257u &&
+           butil::cpuwide_time_us() < deadline) {
+        usleep(1000);
+    }
+    // Keep cleanup reachable even if the worker did not run in time.
+    EXPECT_EQ(257u, state->load(butil::memory_order_relaxed));
     ASSERT_EQ(0, bthread_mutex_unlock(&m));
     ASSERT_EQ(0, bthread_join(th1, nullptr));
     ASSERT_EQ(0u, *get_butex(m));
diff --git a/test/bthread_rwlock_unittest.cpp b/test/bthread_rwlock_unittest.cpp
index 3747c29f..9031a579 100644
--- a/test/bthread_rwlock_unittest.cpp
+++ b/test/bthread_rwlock_unittest.cpp
@@ -21,6 +21,8 @@
 #include <bthread/rwlock.h>
 #include <bthread/condition_variable.h>
 #include <functional>
+#include "bthread/task_group.h"
+#include "bthread/task_meta.h"
 #include <vector>
 
 namespace {
@@ -210,8 +212,21 @@ TEST(RWLockTest, cpp_wrapper) {
     }
 }
 
-bool g_started = false;
-bool g_stopped = false;
+butil::atomic<bool> g_started(false);
+butil::atomic<bool> g_stopped(false);
+
+// Read only synchronized predicates; deadlines bound failures, not latency.
+template <typename Predicate>
+bool WaitForRWLockState(Predicate predicate) {
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    while (!predicate()) {
+        if (butil::cpuwide_time_us() >= deadline) {
+            return false;
+        }
+        bthread_usleep(1000);
+    }
+    return true;
+}
 
 void read_op(bthread_rwlock_t* rw, int64_t sleep_us) {
     ASSERT_EQ(0, bthread_rwlock_rdlock(rw));
@@ -334,18 +349,19 @@ TEST(RWLockTest, writer_priority) {
     WriterPriorityArgs r2arg {&rw, &order, -1, 0};
 
     // (2) Start a writer; it should park inside wrlock() because the read
-    //     lock is held. Sleep long enough for it to fetch_add into
-    //     writer_wait_count and reach the butex_wait on `lock_word'.
+    //     lock is held. Observe its registration before starting a reader.
     bthread_t wth;
     ASSERT_EQ(0, bthread_start_urgent(&wth, nullptr, wp_writer_fn, &warg));
-    bthread_usleep(50 * 1000);
+    EXPECT_TRUE(WaitForRWLockState([&] {
+        return reinterpret_cast<butil::atomic<unsigned>*>(rw.writer_wait_count)
+            ->load(butil::memory_order_relaxed) == 1;
+    }));
 
     // (3) Now spawn a fresh reader. By writer-priority it MUST observe
     //     writer_wait_count > 0 and park on it (NOT join the active read
     //     lock).
     bthread_t r2th;
     ASSERT_EQ(0, bthread_start_urgent(&r2th, nullptr, wp_reader_fn, &r2arg));
-    bthread_usleep(50 * 1000);
 
     // (4) Release the original read lock. The writer should win the race
     //     and complete BEFORE the queued reader.
@@ -396,14 +412,9 @@ TEST(RWLockTest, 
wrlock_failure_does_not_leak_writer_count) {
     // so a new reader MUST acquire the lock immediately.
     ASSERT_EQ(0, bthread_rwlock_unlock(&rw));
 
-    timespec ts = butil::milliseconds_from_now(500);
-    butil::Timer t;
-    t.start();
-    ASSERT_EQ(0, bthread_rwlock_timedrdlock(&rw, &ts));
-    t.stop();
-    EXPECT_LT(t.m_elapsed(), 100)
-        << "Reader was blocked for " << t.m_elapsed() << "ms; "
-        << "writer_wait_count was likely leaked by the cleanup path.";
+    EXPECT_EQ(0u, 
reinterpret_cast<butil::atomic<unsigned>*>(rw.writer_wait_count)
+                      ->load(butil::memory_order_relaxed));
+    ASSERT_EQ(0, bthread_rwlock_tryrdlock(&rw));
 
     ASSERT_EQ(0, bthread_rwlock_unlock(&rw));
     ASSERT_EQ(0, bthread_rwlock_destroy(&rw));
@@ -518,17 +529,14 @@ TEST(RWLockTest, no_writer_starvation) {
     // Let the readers ramp up and saturate the lock.
     bthread_usleep(50 * 1000);
 
-    // A single writer must succeed within a generous budget.
-    butil::Timer t;
-    t.start();
-    ASSERT_EQ(0, bthread_rwlock_wrlock(&rw));
-    t.stop();
-
-    EXPECT_LT(t.m_elapsed(), 1000)
-        << "Writer starved for " << t.m_elapsed() << "ms under "
-        << R << " concurrent readers; writer-priority is broken.";
-
-    ASSERT_EQ(0, bthread_rwlock_unlock(&rw));
+    // A timed acquisition also makes the failure path reachable when the
+    // writer really starves, so readers can be stopped and joined safely.
+    timespec deadline = butil::seconds_from_now(10);
+    int rc = bthread_rwlock_timedwrlock(&rw, &deadline);
+    EXPECT_EQ(0, rc) << "Writer starved under concurrent readers";
+    if (rc == 0) {
+        EXPECT_EQ(0, bthread_rwlock_unlock(&rw));
+    }
 
     g_stopped = true;
     for (int i = 0; i < R; ++i) {
@@ -726,6 +734,16 @@ public:
         }
     }
 
+    void WaitUntilBlocked() {
+        for (bthread_t tid : _tids) {
+            auto* meta = bthread::TaskGroup::address_meta(tid);
+            EXPECT_TRUE(WaitForRWLockState([&] {
+                return meta->current_waiter.load(butil::memory_order_acquire)
+                       != nullptr;
+            }));
+        }
+    }
+
     void Join() {
         for (size_t i = 0; i < _tids.size(); ++i) {
             bthread_join(_tids[i], nullptr);
@@ -745,7 +763,7 @@ private:
 #define CHECK_RWLOCK_LOCKED_VALUE_EQUAL(mutex_name, value, expected_value) \
     {                                                                      \
         std::unique_lock<bthread::Mutex> lock(mutex_name);                 \
-        ASSERT_EQ(value, expected_value);                                  \
+        EXPECT_EQ(value, expected_value);                                  \
     }
 
 void RwlockLockingThreadFunc(bthread_rwlock_t* rw, bool read_lock,
@@ -854,7 +872,10 @@ TEST(RWLockTest, boost_style_only_one_writer_permitted) {
     }
     task_runner.RunTask();
 
-    bthread_usleep(200 * 1000);
+    EXPECT_TRUE(WaitForRWLockState([&] {
+        std::unique_lock<bthread::Mutex> lk(unblocked_count_mutex);
+        return unblocked_count >= 1;
+    }));
 
     CHECK_RWLOCK_LOCKED_VALUE_EQUAL(unblocked_count_mutex, unblocked_count, 
1u);
 
@@ -905,7 +926,7 @@ TEST(RWLockTest, boost_style_reader_blocks_writer) {
                   &simultaneous_running_count, &max_simultaneous_running));
     writer_runner.RunTask();
 
-    bthread_usleep(100 * 1000);
+    writer_runner.WaitUntilBlocked();
     CHECK_RWLOCK_LOCKED_VALUE_EQUAL(unblocked_count_mutex, unblocked_count, 
1u);
 
     finish_lock.unlock();
@@ -945,7 +966,7 @@ TEST(RWLockTest, 
boost_style_unlocking_writer_unblocks_all_readers) {
     }
     task_runner.RunTask();
 
-    bthread_usleep(100 * 1000);
+    task_runner.WaitUntilBlocked();
     CHECK_RWLOCK_LOCKED_VALUE_EQUAL(unblocked_count_mutex, unblocked_count, 
0u);
 
     ASSERT_EQ(0, bthread_rwlock_unlock(&rw));
@@ -1015,7 +1036,7 @@ TEST(RWLockTest, 
boost_style_unlocking_last_reader_only_unblocks_one_writer) {
     }
     writer_runner.RunTask();
 
-    bthread_usleep(100 * 1000);
+    writer_runner.WaitUntilBlocked();
     CHECK_RWLOCK_LOCKED_VALUE_EQUAL(unblocked_count_mutex, unblocked_count,
                                     reader_count);
 
@@ -1027,7 +1048,7 @@ TEST(RWLockTest, 
boost_style_unlocking_last_reader_only_unblocks_one_writer) {
             unblocked_condition.wait(lk);
         }
     }
-    bthread_usleep(100 * 1000);
+    writer_runner.WaitUntilBlocked();
     CHECK_RWLOCK_LOCKED_VALUE_EQUAL(unblocked_count_mutex, unblocked_count,
                                     reader_count + 1);
 
diff --git a/test/bthread_timer_thread_unittest.cpp 
b/test/bthread_timer_thread_unittest.cpp
index c784cf25..13153fe4 100644
--- a/test/bthread_timer_thread_unittest.cpp
+++ b/test/bthread_timer_thread_unittest.cpp
@@ -27,6 +27,20 @@
 
 namespace {
 
+// A generous deadline bounds failures, not the scheduler's response time.
+// Predicates must read atomics or otherwise synchronize with the timer thread.
+template <typename Predicate>
+bool WaitUntil(Predicate predicate) {
+    int64_t deadline = butil::cpuwide_time_us() + 10000000L;
+    while (!predicate()) {
+        if (butil::cpuwide_time_us() >= deadline) {
+            return false;
+        }
+        usleep(1000);
+    }
+    return true;
+}
+
 long timespec_diff_us(const timespec& ts1, const timespec& ts2) {
     return (ts1.tv_sec - ts2.tv_sec) * 1000000L +
         (ts1.tv_nsec - ts2.tv_nsec) / 1000;
@@ -53,16 +67,18 @@ public:
         timespec current_time;
         clock_gettime(CLOCK_REALTIME, &current_time);
         if (_name) {
-            LOG(INFO) << "Run `" << _name << "' task_id=" << _task_id;
+            LOG(INFO) << "Run `" << _name << "'";
         } else {
-            LOG(INFO) << "Run task_id=" << _task_id;
+            LOG(INFO) << "Run timer task";
         }
         _run_times.push_back(current_time);
-        const int saved_sleep_ms = _sleep_ms;
+        _started.store(true, butil::memory_order_release);
+        int saved_sleep_ms = _sleep_ms.load();
         if (saved_sleep_ms > 0) {
             timespec timeout = butil::milliseconds_to_timespec(saved_sleep_ms);
             bthread::futex_wait_private(&_sleep_ms, saved_sleep_ms, &timeout);
         }
+        _finished.store(true, butil::memory_order_release);
     }
 
     void wakeup() {
@@ -85,11 +101,14 @@ public:
     {
         ASSERT_TRUE(!_run_times.empty());
         long diff = timespec_diff_us(_run_times[0], expect_run_time);
-        EXPECT_LE(labs(diff), 50000);
+        EXPECT_GE(diff, 0);
+        // Keep a generous bound to catch a stalled timer without depending on
+        // narrow scheduler-latency assumptions.
+        EXPECT_LT(diff, 10000000L);
     }
     
     void expect_not_run() {
-        EXPECT_TRUE(_run_times.empty());
+        EXPECT_FALSE(_started.load(butil::memory_order_acquire));
     }
 
     static void routine(void *arg)
@@ -98,12 +117,26 @@ public:
         keeper->run();
     }
 
+    bool wait_finished() {
+        return WaitUntil([this] {
+            return _finished.load(butil::memory_order_acquire);
+        });
+    }
+
+    bool wait_started() {
+        return WaitUntil([this] {
+            return _started.load(butil::memory_order_acquire);
+        });
+    }
+
     timespec _expect_run_time;
     bthread::TimerThread::TaskId _task_id;
 
 private:
     const char* _name;
-    int _sleep_ms;
+    butil::atomic<int> _sleep_ms;
+    butil::atomic<bool> _started{false};
+    butil::atomic<bool> _finished{false};
     std::vector<timespec> _run_times;
 };
 
@@ -111,46 +144,43 @@ TEST(TimerThreadTest, RunTasks) {
     bthread::TimerThread timer_thread;
     ASSERT_EQ(0, timer_thread.start(nullptr));
 
-    timespec _2s_later = butil::seconds_from_now(2);
-    TimeKeeper keeper1(_2s_later, "keeper1");
+    timespec time_20ms_later = butil::milliseconds_from_now(20);
+    TimeKeeper keeper1(time_20ms_later, "keeper1");
     keeper1.schedule(&timer_thread);
 
-    TimeKeeper keeper2(_2s_later, "keeper2");  // same time with keeper1
+    timespec time_1h_later = butil::seconds_from_now(3600);
+    TimeKeeper keeper2(time_1h_later, "keeper2");
     keeper2.schedule(&timer_thread);
     
-    timespec _1s_later = butil::seconds_from_now(1);
-    TimeKeeper keeper3(_1s_later, "keeper3");
+    timespec time_10ms_later = butil::milliseconds_from_now(10);
+    TimeKeeper keeper3(time_10ms_later, "keeper3");
     keeper3.schedule(&timer_thread);
 
-    timespec _10s_later = butil::seconds_from_now(10);
-    TimeKeeper keeper4(_10s_later, "keeper4");
+    TimeKeeper keeper4(time_1h_later, "keeper4");
     keeper4.schedule(&timer_thread);
 
-    TimeKeeper keeper5(_10s_later, "keeper5");
+    TimeKeeper keeper5(time_1h_later, "keeper5");
     keeper5.schedule(&timer_thread);
     
-    // sleep 1 second, and unschedule task2
-    LOG(INFO) << "Sleep 1s";
-    sleep(1);
-    timer_thread.unschedule(keeper2._task_id);
-    timer_thread.unschedule(keeper4._task_id);
+    ASSERT_EQ(0, timer_thread.unschedule(keeper2._task_id));
+    ASSERT_EQ(0, timer_thread.unschedule(keeper4._task_id));
 
     timespec old_time = { 0, 0 };
     TimeKeeper keeper6(old_time, "keeper6");
+    timespec keeper6_addtime = butil::seconds_from_now(0);
     keeper6.schedule(&timer_thread);
-    const timespec keeper6_addtime = butil::seconds_from_now(0);
 
-    // sleep 10 seconds and stop.
-    LOG(INFO) << "Sleep 2s";
-    sleep(2);
+    ASSERT_TRUE(keeper1.wait_started());
+    ASSERT_TRUE(keeper3.wait_started());
+    ASSERT_TRUE(keeper6.wait_started());
     LOG(INFO) << "Stop timer_thread";
     butil::Timer tm;
     tm.start();
     timer_thread.stop_and_join();
     tm.stop();
     // stop_and_join() should wake the timer thread instead of waiting for the
-    // tasks scheduled 10 seconds later. Allow for CI runner scheduling delays.
-    ASSERT_LT(tm.m_elapsed(), 1000);
+    // tasks scheduled an hour later. Allow for CI runner scheduling delays.
+    ASSERT_LT(tm.m_elapsed(), 10000);
 
     // verify all runs in expected time range.
     keeper1.expect_first_run();
@@ -215,20 +245,22 @@ TEST(TimerThreadTest, schedule_and_unschedule_in_task) {
     bthread::TimerThread timer_thread;
     timespec past_time = { 0, 0 };
     timespec future_time = { std::numeric_limits<int>::max(), 0 };
-    const timespec _500ms_after = butil::milliseconds_from_now(500);
+    timespec _500ms_after = butil::milliseconds_from_now(500);
 
     TimeKeeper keeper1(future_time, "keeper1");
     TimeKeeper keeper2(past_time, "keeper2");
     TimeKeeper keeper3(past_time, "keeper3");
     TimeKeeper keeper4(past_time, "keeper4");
-    TimeKeeper keeper5(_500ms_after, "keeper5", 10000/*10s*/);
+    TimeKeeper keeper5(_500ms_after, "keeper5", 60000);
 
     ASSERT_EQ(0, timer_thread.start(nullptr));
     keeper1.schedule(&timer_thread);  // start keeper1
-    keeper3.schedule(&timer_thread);  // start keeper3
     timespec keeper3_addtime = butil::seconds_from_now(0);
+    keeper3.schedule(&timer_thread);  // start keeper3
     keeper5.schedule(&timer_thread);  // start keeper5
-    sleep(1);  // let keeper1/3/5 run
+    // Preserve timer-thread cleanup even when a callback starts late.
+    EXPECT_TRUE(keeper3.wait_started());
+    EXPECT_TRUE(keeper5.wait_started());
 
     TestTask test_task1(&timer_thread, &keeper1, &keeper2, 0);
     timer_thread.schedule(TestTask::routine, &test_task1, past_time);
@@ -236,7 +268,6 @@ TEST(TimerThreadTest, schedule_and_unschedule_in_task) {
     TestTask test_task2(&timer_thread, &keeper3, &keeper4, -1);
     timer_thread.schedule(TestTask::routine, &test_task2, past_time);
 
-    sleep(1);
     // test_task1/2 should be both blocked by keeper5.
     keeper2.expect_not_run();
     keeper4.expect_not_run();
@@ -246,7 +277,8 @@ TEST(TimerThreadTest, schedule_and_unschedule_in_task) {
     
     // wake up keeper5 to let test_task1/2 run.
     keeper5.wakeup();
-    sleep(1);
+    EXPECT_TRUE(keeper2.wait_started());
+    EXPECT_TRUE(keeper4.wait_started());
 
     timer_thread.stop_and_join();
     timespec finish_time;
@@ -293,9 +325,9 @@ TEST(TimerThreadTest, sweep_unscheduled_tasks_in_heap) {
     ASSERT_EQ(0, timer_thread.start(nullptr));
 
     // Run far enough in the future that these tasks never fire on their own.
-    const timespec far = butil::seconds_from_now(100000);
-    const size_t kBatch = 2000;
-    const size_t kRounds = 20;
+    timespec far = butil::seconds_from_now(100000);
+    size_t kBatch = 2000;
+    size_t kRounds = 20;
 
     int64_t max_pending = 0;
     for (size_t r = 0; r < kRounds; ++r) {
@@ -308,7 +340,10 @@ TEST(TimerThreadTest, sweep_unscheduled_tasks_in_heap) {
         // buckets, so the far tasks above land in the heap (alive).
         timer_thread.schedule(noop_routine, nullptr,
                               butil::milliseconds_from_now(1));
-        usleep(20000);  // let the timer thread consume the buckets
+        ASSERT_TRUE(WaitUntil([&] {
+            return timer_thread._npending.load(butil::memory_order_relaxed) >=
+                   static_cast<int64_t>(kBatch);
+        }));
 
         // Now unschedule the far tasks: they become dead-in-heap, exactly the
         // case that used to linger until run_time.
@@ -319,11 +354,18 @@ TEST(TimerThreadTest, sweep_unscheduled_tasks_in_heap) {
         // sweep that reclaims the dead tasks.
         timer_thread.schedule(noop_routine, nullptr,
                               butil::milliseconds_from_now(1));
-        usleep(20000);
+        // A callback acknowledgement ensures a timer pass completed. Heap
+        // reclamation is checked across rounds below (sweeps are amortized).
+        TimeKeeper consumed(butil::seconds_from_now(0));
+        consumed.schedule(&timer_thread);
+        if (!consumed.wait_finished()) {
+            timer_thread.stop_and_join();
+            FAIL() << "Timer did not consume the wakeup task";
+        }
 
         // Read the internal heap size directly (brpc tests are built with
         // -fno-access-control, so no public accessor is needed).
-        const int64_t pending =
+        int64_t pending =
             timer_thread._npending.load(butil::memory_order_relaxed);
         LOG(INFO) << "round=" << r << " pending=" << pending;
         max_pending = std::max(max_pending, pending);
@@ -354,13 +396,15 @@ TEST(TimerThreadTest, periodic_wakeup_drains_buckets) {
     // (with even later run_times) are never the "earliest" and thus never wake
     // the timer via schedule() -- only the periodic wakeup can drain them.
     timer_thread.schedule(noop_routine, nullptr, 
butil::seconds_from_now(3600));
-    usleep(100000);  // let the anchor be consumed into the heap
+    EXPECT_TRUE(WaitUntil([&] {
+        return timer_thread._npending.load(butil::memory_order_relaxed) == 1;
+    }));
     // Only the anchor is in the heap so far.
     ASSERT_EQ(1, timer_thread._npending.load(butil::memory_order_relaxed));
 
     // Pile far tasks with strictly-increasing run_times into the buckets. None
     // of these wake the timer.
-    const int kN = 2000;
+    int kN = 2000;
     for (int i = 0; i < kN; ++i) {
         timer_thread.schedule(noop_routine, nullptr,
                               butil::seconds_from_now(3600 + 1 + i));
@@ -369,8 +413,10 @@ TEST(TimerThreadTest, periodic_wakeup_drains_buckets) {
     // Without the periodic wakeup the timer would stay asleep (nearest task is
     // an hour away) and these would sit in the buckets, unconsumed. With it,
     // they are pulled into the heap within a few wakeup intervals.
-    usleep(300000);  // several 50ms intervals
-    const int64_t pending =
+    EXPECT_TRUE(WaitUntil([&] {
+        return timer_thread._npending.load(butil::memory_order_relaxed) == kN 
+ 1;
+    }));
+    int64_t pending =
         timer_thread._npending.load(butil::memory_order_relaxed);
     LOG(INFO) << "pending after bucket fill = " << pending << " (scheduled "
               << kN << " + 1 anchor)";
diff --git a/test/bthread_unittest.cpp b/test/bthread_unittest.cpp
index c86bc83e..6a90ea56 100644
--- a/test/bthread_unittest.cpp
+++ b/test/bthread_unittest.cpp
@@ -501,21 +501,33 @@ TEST_F(BthreadTest, start_latency_when_high_idle) {
 }
 
 void* sleep_for_awhile_with_sleep(void* arg) {
-    bthread_usleep((intptr_t)arg);
+    int rc = bthread_usleep((intptr_t)arg);
+    int error = errno;
+    EXPECT_EQ(-1, rc);
+    EXPECT_EQ(ESTOP, error);
     return nullptr;
 }
 
 TEST_F(BthreadTest, stop_sleep) {
     bthread_t th;
     ASSERT_EQ(0, bthread_start_urgent(
-                  &th, nullptr, sleep_for_awhile_with_sleep, (void*)1000000L));
-    butil::Timer tm;
-    tm.start();
-    bthread_usleep(10000);
+                  &th, nullptr, sleep_for_awhile_with_sleep, 
(void*)60000000L));
+    auto* meta = bthread::TaskGroup::address_meta(th);
+    int64_t deadline = butil::cpuwide_time_us() + 5000000L;
+    bool sleeping = false;
+    do {
+        pthread_spin_lock(&meta->version_lock);
+        sleeping = (meta->current_sleep != 0);
+        pthread_spin_unlock(&meta->version_lock);
+        if (sleeping) {
+            break;
+        }
+        bthread_usleep(1000);
+    } while (butil::cpuwide_time_us() < deadline);
+    // bthread_stop is persistent, so cleanup is safe even before registration.
+    EXPECT_TRUE(sleeping);
     ASSERT_EQ(0, bthread_stop(th));
     ASSERT_EQ(0, bthread_join(th, nullptr));
-    tm.stop();
-    ASSERT_LE(labs(tm.m_elapsed() - 10), 10);
 }
 
 TEST_F(BthreadTest, bthread_exit) {
diff --git a/test/bthread_work_stealing_queue_unittest.cpp 
b/test/bthread_work_stealing_queue_unittest.cpp
index c6738e82..6b33d538 100644
--- a/test/bthread_work_stealing_queue_unittest.cpp
+++ b/test/bthread_work_stealing_queue_unittest.cpp
@@ -25,7 +25,7 @@
 
 namespace {
 typedef size_t value_type;
-bool g_stop = false;
+butil::atomic<bool> g_stop(false);
 const size_t N = 1024*512;
 const size_t CAP = 8;
 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -85,6 +85,7 @@ void* pop_thread(void* arg) {
 
 
 TEST(WSQTest, sanity) {
+    g_stop = false;
     bthread::WorkStealingQueue<value_type> q;
     ASSERT_EQ(0, q.init(CAP));
     pthread_t rth[8];


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to