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

Fixes memory leak in prepared statement cache. Fixes #1143 #1157

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

matthughes
Copy link
Contributor

Prepared statements were created on the server and cached as part of the "prepare cache." However, despite entries being removed from the in-memory cache, the evicted entries were not removed from Postgres itself.

This modifies SemispaceCache to keep track of evicted elements. When a Session is recycled, we close any prepared statements that have been evicted.

Prepared statements were created on the server and cached as part of the
"prepare cache."  However, despite entries being removed from the
in-memory cache, the evicted entries were not removed from Postgres
itself.

This modifies SemispaceCache to keep track of evicted elements.  When a
Session is recycled, we close any prepared statements that have been
evicted.
def insert(k: K, v: V): SemispaceCache[K, V] = {
if (max == 0) this.withEvicted(v :: evicted) // special case, can't insert!
else if (gen0.size < max) SemispaceCache(gen0 + (k -> v), gen1, max, evicted) // room in gen0, done!
else SemispaceCache(Map(k -> v), gen0, max, gen1.values.toList ::: evicted)// no room in gen0, slide it down
Copy link
Member

Choose a reason for hiding this comment

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

Is there some way we can eagerly close the evicted gen 1 items here? Instead of waiting until session end?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know. I was trying to do that for a while but kept getting stuck on the best place to do it. I don't like adding a method to Session but it seems the easiest to ensure everything that is evicted is cleaned up.

For example, if someone used Session.prepare... how would I know when I could safely get rid of that? It's somewhat easier for the execute methods.

The prepare methods seem to conflict with this cache model. Before the cache, you had docs saying to prepare the queries ahead of time and then re-use them each session. But now they are getting cached both manually by the user and in the SemispaceCache. So you could imagine a bug where user has prepared cache size of 10, then they call

session.prepare { pq =>

...
}

Then they create 10 more prepared statements using execute. This pq will get evicted from semispace cache and closed on the Postgres side and when they go to execute it again it will blow up.

Copy link
Member

@mpilquist mpilquist Jan 6, 2025

Choose a reason for hiding this comment

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

I haven't thought much about it yet but what about something like providing SemispaceCache an onEvicted callback? Then at least we know that any statements that have been evicted have also been closed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we could say methods that use Session.prepare/prepareR just aren't cached via SemispaceCache and then update the execute/option/stream methods to close any evicted statements immediately?

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