| 148 | Path(self.lock_file).unlink() |
| 149 | |
| 150 | def _release(self) -> None: |
| 151 | fd = self._context.lock_file_fd |
| 152 | assert fd is not None # ruff:ignore[assert] # _release runs only while held, so the descriptor is set |
| 153 | # Capture the held file's identity before closing so cleanup can refuse to unlink a successor's marker. A |
| 154 | # supported lifetime lease lets a peer break our expired marker and create its own at this path before we |
| 155 | # release; unlinking by path alone would then delete the successor's lock. |
| 156 | identity: tuple[int, int] | None = None |
| 157 | with suppress(OSError): |
| 158 | identity = _file_identity(os.fstat(fd)) |
| 159 | # A failed close may already have released and recycled the descriptor number. Relinquish it before the one |
| 160 | # close attempt so no later release can close an unrelated descriptor that reused the same integer. |
| 161 | self._mark_descriptor_released() |
| 162 | try: |
| 163 | self._close_released_fd(fd, default_suppresses=False) |
| 164 | # Marker cleanup must also run for control-flow exceptions, and both failures must remain observable. |
| 165 | except BaseException as close_error: |
| 166 | try: |
| 167 | self._unlink_held_marker(identity) |
| 168 | except BaseException as cleanup_error: # ruff:ignore[blind-except] # preserve control-flow cleanup failures |
| 169 | _raise_grouped_errors( |
| 170 | "lock descriptor close and marker cleanup both failed", |
| 171 | close_error, |
| 172 | cleanup_error, |
| 173 | ) |
| 174 | raise |
| 175 | self._unlink_held_marker(identity) |
| 176 | |
| 177 | def _unlink_held_marker(self, identity: tuple[int, int] | None) -> None: |
| 178 | if identity is None: |