@@ -19,6 +19,7 @@ import java.io.IOException
1919import  java.io.InterruptedIOException 
2020import  java.util.concurrent.TimeUnit 
2121import  java.util.concurrent.locks.Condition 
22+ import  kotlin.concurrent.Volatile 
2223import  kotlin.time.Duration 
2324import  kotlin.time.DurationUnit 
2425import  kotlin.time.toTimeUnit 
@@ -32,6 +33,12 @@ actual open class Timeout {
3233  private  var  deadlineNanoTime =  0L 
3334  private  var  timeoutNanos =  0L 
3435
36+   /* *
37+    * A sentinel that is updated to a new object on each call to [cancel]. Sample this property 
38+    * before and after an operation to test if the timeout was canceled during the operation. 
39+    */  
40+   @Volatile private  var  cancelMark:  Any?  =  null 
41+ 
3542  /* *
3643   * Wait at most `timeout` time before aborting an operation. Using a per-operation timeout means 
3744   * that as long as forward progress is being made, no sequence of operations will fail. 
@@ -107,6 +114,20 @@ actual open class Timeout {
107114    }
108115  }
109116
117+   /* *
118+    * Prevent all current applications of this timeout from firing. Use this when a time-limited 
119+    * operation should no longer be time-limited because the nature of the operation has changed. 
120+    * 
121+    * This function does not mutate the [deadlineNanoTime] or [timeoutNanos] properties of this 
122+    * timeout. It only applies to active operations that are limited by this timeout, and applies by 
123+    * allowing those operations to run indefinitely. 
124+    * 
125+    * Subclasses that override this method must call `super.cancel()`. 
126+    */  
127+   open  fun  cancel () {
128+     cancelMark =  Any ()
129+   }
130+ 
110131  /* *
111132   * Waits on `monitor` until it is signaled. Throws [InterruptedIOException] if either the thread 
112133   * is interrupted or if this timeout elapses before `monitor` is signaled. 
@@ -239,18 +260,23 @@ actual open class Timeout {
239260        timeoutNanos
240261      }
241262
242-       //  Attempt to wait that long. This will break out early if the monitor is notified.
243-       var  elapsedNanos =  0L 
244-       if  (waitNanos >  0L ) {
245-         val  waitMillis =  waitNanos /  1000000L 
246-         (monitor as  Object ).wait(waitMillis, (waitNanos -  waitMillis *  1000000L ).toInt())
247-         elapsedNanos =  System .nanoTime() -  start
248-       }
263+       if  (waitNanos <=  0 ) throw  InterruptedIOException (" timeout" 
249264
250-       //  Throw if the timeout elapsed before the monitor was notified.
251-       if  (elapsedNanos >=  waitNanos) {
252-         throw  InterruptedIOException (" timeout" 
253-       }
265+       val  cancelMarkBefore =  cancelMark
266+ 
267+       //  Attempt to wait that long. This will return early if the monitor is notified.
268+       val  waitMillis =  waitNanos /  1000000L 
269+       (monitor as  Object ).wait(waitMillis, (waitNanos -  waitMillis *  1000000L ).toInt())
270+       val  elapsedNanos =  System .nanoTime() -  start
271+ 
272+       //  If there's time remaining, we probably got the call we were waiting for.
273+       if  (elapsedNanos <  waitNanos) return 
274+ 
275+       //  Return without throwing if this timeout was canceled while we were waiting. Note that this
276+       //  return is a 'spurious wakeup' because Object.notify() was not called.
277+       if  (cancelMark != =  cancelMarkBefore) return 
278+ 
279+       throw  InterruptedIOException (" timeout" 
254280    } catch  (e:  InterruptedException ) {
255281      Thread .currentThread().interrupt() //  Retain interrupted status.
256282      throw  InterruptedIOException (" interrupted" 
0 commit comments