Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MaxTotal does not take idle resources into account #440

Open
Jasper-M opened this issue Jan 17, 2023 · 2 comments
Open

MaxTotal does not take idle resources into account #440

Jasper-M opened this issue Jan 17, 2023 · 2 comments
Labels
bug Something isn't working

Comments

@Jasper-M
Copy link

Jasper-M commented Jan 17, 2023

scala> val p = IO.ref(0).flatMap(ref =>
     |   KeyPool.Builder((i: Int) => Resource.make(ref.update(_ + 1).as(i))(i => IO.println(s"kill $i") *> ref.update(_ - 1)))
     |     .withMaxTotal(5)
     |     .withMaxIdle(5)
     |     .build.use{ keypool =>
     |       keypool.take(0).replicateA(5).use(_ => ref.get.flatMap(i => IO.println(s"total $i"))) *>
     |       keypool.take(1).replicateA(5).use(_ => ref.get.flatMap(i => IO.println(s"total $i"))) *>
     |       IO.println("DONE") *>
     |       ref.get
     |     }
     | )
val p: cats.effect.IO[Int] = IO(...)

scala> p.unsafeRunSync()
total 5
total 10
kill 1
kill 1
kill 1
kill 1
DONE
kill 0
kill 0
kill 0
kill 0
kill 0
kill 1
val res29: Int = 6

As you can see, at one point 10 resources are allocated, while the max is set to 5. At the end there are still 6 which might be a separate issue...

@rossabaker
Copy link
Member

I think the problem is we're releasing the permit (acquired line 299) after returning items to the pool (line 307), not when the connection is dropped from the pool.

for {
_ <- kp.kpMaxTotalSem.permit
optR <- Resource.eval(kp.kpVar.modify(go))
releasedState <- Resource.eval(Ref[F].of[Reusable](kp.kpDefaultReuseState))
resource <- Resource.make(optR.fold(kp.kpRes(k).allocated)(r => Applicative[F].pure(r))) {
resource =>
for {
reusable <- releasedState.get
out <- reusable match {
case Reusable.Reuse => put(kp, k, resource._1, resource._2).attempt.void
case Reusable.DontReuse => resource._2.attempt.void
}
} yield out
}
} yield new Managed(resource._1, optR.isDefined, releasedState)

Note that the original definition of maxTotal, through 0.4.7, allowed unlimited allocations. There was discussion of the new semantics being released as 0.5 (see #389), but it got quietly released as 0.4.8 before anything was resolved.

@rossabaker
Copy link
Member

It's more fiddly than that: if we don't release when we return the connection to the idle state, then if the pool is full of idle connections, no connection can be taken for a key which has no idle connection. The crufty old http4s-blaze connection pool handles this by releasing an idle connection for another key to make room.

We need to decide whether to fix this in place in an 0.4.9 -- which would be the second consecutive semantic change on that release line -- or push toward 0.5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants