Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@strokyl
Copy link
Contributor

@strokyl strokyl commented Feb 22, 2022

I start working on #50:

The lookup function would return a ZManaged instead of a ZIO and then when the lookup function is run we would evaluate the acquire action of the ZManaged and store the release action in the cache, running it when the cache entry was evicted. The entire cache would likely be created in the context of a managed effect to ensure that all acquired resources were released when that scope was closed.

But I realize that the cache can't just call the release action when the key is evicted because there is no way to know if fibers that acquired the resource are done with it.
To make it work I think the cache should keep track of the number of fibers using the value for that the get would return a Managed[Error, Value] instead of IO[Error, Value], so fibers using value would call a releaser and therefore inform there are done with this value.

At first, I wanted to work on that logic (keeping track of how many fibers currently using a resource in order to clean it at the correct time) without having to think about key and expiration details.
I come with this SharedManaged logic that wraps a regular Managed and provides proxied Managed for it in a way the underlying managed is only created once and cleaned once.
I wrote some code for that and some tests. But because concurrent programming isn't really my cup of tea and I am quite new to ZIO, I would like to know if my approach is correct and if you think I am going in a good direction.
I think next steps would be to make ManagedCache use SharedManager to get a releaser and a manager factory that we would save into the cache.

Here two sequences diagram to explain the idea behind my SharedManaged

sequenceDiagram
   participant f0 as fiber 0
  participant um as UnderlyingManaged
  participant sm as SharedManaged
  participant f1 as fiber 1
  participant f2 as fiber 2
  f0 ->> sm: create shared managed
  note left of sm: st = 0
  f1 ->> sm: acquire
  note left of sm: st = 2
  sm ->> um: acquire
  sm -->> f1: resource
  f1 ->> sm: release
  note left of sm: st = 1
  f2 ->> sm: acquire
  note left of sm: st = 2
  sm -->> f2: resource
  f2 ->> sm: release
  note left of sm: st = 1
  f0 ->> sm: clean  
  note left of sm: st = 0
  sm ->> um: release
Loading
sequenceDiagram
  participant f0 as fiber 0
  participant um as UnderlyingManaged
  participant sm as SharedManaged
  participant f1 as fiber 1
  participant f2 as fiber 2
  f0 ->> sm: create shared managed
  note left of sm: st = 0
  f1 ->> sm: acquire
  note left of sm: st = 2
  sm ->> um: acquire
  sm -->> f1: resource
  f2 ->> sm: acquire
  note left of sm: st = 3
  sm -->> f2: resource
  f1 ->> sm: release
  note left of sm: st = 2
  f0 ->> sm: clean  
  note left of sm: st = 1
  f2 ->> sm: release
  note left of sm: st = 0
  sm ->> um: release
Loading

@adamgfraser
Copy link
Contributor

I think you are definitely on the right track. The signature would likely have to change like this:

sealed trait ManagedCache[-Key, +Error, +Value] {
  def lookup(key: Key): ZManaged[Any, Error, Value]
}

object ManagedCache {
  def make[Key, Environment, Error, Value](lookup: Key => ZManaged[Environment, Error, Value]): ZManaged[Environment, Nothing, ManagedCache[Key, Error, Value] =
  ???
}

The ManagedCache would internally maintain a Ref[Int] representing the number of "observers" of each managed value. The acquire action of the ZManaged returned by lookup would increment this value, along with acquiring the underlying resource if necessary. The release action would decrement the number of observers and release the underlying resource if it was the last observer and the time to live had expired.

You can look at the implementation of the MemoMap data structure in ZLayer for an example of this pattern.

@strokyl
Copy link
Contributor Author

strokyl commented Feb 23, 2022

@adamgfraser Thank you a lot for your answer. Do you also mean I should in the final solution merge SharedManaged logic into ManagedCache instead of using composition?

@adamgfraser
Copy link
Contributor

I don't think there is any need for SharedManaged. It can all be done through ZManaged.

@strokyl strokyl force-pushed the cache_managed_resource branch 5 times, most recently from f5b3ab9 to f4d0c49 Compare May 5, 2022 15:38
@strokyl strokyl force-pushed the cache_managed_resource branch 6 times, most recently from 1b1876e to 7d69a66 Compare May 5, 2022 16:18
@strokyl
Copy link
Contributor Author

strokyl commented May 5, 2022

To go further on my work I think I need to add some unit test about expiration, I think this would be way easier if ManagedCache used ZIO.clock instead of using directly java.time. But I guess Cache use java.time directly for performance reason? Or can I go ahead and use ZIO.clock @adamgfraser ?

@adamgfraser
Copy link
Contributor

@strokyl The functionality to use the Clock for high performance applications exists in ZIO 2.0. I think given the timing it might make sense to update this PR for that. That will also allow you to use Scope which could potentially allow us to further unify the interface.

@strokyl
Copy link
Contributor Author

strokyl commented May 6, 2022

@strokyl The functionality to use the Clock for high performance applications exists in ZIO 2.0. I think given the timing it might make sense to update this PR for that. That will also allow you to use Scope which could potentially allow us to further unify the interface.

To be honest, I am working on this feature also because my team need it and so far with still on ZIO 1. I would prefer to firstly target ZIO 1 for this PR and after we work on making it use Scope

@adamgfraser
Copy link
Contributor

Okay. We are generally doing all new development on ZIO 2.0 but you are welcome to target your PR against whatever version you want. 😃

@strokyl
Copy link
Contributor Author

strokyl commented May 6, 2022

Okay. We are generally doing all new development on ZIO 2.0 but you are welcome to target your PR against whatever version you want. smiley

That sweet :). I wish I will find time to contribute later the migration of this code to ZIO 2.0, and I will probably because sooner or later my team will move to ZIO 2.0 (pretty soon I wish).
So I find a way to still using java.time.Instant and still be able to "control" the actual Instant.now result (cf my last WIP comment).

