Skip to content

Fix race condition on Windows by locking measurement file writes #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/main/scala/scoverage/Invoker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,22 @@ import java.io.FileWriter
/** @author Stephen Samuel */
object Invoker {

/**
* We record that the given id has been invoked by appending its id to the coverage
* data file.
*
* This will happen concurrently on as many threads as the application is using,
* so this method is synchronized.
*
* This method is not thread-safe if the threads are in different JVMs. You may not
* use `scoverage` on multiple processes in parallel without risking corruption
* of the measurement file.
*/
def invoked(id: Int, path: String) = {
val writer = new FileWriter(path, true)
writer.append(id.toString)
writer.append(';')
writer.close()
this.synchronized {
val writer = new FileWriter(path, true)
writer.write(id.toString + ';')
writer.close()
}
}
}
47 changes: 47 additions & 0 deletions src/test/scala/scoverage/InvokerConcurrencyTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package scoverage

import org.scalatest.FunSuite
import org.scalatest.BeforeAndAfter
import org.scalatest.OneInstancePerTest
import java.io.File
import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
import scala.collection.breakOut

/**
* Verify that [[Invoker.invoked()]] is thread-safe
*/
class InvokerConcurrencyTest extends FunSuite with BeforeAndAfter {

val measurementFile = new File("invoker-test.measurement")

before {
measurementFile.delete()
measurementFile.createNewFile()
}

test("calling Invoker.invoked on multiple threads does not corrupt the measurement file") {

val testIds: Set[Int] = (1 to 1000).toSet

// Create 1k "invoked" calls on the common thread pool, to stress test
// the method
val futures: List[Future[Unit]] = testIds.map { i: Int =>
future {
Invoker.invoked(i, measurementFile.toString)
}
}(breakOut)

futures.foreach(Await.result(_, 1.second))

// Now verify that the measurement file is not corrupted by loading it
val idsFromFile = IOUtils.invoked(measurementFile).toSet

idsFromFile === testIds
}

after {
measurementFile.delete()
}
}