-
Notifications
You must be signed in to change notification settings - Fork 531
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
[Core] make per-cloud catalog lookup parallel #4483
base: master
Are you sure you want to change the base?
Changes from 7 commits
26e9067
c9a2ce3
fb6c8ac
3571b55
e8e13bb
dad3a65
0f21c7c
99a1edb
2ff4517
4619413
9bb757e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,21 @@ | ||
"""Rich status spinner utils.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is great! However, since this can affect many other part of the code, let's move this to another PR, and keep this PR clean for only having the parallelism changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but since status of sub thread is silent on master, there would be a UX degradation in between, is that okay? |
||
import contextlib | ||
import threading | ||
from typing import Union | ||
from typing import Dict, Optional, Union | ||
|
||
import rich.console as rich_console | ||
|
||
console = rich_console.Console(soft_wrap=True) | ||
_status = None | ||
_status_nesting_level = 0 | ||
_main_message = None | ||
|
||
_logging_lock = threading.RLock() | ||
|
||
# Track sub thread progress statuses | ||
_thread_statuses: Dict[int, Optional[str]] = {} | ||
_status_lock = threading.RLock() | ||
|
||
|
||
class _NoOpConsoleStatus: | ||
"""An empty class for multi-threaded console.status.""" | ||
|
@@ -35,15 +40,17 @@ class _RevertibleStatus: | |
"""A wrapper for status that can revert to previous message after exit.""" | ||
|
||
def __init__(self, message: str): | ||
if _status is not None: | ||
self.previous_message = _status.status | ||
if _main_message is not None: | ||
self.previous_message = _main_message | ||
else: | ||
self.previous_message = None | ||
self.message = message | ||
|
||
def __enter__(self): | ||
global _status_nesting_level | ||
_status.update(self.message) | ||
global _main_message | ||
_main_message = self.message | ||
refresh() | ||
_status_nesting_level += 1 | ||
_status.__enter__() | ||
return _status | ||
|
@@ -57,10 +64,15 @@ def __exit__(self, exc_type, exc_val, exc_tb): | |
_status.__exit__(exc_type, exc_val, exc_tb) | ||
_status = None | ||
else: | ||
_status.update(self.previous_message) | ||
global _main_message | ||
_main_message = self.previous_message | ||
refresh() | ||
|
||
def update(self, *args, **kwargs): | ||
_status.update(*args, **kwargs) | ||
global _main_message | ||
_main_message = _status.status | ||
refresh() | ||
|
||
def stop(self): | ||
_status.stop() | ||
|
@@ -69,16 +81,65 @@ def start(self): | |
_status.start() | ||
|
||
|
||
def safe_status(msg: str) -> Union['rich_console.Status', _NoOpConsoleStatus]: | ||
class _ThreadStatus: | ||
"""A wrapper of sub thread status""" | ||
|
||
def __init__(self, message: str): | ||
self.thread_id = threading.get_ident() | ||
self.message = message | ||
self.previous_message = _thread_statuses.get(self.thread_id) | ||
|
||
def __enter__(self): | ||
self.start() | ||
return self | ||
|
||
def __exit__(self, exc_type, exc_val, exc_tb): | ||
if self.previous_message is not None: | ||
_thread_statuses[self.thread_id] = self.previous_message | ||
else: | ||
# No previous message, remove the thread status | ||
if self.thread_id in _thread_statuses: | ||
del _thread_statuses[self.thread_id] | ||
refresh() | ||
|
||
def update(self, new_message: str): | ||
self.message = new_message | ||
_thread_statuses[self.thread_id] = new_message | ||
refresh() | ||
|
||
def stop(self): | ||
_thread_statuses[self.thread_id] = None | ||
refresh() | ||
|
||
def start(self): | ||
_thread_statuses[self.thread_id] = self.message | ||
refresh() | ||
|
||
|
||
def refresh(): | ||
"""Refresh status to include all thread statuses.""" | ||
if _status is None or _main_message is None: | ||
return | ||
with _status_lock: | ||
msg = _main_message | ||
for v in _thread_statuses.values(): | ||
if v is not None: | ||
msg = msg + f'\n └─ {v}' | ||
_status.update(msg) | ||
|
||
|
||
def safe_status(msg: str) -> Union['rich_console.Status', '_NoOpConsoleStatus']: | ||
"""A wrapper for multi-threaded console.status.""" | ||
from sky import sky_logging # pylint: disable=import-outside-toplevel | ||
global _status | ||
if (threading.current_thread() is threading.main_thread() and | ||
not sky_logging.is_silent()): | ||
if sky_logging.is_silent(): | ||
return _NoOpConsoleStatus() | ||
if threading.current_thread() is threading.main_thread(): | ||
if _status is None: | ||
_status = console.status(msg, refresh_per_second=8) | ||
return _RevertibleStatus(msg) | ||
return _NoOpConsoleStatus() | ||
else: | ||
return _ThreadStatus(msg) | ||
|
||
|
||
def stop_safe_status(): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
"""Utility functions for subprocesses.""" | ||
import collections | ||
from multiprocessing import pool | ||
import os | ||
import random | ||
|
@@ -113,6 +114,12 @@ def run_in_parallel(func: Callable, | |
A list of the return values of the function func, in the same order as the | ||
arguments. | ||
""" | ||
if isinstance(args, collections.abc.Sized): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: how about we change the input args to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good, I will try this~ |
||
if len(args) == 0: | ||
return [] | ||
# Short-circuit for single element | ||
if len(args) == 1: | ||
return [func(next(iter(args)))] | ||
# Reference: https://stackoverflow.com/questions/25790279/python-multiprocessing-early-termination # pylint: disable=line-too-long | ||
processes = num_threads if num_threads is not None else get_parallel_threads( | ||
) | ||
|
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.
very minor nit: the
run_in_parallel
keeps the original order of the arguments, so no need to havecloud
in the output, butzip(cloud_list, feasible_list)
should be fine.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, with this new knowledge, I personally still tend to loosen the behavior dependency on the
run_in_parallel
function for maintainability, wdyt?