Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### New features

* Python API `crash_limit` now accepts `"unlimited"` and `"never-restart"` (and `0` is rejected)

## v0.27.0

### New features
Expand Down
7 changes: 5 additions & 2 deletions crates/pyhq/python/hyperqueue/ffi/protocol.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import dataclasses
import datetime
from typing import Dict, List, Optional, Sequence, Union
from typing import Dict, List, Literal, Optional, Sequence, Union

from ..output import StdioDef

# Either a positive number of crashes, "unlimited" or "never-restart"
CrashLimit = Union[int, Literal["unlimited", "never-restart"]]


class ResourceRequest:
n_nodes: int = 0
Expand Down Expand Up @@ -42,7 +45,7 @@ class TaskDescription:
task_dir: bool
priority: int
resource_request: Sequence[ResourceRequest]
crash_limit: Optional[int]
crash_limit: Optional[CrashLimit]


@dataclasses.dataclass
Expand Down
12 changes: 7 additions & 5 deletions crates/pyhq/python/hyperqueue/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .common import GenericPath
from .ffi import JobId, TaskId
from .ffi.protocol import JobDescription, ResourceRequest
from .ffi.protocol import CrashLimit, JobDescription, ResourceRequest
from .output import Stdio, default_stderr, default_stdout
from .task.function import PythonFunction
from .task.program import ExternalProgram, ProgramArgs
Expand Down Expand Up @@ -57,7 +57,7 @@ def program(
task_dir: bool = False,
priority: int = 0,
resources: Optional[Union[ResourceRequest, Sequence[ResourceRequest]]] = None,
crash_limit: Optional[int] = None,
crash_limit: Optional[CrashLimit] = None,
) -> ExternalProgram:
"""
Creates a new task that will execute the provided command.
Expand All @@ -77,7 +77,8 @@ def program(
:param priority: Priority of the created task.
:param resources: List of resource requests required by this task.
:param crash_limit: How many times can a worker that executes this task crash before the
task is considered to be failed.
task is considered to be failed. Either a positive number,
``"unlimited"`` or ``"never-restart"``.
"""
task = ExternalProgram(
len(self.tasks),
Expand Down Expand Up @@ -111,7 +112,7 @@ def function(
name: Optional[str] = None,
priority: int = 0,
resources: Optional[Union[ResourceRequest, Sequence[ResourceRequest]]] = None,
crash_limit: Optional[int] = None,
crash_limit: Optional[CrashLimit] = None,
) -> PythonFunction:
"""
Creates a new task that will execute the provided Python function.
Expand All @@ -129,7 +130,8 @@ def function(
:param priority: Priority of the created task.
:param resources: List of resource requests required by this task.
:param crash_limit: How many times can a worker that executes this task crash before the
task is considered to be failed.
task is considered to be failed. Either a positive number,
``"unlimited"`` or ``"never-restart"``.
"""
task = PythonFunction(
len(self.tasks),
Expand Down
4 changes: 2 additions & 2 deletions crates/pyhq/python/hyperqueue/task/function/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from ...common import GenericPath
from ...ffi import TaskId
from ...ffi.protocol import ResourceRequest, TaskDescription
from ...ffi.protocol import CrashLimit, ResourceRequest, TaskDescription
from ..task import EnvType, Task, _make_ffi_requests
from .wrapper import CloudWrapper

Expand Down Expand Up @@ -103,7 +103,7 @@ def __init__(
dependencies=(),
priority: int = 0,
resources: Optional[Union[ResourceRequest, Sequence[ResourceRequest]]] = None,
crash_limit: Optional[int] = None,
crash_limit: Optional[CrashLimit] = None,
):
name = generate_task_name(task_id, name, fn)
super().__init__(
Expand Down
4 changes: 2 additions & 2 deletions crates/pyhq/python/hyperqueue/task/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ..common import GenericPath
from ..ffi import TaskId
from ..ffi.protocol import ResourceRequest, TaskDescription
from ..ffi.protocol import CrashLimit, ResourceRequest, TaskDescription
from ..output import Output, Stdio, gather_outputs
from ..validation import ValidationException, validate_args
from .task import EnvType, Task, _make_ffi_requests
Expand Down Expand Up @@ -30,7 +30,7 @@ def __init__(
task_dir: bool = False,
priority: int = 0,
resources: Optional[ResourceRequest],
crash_limit: Optional[int] = None,
crash_limit: Optional[CrashLimit] = None,
):
super().__init__(
task_id,
Expand Down
4 changes: 2 additions & 2 deletions crates/pyhq/python/hyperqueue/task/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ..common import GenericPath
from ..ffi import TaskId
from ..ffi.protocol import ResourceRequest
from ..ffi.protocol import CrashLimit, ResourceRequest
from ..output import Stdio, StdioDef
from ..validation import ValidationException

Expand Down Expand Up @@ -33,7 +33,7 @@ def __init__(
stdout: Optional[Stdio] = None,
stderr: Optional[Stdio] = None,
name: Optional[str] = None,
crash_limit: Optional[int] = None,
crash_limit: Optional[CrashLimit] = None,
):
assert dependencies is not None
self.task_id = task_id
Expand Down
26 changes: 20 additions & 6 deletions crates/pyhq/src/client/job.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::marshal::FromPy;
use crate::utils::error::ToPyResult;
use crate::{ClientContextPtr, FromPyObject, PyJobId, PyTaskId, borrow_mut, run_future};
use hyperqueue::client::commands::submit::command::{DEFAULT_STDERR_PATH, DEFAULT_STDOUT_PATH};
use hyperqueue::client::commands::submit::command::{
DEFAULT_STDERR_PATH, DEFAULT_STDOUT_PATH, parse_crash_limit,
};
use hyperqueue::client::commands::submit::resource_rq_map_to_vec;
use hyperqueue::client::output::resolve_task_paths;
use hyperqueue::client::resources::parse_allocation_request;
Expand Down Expand Up @@ -38,6 +40,12 @@ enum AllocationValue {
String(String),
}

#[derive(Debug, FromPyObject)]
enum CrashLimitValue {
Int(u16),
String(String),
}

#[derive(Debug, FromPyObject)]
pub struct ResourceRequestDescription {
n_nodes: NumOfNodes,
Expand All @@ -64,7 +72,7 @@ pub struct TaskDescription {
task_dir: bool,
priority: i32,
resource_request: Vec<ResourceRequestDescription>,
crash_limit: Option<u16>,
crash_limit: Option<CrashLimitValue>,
}

#[derive(Debug, FromPyObject)]
Expand Down Expand Up @@ -256,10 +264,16 @@ fn build_task_desc(desc: TaskDescription, submit_dir: &Path) -> anyhow::Result<H
}),
priority: UserPriority::new(desc.priority),
time_limit: None,
crash_limit: desc
.crash_limit
.map(CrashLimit::MaxCrashes)
.unwrap_or_default(),
crash_limit: match desc.crash_limit {
None => CrashLimit::default(),
Some(CrashLimitValue::Int(0)) => {
anyhow::bail!(
"Crash limit cannot be 0. Use `never-restart` or `unlimited` instead."
);
}
Some(CrashLimitValue::Int(v)) => CrashLimit::MaxCrashes(v),
Some(CrashLimitValue::String(v)) => parse_crash_limit(&v)?,
},
})
}

Expand Down
Loading