All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatest.concurrent.Timeouts.scala Maven / Gradle / Ivy

There is a newer version: 2.0.M6-SNAP27
Show newest version
/*
 * Copyright 2001-2012 Artima, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.scalatest.concurrent

import java.util.TimerTask
import java.util.Timer
import org.scalatest.exceptions.StackDepthExceptionHelper.getStackDepthFun
import org.scalatest.Resources
import org.scalatest.exceptions.StackDepthException
import java.nio.channels.ClosedByInterruptException
import java.nio.channels.Selector
import java.net.Socket
import org.scalatest.time.Span
import org.scalatest.exceptions.TestFailedDueToTimeoutException

/**
 * Trait that provides a failAfter construct, which allows you to specify a time limit for an
 * operation passed as a by-name parameter, as well as a way to interrupt it if the operation exceeds its time limit.
 *
 * 

* The time limit is passed as the first parameter, as a Span. The operation is * passed as the second parameter. And an Interruptor, a strategy for interrupting the operation, is * passed as an implicit third parameter. Here's a simple example of its use: *

* *
 * failAfter(Span(100, Millis)) {
 *   Thread.sleep(200)
 * }
 * 
* *

* The above code, after 100 milliseconds, will produce a TestFailedDueToTimeoutException with a message * that indicates a timeout expired: *

* *

* The code passed to failAfter did not complete within 100 milliseconds. *

* *

* If you prefer you can mix in or import the members of SpanSugar and place a units value after the integer timeout. * Here are some examples: *

* *
 * import org.scalatest.time.SpanSugar._
 *
 * failAfter(100 millis) {
 *   Thread.sleep(200)
 * }
 *
 * failAfter(1 second) {
 *   Thread.sleep(2000)
 * }
 * 
* *

* The code passed via the by-name parameter to failAfter will be executed by the thread that invoked * failAfter, so that no synchronization is necessary to access variables declared outside the by-name. *

* *
 * var result = -1 // No need to make this volatile
 * failAfter(100 millis) {
 *   result = accessNetService()
 * }
 * result should be (99)
 * 
* *

* The failAfter method will create a timer that runs on a different thread than the thread that * invoked failAfter, so that it can detect when the timeout has expired and attempt to interrupt * the main thread. Because different operations can require different interruption strategies, the failAfter * method accepts an implicit third parameter of type Interruptor that is responsible for interrupting * the main thread. *

* *

Configuring failAfter with an Interruptor

* *

* This trait declares an implicit val named defaultInterruptor, * initialized with a ThreadInterruptor, which attempts to interrupt the main thread by invoking * Thread.interrupt. If you wish to use a different strategy, you can override this val (or hide * it, for example if you imported the members of Timeouts rather than mixing it in). Here's an example * in which the default interruption method is changed to DoNotInterrupt, which does not attempt to * interrupt the main thread in any way: *

* *
 * override val defaultInterruptor = DoNotInterrupt
 * failAfter(100 millis) {
 *   Thread.sleep(500)
 * }
 * 
* *

* As with the default Interruptor, the above code will eventually produce a * TestFailedDueToTimeoutException with a message that indicates a timeout expired. However, instead * of throwing the exception after approximately 100 milliseconds, it will throw it after approximately 500 milliseconds. *

* *

* This illustrates an important feature of failAfter: it will throw a TestFailedDueToTimeoutException * if the code passed as the by-name parameter takes longer than the specified timeout to execute, even if it * is allowed to run to completion beyond the specified timeout and returns normally. *

* *

* ScalaTest provides the following Interruptor implementations: *

* * * * * * * * * * * * * * * * * * * * * * *
* Interruptor implementation * * Usage *
* ThreadInterruptor * * The default interruptor, invokes interrupt on the main test thread. This will * set the interrupted status for the main test thread and, * if the main thread is blocked, will in some cases cause the main thread complete abruptly with * an InterruptedException. *
* DoNotInterrupt * * Does not attempt to interrupt the main test thread in any way *
* SelectorInterruptor * * Invokes wakeup on the passed java.nio.channels.Selector, which * will cause the main thread, if blocked in Selector.select, to complete abruptly with a * ClosedSelectorException. *
* SocketInterruptor * * Invokes close on the java.io.Socket, which * will cause the main thread, if blocked in a read or write of an java.io.InputStream or * java.io.OutputStream that uses the Socket, to complete abruptly with a * SocketException. *
* *

* You may wish to create your own Interruptor in some situations. For example, if your operation is performing * a loop and can check a volatile flag each pass through the loop. You could in that case write an Interruptor that * sets that flag so that the next time around, the loop would exit. *

* * @author Chua Chee Seng * @author Bill Venners */ trait Timeouts { private class TimeoutTask(testThread: Thread, interrupt: Interruptor) extends TimerTask { @volatile var timedOut = false @volatile var needToResetInterruptedStatus = false override def run() { timedOut = true val beforeIsInterrupted = testThread.isInterrupted() interrupt(testThread) val afterIsInterrupted = testThread.isInterrupted() if(!beforeIsInterrupted && afterIsInterrupted) needToResetInterruptedStatus = true } } /** * Implicit Interruptor value defining a default interruption strategy for the failAfter method. * *

* To change the default Interruptor configuration, override or hide this val with another implicit * Interruptor. *

*/ implicit val defaultInterruptor: Interruptor = ThreadInterruptor /** * Executes the passed function, enforcing the passed time limit by attempting to interrupt the function if the * time limit is exceeded, and throwing TestFailedDueToTimeoutException if the time limit has been * exceeded after the function completes. * *

* If the function completes before the timeout expires: *

* *
    *
  • If the function returns normally, this method will return normally.
  • *
  • If the function completes abruptly with an exception, this method will complete abruptly with that same exception.
  • *
* *

* If the function completes after the timeout expires: *

* *
    *
  • If the function returns normally, this method will complete abruptly with a TestFailedDueToTimeoutException.
  • *
  • If the function completes abruptly with an exception, this method will complete abruptly with a TestFailedDueToTimeoutException that includes the exception thrown by the function as its cause.
  • *
* *

* If the interrupted status of the main test thread (the thread that invoked failAfter) was not invoked * when failAfter was invoked, but is set after the operation times out, it is reset by this method before * it completes abruptly with a TestFailedDueToTimeoutException. The interrupted status will be set by * ThreadInterruptor, the default Interruptor implementation. *

* * @param timeout the maximimum amount of time allowed for the passed operation * @param fun the operation on which to enforce the passed timeout * @param interruptor a strategy for interrupting the passed operation */ def failAfter[T](timeout: Span)(fun: => T)(implicit interruptor: Interruptor): T = { timeoutAfter( timeout, fun, interruptor, t => new TestFailedDueToTimeoutException( sde => Some(Resources("timeoutFailedAfter", timeout.prettyString)), t, getStackDepthFun("Timeouts.scala", "failAfter"), None, timeout ) ) } /* Uncomment for 2.0 def cancelAfter[T](timeout: Span)(f: => T)(implicit interruptor: Interruptor): T = { timeoutAfter(timeout, f, interruptor, t => new TestCanceledException(sde => Some(Resources("timeoutCanceledAfter", timeout.toString)), t, getStackDepthFun("Timeouts.scala", "cancelAfter"))) } */ private def timeoutAfter[T](timeout: Span, f: => T, interruptor: Interruptor, exceptionFun: Option[Throwable] => StackDepthException): T = { val timer = new Timer() val task = new TimeoutTask(Thread.currentThread(), interruptor) timer.schedule(task, timeout.totalNanos / 1000 / 1000) // TODO: Probably use a sleep so I can use nanos try { val result = f timer.cancel() if (task.timedOut) { if (task.needToResetInterruptedStatus) Thread.interrupted() // To reset the flag probably. He only does this if it was not set before and was set after, I think. throw exceptionFun(None) } result } catch { case t: Throwable => timer.cancel() // Duplicate code could be factored out I think. Maybe into a finally? Oh, not that doesn't work. So a method. if(task.timedOut) { if (task.needToResetInterruptedStatus) Thread.interrupted() // Clear the interrupt status (There's a race condition here, but not sure we an do anything about that.) throw exceptionFun(Some(t)) } else throw t } } } /** * Companion object that facilitates the importing of Timeouts members as * an alternative to mixing in the trait. One use case is to import Timeouts's members so you can use * them in the Scala interpreter. */ object Timeouts extends Timeouts




© 2015 - 2024 Weber Informatics LLC | Privacy Policy