1

Building on the answer provided at Is there a FIFO stream in Scala?, I would like to know how to use the FIFOStream (renamed to QueueStream below) in practice, and concurrently. Since I'm rather new to Scala and Rx, I'm sticking with Futures so far, but I'd welcome alternatives (especially if using Futures in this way is too painful).

The problem is exemplified by the FIXME comment in the below worksheet; the comment says it doesn't run, but based on past experience I think it may be deadlocking somehow.

import java.util.concurrent.{Executors, BlockingQueue, LinkedBlockingQueue}
import scala.collection.JavaConversions._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
import scala.language.postfixOps

implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(8))


class QueueStream[A]( private val queue: BlockingQueue[Option[A]] ) {

  implicit protected val defaultTimeout: Duration = 100 milliseconds

  def toStream(q: BlockingQueue[Option[A]] = queue): Future[Stream[A]] = {
    def computeStream(q: BlockingQueue[Option[A]] = queue): Stream[A] =
      queue.take() match {
        case Some(a) => Stream cons(a, computeStream())
        case None => Stream.empty
      }
    Future {
      computeStream()
    }
  }
  def toStreamNoWait(q: BlockingQueue[Option[A]] = queue ):
  Future[Stream[A]] = {
    def computeStream(q: BlockingQueue[Option[A]] = queue): Stream[A] = {
      val timeout = implicitly[Duration]
      queue.poll(timeout.toMillis, MILLISECONDS) match {
        case Some(a) => Stream cons(a, computeStream())
        case None => Stream.empty
      }
    }
    Future {
      computeStream()
    }
  }

  def size () = queue.size()
  def close() = queue add None
  def enqueue( as: A* ) = queue addAll as.map( Some(_) )
}
object QueueStream {
  def apply[A]() = new QueueStream[A](new LinkedBlockingQueue)
}
def printIntsInQueue(queue: QueueStream[Int]): Unit = {
  println("About to print some Ints...")
  //FIXME: this doesn't run!:
  queue.toStream().foreach { ff => ff foreach { ss =>
    println(s"LENGTH IS: $ss")
  }}
}
val stringList = List("abc", "123", "abc123")
val stringQueue = QueueStream[String]()
stringList.foreach{ii => stringQueue.enqueue(ii)}
val strlenQueue = QueueStream[Int]()
var sz = strlenQueue.size()
println(s"strlenQueue size is $sz")

stringQueue.toStream().foreach{ ff => ff foreach { ss =>
  strlenQueue.enqueue(ss.length)
}}
sz = strlenQueue.size()
println(s"strlenQueue size is $sz")
printIntsInQueue(strlenQueue)

Edit: Working code

import java.util.concurrent.{Executors, BlockingQueue, LinkedBlockingQueue}
import scala.collection.JavaConversions._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
import scala.language.postfixOps

implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(8))

class QueueStream[A]( private val queue: BlockingQueue[Option[A]] ) {

  implicit protected val defaultTimeout: Duration = 100 milliseconds

  def toStream(q: BlockingQueue[Option[A]] = queue ): Stream[A] =
    queue.take() match {
      case Some(a) => Stream cons(a, toStream())
      case None => Stream.empty
    }
  def toStreamNoWait(q: BlockingQueue[Option[A]] = queue ): Stream[A] = {
    val timeout = implicitly[Duration]
    queue.poll(timeout.toMillis, MILLISECONDS) match {
      case Some(a) => Stream cons(a, toStreamNoWait())
      case null => Stream.empty
      case None => Stream.empty
    }
  }

  def size () = queue.size()
  def close() = queue add None
  def enqueue( as: A* ) = queue addAll as.map( Some(_) )
}
object QueueStream {
  def apply[A]() = new QueueStream[A](new LinkedBlockingQueue)
}

def printIntsInQueue(queue: QueueStream[Int]): Unit = {
  println("About to print some Ints...")
  //FIXME: this doesn't run!:
  queue.toStream().foreach {ss =>
    println(s"LENGTH IS: $ss")
  }
}
val stringList = List("abc", "123", "abc123")
val stringQueue = QueueStream[String]()
stringList.foreach{ii => stringQueue.enqueue(ii)}
stringQueue.close()
val strlenQueue = QueueStream[Int]()
var sz = strlenQueue.size()
println(s"strlenQueue size is $sz")
stringQueue.toStream().foreach { ss =>
  strlenQueue.enqueue(ss.length)
}
strlenQueue.close()
sz = strlenQueue.size()
println(s"strlenQueue size is $sz")
printIntsInQueue(strlenQueue)
Community
  • 1
  • 1
bbarker
  • 11,636
  • 9
  • 38
  • 62

1 Answers1

2

Getting the next element from a blocking queue is a blocking operation; it suspends the current thread until the next element is available.

QueueStream is backed by a blocking queue, such that when you ask it to process "all of the elements" in the queue (via foreach), it will block waiting for more elements until it has reached the end of the stream. For this particular class, the end of the stream is denoted by a None element, which gets inserted into the queue when you call close().

So it's "hanging" because you haven't closed the stream using close().

Wrapping the stream in a future as you have done is generally not needed since streams are computed lazily on demand. The caller could still traverse the stream in a background thread or future if desired.

JimN
  • 3,120
  • 22
  • 35
  • Thanks! Very silly mistake indeed. Also, initially I wasn't wrapping it in a Future, and have reverted - this also seemed to help solve the problem more easily. I'll paste in the working code above. – bbarker Aug 25 '15 at 01:08
  • Unfortunately, I'm not sure this will work for infinite streams though (which I'm trying to use in production code). Blocking for the last element is fine, but it seems foreach is not lazy if it requires us to have the last element before initiating computation? – bbarker Aug 25 '15 at 01:23
  • You don't need the last element before starting; you just need the last element so that foreach can complete. The close() method can be called by a different thread than the thread invoking foreach. If the stream is truly infinite, foreach will never complete. Which might be ok depending on your use case. – JimN Aug 25 '15 at 01:42