Skip to content

Commit

Permalink
Use a single exception class for all resource specification errors (#…
Browse files Browse the repository at this point in the history
…3627)


There are three places where the executors raise the errors regarding resource specification.

* when passing the resource_specification parameter to the htex, it raises the mpi_prefix_composer InvalidResourceSpecification from validate_resource_spec method. https://github.com/Parsl/parsl/blob/master/parsl/executors/high_throughput/executor.py#L342

* ThreadPoolExecutor also raises the UnsupportedFeatureError when passing resource specification to it, https://github.com/Parsl/parsl/blob/master/parsl/executors/threads.py#L54

* WorkQueueExecutor raises two errors similar to this but as a Base ExecutorError, one when there is invalid resource_specification ; https://github.com/Parsl/parsl/blob/master/parsl/executors/workqueue/executor.py#L418 and another one when the autolabel is false and resource_specification is not right, https://github.com/Parsl/parsl/blob/master/parsl/executors/workqueue/executor.py#L429

This PR rationalize these errors by moving InvalidResourceSpecification to parsl.executors.error and using it across the executors.
  • Loading branch information
Harichandra-Prasath authored Oct 14, 2024
1 parent 8356233 commit 264d128
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 60 deletions.
13 changes: 13 additions & 0 deletions parsl/executors/errors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Exceptions raise by Executors."""
from typing import Set

from parsl.errors import ParslError
from parsl.executors.base import ParslExecutor

Expand Down Expand Up @@ -44,6 +46,17 @@ def __str__(self):
self.current_executor)


class InvalidResourceSpecification(ExecutorError):
"""Error raised when Invalid input is supplied via resource Specification"""

def __init__(self, invalid_keys: Set[str], message: str = ''):
self.invalid_keys = invalid_keys
self.message = message

def __str__(self):
return f"Invalid Resource Specification Supplied: {self.invalid_keys}. {self.message}"


class ScalingFailed(ExecutorError):
"""Scaling failed due to error in Execution provider."""

Expand Down
9 changes: 5 additions & 4 deletions parsl/executors/high_throughput/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
from parsl.addresses import get_all_addresses
from parsl.app.errors import RemoteExceptionWrapper
from parsl.data_provider.staging import Staging
from parsl.executors.errors import BadMessage, ScalingFailed
from parsl.executors.errors import (
BadMessage,
InvalidResourceSpecification,
ScalingFailed,
)
from parsl.executors.high_throughput import zmq_pipes
from parsl.executors.high_throughput.errors import CommandClientTimeoutError
from parsl.executors.high_throughput.manager_selector import (
ManagerSelector,
RandomManagerSelector,
)
from parsl.executors.high_throughput.mpi_prefix_composer import (
InvalidResourceSpecification,
)
from parsl.executors.status_handling import BlockProviderExecutor
from parsl.jobs.states import TERMINAL_STATES, JobState, JobStatus
from parsl.process_loggers import wrap_with_logs
Expand Down
28 changes: 5 additions & 23 deletions parsl/executors/high_throughput/mpi_prefix_composer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
from typing import Dict, List, Set, Tuple
from typing import Dict, List, Tuple

from parsl.executors.errors import InvalidResourceSpecification

logger = logging.getLogger(__name__)

Expand All @@ -8,27 +10,6 @@
'mpiexec')


class MissingResourceSpecification(Exception):
"""Exception raised when input is not supplied a resource specification"""

def __init__(self, reason: str):
self.reason = reason

def __str__(self):
return f"Missing resource specification: {self.reason}"


class InvalidResourceSpecification(Exception):
"""Exception raised when Invalid input is supplied via resource specification"""

def __init__(self, invalid_keys: Set[str], message: str = ''):
self.invalid_keys = invalid_keys
self.message = message

def __str__(self):
return f"Invalid resource specification options supplied: {self.invalid_keys} {self.message}"


def validate_resource_spec(resource_spec: Dict[str, str]):
"""Basic validation of keys in the resource_spec
Expand All @@ -40,7 +21,8 @@ def validate_resource_spec(resource_spec: Dict[str, str]):
# empty resource_spec when mpi_mode is set causes parsl to hang
# ref issue #3427
if len(user_keys) == 0:
raise MissingResourceSpecification('MPI mode requires optional parsl_resource_specification keyword argument to be configured')
raise InvalidResourceSpecification(user_keys,
'MPI mode requires optional parsl_resource_specification keyword argument to be configured')

