-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
finagle-core: newClient closed service lifecycle
Problem: Closing a session created by newClient's `ServiceFactory` does not prevent its reuse, as the connection remains alive in the connection pool Solution / Result: New client stack module that sits above `DefaultPool`. If FactoryToService is not enabled, the returned service from DefaultPool is wrapped in a `ClosableService` that ensures closed sessions are not reused. JIRA Issues: CSL-9151 Differential Revision: https://phabricator.twitter.biz/D407805
- Loading branch information
1 parent
bcfdbd3
commit c64bea0
Showing
10 changed files
with
190 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
finagle-core/src/main/scala/com/twitter/finagle/service/ClosableService.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.twitter.finagle.service | ||
|
||
import com.twitter.finagle._ | ||
import com.twitter.util.{Future, Time} | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
private[finagle] object ClosableService { | ||
val role = Stack.Role("ClosableService") | ||
|
||
/** | ||
* Client stack module that creates a [[ClosableService]] wrapper when services are managed | ||
* independently of the client stack. | ||
*/ | ||
def client[Req, Rep]: Stackable[ServiceFactory[Req, Rep]] = | ||
new Stack.Module1[FactoryToService.Enabled, ServiceFactory[Req, Rep]] { | ||
val role = ClosableService.role | ||
val description = "Explictly prevent reuse of a closed session" | ||
def make( | ||
factoryToService: FactoryToService.Enabled, | ||
next: ServiceFactory[Req, Rep] | ||
): ServiceFactory[Req, Rep] = { | ||
if (factoryToService.enabled) next | ||
else { | ||
// session lifecycle is managed independently. Connections can be pooled so we must | ||
// make sure a closed session is not reused | ||
next.map(new ClosableService(_) { | ||
def closedException = new ServiceReturnedToPoolException | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* A service wrapper that prevents reuse of the `underlying` service after the first call to | ||
* `.close`. | ||
*/ | ||
private[service] abstract class ClosableService[Req, Rep](underlying: Service[Req, Rep]) | ||
extends Service[Req, Rep] { | ||
private val closed = new AtomicBoolean(false) | ||
|
||
protected def closedException: Exception | ||
|
||
override def apply(req: Req): Future[Rep] = { | ||
if (closed.get) Future.exception(closedException) | ||
else underlying(req) | ||
} | ||
|
||
override def close(deadline: Time): Future[Unit] = { | ||
val closeUnderlying: Boolean = closed.compareAndSet(false, true) | ||
|
||
if (closeUnderlying) underlying.close(deadline) | ||
else Future.Done | ||
} | ||
|
||
override def status: Status = { | ||
if (closed.get) Status.Closed | ||
else underlying.status | ||
} | ||
} |
33 changes: 0 additions & 33 deletions
33
finagle-core/src/main/scala/com/twitter/finagle/service/CloseOnReleaseService.scala
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
finagle-core/src/test/scala/com/twitter/finagle/service/ClosableServiceTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.twitter.finagle.service | ||
|
||
import com.twitter.conversions.DurationOps._ | ||
import com.twitter.finagle.{Service, ServiceClosedException, Status} | ||
import com.twitter.util.{Await, Future, Time} | ||
import org.scalatest.FunSuite | ||
|
||
class ClosableServiceTest extends FunSuite { | ||
def await[A](f: Future[A]): A = Await.result(f, 5.seconds) | ||
|
||
trait Ctx { | ||
var numClosed = 0 | ||
private val noOpService = new Service[Unit, Unit] { | ||
def apply(req: Unit): Future[Unit] = Future.Done | ||
override def close(time: Time) = { | ||
numClosed += 1 | ||
Future.Done | ||
} | ||
} | ||
|
||
val svc = new ClosableService(noOpService) { | ||
val closedException = new ServiceClosedException | ||
} | ||
} | ||
|
||
test("cannot reuse a closed session")(new Ctx { | ||
await(svc()) | ||
|
||
assert(svc.status == Status.Open) | ||
assert(numClosed == 0) | ||
|
||
await(svc.close()) | ||
intercept[Exception] { | ||
await(svc()) | ||
} | ||
|
||
assert(svc.status == Status.Closed) | ||
assert(numClosed == 1) | ||
}) | ||
|
||
test("only closes the underlying session once")(new Ctx { | ||
await(svc.close()) | ||
await(svc.close()) | ||
|
||
assert(numClosed == 1) | ||
}) | ||
} |
46 changes: 0 additions & 46 deletions
46
finagle-core/src/test/scala/com/twitter/finagle/service/CloseOnReleaseServiceTest.scala
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters