Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

wait.py 4.3KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import annotations
  2. import select
  3. import socket
  4. from functools import partial
  5. __all__ = ["wait_for_read", "wait_for_write"]
  6. # How should we wait on sockets?
  7. #
  8. # There are two types of APIs you can use for waiting on sockets: the fancy
  9. # modern stateful APIs like epoll/kqueue, and the older stateless APIs like
  10. # select/poll. The stateful APIs are more efficient when you have a lots of
  11. # sockets to keep track of, because you can set them up once and then use them
  12. # lots of times. But we only ever want to wait on a single socket at a time
  13. # and don't want to keep track of state, so the stateless APIs are actually
  14. # more efficient. So we want to use select() or poll().
  15. #
  16. # Now, how do we choose between select() and poll()? On traditional Unixes,
  17. # select() has a strange calling convention that makes it slow, or fail
  18. # altogether, for high-numbered file descriptors. The point of poll() is to fix
  19. # that, so on Unixes, we prefer poll().
  20. #
  21. # On Windows, there is no poll() (or at least Python doesn't provide a wrapper
  22. # for it), but that's OK, because on Windows, select() doesn't have this
  23. # strange calling convention; plain select() works fine.
  24. #
  25. # So: on Windows we use select(), and everywhere else we use poll(). We also
  26. # fall back to select() in case poll() is somehow broken or missing.
  27. def select_wait_for_socket(
  28. sock: socket.socket,
  29. read: bool = False,
  30. write: bool = False,
  31. timeout: float | None = None,
  32. ) -> bool:
  33. if not read and not write:
  34. raise RuntimeError("must specify at least one of read=True, write=True")
  35. rcheck = []
  36. wcheck = []
  37. if read:
  38. rcheck.append(sock)
  39. if write:
  40. wcheck.append(sock)
  41. # When doing a non-blocking connect, most systems signal success by
  42. # marking the socket writable. Windows, though, signals success by marked
  43. # it as "exceptional". We paper over the difference by checking the write
  44. # sockets for both conditions. (The stdlib selectors module does the same
  45. # thing.)
  46. fn = partial(select.select, rcheck, wcheck, wcheck)
  47. rready, wready, xready = fn(timeout)
  48. return bool(rready or wready or xready)
  49. def poll_wait_for_socket(
  50. sock: socket.socket,
  51. read: bool = False,
  52. write: bool = False,
  53. timeout: float | None = None,
  54. ) -> bool:
  55. if not read and not write:
  56. raise RuntimeError("must specify at least one of read=True, write=True")
  57. mask = 0
  58. if read:
  59. mask |= select.POLLIN
  60. if write:
  61. mask |= select.POLLOUT
  62. poll_obj = select.poll()
  63. poll_obj.register(sock, mask)
  64. # For some reason, poll() takes timeout in milliseconds
  65. def do_poll(t: float | None) -> list[tuple[int, int]]:
  66. if t is not None:
  67. t *= 1000
  68. return poll_obj.poll(t)
  69. return bool(do_poll(timeout))
  70. def _have_working_poll() -> bool:
  71. # Apparently some systems have a select.poll that fails as soon as you try
  72. # to use it, either due to strange configuration or broken monkeypatching
  73. # from libraries like eventlet/greenlet.
  74. try:
  75. poll_obj = select.poll()
  76. poll_obj.poll(0)
  77. except (AttributeError, OSError):
  78. return False
  79. else:
  80. return True
  81. def wait_for_socket(
  82. sock: socket.socket,
  83. read: bool = False,
  84. write: bool = False,
  85. timeout: float | None = None,
  86. ) -> bool:
  87. # We delay choosing which implementation to use until the first time we're
  88. # called. We could do it at import time, but then we might make the wrong
  89. # decision if someone goes wild with monkeypatching select.poll after
  90. # we're imported.
  91. global wait_for_socket
  92. if _have_working_poll():
  93. wait_for_socket = poll_wait_for_socket
  94. elif hasattr(select, "select"):
  95. wait_for_socket = select_wait_for_socket
  96. return wait_for_socket(sock, read, write, timeout)
  97. def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool:
  98. """Waits for reading to be available on a given socket.
  99. Returns True if the socket is readable, or False if the timeout expired.
  100. """
  101. return wait_for_socket(sock, read=True, timeout=timeout)
  102. def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool:
  103. """Waits for writing to be available on a given socket.
  104. Returns True if the socket is readable, or False if the timeout expired.
  105. """
  106. return wait_for_socket(sock, write=True, timeout=timeout)