144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
import sys
|
||
|
from typing import TYPE_CHECKING
|
||
|
|
||
|
from . import _core
|
||
|
from ._abc import ReceiveStream, SendStream
|
||
|
from ._core._windows_cffi import _handle, kernel32, raise_winerror
|
||
|
from ._util import ConflictDetector, final
|
||
|
|
||
|
assert sys.platform == "win32" or not TYPE_CHECKING
|
||
|
|
||
|
# XX TODO: don't just make this up based on nothing.
|
||
|
DEFAULT_RECEIVE_SIZE = 65536
|
||
|
|
||
|
|
||
|
# See the comments on _unix_pipes._FdHolder for discussion of why we set the
|
||
|
# handle to -1 when it's closed.
|
||
|
class _HandleHolder:
|
||
|
def __init__(self, handle: int) -> None:
|
||
|
self.handle = -1
|
||
|
if not isinstance(handle, int):
|
||
|
raise TypeError("handle must be an int")
|
||
|
self.handle = handle
|
||
|
_core.register_with_iocp(self.handle)
|
||
|
|
||
|
@property
|
||
|
def closed(self) -> bool:
|
||
|
return self.handle == -1
|
||
|
|
||
|
def close(self) -> None:
|
||
|
if self.closed:
|
||
|
return
|
||
|
handle = self.handle
|
||
|
self.handle = -1
|
||
|
if not kernel32.CloseHandle(_handle(handle)):
|
||
|
raise_winerror()
|
||
|
|
||
|
def __del__(self) -> None:
|
||
|
self.close()
|
||
|
|
||
|
|
||
|
@final
|
||
|
class PipeSendStream(SendStream):
|
||
|
"""Represents a send stream over a Windows named pipe that has been
|
||
|
opened in OVERLAPPED mode.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, handle: int) -> None:
|
||
|
self._handle_holder = _HandleHolder(handle)
|
||
|
self._conflict_detector = ConflictDetector(
|
||
|
"another task is currently using this pipe"
|
||
|
)
|
||
|
|
||
|
async def send_all(self, data: bytes) -> None:
|
||
|
with self._conflict_detector:
|
||
|
if self._handle_holder.closed:
|
||
|
raise _core.ClosedResourceError("this pipe is already closed")
|
||
|
|
||
|
if not data:
|
||
|
await _core.checkpoint()
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
written = await _core.write_overlapped(self._handle_holder.handle, data)
|
||
|
except BrokenPipeError as ex:
|
||
|
raise _core.BrokenResourceError from ex
|
||
|
# By my reading of MSDN, this assert is guaranteed to pass so long
|
||
|
# as the pipe isn't in nonblocking mode, but... let's just
|
||
|
# double-check.
|
||
|
assert written == len(data)
|
||
|
|
||
|
async def wait_send_all_might_not_block(self) -> None:
|
||
|
with self._conflict_detector:
|
||
|
if self._handle_holder.closed:
|
||
|
raise _core.ClosedResourceError("This pipe is already closed")
|
||
|
|
||
|
# not implemented yet, and probably not needed
|
||
|
await _core.checkpoint()
|
||
|
|
||
|
def close(self) -> None:
|
||
|
self._handle_holder.close()
|
||
|
|
||
|
async def aclose(self) -> None:
|
||
|
self.close()
|
||
|
await _core.checkpoint()
|
||
|
|
||
|
|
||
|
@final
|
||
|
class PipeReceiveStream(ReceiveStream):
|
||
|
"""Represents a receive stream over an os.pipe object."""
|
||
|
|
||
|
def __init__(self, handle: int) -> None:
|
||
|
self._handle_holder = _HandleHolder(handle)
|
||
|
self._conflict_detector = ConflictDetector(
|
||
|
"another task is currently using this pipe"
|
||
|
)
|
||
|
|
||
|
async def receive_some(self, max_bytes: int | None = None) -> bytes:
|
||
|
with self._conflict_detector:
|
||
|
if self._handle_holder.closed:
|
||
|
raise _core.ClosedResourceError("this pipe is already closed")
|
||
|
|
||
|
if max_bytes is None:
|
||
|
max_bytes = DEFAULT_RECEIVE_SIZE
|
||
|
else:
|
||
|
if not isinstance(max_bytes, int):
|
||
|
raise TypeError("max_bytes must be integer >= 1")
|
||
|
if max_bytes < 1:
|
||
|
raise ValueError("max_bytes must be integer >= 1")
|
||
|
|
||
|
buffer = bytearray(max_bytes)
|
||
|
try:
|
||
|
size = await _core.readinto_overlapped(
|
||
|
self._handle_holder.handle, buffer
|
||
|
)
|
||
|
except BrokenPipeError:
|
||
|
if self._handle_holder.closed:
|
||
|
raise _core.ClosedResourceError(
|
||
|
"another task closed this pipe"
|
||
|
) from None
|
||
|
|
||
|
# Windows raises BrokenPipeError on one end of a pipe
|
||
|
# whenever the other end closes, regardless of direction.
|
||
|
# Convert this to the Unix behavior of returning EOF to the
|
||
|
# reader when the writer closes.
|
||
|
#
|
||
|
# And since we're not raising an exception, we have to
|
||
|
# checkpoint. But readinto_overlapped did raise an exception,
|
||
|
# so it might not have checkpointed for us. So we have to
|
||
|
# checkpoint manually.
|
||
|
await _core.checkpoint()
|
||
|
return b""
|
||
|
else:
|
||
|
del buffer[size:]
|
||
|
return buffer
|
||
|
|
||
|
def close(self) -> None:
|
||
|
self._handle_holder.close()
|
||
|
|
||
|
async def aclose(self) -> None:
|
||
|
self.close()
|
||
|
await _core.checkpoint()
|