But while doing a test about expiration, I wonder what is better:

  1. ensure the resource isn't too old only on managedCache.get call
  2. ensure the resource isn't too old each time the manager returns by managedCache.get is used (call to .use on it).

I have the feeling 2 is the way to go but I am not sure about it.

@strokyl strokyl force-pushed the cache_managed_resource branch 8 times, most recently from c1b0c15 to 7b0b659 Compare May 11, 2022 11:54
@strokyl
Copy link
Contributor Author

strokyl commented May 11, 2022

Ok so I think my PR is now ready to review, I try to make reviews easier by the way I organize commits. In fact, my approach to code this feature was:

  • to define the interface that managed cache should have (commit 3).
  • use code of regular cache has a starting point (commit 4 which does not even compile)
  • add a lot of tests and adapt the code copied from Cache (commit 5) to this new context.

The point was to make it easy to see how the cache code was adapted in the last commit.
About the test, I went a bit far with property testing but it definitely help me catch a lot of bugs. The consequence is that there is a lot of logic in ManagedCacheSpec, maybe I should move that logic (helpers and generator) to a separate file?

@strokyl strokyl marked this pull request as ready for review May 11, 2022 14:24
@strokyl strokyl changed the title WIP: cache managed resource Cache managed resource May 11, 2022
def entryStats(key: Key): UIO[Option[EntryStats]]

/**
* Give a proxy managed on the resource already created for this key if any.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would more conceptualize this as "gets the value from the cache if it exists or otherwise computes it, the release action signals to the cache that the value is no longer being used and can potentially be finalized subject to the policies of the cache".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went for:

  /**
   * Return a managed that gets the value from the cache if it exists or otherwise computes it, the release action
   * signals to the cache that the value is no longer being used and can potentially be finalized subject to the
   * policies of the cache
   * @param key
   * @return
   */

Thought return a managed was important, because I want to make sure user understand all of that is happening only once he use the returned Managed

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay but that is true for every value in ZIO. Regardless of whether it is a ZManaged or a ZIO nothing happens unless it is evaluated. So at least to me it is confusing in that it implies that something different is going on here whereas what is going on is exactly the same as everything else in ZIO. One thing we should make sure of here is that it should not even look up the value in the cache until the ZManaged is evaluated since otherwise that would be a violation of referential transparency.

Copy link
Contributor Author

@strokyl strokyl May 31, 2022

Choose a reason for hiding this comment

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

Okay but that is true for every value in ZIO. Regardless of whether it is a ZManaged or a ZIO nothing happens unless it is evaluated.

That is a really good point, and it make me reconsider the change I did to your proposal. In fact, specifying here the normal behaviour of ZIO is confusing, I went back to your exact proposition.

One thing we should make sure of here is that it should not even look up the value in the cache until the ZManaged is evaluated since otherwise that would be a violation of referential transparency.

I already had test about making sure that the value isn't lookup before the ZManaged is evaluated! But I didn't about checking that contains is returning false before the ZManaged is evaluated, I just added one

adamgfraser
adamgfraser previously approved these changes May 29, 2022
Copy link
Contributor

@adamgfraser adamgfraser left a comment

Choose a reason for hiding this comment

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

Minor comments on documentation but otherwise looks good!

@strokyl
Copy link
Contributor Author