legal_keys = set(("ranks_per_node",
"num_nodes",
Expand Down
5 changes: 3 additions & 2 deletions parsl/executors/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from parsl.data_provider.staging import Staging
from parsl.executors.base import ParslExecutor
from parsl.executors.errors import UnsupportedFeatureError
from parsl.executors.errors import InvalidResourceSpecification
from parsl.utils import RepresentationMixin

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -54,7 +54,8 @@ def submit(self, func, resource_specification, *args, **kwargs):
if resource_specification:
logger.error("Ignoring the resource specification. "
"Parsl resource specification is not supported in ThreadPool Executor.")
raise UnsupportedFeatureError('resource specification', 'ThreadPool Executor', None)
raise InvalidResourceSpecification(set(resource_specification.keys()),
"Parsl resource specification is not supported in ThreadPool Executor.")

return self.executor.submit(func, *args, **kwargs)

Expand Down
11 changes: 6 additions & 5 deletions parsl/executors/workqueue/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from parsl.data_provider.files import File
from parsl.data_provider.staging import Staging
from parsl.errors import OptionalModuleMissing
from parsl.executors.errors import ExecutorError
from parsl.executors.errors import ExecutorError, InvalidResourceSpecification
from parsl.executors.status_handling import BlockProviderExecutor
from parsl.executors.workqueue import exec_parsl_function
from parsl.process_loggers import wrap_with_logs
Expand Down Expand Up @@ -419,7 +419,7 @@ def submit(self, func, resource_specification, *args, **kwargs):
message = "Task resource specification only accepts these types of resources: {}".format(
', '.join(acceptable_fields))
logger.error(message)
raise ExecutorError(self, message)
raise InvalidResourceSpecification(keys, message)

# this checks that either all of the required resource types are specified, or
# that none of them are: the `required_resource_types` are not actually required,
Expand All @@ -430,9 +430,10 @@ def submit(self, func, resource_specification, *args, **kwargs):
logger.error("Running with `autolabel=False`. In this mode, "
"task resource specification requires "
"three resources to be specified simultaneously: cores, memory, and disk")
raise ExecutorError(self, "Task resource specification requires "
"three resources to be specified simultaneously: cores, memory, and disk. "
"Try setting autolabel=True if you are unsure of the resource usage")
raise InvalidResourceSpecification(keys,
"Task resource specification requires "
"three resources to be specified simultaneously: cores, memory, and disk. "
"Try setting autolabel=True if you are unsure of the resource usage")

for k in keys:
if k == 'cores':
Expand Down
24 changes: 10 additions & 14 deletions parsl/tests/test_error_handling/test_resource_spec.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import parsl
from parsl.app.app import python_app
from parsl.executors import WorkQueueExecutor
from parsl.executors.errors import ExecutorError, UnsupportedFeatureError
from parsl.executors.errors import InvalidResourceSpecification
from parsl.executors.high_throughput.executor import HighThroughputExecutor
from parsl.executors.high_throughput.mpi_prefix_composer import (
InvalidResourceSpecification,
)
from parsl.executors.threads import ThreadPoolExecutor


@python_app
Expand All @@ -27,11 +25,10 @@ def test_resource(n=2):
try:
fut.result()
except InvalidResourceSpecification:
assert isinstance(executor, HighThroughputExecutor)
except UnsupportedFeatureError:
assert not isinstance(executor, WorkQueueExecutor)
except Exception as e:
assert isinstance(e, ExecutorError)
assert (
isinstance(executor, HighThroughputExecutor) or
isinstance(executor, WorkQueueExecutor) or
isinstance(executor, ThreadPoolExecutor))

# Specify resources with wrong types
# 'cpus' is incorrect, should be 'cores'
Expand All @@ -40,8 +37,7 @@ def test_resource(n=2):
try:
fut.result()
except InvalidResourceSpecification:
assert isinstance(executor, HighThroughputExecutor)
except UnsupportedFeatureError:
assert not isinstance(executor, WorkQueueExecutor)
except Exception as e:
assert isinstance(e, ExecutorError)
assert (
isinstance(executor, HighThroughputExecutor) or
isinstance(executor, WorkQueueExecutor) or
isinstance(executor, ThreadPoolExecutor))
4 changes: 1 addition & 3 deletions parsl/tests/test_htex/test_resource_spec_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import pytest

from parsl.executors import HighThroughputExecutor
from parsl.executors.high_throughput.mpi_prefix_composer import (
InvalidResourceSpecification,
)
from parsl.executors.errors import InvalidResourceSpecification


def double(x):
Expand Down
6 changes: 2 additions & 4 deletions parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import parsl
from parsl import Config, bash_app, python_app
from parsl.executors import MPIExecutor
from parsl.executors.high_throughput.mpi_prefix_composer import (
MissingResourceSpecification,
)
from parsl.executors.errors import InvalidResourceSpecification
from parsl.launchers import SimpleLauncher
from parsl.providers import LocalProvider

Expand Down Expand Up @@ -185,6 +183,6 @@ def test_simulated_load(rounds: int = 100):
@pytest.mark.local
def test_missing_resource_spec():

with pytest.raises(MissingResourceSpecification):
with pytest.raises(InvalidResourceSpecification):
future = mock_app(sleep_dur=0.4)
future.result(timeout=10)
7 changes: 2 additions & 5 deletions parsl/tests/test_mpi_apps/test_resource_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
import pytest

from parsl.app.app import python_app
from parsl.executors.errors import InvalidResourceSpecification
from parsl.executors.high_throughput.executor import HighThroughputExecutor
from parsl.executors.high_throughput.mpi_executor import MPIExecutor
from parsl.executors.high_throughput.mpi_prefix_composer import (
InvalidResourceSpecification,
MissingResourceSpecification,
)
from parsl.executors.high_throughput.mpi_resource_management import (
get_nodes_in_batchjob,
get_pbs_hosts_list,
Expand Down Expand Up @@ -105,7 +102,7 @@ def test_top_level():
({"num_nodes": 2, "ranks_per_node": 1}, None),
({"launcher_options": "--debug_foo"}, None),
({"num_nodes": 2, "BAD_OPT": 1}, InvalidResourceSpecification),
({}, MissingResourceSpecification),
({}, InvalidResourceSpecification),
)
)
def test_mpi_resource_spec(resource_spec: Dict, exception):
Expand Down

0 comments on commit 264d128

Please sign in to comment.