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

Make UUIDGen more performant #3647

Open
wants to merge 25 commits into
base: series/3.x
Choose a base branch
from

Conversation

diogocanut
Copy link
Contributor

@diogocanut diogocanut commented May 25, 2023

On this PR I'm trying to address #2882 .

@diogocanut diogocanut changed the title WIP: Make UUIDGen more performant #2882 WIP: Make UUIDGen more performant May 25, 2023
@diogocanut diogocanut marked this pull request as draft May 25, 2023 22:26
Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice start!!

@armanbilge armanbilge linked an issue May 25, 2023 that may be closed by this pull request
@diogocanut diogocanut changed the title WIP: Make UUIDGen more performant Make UUIDGen more performant May 30, 2023
@diogocanut diogocanut marked this pull request as ready for review May 30, 2023 22:21
@diogocanut
Copy link
Contributor Author

After deprecating old UUIDGen fromSync methods, there's one pipeline check failing, because it's running in no warning mode, and there's a warning related to this line of code in IO.scala.

  def randomUUID: IO[UUID] = UUIDGen[IO].randomUUID

Warning:

[warn] /Users/diogocanut/Projects/cats-effect/core/shared/src/main/scala/cats/effect/IO.scala:1447:37: method fromSync in trait UUIDGenCompanionPlatformLowPriority is deprecated (since 3.6.0): Put an implicit `SecureRandom.javaSecuritySecureRandom` into scope to get a more efficient `UUIDGen`, or directly call `UUIDGen.fromSecureRandom`
[warn]   def randomUUID: IO[UUID] = UUIDGen[IO].randomUUID
[warn]                                     ^
[warn] one warning found

Should we provide an implicit for SecureRandom.javaSecuritySecureRandom somehow?

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're almost there ... this PR turned out to be incredibly complicated to figure out how to organize everything. Suffice to say, very very nicely done with this, thanks for persevering 💯

std/shared/src/main/scala/cats/effect/std/Random.scala Outdated Show resolved Hide resolved
@@ -1963,6 +1963,9 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits {
// later in the file
private[this] val _never: IO[Nothing] = asyncForIO.never

implicit def secureRandom: SecureRandom[IO] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise it will create a new one each time :)

Suggested change
implicit def secureRandom: SecureRandom[IO] =
lazy val secureRandom: SecureRandom[IO] =

Call-out for other reviewers: are we okay about unsafely creating a global secure random like this? I think having this would greatly enhance UX, but it creates a muddy precedent to use a pattern that has gone wrong sometimes e.g. FS2 Network ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, how to add the implicit to scope having it as lazy val?

[warn] /Users/diogocanut/Projects/cats-effect/core/shared/src/main/scala/cats/effect/IO.scala:1447:37: method fromSync in trait UUIDGenCompanionPlatformLowPriority is deprecated (since 3.6.0): Put an implicit `SecureRandom.javaSecuritySecureRandom` into scope to get a more efficient `UUIDGen`, or directly call `UUIDGen.fromSecureRandom`
[warn]   def randomUUID: IO[UUID] = UUIDGen[IO].randomUUID

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I meant to write implicit lazy val :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armanbilge What was the problem in FS2 Network?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it's a rather involved discussion :) so the problem is that FS2 promises an implicit Network[IO], but this is technically encapsulating some global resources (namely, I/O threads). This was working well enough, until we started the polling system work. It created a significant challenge, since now we have to thread the IOApps polling system into this globally promised Network[IO].

If instead FS2 had been offering a Resource[IO, Network[IO]] that you had to construct explicitly, it would have been a lot easier to work with.

The fundamental question here is, when is it okay to "cheat" and unsafely allocate global shared state or resources, instead of forcing it to be explicitly allocated in IO or Resource by the user. It's some kind of UX trade-off.

See a similar place where we faced this in:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think I understand. Btw, I'm not convinced a Random[IO] must be implicitly available: there isn't "one obvious preferable" Random[IO]. However, for SecureRandom, there could be: in fact, this PR tries to do that... so I think I'm okay with it in this specific case. (But it's possible i just don't appreciate the possible future pain this could cause :-)

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last minor thing, I think!

.delay(new JavaSecureRandom())
.map(r => new ScalaRandom[F](Applicative[F].pure(r)) with SecureRandom[F] {})
override def javaSecuritySecureRandom[F[_]: Sync]: F[SecureRandom[F]] =
super.javaSecuritySecureRandom[F]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other overload above (line 114) refers to this method. However, the behavior of the 2 are now quite different. I think the other overload could also benefit from the new improvements, although the n: Int does not make a lot of sense for the best case (nonblocking) PRNG.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for the best case n: Int. It will only sense in the worst case, when we fall back to a pool.

  private def pool[F[_]: Sync](n: Int): SecureRandom[F] = {
    val index = new AtomicInteger(0)
    val array = Array.fill(n)(new SRandom(new SecureRandom.JavaSecureRandom))

    def selectRandom: F[SRandom] = Sync[F].delay {
      val currentIndex = index.getAndUpdate(i => if (i < (n - 1)) i + 1 else 0)
      array(currentIndex)
    }

    new ScalaRandom[F](selectRandom) with SecureRandom[F] {}
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't very clear: I'm thinking about the fact that the method on line 116 is creating an rng which is "sharded", but calls (potentially) blocking code in delay. The latter is solved by this PR for the other overload. I think it should be solved for the overload in line 116 too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what's your suggestion for implementing method on line 116 with our new implementation of javaSecuritySecureRandom?

Comment on lines +1966 to +1967
implicit lazy val secureRandom: SecureRandom[IO] =
SecureRandom.unsafeJavaSecuritySecureRandom[IO]()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suddenly introduces an implicit Random[IO], which doesn't exist right now, and means that SecureRandom will end up being the de facto Random used in most situations. Is that what we want?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good catch, I totally missed this because of the inheritance relationship, and maybe this is what @durban was getting at in #3647 (comment).

Well, it's tricky. To be honest I'm not entirely sure if I see the harm, but these sorts of implicits have bitten us in the past. In fact, UUIDGen being an implicit is biting us already ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think about this when I wrote that coment, but I agree that it could be a problem. Although, the worst problem I can think of right now is that the SecureRandom is probably slower than a non-secure Random, which is not the end of the world... but it is still not clear to me how Random is expected to be used: a user creates an instance themselves, then passes that instance implicitly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's not the end of the world. Much better to default to SecureRandom than the other way around lol ...

but it is still not clear to me how Random is expected to be used: a user creates an instance themselves, then passes that instance implicitly?

See also:

@durban durban self-requested a review June 7, 2023 20:01
Copy link
Contributor

@durban durban left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general comment: does this PR contain copied code from http4s? If yes, maybe that should be mentioned somewhere...

@durban durban dismissed their stale review June 7, 2023 20:08

outdated

@djspiewak djspiewak modified the milestones: v3.5.next, v3.6.0 Feb 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make UUIDGen more performant
4 participants