-
Notifications
You must be signed in to change notification settings - Fork 304
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
progress messages for jupyterhub 0.9 #153
Conversation
3b2f3dd
to
87f98bc
Compare
This is now using the api provided by jupyterhub/jupyterhub#1771 |
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 left a comment about the queue being a class attributed and how to fix that, but I'm not 100% sure that a Queue is what we want. Specifically, progress may be called multiple times, concurrently, so it should behave appropriately when there are multiple consumers of the progress events. As it is now, due to the consumption of events in queue.get
, only one consumer will get each event.
Other patterns that could work:
- share no state between progress calls and create an event reflector for each call (most likely simplest, but most costly if used a lot. It won't be, though)
- process events into a history, on new progress call, replay history and then connect to queue (queue must support multiple consumers). This is the most complicated, since we need to record the history and also delete it at some point.
- ignore history, only implement multiple-consumer aspect of 2.
Since this will likely be a common issue, we could try to handle it generically at the JupyterHub level so you don't have to deal with it in each Spawner. What do you think?
kubespawner/spawner.py
Outdated
@@ -45,6 +46,7 @@ def pods(self): | |||
|
|||
class EventReflector(NamespacedResourceReflector): | |||
kind = 'events' | |||
queue = Queue(20) |
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 a class attribute, so all EventReflectors share the same Queue, which I don't think we want.
Use
queue = Instance(Queue, args=(20,))
and the queue will be created as an instance attribute.
kubespawner/spawner.py
Outdated
@async_generator | ||
async def progress(self): | ||
while True: | ||
event = self.events.queue.get() |
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 will block the main thread while there are no events ready to get. We need an async wait for the queue. Perhaps an asyncio Queue is the right object, but it is not threadsafe, so you will need a little extra work (IOLoop.add_callback) to put messages on the queue.
Thanks for the review @minrk! It seems reasonable to handle this generically in jupyterhub, as progress can indeed be called multiple times when multiple opened tabs, or if we decide to inspect events in admin ui. this is quite spawner-agnostic. I suppose a static event array appended by the reflector, with individual queue for generator using an index seems to be the simplest way for doing this? btw that'd be awfully similar to the original implementation before the async progress api ;) |
The original implementation couldn't handle multiple watchers or notifying of new events, which are the main issues that we need to solve. To avoid creating multiple reflectors (which is by far the simplest option), we would need to:
I'll have a stab at doing this on the jupyterhub side. If Spawner progress yielded events from the beginning every time, this would be vastly simpler, which might be my preference. |
df5fe5d
to
cd3fd9e
Compare
'message': "%s [%s] %s" % (event.last_timestamp, event.type, event.message) | ||
}) | ||
next_event = len_events | ||
await sleep(1) |
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 while loop needs to end at some point. How do you signal that there will be no more events?
Event reflectors are stopped when pods are successfully launched. slow/failed launch needs this to cleanup watcher.
@minrk thanks for the review. I added a check to break if the reflector is stopped for the generator. However it is unclear to me what happens when the event stream connection is closed. Does the |
Yes, I believe it does. |
Let's give this a try. Thanks! |
With jupyterhub/jupyterhub#1767 the pending_messages are rendered:
not super fancy, but informative enough for now.
Closes #141
Closes #148