6

Refering to the following implementation mentioned in:

http://doc.akka.io/docs/akka-http/10.0.5/scala/http/client-side/host-level.html

val poolClientFlow = Http().cachedHostConnectionPool[Promise[HttpResponse]]("akka.io")
val queue =
  Source.queue[(HttpRequest, Promise[HttpResponse])](QueueSize, OverflowStrategy.dropNew)
    .via(poolClientFlow)
    .toMat(Sink.foreach({
      case ((Success(resp), p)) => p.success(resp)
      case ((Failure(e), p))    => p.failure(e)
    }))(Keep.left)
    .run()

Is it thread safe to offer the queue http requests from multiple threads ? If it isn't, what is the best way to implement such requirement ? using a dedicated actor perhaps ?

Niv-Nik
  • 61
  • 2

2 Answers2

2

No, it is not thread safe, as per the api doc: SourceQueue that current source is materialized to is for single thread usage only.

A dedicated actor would work fine but, if you can, using Source.actorRef (doc link) instead of Source.queue would be easier.

In general, the downside of Source.actorRef is the lack of backpressure, but as you use OverflowStrategy.dropNew, it is clear you don't expect backpressure. As such, you can get the same behaviour using Source.actorRef.

Frederic A.
  • 3,504
  • 10
  • 17
  • Thank you very much for your comment. I have a requirement to signal some sort of failure in case of buffer overflow, such as return Future.failed(BufferFlowException), which to my understanding can't be implemented using the Source.actorRef. Source Queue fits the description using the QueueOfferResult API. – Niv-Nik Mar 25 '17 at 15:23
  • @Nik I still think I answered the original question properly. As for your new requirement, I would then indeed implement an Actor specialized in handling the queue. It would need to add to the queue using `queue.offer(???) pipeTo self`, and then it would be able to react to failure by handling the various subtypes of `QueueOfferResult` in its `receive` method. – Frederic A. Mar 25 '17 at 16:48
  • To prevent actor mailbox overflow you can use `NonBlockingBoundedMailbox` (http://doc.akka.io/docs/akka/current/scala/mailboxes.html). – khiramatsu Mar 26 '17 at 07:00
  • 1
    The most current docs have the "single thread usage only" line removed: http://doc.akka.io/api/akka/2.5/akka/stream/scaladsl/Source$.html#queue[T](bufferSize:Int,overflowStrategy:akka.stream.OverflowStrategy):akka.stream.scaladsl.Source[T,akka.stream.scaladsl.SourceQueueWithComplete[T]] – Dan Li Sep 12 '17 at 20:52
  • 1
    That's correct, its removal can be tracked here: https://github.com/akka/akka/issues/23081 – Frederic A. Sep 13 '17 at 00:55
2

As correctly stated by @frederic-a, SourceQueue is not a thread safe solution.

Perhaps a fit solution would be to use a MergeHub(see docs for more details). This effectively allows you to run your graph in two stages.

  1. from your hub to your sink (this materializes to a sink)
  2. distribute the sink materialized at point 1 to your users. Sinks are actually designed to be distributed, so this is perfectly safe.

This solution would be safe backpressure-wise, as per MergeHub behaviour

If the consumer cannot keep up then all of the producers are backpressured.

Code example below:

val reqSink: Sink[(HttpRequest, Promise[HttpResponse]), NotUsed] =
  MergeHub.source[(HttpRequest, Promise[HttpResponse])](perProducerBufferSize = 16)
  .via(poolClientFlow)
  .toMat(Sink.foreach({
    case ((Success(resp), p)) => p.success(resp)
    case ((Failure(e), p))    => p.failure(e)
  }))(Keep.left)
  .run()

// on the user threads

val source: Source[(HttpRequest, Promise[HttpResponse]), NotUsed] = ???
source.runWith(reqSink)
Stefano Bonetti
  • 8,973
  • 1
  • 25
  • 44
  • Any official doc to support this? I'm still doesn't sure entirely as there are many versions on the web, some claim that it is, some that it isn't. – Niv-Nik Feb 17 '19 at 08:44