strokyl commented May 30, 2022

Minor comments on documentation but otherwise looks good!

Thank you for the review, I adapted the documentation in a new separated commit, tell me if this is ok for you and then we can merge the PR!

adamgfraser
adamgfraser previously approved these changes Jun 2, 2022
@adamgfraser
Copy link
Contributor

@strokyl Can you merge upstream changes?

@strokyl strokyl force-pushed the cache_managed_resource branch from f9879d4 to 2342d7d Compare June 2, 2022 09:42
@strokyl
Copy link
Contributor Author

strokyl commented Jun 2, 2022

I can't merge this PR because of:

 license/cla Expected — Waiting for status to be reported 

@strokyl
Copy link
Contributor Author

strokyl commented Jun 3, 2022

Ok so I follow this procedure:

Fix a stuck status check

Occasionally there is a glitch, whereby this status check fails to register, even though the contributor has already acknowledged their CLA. The message on the GitHub PR will be something like:
“license/cla Expected — Waiting for status to be reported”.

Being a required status check, this results in hold-ups with the PR.

One cause is when GitHub status is reporting problems with its notifications API, which the CLA agent uses to check PRs. Another cause might be the busy agent facility.

However, be patient. Sometimes it is just slow, and doing manual refresh might not help.

If it is truly seized, then the status check can be manually refreshed using a URL of the following form. Replace the <repo> with the repository name, and the <pr-number> with the pull-request number.

https://cla-assistant.io/check/folio-org/<repo>?pullRequest=<pr-number>

From: https://dev.folio.org/guidelines/cla-process/ and now it's all green

@adamgfraser adamgfraser merged commit 4ec7a42 into zio:master Jun 3, 2022
vigoo added a commit that referenced this pull request Jun 25, 2022
* Implement Cache#invalidateAll (#51)

* implement Cache#invalidateAll

* format

* Added size to `CacheStats`. (#53)

* Added size and capacity to `CacheStats`.

* Removed `capacity` from stats.

* Update Node.js to v14.18.0 (#55)

Co-authored-by: Renovate Bot <[email protected]>

* Add `refresh` method to trigger a lookup call. (#52)

* First attempt to resolve #42

* Fixed a concurrency issue

* Refactoring

* Minor refactorings, scalafmt & sbt upgrades

* Fixing build

* Refactoring: replaced vars with ref

* Reformatted build.sbt to fix build.

* Fixing build

* Fixing build

* Updated docs

* Fixed formatting and addressed PR comments.

* Fixed docs

* Added a unit test for `refresh` method.

* Improved docs

* Minor improvement

* Bumped scalafmt

* Bumped scalajs

* Update Node.js to v14.18.1 (#58)

Co-authored-by: Renovate Bot <[email protected]>

* Update actions/checkout action to v2.3.5 (#59)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16 (#61)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.13.0 (#62)

Co-authored-by: Renovate Bot <[email protected]>

* Update actions/checkout action to v2.4.0 (#64)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.13.1 (#65)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.13.2 (#68)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.14.0 (#70)

Co-authored-by: Renovate Bot <[email protected]>

* Update actions/checkout action (#72)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.14.1 (#73)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.14.2 (#74)

Co-authored-by: Renovate Bot <[email protected]>

* Update hmarr/auto-approve-action action to v2.2.0 (#77)

Co-authored-by: Renovate Bot <[email protected]>

* Update actions/checkout action to v3.0.1 (#82)

Co-authored-by: Renovate Bot <[email protected]>

* Update actions/checkout action to v3.0.2 (#83)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.15.0 (#84)

Co-authored-by: Renovate Bot <[email protected]>

* Update Node.js to v16.15.1 (#87)

Co-authored-by: Renovate Bot <[email protected]>

* Cache managed resource (#71)

* Add .bsp to .gitignore

* Extract KeySet and MapKey out of Cache

Because they probably going to be shared with ManagedCache

* define interface for a ManagedCache

* Copy/Paste: cache implementation and cache test for managedCache

* Adapt cache implementation and test for ManagedCache

* make ManagedCache.get return Managed[Error, Value] instead of UIO[Managed[Error, Value]]

* Make ManagedCache work on 2.11 and Scala 3

* Fix doc after Adam's review

* fixup! Fix doc after Adam's review

* fixup! Fix doc after Adam's review

* fix managed cache invalidate not being lazy (#88)

* run finalizer after acquire fails

* fix compilation issue

* format

Co-authored-by: Adam Fraser <[email protected]>
Co-authored-by: Terry L <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <[email protected]>
Co-authored-by: LUC DUZAN <[email protected]>
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.

2 participants