fileobject.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. """
  2. Wrappers to make file-like objects cooperative.
  3. .. class:: FileObject
  4. The main entry point to the file-like gevent-compatible behaviour. It will be defined
  5. to be the best available implementation.
  6. There are two main implementations of ``FileObject``. On all systems,
  7. there is :class:`FileObjectThread` which uses the built-in native
  8. threadpool to avoid blocking the entire interpreter. On UNIX systems
  9. (those that support the :mod:`fcntl` module), there is also
  10. :class:`FileObjectPosix` which uses native non-blocking semantics.
  11. A third class, :class:`FileObjectBlock`, is simply a wrapper that executes everything
  12. synchronously (and so is not gevent-compatible). It is provided for testing and debugging
  13. purposes.
  14. Configuration
  15. =============
  16. You may change the default value for ``FileObject`` using the
  17. ``GEVENT_FILE`` environment variable. Set it to ``posix``, ``thread``,
  18. or ``block`` to choose from :class:`FileObjectPosix`,
  19. :class:`FileObjectThread` and :class:`FileObjectBlock`, respectively.
  20. You may also set it to the fully qualified class name of another
  21. object that implements the file interface to use one of your own
  22. objects.
  23. .. note:: The environment variable must be set at the time this module
  24. is first imported.
  25. Classes
  26. =======
  27. """
  28. from __future__ import absolute_import
  29. import functools
  30. import sys
  31. import os
  32. from gevent._fileobjectcommon import FileObjectClosed
  33. from gevent._fileobjectcommon import FileObjectBase
  34. from gevent.hub import get_hub
  35. from gevent._compat import integer_types
  36. from gevent._compat import reraise
  37. from gevent.lock import Semaphore, DummySemaphore
  38. PYPY = hasattr(sys, 'pypy_version_info')
  39. if hasattr(sys, 'exc_clear'):
  40. def _exc_clear():
  41. sys.exc_clear()
  42. else:
  43. def _exc_clear():
  44. return
  45. __all__ = [
  46. 'FileObjectPosix',
  47. 'FileObjectThread',
  48. 'FileObject',
  49. ]
  50. try:
  51. from fcntl import fcntl
  52. except ImportError:
  53. __all__.remove("FileObjectPosix")
  54. else:
  55. del fcntl
  56. from gevent._fileobjectposix import FileObjectPosix
  57. class FileObjectThread(FileObjectBase):
  58. """
  59. A file-like object wrapping another file-like object, performing all blocking
  60. operations on that object in a background thread.
  61. .. caution::
  62. Attempting to change the threadpool or lock of an existing FileObjectThread
  63. has undefined consequences.
  64. .. versionchanged:: 1.1b1
  65. The file object is closed using the threadpool. Note that whether or
  66. not this action is synchronous or asynchronous is not documented.
  67. """
  68. def __init__(self, fobj, mode=None, bufsize=-1, close=True, threadpool=None, lock=True):
  69. """
  70. :param fobj: The underlying file-like object to wrap, or an integer fileno
  71. that will be pass to :func:`os.fdopen` along with *mode* and *bufsize*.
  72. :keyword bool lock: If True (the default) then all operations will
  73. be performed one-by-one. Note that this does not guarantee that, if using
  74. this file object from multiple threads/greenlets, operations will be performed
  75. in any particular order, only that no two operations will be attempted at the
  76. same time. You can also pass your own :class:`gevent.lock.Semaphore` to synchronize
  77. file operations with an external resource.
  78. :keyword bool close: If True (the default) then when this object is closed,
  79. the underlying object is closed as well.
  80. """
  81. closefd = close
  82. self.threadpool = threadpool or get_hub().threadpool
  83. self.lock = lock
  84. if self.lock is True:
  85. self.lock = Semaphore()
  86. elif not self.lock:
  87. self.lock = DummySemaphore()
  88. if not hasattr(self.lock, '__enter__'):
  89. raise TypeError('Expected a Semaphore or boolean, got %r' % type(self.lock))
  90. if isinstance(fobj, integer_types):
  91. if not closefd:
  92. # we cannot do this, since fdopen object will close the descriptor
  93. raise TypeError('FileObjectThread does not support close=False on an fd.')
  94. if mode is None:
  95. assert bufsize == -1, "If you use the default mode, you can't choose a bufsize"
  96. fobj = os.fdopen(fobj)
  97. else:
  98. fobj = os.fdopen(fobj, mode, bufsize)
  99. self.__io_holder = [fobj] # signal for _wrap_method
  100. super(FileObjectThread, self).__init__(fobj, closefd)
  101. def _do_close(self, fobj, closefd):
  102. self.__io_holder[0] = None # for _wrap_method
  103. try:
  104. with self.lock:
  105. self.threadpool.apply(fobj.flush)
  106. finally:
  107. if closefd:
  108. # Note that we're not taking the lock; older code
  109. # did fobj.close() without going through the threadpool at all,
  110. # so acquiring the lock could potentially introduce deadlocks
  111. # that weren't present before. Avoiding the lock doesn't make
  112. # the existing race condition any worse.
  113. # We wrap the close in an exception handler and re-raise directly
  114. # to avoid the (common, expected) IOError from being logged by the pool
  115. def close():
  116. try:
  117. fobj.close()
  118. except: # pylint:disable=bare-except
  119. return sys.exc_info()
  120. exc_info = self.threadpool.apply(close)
  121. if exc_info:
  122. reraise(*exc_info)
  123. def _do_delegate_methods(self):
  124. super(FileObjectThread, self)._do_delegate_methods()
  125. if not hasattr(self, 'read1') and 'r' in getattr(self._io, 'mode', ''):
  126. self.read1 = self.read
  127. self.__io_holder[0] = self._io
  128. def _extra_repr(self):
  129. return ' threadpool=%r' % (self.threadpool,)
  130. def __iter__(self):
  131. return self
  132. def next(self):
  133. line = self.readline()
  134. if line:
  135. return line
  136. raise StopIteration
  137. __next__ = next
  138. def _wrap_method(self, method):
  139. # NOTE: We are careful to avoid introducing a refcycle
  140. # within self. Our wrapper cannot refer to self.
  141. io_holder = self.__io_holder
  142. lock = self.lock
  143. threadpool = self.threadpool
  144. @functools.wraps(method)
  145. def thread_method(*args, **kwargs):
  146. if io_holder[0] is None:
  147. # This is different than FileObjectPosix, etc,
  148. # because we want to save the expensive trip through
  149. # the threadpool.
  150. raise FileObjectClosed()
  151. with lock:
  152. return threadpool.apply(method, args, kwargs)
  153. return thread_method
  154. try:
  155. FileObject = FileObjectPosix
  156. except NameError:
  157. FileObject = FileObjectThread
  158. class FileObjectBlock(FileObjectBase):
  159. def __init__(self, fobj, *args, **kwargs):
  160. closefd = kwargs.pop('close', True)
  161. if kwargs:
  162. raise TypeError('Unexpected arguments: %r' % kwargs.keys())
  163. if isinstance(fobj, integer_types):
  164. if not closefd:
  165. # we cannot do this, since fdopen object will close the descriptor
  166. raise TypeError('FileObjectBlock does not support close=False on an fd.')
  167. fobj = os.fdopen(fobj, *args)
  168. super(FileObjectBlock, self).__init__(fobj, closefd)
  169. def _do_close(self, fobj, closefd):
  170. fobj.close()
  171. config = os.environ.get('GEVENT_FILE')
  172. if config:
  173. klass = {'thread': 'gevent.fileobject.FileObjectThread',
  174. 'posix': 'gevent.fileobject.FileObjectPosix',
  175. 'block': 'gevent.fileobject.FileObjectBlock'}.get(config, config)
  176. if klass.startswith('gevent.fileobject.'):
  177. FileObject = globals()[klass.split('.', 2)[-1]]
  178. else:
  179. from gevent.hub import _import
  180. FileObject = _import(klass)
  181. del klass