-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
Add wait_for_termination method to grpc.Server #19299
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks nice! This will be a great improvement! Three high-level thoughts:
- This needs to be our new canonical form. No more
time.sleep(SECONDS_IN_A_DAY)
. We need to do a sweep of our example code and documentation, both internally and externally. - This is definitely going to require a cherrypick.
- A change in the abstract interfaces got my spidey senses tingling. We've seen trouble in the past with
opencensus
andgrpcio-testing
when making this sort of change. I did a cursory look throughgrpcio-testing
and this looks like it should be okay. There might be another use that I've missed though.
src/python/grpcio/grpc/__init__.py
Outdated
2) The `__del__` of the server object is invoked. | ||
|
||
Args: | ||
grace: A duration of time in seconds or None. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we define more clearly what grace
does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grace
variable is removed. Now, the wait_for_termination
function accepts no argument.
My original design is to replace our internal API which have both delay
and grace
. However, the semantic will become a lot messy. And it is hard to explain that:
- The
grace
variable set here is not necessary the source of truth, because other thread can callserver.Stop
as well; - The
delay
variable will only work in main thread, what should it do in other thread; - The semantic of
delay
is hard to define that which signal should it react to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just a couple of nits.
time.sleep(_WAIT_FOR_BLOCKING.total_seconds()) | ||
|
||
# Invoke manually here, in Python 2 it will be invoked by GC sometime. | ||
server.__del__() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think del server
is more conventional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
del server
is not going to trigger __del__
stably in Python 2, and won't trigger __del__
in Python 3 unless it is the very last ref.
src/python/grpcio/grpc/__init__.py
Outdated
This is an EXPERIMENTAL API. | ||
|
||
The wait will not consume computational resources during blocking, and it | ||
will block indefinitely. There are two ways to unblock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It's a bit contradictory to say it will block indefinitely and then to define to conditions under which it will stop blocking. How about "...and it will block until one of the two following conditions are met:"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the advice. This is much better!
Why not return the
Sounds simpler to implement and with less book-keeping (no arrays and locks etc. needed). Simply create a single P.S. there's one valid reason not to expose the event object directly, which is the user can screwup and set or clear the event. I am not too worried as this is python and private APIs are just a suggestion anyway, but if you are worried about that, here's my suggestion:
|
src/python/grpcio/grpc/__init__.py
Outdated
will block indefinitely. There are two ways to unblock: | ||
|
||
1) Calling `stop` on the server in another thread; | ||
2) The `__del__` of the server object is invoked. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__del__
should never appear in the docs. It's an implementation detail and not part of the guaranteed public interface of the object. We can choose to kill __del__
should we want to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Removed.
src/python/grpcio/grpc/_server.py
Outdated
|
||
with self._state.lock: | ||
if self._state.stage is _ServerStage.STOPPED: | ||
raise ValueError('Failed to wait for a stopped server.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why raise an exception at all. If we are already stopped it should just return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sense. And it still align with the semantic of blocking until the server is stopped.
@mehrdada The first change you gave might be too much. As you said, the user might abuse the new API. I think the second suggestion is valid that adding a New tests added. Docstring updated. Changes reflected to gRFC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks--I may have not been clear about my implementation suggestion, so I wrote them down in-line with code
src/python/grpcio/grpc/_server.py
Outdated
@@ -764,7 +764,7 @@ def __init__(self, completion_queue, server, generic_handlers, | |||
self.interceptor_pipeline = interceptor_pipeline | |||
self.thread_pool = thread_pool | |||
self.stage = _ServerStage.STOPPED | |||
self.shutdown_events = None | |||
self.shutdown_events = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to:
self.termination_event = threading.Event()
self.shutdown_events = [self.termination_event]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the use case you see in terms of giving applications access of the actual Event
object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not giving applications access to Event
; it's just simplifying your implementation (no locks, just a single event to keep track of all waiters). All of this belongs to the _state
object, which is private, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That make sense. Changed.
src/python/grpcio/grpc/_server.py
Outdated
@@ -959,6 +958,17 @@ def add_secure_port(self, address, server_credentials): | |||
def start(self): | |||
_start(self._state) | |||
|
|||
def wait_for_termination(self, timeout=None): | |||
termination_event = threading.Event() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to:
def wait_for_termination(self, timeout=None):
return self._state.termination_event.wait(timeout=timeout)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return
is also important, because it helps the user identify if timeout expired or not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. This is my oversight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks looks good now. Have you manually tested an example under the new API to ensure it works well with Ctrl+C?
@mehrdada Interestingly, the |
If this interferes with signals in production and examples alike, what's the point of this feature? |
@mehrdada Alternatively, we can set the default timeout to the maximum timestamp. |
@lidizheng does that help with Ctrl+C? I assume not. The solution is to do it in a loop with small timeouts, but that sounds bad too. I think you should just axe this feature. Is there a concrete ask here? |
@gnossen @mehrdada It does help with CTRL+C. With the Does CTRL+C work for
I would say setting the |
An attempt to add a "run_forever" like method to
grpc.Server
.