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.

messages.py 9.3KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. from __future__ import annotations
  2. import codecs
  3. import queue
  4. import threading
  5. from typing import Iterator, List, Optional, cast
  6. from ..frames import Frame, Opcode
  7. from ..typing import Data
  8. __all__ = ["Assembler"]
  9. UTF8Decoder = codecs.getincrementaldecoder("utf-8")
  10. class Assembler:
  11. """
  12. Assemble messages from frames.
  13. """
  14. def __init__(self) -> None:
  15. # Serialize reads and writes -- except for reads via synchronization
  16. # primitives provided by the threading and queue modules.
  17. self.mutex = threading.Lock()
  18. # We create a latch with two events to ensure proper interleaving of
  19. # writing and reading messages.
  20. # put() sets this event to tell get() that a message can be fetched.
  21. self.message_complete = threading.Event()
  22. # get() sets this event to let put() that the message was fetched.
  23. self.message_fetched = threading.Event()
  24. # This flag prevents concurrent calls to get() by user code.
  25. self.get_in_progress = False
  26. # This flag prevents concurrent calls to put() by library code.
  27. self.put_in_progress = False
  28. # Decoder for text frames, None for binary frames.
  29. self.decoder: Optional[codecs.IncrementalDecoder] = None
  30. # Buffer of frames belonging to the same message.
  31. self.chunks: List[Data] = []
  32. # When switching from "buffering" to "streaming", we use a thread-safe
  33. # queue for transferring frames from the writing thread (library code)
  34. # to the reading thread (user code). We're buffering when chunks_queue
  35. # is None and streaming when it's a SimpleQueue. None is a sentinel
  36. # value marking the end of the stream, superseding message_complete.
  37. # Stream data from frames belonging to the same message.
  38. # Remove quotes around type when dropping Python < 3.9.
  39. self.chunks_queue: Optional["queue.SimpleQueue[Optional[Data]]"] = None
  40. # This flag marks the end of the stream.
  41. self.closed = False
  42. def get(self, timeout: Optional[float] = None) -> Data:
  43. """
  44. Read the next message.
  45. :meth:`get` returns a single :class:`str` or :class:`bytes`.
  46. If the message is fragmented, :meth:`get` waits until the last frame is
  47. received, then it reassembles the message and returns it. To receive
  48. messages frame by frame, use :meth:`get_iter` instead.
  49. Args:
  50. timeout: If a timeout is provided and elapses before a complete
  51. message is received, :meth:`get` raises :exc:`TimeoutError`.
  52. Raises:
  53. EOFError: If the stream of frames has ended.
  54. RuntimeError: If two threads run :meth:`get` or :meth:``get_iter`
  55. concurrently.
  56. """
  57. with self.mutex:
  58. if self.closed:
  59. raise EOFError("stream of frames ended")
  60. if self.get_in_progress:
  61. raise RuntimeError("get or get_iter is already running")
  62. self.get_in_progress = True
  63. # If the message_complete event isn't set yet, release the lock to
  64. # allow put() to run and eventually set it.
  65. # Locking with get_in_progress ensures only one thread can get here.
  66. completed = self.message_complete.wait(timeout)
  67. with self.mutex:
  68. self.get_in_progress = False
  69. # Waiting for a complete message timed out.
  70. if not completed:
  71. raise TimeoutError(f"timed out in {timeout:.1f}s")
  72. # get() was unblocked by close() rather than put().
  73. if self.closed:
  74. raise EOFError("stream of frames ended")
  75. assert self.message_complete.is_set()
  76. self.message_complete.clear()
  77. joiner: Data = b"" if self.decoder is None else ""
  78. # mypy cannot figure out that chunks have the proper type.
  79. message: Data = joiner.join(self.chunks) # type: ignore
  80. assert not self.message_fetched.is_set()
  81. self.message_fetched.set()
  82. self.chunks = []
  83. assert self.chunks_queue is None
  84. return message
  85. def get_iter(self) -> Iterator[Data]:
  86. """
  87. Stream the next message.
  88. Iterating the return value of :meth:`get_iter` yields a :class:`str` or
  89. :class:`bytes` for each frame in the message.
  90. The iterator must be fully consumed before calling :meth:`get_iter` or
  91. :meth:`get` again. Else, :exc:`RuntimeError` is raised.
  92. This method only makes sense for fragmented messages. If messages aren't
  93. fragmented, use :meth:`get` instead.
  94. Raises:
  95. EOFError: If the stream of frames has ended.
  96. RuntimeError: If two threads run :meth:`get` or :meth:``get_iter`
  97. concurrently.
  98. """
  99. with self.mutex:
  100. if self.closed:
  101. raise EOFError("stream of frames ended")
  102. if self.get_in_progress:
  103. raise RuntimeError("get or get_iter is already running")
  104. chunks = self.chunks
  105. self.chunks = []
  106. self.chunks_queue = cast(
  107. # Remove quotes around type when dropping Python < 3.9.
  108. "queue.SimpleQueue[Optional[Data]]",
  109. queue.SimpleQueue(),
  110. )
  111. # Sending None in chunk_queue supersedes setting message_complete
  112. # when switching to "streaming". If message is already complete
  113. # when the switch happens, put() didn't send None, so we have to.
  114. if self.message_complete.is_set():
  115. self.chunks_queue.put(None)
  116. self.get_in_progress = True
  117. # Locking with get_in_progress ensures only one thread can get here.
  118. yield from chunks
  119. while True:
  120. chunk = self.chunks_queue.get()
  121. if chunk is None:
  122. break
  123. yield chunk
  124. with self.mutex:
  125. self.get_in_progress = False
  126. assert self.message_complete.is_set()
  127. self.message_complete.clear()
  128. # get_iter() was unblocked by close() rather than put().
  129. if self.closed:
  130. raise EOFError("stream of frames ended")
  131. assert not self.message_fetched.is_set()
  132. self.message_fetched.set()
  133. assert self.chunks == []
  134. self.chunks_queue = None
  135. def put(self, frame: Frame) -> None:
  136. """
  137. Add ``frame`` to the next message.
  138. When ``frame`` is the final frame in a message, :meth:`put` waits until
  139. the message is fetched, either by calling :meth:`get` or by fully
  140. consuming the return value of :meth:`get_iter`.
  141. :meth:`put` assumes that the stream of frames respects the protocol. If
  142. it doesn't, the behavior is undefined.
  143. Raises:
  144. EOFError: If the stream of frames has ended.
  145. RuntimeError: If two threads run :meth:`put` concurrently.
  146. """
  147. with self.mutex:
  148. if self.closed:
  149. raise EOFError("stream of frames ended")
  150. if self.put_in_progress:
  151. raise RuntimeError("put is already running")
  152. if frame.opcode is Opcode.TEXT:
  153. self.decoder = UTF8Decoder(errors="strict")
  154. elif frame.opcode is Opcode.BINARY:
  155. self.decoder = None
  156. elif frame.opcode is Opcode.CONT:
  157. pass
  158. else:
  159. # Ignore control frames.
  160. return
  161. data: Data
  162. if self.decoder is not None:
  163. data = self.decoder.decode(frame.data, frame.fin)
  164. else:
  165. data = frame.data
  166. if self.chunks_queue is None:
  167. self.chunks.append(data)
  168. else:
  169. self.chunks_queue.put(data)
  170. if not frame.fin:
  171. return
  172. # Message is complete. Wait until it's fetched to return.
  173. assert not self.message_complete.is_set()
  174. self.message_complete.set()
  175. if self.chunks_queue is not None:
  176. self.chunks_queue.put(None)
  177. assert not self.message_fetched.is_set()
  178. self.put_in_progress = True
  179. # Release the lock to allow get() to run and eventually set the event.
  180. self.message_fetched.wait()
  181. with self.mutex:
  182. self.put_in_progress = False
  183. assert self.message_fetched.is_set()
  184. self.message_fetched.clear()
  185. # put() was unblocked by close() rather than get() or get_iter().
  186. if self.closed:
  187. raise EOFError("stream of frames ended")
  188. self.decoder = None
  189. def close(self) -> None:
  190. """
  191. End the stream of frames.
  192. Callling :meth:`close` concurrently with :meth:`get`, :meth:`get_iter`,
  193. or :meth:`put` is safe. They will raise :exc:`EOFError`.
  194. """
  195. with self.mutex:
  196. if self.closed:
  197. return
  198. self.closed = True
  199. # Unblock get or get_iter.
  200. if self.get_in_progress:
  201. self.message_complete.set()
  202. if self.chunks_queue is not None:
  203. self.chunks_queue.put(None)
  204. # Unblock put().
  205. if self.put_in_progress:
  206. self.message_fetched.set()