@@ -24,11 +24,13 @@ import akka.http.scaladsl.marshalling._
24
24
import akka .http .scaladsl .model ._
25
25
import akka .http .scaladsl .settings .ConnectionPoolSettings
26
26
import akka .http .scaladsl .unmarshalling ._
27
- import akka .stream .{OverflowStrategy , QueueOfferResult }
28
27
import akka .stream .scaladsl .{Flow , _ }
28
+ import akka .stream .{KillSwitches , QueueOfferResult }
29
+ import org .apache .openwhisk .common .AkkaLogging
29
30
import spray .json ._
30
- import scala . concurrent .{ ExecutionContext , Future , Promise }
31
+
31
32
import scala .concurrent .duration ._
33
+ import scala .concurrent .{ExecutionContext , Future , Promise }
32
34
import scala .util .{Failure , Success , Try }
33
35
34
36
/**
@@ -40,14 +42,16 @@ import scala.util.{Failure, Success, Try}
40
42
* extra queueing mechanism.
41
43
*/
42
44
class PoolingRestClient (
43
- protocol : String ,
44
- host : String ,
45
- port : Int ,
46
- queueSize : Int ,
47
- httpFlow : Option [Flow [(HttpRequest , Promise [HttpResponse ]), (Try [HttpResponse ], Promise [HttpResponse ]), Any ]] = None ,
48
- timeout : Option [FiniteDuration ] = None )(implicit system : ActorSystem ) {
45
+ protocol : String ,
46
+ host : String ,
47
+ port : Int ,
48
+ queueSize : Int ,
49
+ httpFlow : Option [Flow [(HttpRequest , Promise [HttpResponse ]), (Try [HttpResponse ], Promise [HttpResponse ]), Any ]] = None ,
50
+ timeout : Option [FiniteDuration ] = None )(implicit system : ActorSystem ) {
49
51
require(protocol == " http" || protocol == " https" , " Protocol must be one of { http, https }." )
50
52
53
+ private val logging = new AkkaLogging (system.log)
54
+
51
55
protected implicit val context : ExecutionContext = system.dispatcher
52
56
53
57
// if specified, override the ClientConnection idle-timeout and keepalive socket option value
@@ -72,16 +76,19 @@ class PoolingRestClient(
72
76
// Additional queue in case all connections are busy. Should hardly ever be
73
77
// filled in practice but can be useful, e.g., in tests starting many
74
78
// asynchronous requests in a very short period of time.
75
- private val requestQueue = Source
76
- .queue(queueSize, OverflowStrategy .dropNew )
79
+ private val (( requestQueue, killSwitch), sinkCompletion) = Source
80
+ .queue(queueSize)
77
81
.via(httpFlow.getOrElse(pool))
82
+ .viaMat(KillSwitches .single)(Keep .both)
78
83
.toMat(Sink .foreach({
79
84
case (Success (response), p) =>
80
85
p.success(response)
81
86
case (Failure (error), p) =>
82
87
p.failure(error)
83
- }))(Keep .left)
84
- .run
88
+ }))(Keep .both)
89
+ .run()
90
+
91
+ sinkCompletion.onComplete(_ => shutdown())
85
92
86
93
/**
87
94
* Execute an HttpRequest on the underlying connection pool.
@@ -96,11 +103,11 @@ class PoolingRestClient(
96
103
97
104
// When the future completes, we know whether the request made it
98
105
// through the queue.
99
- requestQueue.offer(request -> promise).flatMap {
100
- case QueueOfferResult .Enqueued => promise.future
101
- case QueueOfferResult .Dropped => Future .failed(new Exception (" DB request queue is full." ))
102
- case QueueOfferResult .QueueClosed => Future .failed(new Exception (" DB request queue was closed." ))
103
- case QueueOfferResult .Failure (f) => Future .failed(f)
106
+ requestQueue.offer(request -> promise) match {
107
+ case QueueOfferResult .Enqueued => promise.future
108
+ case QueueOfferResult .Dropped => Future .failed(new Exception (" Request queue is full." ))
109
+ case QueueOfferResult .QueueClosed => Future .failed(new Exception (" Request queue was closed." ))
110
+ case QueueOfferResult .Failure (f) => Future .failed(f)
104
111
}
105
112
}
106
113
@@ -127,7 +134,13 @@ class PoolingRestClient(
127
134
}
128
135
}
129
136
130
- def shutdown (): Future [Unit ] = Future .unit
137
+ def shutdown (): Future [Unit ] = {
138
+ killSwitch.shutdown()
139
+ Try (requestQueue.complete()).recover {
140
+ case t : IllegalStateException => logging.error(this , t.getMessage)
141
+ }
142
+ Future .unit
143
+ }
131
144
}
132
145
133
146
object PoolingRestClient {
0 commit comments