Development of an internal social media platform with personalised dashboards for students
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.

heap.py 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #
  2. # Module which supports allocation of memory from an mmap
  3. #
  4. # multiprocessing/heap.py
  5. #
  6. # Copyright (c) 2006-2008, R Oudkerk
  7. # Licensed to PSF under a Contributor Agreement.
  8. #
  9. from __future__ import absolute_import
  10. import bisect
  11. import mmap
  12. import os
  13. import sys
  14. import threading
  15. import itertools
  16. from ._ext import _billiard, win32
  17. from .util import Finalize, info, get_temp_dir
  18. from .forking import assert_spawning
  19. from .reduction import ForkingPickler
  20. __all__ = ['BufferWrapper']
  21. try:
  22. maxsize = sys.maxsize
  23. except AttributeError:
  24. maxsize = sys.maxint
  25. #
  26. # Inheirtable class which wraps an mmap, and from which blocks can be allocated
  27. #
  28. if sys.platform == 'win32':
  29. class Arena(object):
  30. _counter = itertools.count()
  31. def __init__(self, size):
  32. self.size = size
  33. self.name = 'pym-%d-%d' % (os.getpid(), next(Arena._counter))
  34. self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
  35. assert win32.GetLastError() == 0, 'tagname already in use'
  36. self._state = (self.size, self.name)
  37. def __getstate__(self):
  38. assert_spawning(self)
  39. return self._state
  40. def __setstate__(self, state):
  41. self.size, self.name = self._state = state
  42. self.buffer = mmap.mmap(-1, self.size, tagname=self.name)
  43. assert win32.GetLastError() == win32.ERROR_ALREADY_EXISTS
  44. else:
  45. class Arena(object):
  46. _counter = itertools.count()
  47. def __init__(self, size, fileno=-1):
  48. from .forking import _forking_is_enabled
  49. self.size = size
  50. self.fileno = fileno
  51. if fileno == -1 and not _forking_is_enabled:
  52. name = os.path.join(
  53. get_temp_dir(),
  54. 'pym-%d-%d' % (os.getpid(), next(self._counter)))
  55. self.fileno = os.open(
  56. name, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o600)
  57. os.unlink(name)
  58. os.ftruncate(self.fileno, size)
  59. self.buffer = mmap.mmap(self.fileno, self.size)
  60. def reduce_arena(a):
  61. if a.fileno == -1:
  62. raise ValueError('Arena is unpicklable because'
  63. 'forking was enabled when it was created')
  64. return Arena, (a.size, a.fileno)
  65. ForkingPickler.register(Arena, reduce_arena)
  66. #
  67. # Class allowing allocation of chunks of memory from arenas
  68. #
  69. class Heap(object):
  70. _alignment = 8
  71. def __init__(self, size=mmap.PAGESIZE):
  72. self._lastpid = os.getpid()
  73. self._lock = threading.Lock()
  74. self._size = size
  75. self._lengths = []
  76. self._len_to_seq = {}
  77. self._start_to_block = {}
  78. self._stop_to_block = {}
  79. self._allocated_blocks = set()
  80. self._arenas = []
  81. # list of pending blocks to free - see free() comment below
  82. self._pending_free_blocks = []
  83. @staticmethod
  84. def _roundup(n, alignment):
  85. # alignment must be a power of 2
  86. mask = alignment - 1
  87. return (n + mask) & ~mask
  88. def _malloc(self, size):
  89. # returns a large enough block -- it might be much larger
  90. i = bisect.bisect_left(self._lengths, size)
  91. if i == len(self._lengths):
  92. length = self._roundup(max(self._size, size), mmap.PAGESIZE)
  93. self._size *= 2
  94. info('allocating a new mmap of length %d', length)
  95. arena = Arena(length)
  96. self._arenas.append(arena)
  97. return (arena, 0, length)
  98. else:
  99. length = self._lengths[i]
  100. seq = self._len_to_seq[length]
  101. block = seq.pop()
  102. if not seq:
  103. del self._len_to_seq[length], self._lengths[i]
  104. (arena, start, stop) = block
  105. del self._start_to_block[(arena, start)]
  106. del self._stop_to_block[(arena, stop)]
  107. return block
  108. def _free(self, block):
  109. # free location and try to merge with neighbours
  110. (arena, start, stop) = block
  111. try:
  112. prev_block = self._stop_to_block[(arena, start)]
  113. except KeyError:
  114. pass
  115. else:
  116. start, _ = self._absorb(prev_block)
  117. try:
  118. next_block = self._start_to_block[(arena, stop)]
  119. except KeyError:
  120. pass
  121. else:
  122. _, stop = self._absorb(next_block)
  123. block = (arena, start, stop)
  124. length = stop - start
  125. try:
  126. self._len_to_seq[length].append(block)
  127. except KeyError:
  128. self._len_to_seq[length] = [block]
  129. bisect.insort(self._lengths, length)
  130. self._start_to_block[(arena, start)] = block
  131. self._stop_to_block[(arena, stop)] = block
  132. def _absorb(self, block):
  133. # deregister this block so it can be merged with a neighbour
  134. (arena, start, stop) = block
  135. del self._start_to_block[(arena, start)]
  136. del self._stop_to_block[(arena, stop)]
  137. length = stop - start
  138. seq = self._len_to_seq[length]
  139. seq.remove(block)
  140. if not seq:
  141. del self._len_to_seq[length]
  142. self._lengths.remove(length)
  143. return start, stop
  144. def _free_pending_blocks(self):
  145. # Free all the blocks in the pending list - called with the lock held
  146. while 1:
  147. try:
  148. block = self._pending_free_blocks.pop()
  149. except IndexError:
  150. break
  151. self._allocated_blocks.remove(block)
  152. self._free(block)
  153. def free(self, block):
  154. # free a block returned by malloc()
  155. # Since free() can be called asynchronously by the GC, it could happen
  156. # that it's called while self._lock is held: in that case,
  157. # self._lock.acquire() would deadlock (issue #12352). To avoid that, a
  158. # trylock is used instead, and if the lock can't be acquired
  159. # immediately, the block is added to a list of blocks to be freed
  160. # synchronously sometimes later from malloc() or free(), by calling
  161. # _free_pending_blocks() (appending and retrieving from a list is not
  162. # strictly thread-safe but under cPython it's atomic thanks
  163. # to the GIL).
  164. assert os.getpid() == self._lastpid
  165. if not self._lock.acquire(False):
  166. # can't aquire the lock right now, add the block to the list of
  167. # pending blocks to free
  168. self._pending_free_blocks.append(block)
  169. else:
  170. # we hold the lock
  171. try:
  172. self._free_pending_blocks()
  173. self._allocated_blocks.remove(block)
  174. self._free(block)
  175. finally:
  176. self._lock.release()
  177. def malloc(self, size):
  178. # return a block of right size (possibly rounded up)
  179. assert 0 <= size < maxsize
  180. if os.getpid() != self._lastpid:
  181. self.__init__() # reinitialize after fork
  182. self._lock.acquire()
  183. self._free_pending_blocks()
  184. try:
  185. size = self._roundup(max(size, 1), self._alignment)
  186. (arena, start, stop) = self._malloc(size)
  187. new_stop = start + size
  188. if new_stop < stop:
  189. self._free((arena, new_stop, stop))
  190. block = (arena, start, new_stop)
  191. self._allocated_blocks.add(block)
  192. return block
  193. finally:
  194. self._lock.release()
  195. #
  196. # Class representing a chunk of an mmap -- can be inherited
  197. #
  198. class BufferWrapper(object):
  199. _heap = Heap()
  200. def __init__(self, size):
  201. assert 0 <= size < maxsize
  202. block = BufferWrapper._heap.malloc(size)
  203. self._state = (block, size)
  204. Finalize(self, BufferWrapper._heap.free, args=(block,))
  205. def get_address(self):
  206. (arena, start, stop), size = self._state
  207. address, length = _billiard.address_of_buffer(arena.buffer)
  208. assert size <= length
  209. return address + start
  210. def get_size(self):
  211. return self._state[1]