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.
 
 
 
 
 
 

151 regels
4.9 KiB

  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """A circular doubly linked list implementation.
  15. """
  16. import threading
  17. from typing import Generic, Optional, Type, TypeVar
  18. P = TypeVar("P")
  19. LN = TypeVar("LN", bound="ListNode")
  20. class ListNode(Generic[P]):
  21. """A node in a circular doubly linked list, with an (optional) reference to
  22. a cache entry.
  23. The reference should only be `None` for the root node or if the node has
  24. been removed from the list.
  25. """
  26. # A lock to protect mutating the list prev/next pointers.
  27. _LOCK = threading.Lock()
  28. # We don't use attrs here as in py3.6 you can't have `attr.s(slots=True)`
  29. # and inherit from `Generic` for some reason
  30. __slots__ = [
  31. "cache_entry",
  32. "prev_node",
  33. "next_node",
  34. ]
  35. def __init__(self, cache_entry: Optional[P] = None) -> None:
  36. self.cache_entry = cache_entry
  37. self.prev_node: Optional[ListNode[P]] = None
  38. self.next_node: Optional[ListNode[P]] = None
  39. @classmethod
  40. def create_root_node(cls: Type["ListNode[P]"]) -> "ListNode[P]":
  41. """Create a new linked list by creating a "root" node, which is a node
  42. that has prev_node/next_node pointing to itself and no associated cache
  43. entry.
  44. """
  45. root = cls()
  46. root.prev_node = root
  47. root.next_node = root
  48. return root
  49. @classmethod
  50. def insert_after(
  51. cls: Type[LN],
  52. cache_entry: P,
  53. node: "ListNode[P]",
  54. ) -> LN:
  55. """Create a new list node that is placed after the given node.
  56. Args:
  57. cache_entry: The associated cache entry.
  58. node: The existing node in the list to insert the new entry after.
  59. """
  60. new_node = cls(cache_entry)
  61. with cls._LOCK:
  62. new_node._refs_insert_after(node)
  63. return new_node
  64. def remove_from_list(self) -> None:
  65. """Remove this node from the list."""
  66. with self._LOCK:
  67. self._refs_remove_node_from_list()
  68. # We drop the reference to the cache entry to break the reference cycle
  69. # between the list node and cache entry, allowing the two to be dropped
  70. # immediately rather than at the next GC.
  71. self.cache_entry = None
  72. def move_after(self, node: "ListNode[P]") -> None:
  73. """Move this node from its current location in the list to after the
  74. given node.
  75. """
  76. with self._LOCK:
  77. # We assert that both this node and the target node is still "alive".
  78. assert self.prev_node
  79. assert self.next_node
  80. assert node.prev_node
  81. assert node.next_node
  82. assert self is not node
  83. # Remove self from the list
  84. self._refs_remove_node_from_list()
  85. # Insert self back into the list, after target node
  86. self._refs_insert_after(node)
  87. def _refs_remove_node_from_list(self) -> None:
  88. """Internal method to *just* remove the node from the list, without
  89. e.g. clearing out the cache entry.
  90. """
  91. if self.prev_node is None or self.next_node is None:
  92. # We've already been removed from the list.
  93. return
  94. prev_node = self.prev_node
  95. next_node = self.next_node
  96. prev_node.next_node = next_node
  97. next_node.prev_node = prev_node
  98. # We set these to None so that we don't get circular references,
  99. # allowing us to be dropped without having to go via the GC.
  100. self.prev_node = None
  101. self.next_node = None
  102. def _refs_insert_after(self, node: "ListNode[P]") -> None:
  103. """Internal method to insert the node after the given node."""
  104. # This method should only be called when we're not already in the list.
  105. assert self.prev_node is None
  106. assert self.next_node is None
  107. # We expect the given node to be in the list and thus have valid
  108. # prev/next refs.
  109. assert node.next_node
  110. assert node.prev_node
  111. prev_node = node
  112. next_node = node.next_node
  113. self.prev_node = prev_node
  114. self.next_node = next_node
  115. prev_node.next_node = self
  116. next_node.prev_node = self
  117. def get_cache_entry(self) -> Optional[P]:
  118. """Get the cache entry, returns None if this is the root node (i.e.
  119. cache_entry is None) or if the entry has been dropped.
  120. """
  121. return self.cache_entry