Search code examples
scalaconcurrencyspdf

Concurrency with scala.sys.process.ProcessBuilder


I'm using sPdf's run method to render HTML as a PDF file.

run uses scala.sys.process.ProcessBuilder and it's ! method:

/** Starts the process represented by this builder, blocks until it exits, and
    * returns the exit code.  Standard output and error are sent to the console.
    */
  def ! : Int

My controller is using a Future to asynchronously execute the conversion but won't spdf block all other interim executions?

 Future { pdf.run(sourceUrl, outputStream) } map { exitCode =>
     outputSteam.toByteArray
 }

UPDATE

Thanks for your answer Paul did a little test and yeah looks to be that way :)

If I update sPdf's run like so:

  def run[A, B](sourceDocument: A, destinationDocument: B)(implicit sourceDocumentLike: SourceDocumentLike[A], destinationDocumentLike: DestinationDocumentLike[B]): Int = {
      println("start/ " + System.currentTimeMillis)
      < ... code removed ... >
      val result = (sink compose source)(process).!
      println("finish/ " + System.currentTimeMillis)
      result
  }

and I execute three consecutive requests the stdout prints

start 1461288779013
start 1461288779014
start 1461288779014
finish 1461288781018
finish 1461288781020
finish 1461288781020

Which looks like asynchronous execution.


Solution

  • This is Pdf#run:

      def run[A, B](sourceDocument: A, destinationDocument: B)(implicit sourceDocumentLike: SourceDocumentLike[A], destinationDocumentLike: DestinationDocumentLike[B]): Int = {
        val commandLine = toCommandLine(sourceDocument, destinationDocument)
        val process = Process(commandLine)
        def source = sourceDocumentLike.sourceFrom(sourceDocument) _
        def sink = destinationDocumentLike.sinkTo(destinationDocument) _
    
        (sink compose source)(process).!
      }
    

    There's no synchronization that prevents parallel execution. Assuming that the ExecutionContext has enough available threads,

    Future { pdf.run(sourceUrl, outputStream) } map { exitCode =>
        outputSteam.toByteArray
    }
    
    Future { pdf.run(sourceUrl, outputStream) } map { exitCode =>
        outputSteam.toByteArray
    }
    

    will execute in parallel.


    If instead, the run method had been, say, surrounded by a synchronized block, only one invocation would execute per Pdf instance. But there's no reason to prevent concurrency here, so the author didn't.