Repository navigation
Fix stuck jobs when machines are out of stock - #6525
siddhartha-quad wants to merge 22 commits into
Conversation
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses issues with stuck jobs when GCE capacity is exhausted. It introduces a capacity circuit breaker for Slurm to manage nodesets during capacity errors and improves HTCondor's handling of VM shutdowns and job re-queuing. Additionally, it refines the HTCondor autoscaler logic to ensure more accurate scaling decisions. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a capacity circuit breaker mechanism for the Slurm GCP v6 controller to temporarily remove GCE nodesets from scheduling after capacity errors, along with HTCondor improvements including shutdown scripts, automatic job re-queuing on Windows shutdown, and autoscaler refinements. The review feedback highlights several critical robustness improvements: preventing an AttributeError in resume.py by checking for _get_reason existence, avoiding a potential KeyError in capacity_circuit.py when accessing nodesets, safely handling cases where the node state is returned as a string rather than a list, and explicitly specifying UTF-8 encoding when reading the state file.
| failed_nodes = grouped_nodes[ident].nodes | ||
| trip = trip_capacity_circuit( | ||
| failed_nodes, | ||
| capacity_circuit.exception_error_codes(exc), | ||
| lkp, | ||
| ) | ||
| down_nodes_notify_jobs( | ||
| failed_nodes, | ||
| f"GCP Error: {exc._get_reason()}", # type: ignore | ||
| resume_data, | ||
| trip, | ||
| ) |
There was a problem hiding this comment.
Calling exc._get_reason() directly without checking if the attribute exists will raise an AttributeError if exc is a standard Python exception or any exception that doesn't implement _get_reason(). The original code safely handled this by checking hasattr(exc, "_get_reason") before calling it.
| failed_nodes = grouped_nodes[ident].nodes | |
| trip = trip_capacity_circuit( | |
| failed_nodes, | |
| capacity_circuit.exception_error_codes(exc), | |
| lkp, | |
| ) | |
| down_nodes_notify_jobs( | |
| failed_nodes, | |
| f"GCP Error: {exc._get_reason()}", # type: ignore | |
| resume_data, | |
| trip, | |
| ) | |
| failed_nodes = grouped_nodes[ident].nodes | |
| reason = exc._get_reason() if hasattr(exc, "_get_reason") else str(exc) | |
| trip = trip_capacity_circuit( | |
| failed_nodes, | |
| capacity_circuit.exception_error_codes(exc), | |
| lkp, | |
| ) | |
| down_nodes_notify_jobs( | |
| failed_nodes, | |
| f"GCP Error: {reason}", | |
| resume_data, | |
| trip, | |
| ) |
| nodeset = lkp.cfg.nodeset[nodeset_name] | ||
| powered_down = _powered_down_nodes(nodeset, lkp) |
There was a problem hiding this comment.
nodeset = lkp.cfg.nodeset[nodeset_name] will raise a KeyError if nodeset_name is not found in lkp.cfg.nodeset (for example, if it is a TPU nodeset or dynamic nodeset). Using .get(nodeset_name) and returning None if it is not found is much safer and prevents crashes.
| nodeset = lkp.cfg.nodeset[nodeset_name] | |
| powered_down = _powered_down_nodes(nodeset, lkp) | |
| nodeset = lkp.cfg.nodeset.get(nodeset_name) | |
| if nodeset is None: | |
| return None | |
| powered_down = _powered_down_nodes(nodeset, lkp) |
| current_reason = current.get("reason") or "" | ||
| current_states = set(current.get("state") or []) | ||
| if current_states.intersection( | ||
| restricted_states | ||
| ) and not current_reason.startswith(reason_prefix): |
There was a problem hiding this comment.
current_states = set(current.get("state") or []) assumes current.get("state") is always a list of strings. However, depending on the Slurm version or JSON schema, state can sometimes be returned as a single string (e.g., "DOWN"). If it is a string, set("DOWN") will split it into individual characters {'D', 'O', 'W', 'N'}, causing the intersection check with restricted_states to fail. Checking if it is a string and wrapping it in a set/list prevents this.
current_reason = current.get("reason") or ""
raw_state = current.get("state") or []
current_states = {raw_state} if isinstance(raw_state, str) else set(raw_state)
if current_states.intersection(
restricted_states
) and not current_reason.startswith(reason_prefix):|
|
||
| def _read_state_unlocked() -> dict[str, Any]: | ||
| try: | ||
| state = json.loads(STATE_FILE.read_text()) |
There was a problem hiding this comment.
STATE_FILE.read_text() is called without specifying an encoding. This can lead to platform-dependent encoding issues (e.g., defaulting to CP1252 on some Windows environments or other locales). It is safer to explicitly specify encoding="utf-8", especially since _write_state_unlocked explicitly writes using encoding="utf-8".
| state = json.loads(STATE_FILE.read_text()) | |
| state = json.loads(STATE_FILE.read_text(encoding="utf-8")) |
Fix stuck jobs when machines are out of stock