@ -29,6 +29,7 @@
# include <vector>
# include "gtest/gtest.h"
# include "absl/base/attributes.h"
# include "absl/base/internal/raw_logging.h"
# include "absl/base/internal/sysinfo.h"
# include "absl/memory/memory.h"
@ -54,8 +55,8 @@ CreateDefaultPool() {
// Hack to schedule a function to run on a thread pool thread after a
// duration has elapsed.
static void ScheduleAfter ( absl : : synchronization_internal : : ThreadPool * tp ,
const std : : function < void ( ) > & func ,
absl : : Duration after ) {
absl : : Duration after ,
const std : : function < void ( ) > & func ) {
tp - > Schedule ( [ func , after ] {
absl : : SleepFor ( after ) ;
func ( ) ;
@ -1150,249 +1151,369 @@ TEST(Mutex, DeadlockIdBug) NO_THREAD_SAFETY_ANALYSIS {
// and so never expires/passes, and one that will expire/pass in the near
// future.
// Encapsulate a Mutex-protected bool with its associated Condition/CondVar.
class Cond {
public :
explicit Cond ( bool use_deadline ) : use_deadline_ ( use_deadline ) , c_ ( & b_ ) { }
void Set ( bool v ) {
absl : : MutexLock lock ( & mu_ ) ;
b_ = v ;
static absl : : Duration TimeoutTestAllowedSchedulingDelay ( ) {
// Note: we use a function here because Microsoft Visual Studio fails to
// properly initialize constexpr static absl::Duration variables.
return absl : : Milliseconds ( 150 ) ;
}
// Returns true if `actual_delay` is close enough to `expected_delay` to pass
// the timeouts/deadlines test. Otherwise, logs warnings and returns false.
ABSL_MUST_USE_RESULT
static bool DelayIsWithinBounds ( absl : : Duration expected_delay ,
absl : : Duration actual_delay ) {
bool pass = true ;
// Do not allow the observed delay to be less than expected. This may occur
// in practice due to clock skew or when the synchronization primitives use a
// different clock than absl::Now(), but these cases should be handled by the
// the retry mechanism in each TimeoutTest.
if ( actual_delay < expected_delay ) {
ABSL_RAW_LOG ( WARNING ,
" Actual delay %s was too short, expected %s (difference %s) " ,
absl : : FormatDuration ( actual_delay ) . c_str ( ) ,
absl : : FormatDuration ( expected_delay ) . c_str ( ) ,
absl : : FormatDuration ( actual_delay - expected_delay ) . c_str ( ) ) ;
pass = false ;
}
bool AwaitWithTimeout ( absl : : Duration timeout ) {
absl : : MutexLock lock ( & mu_ ) ;
return use_deadline_ ? mu_ . AwaitWithDeadline ( c_ , absl : : Now ( ) + timeout )
: mu_ . AwaitWithTimeout ( c_ , timeout ) ;
// If the expected delay is <= zero then allow a small error tolerance, since
// we do not expect context switches to occur during test execution.
// Otherwise, thread scheduling delays may be substantial in rare cases, so
// tolerate up to kTimeoutTestAllowedSchedulingDelay of error.
absl : : Duration tolerance = expected_delay < = absl : : ZeroDuration ( )
? absl : : Milliseconds ( 10 )
: TimeoutTestAllowedSchedulingDelay ( ) ;
if ( actual_delay > expected_delay + tolerance ) {
ABSL_RAW_LOG ( WARNING ,
" Actual delay %s was too long, expected %s (difference %s) " ,
absl : : FormatDuration ( actual_delay ) . c_str ( ) ,
absl : : FormatDuration ( expected_delay ) . c_str ( ) ,
absl : : FormatDuration ( actual_delay - expected_delay ) . c_str ( ) ) ;
pass = false ;
}
return pass ;
}
// Parameters for TimeoutTest, below.
struct TimeoutTestParam {
// The file and line number (used for logging purposes only).
const char * from_file ;
int from_line ;
// Should the absolute deadline API based on absl::Time be tested? If false,
// the relative deadline API based on absl::Duration is tested.
bool use_absolute_deadline ;
// The deadline/timeout used when calling the API being tested
// (e.g. Mutex::LockWhenWithDeadline).
absl : : Duration wait_timeout ;
// The delay before the condition will be set true by the test code. If zero
// or negative, the condition is set true immediately (before calling the API
// being tested). Otherwise, if infinite, the condition is never set true.
// Otherwise a closure is scheduled for the future that sets the condition
// true.
absl : : Duration satisfy_condition_delay ;
// The expected result of the condition after the call to the API being
// tested. Generally `true` means the condition was true when the API returns,
// `false` indicates an expected timeout.
bool expected_result ;
// The expected delay before the API under test returns. This is inherently
// flaky, so some slop is allowed (see `DelayIsWithinBounds` above), and the
// test keeps trying indefinitely until this constraint passes.
absl : : Duration expected_delay ;
} ;
bool LockWhenWithTimeout ( absl : : Duration timeout ) {
bool b = use_deadline_ ? mu_ . LockWhenWithDeadline ( c_ , absl : : Now ( ) + timeout )
: mu_ . LockWhenWithTimeout ( c_ , timeout ) ;
mu_ . Unlock ( ) ;
return b ;
// Print a `TimeoutTestParam` to a debug log.
std : : ostream & operator < < ( std : : ostream & os , const TimeoutTestParam & param ) {
return os < < " from: " < < param . from_file < < " : " < < param . from_line
< < " use_absolute_deadline: "
< < ( param . use_absolute_deadline ? " true " : " false " )
< < " wait_timeout: " < < param . wait_timeout
< < " satisfy_condition_delay: " < < param . satisfy_condition_delay
< < " expected_result: "
< < ( param . expected_result ? " true " : " false " )
< < " expected_delay: " < < param . expected_delay ;
}
std : : string FormatString ( const TimeoutTestParam & param ) {
std : : ostringstream os ;
os < < param ;
return os . str ( ) ;
}
// Like `thread::Executor::ScheduleAt` except:
// a) Delays zero or negative are executed immediately in the current thread.
// b) Infinite delays are never scheduled.
// c) Calls this test's `ScheduleAt` helper instead of using `pool` directly.
static void RunAfterDelay ( absl : : Duration delay ,
absl : : synchronization_internal : : ThreadPool * pool ,
const std : : function < void ( ) > & callback ) {
if ( delay < = absl : : ZeroDuration ( ) ) {
callback ( ) ; // immediate
} else if ( delay ! = absl : : InfiniteDuration ( ) ) {
ScheduleAfter ( pool , delay , callback ) ;
}
}
bool ReaderLockWhenWithTimeout ( absl : : Duration timeout ) {
bool b = use_deadline_
? mu_ . ReaderLockWhenWithDeadline ( c_ , absl : : Now ( ) + timeout )
: mu_ . ReaderLockWhenWithTimeout ( c_ , timeout ) ;
mu_ . ReaderUnlock ( ) ;
return b ;
}
class TimeoutTest : public : : testing : : Test ,
public : : testing : : WithParamInterface < TimeoutTestParam > { } ;
void Await ( ) {
absl : : MutexLock lock ( & mu_ ) ;
mu_ . Await ( c_ ) ;
}
std : : vector < TimeoutTestParam > MakeTimeoutTestParamValues ( ) {
// The `finite` delay is a finite, relatively short, delay. We make it larger
// than our allowed scheduling delay (slop factor) to avoid confusion when
// diagnosing test failures. The other constants here have clear meanings.
const absl : : Duration finite = 3 * TimeoutTestAllowedSchedulingDelay ( ) ;
const absl : : Duration never = absl : : InfiniteDuration ( ) ;
const absl : : Duration negative = - absl : : InfiniteDuration ( ) ;
const absl : : Duration immediate = absl : : ZeroDuration ( ) ;
void Signal ( bool v ) {
absl : : MutexLock lock ( & mu_ ) ;
b_ = v ;
cv_ . Signal ( ) ;
// Every test case is run twice; once using the absolute deadline API and once
// using the relative timeout API.
std : : vector < TimeoutTestParam > values ;
for ( bool use_absolute_deadline : { false , true } ) {
// Tests with a negative timeout (deadline in the past), which should
// immediately return current state of the condition.
// The condition is already true:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
negative , // wait_timeout
immediate , // satisfy_condition_delay
true , // expected_result
immediate , // expected_delay
} ) ;
// The condition becomes true, but the timeout has already expired:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
negative , // wait_timeout
finite , // satisfy_condition_delay
false , // expected_result
immediate // expected_delay
} ) ;
// The condition never becomes true:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
negative , // wait_timeout
never , // satisfy_condition_delay
false , // expected_result
immediate // expected_delay
} ) ;
// Tests with an infinite timeout (deadline in the infinite future), which
// should only return when the condition becomes true.
// The condition is already true:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
never , // wait_timeout
immediate , // satisfy_condition_delay
true , // expected_result
immediate // expected_delay
} ) ;
// The condition becomes true before the (infinite) expiry:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
never , // wait_timeout
finite , // satisfy_condition_delay
true , // expected_result
finite , // expected_delay
} ) ;
// Tests with a (small) finite timeout (deadline soon), with the condition
// becoming true both before and after its expiry.
// The condition is already true:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
never , // wait_timeout
immediate , // satisfy_condition_delay
true , // expected_result
immediate // expected_delay
} ) ;
// The condition becomes true before the expiry:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
finite * 2 , // wait_timeout
finite , // satisfy_condition_delay
true , // expected_result
finite // expected_delay
} ) ;
// The condition becomes true, but the timeout has already expired:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
finite , // wait_timeout
finite * 2 , // satisfy_condition_delay
false , // expected_result
finite // expected_delay
} ) ;
// The condition never becomes true:
values . push_back ( TimeoutTestParam {
__FILE__ , __LINE__ , use_absolute_deadline ,
finite , // wait_timeout
never , // satisfy_condition_delay
false , // expected_result
finite // expected_delay
} ) ;
}
bool WaitWithTimeout ( absl : : Duration timeout ) {
absl : : MutexLock lock ( & mu_ ) ;
absl : : Time deadline = absl : : Now ( ) + timeout ;
if ( use_deadline_ ) {
while ( ! b_ & & ! cv_ . WaitWithDeadline ( & mu_ , deadline ) ) {
}
} else {
while ( ! b_ & & ! cv_ . WaitWithTimeout ( & mu_ , timeout ) ) {
timeout = deadline - absl : : Now ( ) ; // recompute timeout
}
return values ;
}
// Instantiate `TimeoutTest` with `MakeTimeoutTestParamValues()`.
INSTANTIATE_TEST_CASE_P ( All , TimeoutTest ,
testing : : ValuesIn ( MakeTimeoutTestParamValues ( ) ) ) ;
TEST_P ( TimeoutTest , Await ) {
const TimeoutTestParam params = GetParam ( ) ;
ABSL_RAW_LOG ( INFO , " Params: %s " , FormatString ( params ) . c_str ( ) ) ;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for ( int attempt = 1 ; ; + + attempt ) {
ABSL_RAW_LOG ( INFO , " Attempt %d " , attempt ) ;
absl : : Mutex mu ;
bool value = false ; // condition value (under mu)
std : : unique_ptr < absl : : synchronization_internal : : ThreadPool > pool =
CreateDefaultPool ( ) ;
RunAfterDelay ( params . satisfy_condition_delay , pool . get ( ) , [ & ] {
absl : : MutexLock l ( & mu ) ;
value = true ;
} ) ;
absl : : MutexLock lock ( & mu ) ;
absl : : Time start_time = absl : : Now ( ) ;
absl : : Condition cond ( & value ) ;
bool result =
params . use_absolute_deadline
? mu . AwaitWithDeadline ( cond , start_time + params . wait_timeout )
: mu . AwaitWithTimeout ( cond , params . wait_timeout ) ;
if ( DelayIsWithinBounds ( params . expected_delay , absl : : Now ( ) - start_time ) ) {
EXPECT_EQ ( params . expected_result , result ) ;
break ;
}
return b_ ;
}
void Wait ( ) {
absl : : MutexLock lock ( & mu_ ) ;
while ( ! b_ ) cv_ . Wait ( & mu_ ) ;
}
private :
const bool use_deadline_ ;
bool b_ ;
absl : : Condition c_ ;
absl : : CondVar cv_ ;
absl : : Mutex mu_ ;
} ;
class OperationTimer {
public :
OperationTimer ( ) : start_ ( absl : : Now ( ) ) { }
absl : : Duration Get ( ) const { return absl : : Now ( ) - start_ ; }
private :
const absl : : Time start_ ;
} ;
static void CheckResults ( bool exp_result , bool act_result ,
absl : : Duration exp_duration ,
absl : : Duration act_duration ) {
ABSL_RAW_CHECK ( exp_result = = act_result , " CheckResults failed " ) ;
// Allow for some worse-case scheduling delay and clock skew.
if ( ( exp_duration - absl : : Milliseconds ( 40 ) > act_duration ) | |
( exp_duration + absl : : Milliseconds ( 150 ) < act_duration ) ) {
ABSL_RAW_LOG ( FATAL , " CheckResults failed: operation took %s, expected %s " ,
absl : : FormatDuration ( act_duration ) . c_str ( ) ,
absl : : FormatDuration ( exp_duration ) . c_str ( ) ) ;
}
}
static void TestAwaitTimeout ( Cond * cp , absl : : Duration timeout , bool exp_result ,
absl : : Duration exp_duration ) {
OperationTimer t ;
bool act_result = cp - > AwaitWithTimeout ( timeout ) ;
CheckResults ( exp_result , act_result , exp_duration , t . Get ( ) ) ;
}
static void TestLockWhenTimeout ( Cond * cp , absl : : Duration timeout ,
bool exp_result , absl : : Duration exp_duration ) {
OperationTimer t ;
bool act_result = cp - > LockWhenWithTimeout ( timeout ) ;
CheckResults ( exp_result , act_result , exp_duration , t . Get ( ) ) ;
}
TEST_P ( TimeoutTest , LockWhen ) {
const TimeoutTestParam params = GetParam ( ) ;
ABSL_RAW_LOG ( INFO , " Params: %s " , FormatString ( params ) . c_str ( ) ) ;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for ( int attempt = 1 ; ; + + attempt ) {
ABSL_RAW_LOG ( INFO , " Attempt %d " , attempt ) ;
absl : : Mutex mu ;
bool value = false ; // condition value (under mu)
std : : unique_ptr < absl : : synchronization_internal : : ThreadPool > pool =
CreateDefaultPool ( ) ;
RunAfterDelay ( params . satisfy_condition_delay , pool . get ( ) , [ & ] {
absl : : MutexLock l ( & mu ) ;
value = true ;
} ) ;
absl : : Time start_time = absl : : Now ( ) ;
absl : : Condition cond ( & value ) ;
bool result =
params . use_absolute_deadline
? mu . LockWhenWithDeadline ( cond , start_time + params . wait_timeout )
: mu . LockWhenWithTimeout ( cond , params . wait_timeout ) ;
mu . Unlock ( ) ;
static void TestReaderLockWhenTimeout ( Cond * cp , absl : : Duration timeout ,
bool exp_result ,
absl : : Duration exp_duration ) {
OperationTimer t ;
bool act_result = cp - > ReaderLockWhenWithTimeout ( timeout ) ;
CheckResults ( exp_result , act_result , exp_duration , t . Get ( ) ) ;
if ( DelayIsWithinBounds ( params . expected_delay , absl : : Now ( ) - start_time ) ) {
EXPECT_EQ ( params . expected_result , result ) ;
break ;
}
}
}
static void TestWaitTimeout ( Cond * cp , absl : : Duration timeout , bool exp_result ,
absl : : Duration exp_duration ) {
OperationTimer t ;
bool act_result = cp - > WaitWithTimeout ( timeout ) ;
CheckResults ( exp_result , act_result , exp_duration , t . Get ( ) ) ;
TEST_P ( TimeoutTest , ReaderLockWhen ) {
const TimeoutTestParam params = GetParam ( ) ;
ABSL_RAW_LOG ( INFO , " Params: %s " , FormatString ( params ) . c_str ( ) ) ;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for ( int attempt = 0 ; ; + + attempt ) {
ABSL_RAW_LOG ( INFO , " Attempt %d " , attempt ) ;
absl : : Mutex mu ;
bool value = false ; // condition value (under mu)
std : : unique_ptr < absl : : synchronization_internal : : ThreadPool > pool =
CreateDefaultPool ( ) ;
RunAfterDelay ( params . satisfy_condition_delay , pool . get ( ) , [ & ] {
absl : : MutexLock l ( & mu ) ;
value = true ;
} ) ;
absl : : Time start_time = absl : : Now ( ) ;
bool result =
params . use_absolute_deadline
? mu . ReaderLockWhenWithDeadline ( absl : : Condition ( & value ) ,
start_time + params . wait_timeout )
: mu . ReaderLockWhenWithTimeout ( absl : : Condition ( & value ) ,
params . wait_timeout ) ;
mu . ReaderUnlock ( ) ;
if ( DelayIsWithinBounds ( params . expected_delay , absl : : Now ( ) - start_time ) ) {
EXPECT_EQ ( params . expected_result , result ) ;
break ;
}
}
}
// Tests with a negative timeout (deadline in the past), which should
// immediately return the current state of the condition.
static void TestNegativeTimeouts ( absl : : synchronization_internal : : ThreadPool * tp ,
Cond * cp ) {
const absl : : Duration negative = - absl : : InfiniteDuration ( ) ;
const absl : : Duration immediate = absl : : ZeroDuration ( ) ;
// The condition is already true:
cp - > Set ( true ) ;
TestAwaitTimeout ( cp , negative , true , immediate ) ;
TestLockWhenTimeout ( cp , negative , true , immediate ) ;
TestReaderLockWhenTimeout ( cp , negative , true , immediate ) ;
TestWaitTimeout ( cp , negative , true , immediate ) ;
// The condition becomes true, but the timeout has already expired:
const absl : : Duration delay = absl : : Milliseconds ( 200 ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , 3 * delay ) ;
TestAwaitTimeout ( cp , negative , false , immediate ) ;
TestLockWhenTimeout ( cp , negative , false , immediate ) ;
TestReaderLockWhenTimeout ( cp , negative , false , immediate ) ;
cp - > Await ( ) ; // wait for the scheduled Set() to complete
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Signal , cp , true ) , delay ) ;
TestWaitTimeout ( cp , negative , false , immediate ) ;
cp - > Wait ( ) ; // wait for the scheduled Signal() to complete
// The condition never becomes true:
cp - > Set ( false ) ;
TestAwaitTimeout ( cp , negative , false , immediate ) ;
TestLockWhenTimeout ( cp , negative , false , immediate ) ;
TestReaderLockWhenTimeout ( cp , negative , false , immediate ) ;
TestWaitTimeout ( cp , negative , false , immediate ) ;
}
// Tests with an infinite timeout (deadline in the infinite future), which
// should only return when the condition becomes true.
static void TestInfiniteTimeouts ( absl : : synchronization_internal : : ThreadPool * tp ,
Cond * cp ) {
const absl : : Duration infinite = absl : : InfiniteDuration ( ) ;
const absl : : Duration immediate = absl : : ZeroDuration ( ) ;
// The condition is already true:
cp - > Set ( true ) ;
TestAwaitTimeout ( cp , infinite , true , immediate ) ;
TestLockWhenTimeout ( cp , infinite , true , immediate ) ;
TestReaderLockWhenTimeout ( cp , infinite , true , immediate ) ;
TestWaitTimeout ( cp , infinite , true , immediate ) ;
// The condition becomes true before the (infinite) expiry:
const absl : : Duration delay = absl : : Milliseconds ( 200 ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , delay ) ;
TestAwaitTimeout ( cp , infinite , true , delay ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , delay ) ;
TestLockWhenTimeout ( cp , infinite , true , delay ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , delay ) ;
TestReaderLockWhenTimeout ( cp , infinite , true , delay ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Signal , cp , true ) , delay ) ;
TestWaitTimeout ( cp , infinite , true , delay ) ;
}
// Tests with a (small) finite timeout (deadline soon), with the condition
// becoming true both before and after its expiry.
static void TestFiniteTimeouts ( absl : : synchronization_internal : : ThreadPool * tp ,
Cond * cp ) {
const absl : : Duration finite = absl : : Milliseconds ( 400 ) ;
const absl : : Duration immediate = absl : : ZeroDuration ( ) ;
TEST_P ( TimeoutTest , Wait ) {
const TimeoutTestParam params = GetParam ( ) ;
ABSL_RAW_LOG ( INFO , " Params: %s " , FormatString ( params ) . c_str ( ) ) ;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for ( int attempt = 0 ; ; + + attempt ) {
ABSL_RAW_LOG ( INFO , " Attempt %d " , attempt ) ;
absl : : Mutex mu ;
bool value = false ; // condition value (under mu)
absl : : CondVar cv ; // signals a change of `value`
std : : unique_ptr < absl : : synchronization_internal : : ThreadPool > pool =
CreateDefaultPool ( ) ;
RunAfterDelay ( params . satisfy_condition_delay , pool . get ( ) , [ & ] {
absl : : MutexLock l ( & mu ) ;
value = true ;
cv . Signal ( ) ;
} ) ;
absl : : MutexLock lock ( & mu ) ;
absl : : Time start_time = absl : : Now ( ) ;
absl : : Duration timeout = params . wait_timeout ;
absl : : Time deadline = start_time + timeout ;
while ( ! value ) {
if ( params . use_absolute_deadline ? cv . WaitWithDeadline ( & mu , deadline )
: cv . WaitWithTimeout ( & mu , timeout ) ) {
break ; // deadline/timeout exceeded
}
timeout = deadline - absl : : Now ( ) ; // recompute
}
bool result = value ; // note: `mu` is still held
// The condition is already true:
cp - > Set ( true ) ;
TestAwaitTimeout ( cp , finite , true , immediate ) ;
TestLockWhenTimeout ( cp , finite , true , immediate ) ;
TestReaderLockWhenTimeout ( cp , finite , true , immediate ) ;
TestWaitTimeout ( cp , finite , true , immediate ) ;
// The condition becomes true before the expiry:
const absl : : Duration delay1 = finite / 2 ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , delay1 ) ;
TestAwaitTimeout ( cp , finite , true , delay1 ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , delay1 ) ;
TestLockWhenTimeout ( cp , finite , true , delay1 ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , delay1 ) ;
TestReaderLockWhenTimeout ( cp , finite , true , delay1 ) ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Signal , cp , true ) , delay1 ) ;
TestWaitTimeout ( cp , finite , true , delay1 ) ;
// The condition becomes true, but the timeout has already expired:
const absl : : Duration delay2 = finite * 2 ;
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Set , cp , true ) , 3 * delay2 ) ;
TestAwaitTimeout ( cp , finite , false , finite ) ;
TestLockWhenTimeout ( cp , finite , false , finite ) ;
TestReaderLockWhenTimeout ( cp , finite , false , finite ) ;
cp - > Await ( ) ; // wait for the scheduled Set() to complete
cp - > Set ( false ) ;
ScheduleAfter ( tp , std : : bind ( & Cond : : Signal , cp , true ) , delay2 ) ;
TestWaitTimeout ( cp , finite , false , finite ) ;
cp - > Wait ( ) ; // wait for the scheduled Signal() to complete
// The condition never becomes true:
cp - > Set ( false ) ;
TestAwaitTimeout ( cp , finite , false , finite ) ;
TestLockWhenTimeout ( cp , finite , false , finite ) ;
TestReaderLockWhenTimeout ( cp , finite , false , finite ) ;
TestWaitTimeout ( cp , finite , false , finite ) ;
}
TEST ( Mutex , Timeouts ) {
auto tp = CreateDefaultPool ( ) ;
for ( bool use_deadline : { false , true } ) {
Cond cond ( use_deadline ) ;
TestNegativeTimeouts ( tp . get ( ) , & cond ) ;
TestInfiniteTimeouts ( tp . get ( ) , & cond ) ;
TestFiniteTimeouts ( tp . get ( ) , & cond ) ;
if ( DelayIsWithinBounds ( params . expected_delay , absl : : Now ( ) - start_time ) ) {
EXPECT_EQ ( params . expected_result , result ) ;
break ;
}
}
}