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
29 changes: 29 additions & 0 deletions distributed/tests/test_worker_state_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
GatherDep,
GatherDepSuccessEvent,
Instruction,
InvalidTaskState,
InvalidTransition,
PauseEvent,
RecommendationsConflict,
RefreshWhoHasEvent,
Expand All @@ -44,6 +46,7 @@
SerializedTask,
StateMachineEvent,
TaskState,
TransitionCounterMaxExceeded,
UnpauseEvent,
UpdateDataEvent,
merge_recs_instructions,
Expand Down Expand Up @@ -194,6 +197,32 @@ def test_WorkerState_pickle(ws):
assert ws2.data == {"y": 123}


@pytest.mark.parametrize(
"cls,kwargs",
[
(
InvalidTransition,
dict(key="x", start="released", finish="waiting", story=[]),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally prefer dict literals over their constructors, do we have any rule of thumb around what to use? It looks like nothing enforces one or the other.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are function parameters, so it felt more natural to use a dict constructor. YMMV.

),
(
TransitionCounterMaxExceeded,
dict(key="x", start="released", finish="waiting", story=[]),
),
(InvalidTaskState, dict(key="x", state="released", story=[])),
],
)
@pytest.mark.parametrize("positional", [False, True])
def test_pickle_exceptions(cls, kwargs, positional):
if positional:
e = cls(*kwargs.values())
else:
e = cls(**kwargs)
e2 = pickle.loads(pickle.dumps(e))
assert type(e2) is type(e)
for k, v in kwargs.items():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we instead make use of self.args (and initialize it properly) to compare the two instances?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception has a weird design. Yes it would be cleaner to initialise args to the parameters and then offer @property accessors, but it would also be very verbose and IMHO overkill.

assert getattr(e2, k) == v

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

positional=True passes without any changes to worker_state_machine.py; positional=False crashes with TypeError



def traverse_subclasses(cls: type) -> Iterator[type]:
yield cls
for subcls in cls.__subclasses__():
Expand Down
11 changes: 10 additions & 1 deletion distributed/worker_state_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ def __init__(
self.finish = finish
self.story = story

def __reduce__(self) -> tuple[Callable, tuple]:
return type(self), (self.key, self.start, self.finish, self.story)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}: {self.key} :: {self.start}->{self.finish}"
Expand Down Expand Up @@ -169,6 +172,9 @@ def __init__(
self.state = state
self.story = story

def __reduce__(self) -> tuple[Callable, tuple]:
return type(self), (self.key, self.state, self.story)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}: {self.key} :: {self.state}"
Expand Down Expand Up @@ -2415,7 +2421,10 @@ def _transition(
# final
ts.state,
# new recommendations
{ts.key: new for ts, new in recs.items()},
{
ts.key: new[0] if isinstance(new, tuple) else new
for ts, new in recs.items()
},
stimulus_id,
time(),
)
Expand Down