From 4938b5000fd10db40345e4f369453965f2fb22d2 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 15 Nov 2023 21:28:55 +0100 Subject: [PATCH] Fix `Channel.__hash__` in multiprocessing contexts (#11251) * Fix `Channel.__hash__` in multiprocessing contexts Storing an explicit hash key is fragile in cases that a channel might be created in a different process to where it might be compared or the hash used, because the hash seeding can vary depending on how the new interpreter process was created, especially if it's not done by `fork`. In this case, transmitting the stored `_hash` over pickle meant that a `DriveChannel(0)` created in the main process of a macOS runner could compare equal to a `DriveChannel(0)` created in a separate process (standard start method `spawn`) and pickled over the wire to the main process, but have different hashes, violating the Python data model. Instead, we can just use the standard Python behaviour of creating the hash on demand when requested; this should typically be preferred unless absolutely necessary for critical performance reasons, because it will generally fail safe. * Fix `hash` and equality in other pulse objects This removes all caching of items' `hash`es. This practice is quite fraught in multiprocessing contexts, and should only be done when it is absolutely performance critical. In a couple of cases, the pulse objects were using the cached `hash` as the main component of their `__eq__` methods, which is not correct; it's totally valid to have hash collisions without implying that two objects are equal. (cherry picked from commit 3c1a87c48a9ff33d999aa71749bdf64a9c008a97) --- qiskit/pulse/channels.py | 3 +-- qiskit/pulse/instructions/instruction.py | 5 +---- .../notes/fix-pulse-channel-hash-549a8fb5d8738c4d.yaml | 6 ++++++ 3 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/fix-pulse-channel-hash-549a8fb5d8738c4d.yaml diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index 687b837700d3..e3da9b923ca4 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -96,7 +96,6 @@ def __init__(self, index: int): """ self._validate_index(index) self._index = index - self._hash = hash((self.__class__.__name__, self._index)) @property def index(self) -> Union[int, ParameterExpression]: @@ -156,7 +155,7 @@ def __eq__(self, other: "Channel") -> bool: return type(self) is type(other) and self._index == other._index def __hash__(self): - return self._hash + return hash((type(self), self._index)) class PulseChannel(Channel, metaclass=ABCMeta): diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 7c6af5788e8b..559b4fb38dc7 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -53,7 +53,6 @@ def __init__( """ self._operands = operands self._name = name - self._hash = None self._validate() def _validate(self): @@ -301,9 +300,7 @@ def __eq__(self, other: "Instruction") -> bool: return isinstance(other, type(self)) and self.operands == other.operands def __hash__(self) -> int: - if self._hash is None: - self._hash = hash((type(self), self.operands, self.name)) - return self._hash + return hash((type(self), self.operands, self.name)) def __add__(self, other): """Return a new schedule with `other` inserted within `self` at `start_time`. diff --git a/releasenotes/notes/fix-pulse-channel-hash-549a8fb5d8738c4d.yaml b/releasenotes/notes/fix-pulse-channel-hash-549a8fb5d8738c4d.yaml new file mode 100644 index 000000000000..19d022572963 --- /dev/null +++ b/releasenotes/notes/fix-pulse-channel-hash-549a8fb5d8738c4d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed the :func:`hash` of Qiskit Pulse ``Channel`` objects (such as :class:`.DriveChannel`) in + cases where the channel was transferred from one Python process to another that used a different + hash seed.