diff --git a/docs/handbook/index.rst b/docs/handbook/index.rst index 23255eb8ac0..d187c68c404 100644 --- a/docs/handbook/index.rst +++ b/docs/handbook/index.rst @@ -8,4 +8,5 @@ Handbook tutorial concepts appendices + thread-safety security diff --git a/docs/handbook/thread-safety.rst b/docs/handbook/thread-safety.rst new file mode 100644 index 00000000000..f17ffdb64fa --- /dev/null +++ b/docs/handbook/thread-safety.rst @@ -0,0 +1,84 @@ +.. _thread-safety: + +Thread safety +============= + +Pillow can be used from multiple threads, +both on the default build of Python and on the free-threaded build (:pep:`703`). + +Many operations on an image release the :term:`global interpreter lock ` +while they process pixel data, so on either build, +other threads may run while an operation is still in progress. + +Pillow does **not** add locks around individual images. +Sharing an image between threads is safe only in the ways described below. +Anything beyond that needs to be synchronized by your application. + +Safe +---- + +* **Separate images in separate threads.** + Opening, processing and saving different :py:class:`~PIL.Image.Image` objects + concurrently is supported. This is a natural way to parallelize Pillow work. + +* **Reading a shared, loaded image.** + Several threads may read the same image at once, as long as no thread modifies it. + This includes operations that return a new image, such as resizing, cropping, + filtering, etc. + Saving is safe in most cases, but some encoders may concurrently modify an image's + ``encoderinfo``, causing issues. Conversions that do not involve palette quantization + are also safe between threads. + However, take care to call :py:meth:`~PIL.Image.Image.load` from a single thread + before sharing an image, because otherwise several threads may try to read from + the same file object at once. + +* **Fonts.** + A :py:class:`~PIL.ImageFont.FreeTypeFont` may be used by several threads at once + to measure and render text. + +Not safe +-------- + +* **Modifying an image while another thread uses it.** + This includes in-place operations such as :py:meth:`~PIL.Image.Image.paste`, + :py:meth:`~PIL.Image.Image.putpixel`, :py:meth:`~PIL.Image.Image.putdata`, + :py:meth:`~PIL.Image.Image.putpalette`, :py:meth:`~PIL.Image.Image.putalpha`, + drawing with :py:meth:`~PIL.ImageDraw.Draw`, and writing through the + :py:class:`PixelAccess` class (as returned by :py:meth:`~PIL.Image.Image.load`). + The results are undefined: the reading thread may see partly updated pixels, + and some combinations could crash the interpreter. + This applies to the default build as well as to the free-threaded build. + +* **Changing the frame of a shared image.** + :py:meth:`~PIL.Image.Image.seek` modifies the image, + so a multi-frame image must not be seeked while other threads use it. + You can use either multiple copies of the same multi-frame image file, + or load and :py:meth:`~PIL.Image.Image.copy` them in a single thread up-front. + +* **Closing an image while another thread uses it.** + :py:meth:`~PIL.Image.Image.close` releases the image's data, + so other threads must have finished with the image first. + +* **Varying a font.** + :py:meth:`~PIL.ImageFont.FreeTypeFont.set_variation_by_name` and + :py:meth:`~PIL.ImageFont.FreeTypeFont.set_variation_by_axes` change the font + in-place for every thread that uses it. + You can use :py:meth:`~PIL.ImageFont.FreeTypeFont.font_variant` to create a + separate variant font object instead. + +If you need to modify an image that other threads are using, you'll need to +synchronize access with e.g. a :py:class:`threading.Lock` or other method. + +Global settings +--------------- + +Module-level settings are shared by all threads. These include: + +* :py:data:`PIL.Image.MAX_IMAGE_PIXELS` +* :py:data:`PIL.ImageFile.LOAD_TRUNCATED_IMAGES` +* :py:data:`PIL.ImageFile.MAXBLOCK` +* :py:data:`PIL.ImageFile.SAFEBLOCK` +* the plugin registries populated by functions such as :py:func:`PIL.Image.register_open` +* the :ref:`block allocator ` settings + +You may wish to set these once, in a single thread, as they will affect others. diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index 4c34ff8128b..842b04ce28e 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -75,5 +75,6 @@ Constants .. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES .. autodata:: PIL.ImageFile.MAXBLOCK +.. autodata:: PIL.ImageFile.SAFEBLOCK .. autodata:: PIL.ImageFile.ERRORS :annotation: diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index c0ca038579d..3309888a6ae 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -64,6 +64,16 @@ """ SAFEBLOCK = 1024 * 1024 +""" +When a plugin reads a chunk of data whose size is given by the image file itself, +Pillow does not trust that size. + +If it is greater than this number of bytes, the data is read in blocks of this size +instead of all at once. This stops a malicious file from causing a large allocation +by claiming more data than it contains. + +User code may set this to another number. +""" LOAD_TRUNCATED_IMAGES = False """Whether or not to load truncated image files. User code may change this."""