11extern crate futures;
22
3+ use std:: sync:: { Arc , Weak } ;
34use std:: thread;
5+ use std:: time:: { Duration , Instant } ;
46
57use futures:: prelude:: * ;
68use futures:: sync:: mpsc:: * ;
9+ use futures:: task;
710
811#[ test]
912fn smoke ( ) {
@@ -19,3 +22,131 @@ fn smoke() {
1922
2023 t. join ( ) . unwrap ( )
2124}
25+
26+ // Stress test that `try_send()`s occurring concurrently with receiver
27+ // close/drops don't appear as successful sends.
28+ #[ test]
29+ fn stress_try_send_as_receiver_closes ( ) {
30+ const AMT : usize = 10000 ;
31+ // To provide variable timing characteristics (in the hopes of
32+ // reproducing the collision that leads to a race), we busy-re-poll
33+ // the test MPSC receiver a variable number of times before actually
34+ // stopping. We vary this countdown between 1 and the following
35+ // value.
36+ const MAX_COUNTDOWN : usize = 20 ;
37+ // When we detect that a successfully sent item is still in the
38+ // queue after a disconnect, we spin for up to 100ms to confirm that
39+ // it is a persistent condition and not a concurrency illusion.
40+ const SPIN_TIMEOUT_S : u64 = 10 ;
41+ const SPIN_SLEEP_MS : u64 = 10 ;
42+ struct TestRx {
43+ rx : Receiver < Arc < ( ) > > ,
44+ // The number of times to query `rx` before dropping it.
45+ poll_count : usize
46+ }
47+ struct TestTask {
48+ command_rx : Receiver < TestRx > ,
49+ test_rx : Option < Receiver < Arc < ( ) > > > ,
50+ countdown : usize ,
51+ }
52+ impl TestTask {
53+ /// Create a new TestTask
54+ fn new ( ) -> ( TestTask , Sender < TestRx > ) {
55+ let ( command_tx, command_rx) = channel :: < TestRx > ( 0 ) ;
56+ (
57+ TestTask {
58+ command_rx : command_rx,
59+ test_rx : None ,
60+ countdown : 0 , // 0 means no countdown is in progress.
61+ } ,
62+ command_tx,
63+ )
64+ }
65+ }
66+ impl Future for TestTask {
67+ type Item = ( ) ;
68+ type Error = ( ) ;
69+ fn poll ( & mut self ) -> Poll < ( ) , ( ) > {
70+ // Poll the test channel, if one is present.
71+ if let Some ( ref mut rx) = self . test_rx {
72+ if let Ok ( Async :: Ready ( v) ) = rx. poll ( ) {
73+ let _ = v. expect ( "test finished unexpectedly!" ) ;
74+ }
75+ self . countdown -= 1 ;
76+ // Busy-poll until the countdown is finished.
77+ task:: current ( ) . notify ( ) ;
78+ }
79+ // Accept any newly submitted MPSC channels for testing.
80+ match self . command_rx . poll ( ) ? {
81+ Async :: Ready ( Some ( TestRx { rx, poll_count } ) ) => {
82+ self . test_rx = Some ( rx) ;
83+ self . countdown = poll_count;
84+ task:: current ( ) . notify ( ) ;
85+ } ,
86+ Async :: Ready ( None ) => return Ok ( Async :: Ready ( ( ) ) ) ,
87+ _ => { } ,
88+ }
89+ if self . countdown == 0 {
90+ // Countdown complete -- drop the Receiver.
91+ self . test_rx = None ;
92+ }
93+ Ok ( Async :: NotReady )
94+ }
95+ }
96+ let ( f, mut cmd_tx) = TestTask :: new ( ) ;
97+ let bg = thread:: spawn ( move || f. wait ( ) ) ;
98+ for i in 0 ..AMT {
99+ let ( mut test_tx, rx) = channel ( 0 ) ;
100+ let poll_count = i % MAX_COUNTDOWN ;
101+ cmd_tx. try_send ( TestRx { rx : rx, poll_count : poll_count } ) . unwrap ( ) ;
102+ let mut prev_weak: Option < Weak < ( ) > > = None ;
103+ let mut attempted_sends = 0 ;
104+ let mut successful_sends = 0 ;
105+ loop {
106+ // Create a test item.
107+ let item = Arc :: new ( ( ) ) ;
108+ let weak = Arc :: downgrade ( & item) ;
109+ match test_tx. try_send ( item) {
110+ Ok ( _) => {
111+ prev_weak = Some ( weak) ;
112+ successful_sends += 1 ;
113+ }
114+ Err ( ref e) if e. is_full ( ) => { }
115+ Err ( ref e) if e. is_disconnected ( ) => {
116+ // Test for evidence of the race condition.
117+ if let Some ( prev_weak) = prev_weak {
118+ if prev_weak. upgrade ( ) . is_some ( ) {
119+ // The previously sent item is still allocated.
120+ // However, there appears to be some aspect of the
121+ // concurrency that can legitimately cause the Arc
122+ // to be momentarily valid. Spin for up to 100ms
123+ // waiting for the previously sent item to be
124+ // dropped.
125+ let t0 = Instant :: now ( ) ;
126+ let mut spins = 0 ;
127+ loop {
128+ if prev_weak. upgrade ( ) . is_none ( ) {
129+ break ;
130+ }
131+ assert ! ( t0. elapsed( ) < Duration :: from_secs( SPIN_TIMEOUT_S ) ,
132+ "item not dropped on iteration {} after \
133+ {} sends ({} successful). spin=({})",
134+ i, attempted_sends, successful_sends, spins
135+ ) ;
136+ spins += 1 ;
137+ thread:: sleep ( Duration :: from_millis ( SPIN_SLEEP_MS ) ) ;
138+ }
139+ }
140+ }
141+ break ;
142+ }
143+ Err ( ref e) => panic ! ( "unexpected error: {}" , e) ,
144+ }
145+ attempted_sends += 1 ;
146+ }
147+ }
148+ drop ( cmd_tx) ;
149+ bg. join ( )
150+ . expect ( "background thread join" )
151+ . expect ( "background thread result" ) ;
152+ }
0 commit comments