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

Spanner: Do not hand out a closed Spanner instance from SpannerOptions.getService() #5187

Closed

Conversation

olavloite
Copy link

SpannerOptions caches any Spanner instance that has been created, and hands this cached instance out to all subsequent calls to SpannerOptions.getService(). This also included closed Spanner instances. The getService() method now returns an error if the Spanner instance has already been closed.

@olavloite olavloite requested a review from a team as a code owner May 15, 2019 06:57
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label May 15, 2019
@codecov
Copy link

codecov bot commented May 15, 2019

Codecov Report

Merging #5187 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #5187   +/-   ##
=========================================
  Coverage      50.4%    50.4%           
  Complexity    23785    23785           
=========================================
  Files          2251     2251           
  Lines        226792   226792           
  Branches      24966    24966           
=========================================
  Hits         114320   114320           
  Misses       103865   103865           
  Partials       8607     8607

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 52dc8d0...d2c1746. Read the comment docs.

SpannerOptions caches any Spanner instance that has been created, and
hands this cached instance out to all subsequent calls to
SpannerOptions.getService(). This also included closed Spanner
instances. The getService() method now returns an error if the Spanner
instance has already been closed.
@olavloite olavloite force-pushed the spanner-check-closed-spanner branch from 1b723dd to d2c1746 Compare May 15, 2019 14:46
@snehashah16
Copy link
Contributor

I would err on side of caution when returning errors/exception. I would prefer we either evict entries from cache on closing spanner instances OR try getting another entry from the cache if at all it is closed.

@olavloite
Copy link
Author

There's no real cache in this case. The default behavior is defined in the base class of SpannerOptions, com.google.cloud.ServiceOptions#getService():

  public ServiceT getService() {
    if (service == null) {
      service = serviceFactory.create((OptionsT) this);
    }
    return service;
  }

Overriding this method and returning a new instance instead of an existing (closed) instance, is possible.

Copy link
Contributor

@kolea2 kolea2 left a comment

Choose a reason for hiding this comment

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

Can we explore something like what Sneha is suggesting and not throw an error back to the user?

@olavloite
Copy link
Author

The SpannerOptions#getService() method is public and not final so we could easily implement alternatives. As far as I can see it, the following alternatives would be possible:

  1. The current implementation in master: The method creates a new Spanner instance on the first call, and then returns this instance in every subsequent call. This is the default behavior that is defined in com.google.cloud.ServiceOptions, the base class of SpannerOptions. The drawback of this implementation is that it will also return a closed Spanner instance if the user has closed one that has been returned by the method.
  2. The implementation in this PR: The method creates a new Spanner instance on the first call, and then returns this instance in every subsequent call, unless the Spanner instance has been closed. In that case, the method throws an exception. Pro: The user gets the exception at an earlier stage, instead of at the next database call, and the behavior of the method is consistent with the default for Service classes. Con: The user gets an exception.
  3. Creating a new Spanner instance if the cached instance has been closed: The method creates a new Spanner instance on the first call, and then returns this instance in every subsequent call, unless the Spanner instance has been closed. In that case, the method creates a new instance and returns that. Pro: The method does not throw an exception and always returns a valid Spanner instance. Con: The method sometimes returns an existing instance and sometimes a new instance, depending on the state of a previously created instance.
  4. Creating a new Spanner instance in all cases: The method creates a new Spanner instance on every call to the method. Pro: No shared Spanner instances. Con: Major change from the current behavior, possibly causing larger resource leaks. Anyone that currently calls SpannerOptions#getService() repeatedly without ever closing the returned Spanner instance, will run into out-of-memory conditions very quickly.

A quick scan of other Service classes show that these use the default implementation, i.e. option 1 above. My gut feeling is to stick to option 2 from above, as it has the least impact on the current behavior (the user now also gets an exception, just at a later moment), and keeps it in line with other Service classes. @kolea2 WDYT?

@kolea2
Copy link
Contributor

kolea2 commented May 17, 2019

I think option 3 is what I'm leaning toward, and I think it is closest to what @snehashah16 suggested (instead of throw an error, have the library manage and handle).

@snehashah16
Copy link
Contributor

snehashah16 commented May 18, 2019 via email

@olavloite
Copy link
Author

In option 3:

SpannerOptions.getService() will return the same Spanner instance as long as the Spanner instance has not been closed. Once the instance has been closed, SpannerOptions.getService() will create a new instance and start returning that instance. This new instance will have the exact same properties and behave the same as the initial instance, but it will not be the same instance. It is not possible to reopen an already closed Spanner instance.

The potential downside that I see with this approach is that we 'encourage' opening and closing Spanner instances during the lifetime of an application, while users should normally keep a Spanner instance open during the entire lifetime of the application. Closing a Spanner instance means that its session pool will be closed and all sessions will be deleted. Opening a new Spanner instance means initializing a new session pool.

It's a small change, so I'll make a separate PR with option 3 so it can be reviewed.

@ajaaym
Copy link
Contributor

ajaaym commented May 24, 2019

I think the correct usage is SpannerOptions.getDefaultInstance().getService() or something like this SpannerOptions.getDefaultInstance().toBuilder().set*().build().getService() once the service returned by the SpannerOption is closed. ServiceOptions is not meant to be a factory for Service.

@sduskis
Copy link
Contributor

sduskis commented May 27, 2019

Option #3 should cache the service and Rpc, until it's closed. After that, it should create a new Service/Rpc and cache it until it's closed (and repeat). That seems like a straightforward algorithm.

@olavloite
Copy link
Author

@sduskis @ajaaym @kolea2
Just to be sure everyone is on the same page here:

@sduskis
Copy link
Contributor

sduskis commented May 27, 2019

@olavloite, I don't see how the service is cached after the initial service is closed. Perhaps we can do a VC.

@yoshi-automation yoshi-automation added the 🚨 This issue needs some love. label Jun 4, 2019
@sduskis sduskis added do not merge Indicates a pull request not ready for merge, due to either quality or timing. needs work This is a pull request that needs a little love. and removed 🚨 This issue needs some love. labels Jun 6, 2019
@sduskis sduskis self-requested a review June 6, 2019 15:57
@kolea2
Copy link
Contributor

kolea2 commented Jun 19, 2019

Closing in favor of #5200

@kolea2 kolea2 closed this Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement. do not merge Indicates a pull request not ready for merge, due to either quality or timing. needs work This is a pull request that needs a little love.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants