Repository: python-pillow/Pillow

Issue:
Image.open: dictionary changed size during iteration (race condition)
<!--
Thank you for reporting an issue.

Follow these guidelines to ensure your issue is handled properly.

If you have a ...

1.  General question: consider asking the question on Stack Overflow
    with the python-imaging-library tag:

    * https://stackoverflow.com/questions/tagged/python-imaging-library

    Do not ask a question in both places.

    If you think you have found a bug or have an unexplained exception
    then file a bug report here.

2.  Bug report: include a self-contained, copy-pastable example that
    generates the issue if possible. Be concise with code posted.
    Guidelines on how to provide a good bug report:

    * https://stackoverflow.com/help/mcve

    Bug reports which follow these guidelines are easier to diagnose,
    and are often handled much more quickly.

3.  Feature request: do a quick search of existing issues
    to make sure this has not been asked before.

We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->

### What did you do?

We are running a Django server with Gunicorn, with a view that is returning a thumbnail from an existing PNG image.
We open it from a BytesIO with `PIL.Image.open`.

### What did you expect to happen?

The `Image` instance is returned by `open`.

### What actually happened?

Python interpreter raised `RuntimeError(dictionary changed size during iteration)` on [this line](https://github.com/python-pillow/Pillow/blob/6956d0b2853f5c7ec5f6ec4c60725c5a7ee73aeb/src/PIL/ImageMath.py#L217) :

```python
  File "PIL/Image.py", line 3258, in open
    preinit()
  File "PIL/Image.py", line 320, in preinit
    from . import GifImagePlugin
  File "PIL/GifImagePlugin.py", line 34, in <module>
    from . import (
  File "PIL/ImageMath.py", line 217, in <module>
    for k, v in list(globals().items()):
```

### What are your OS, Python and Pillow versions?

* OS: Ubuntu 22.04 AArch64
* Python: 3.9.13 (CPython)
* Pillow: 10.2.0

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->

Unfortunately, it looks like it is a race condition on `globals()` with another lazy-loaded dependency, I don't have a minimal code that can reproduce this bug consistently. It is highly unlikely to happen, but is has happened once on our servers.

```python
# in a Django view
pil_image = Image.open(raw_image_bytes)
```



Discussion:
I created #7718 with an idea for this.

Resolving patch:
diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py
--- a/src/PIL/ImageMath.py
+++ b/src/PIL/ImageMath.py
@@ -224,10 +224,15 @@ def imagemath_convert(self: _Operand, mode: str) -> _Operand:
     return _Operand(self.im.convert(mode))
 
 
-ops = {}
-for k, v in list(globals().items()):
-    if k[:10] == "imagemath_":
-        ops[k[10:]] = v
+ops = {
+    "int": imagemath_int,
+    "float": imagemath_float,
+    "equal": imagemath_equal,
+    "notequal": imagemath_notequal,
+    "min": imagemath_min,
+    "max": imagemath_max,
+    "convert": imagemath_convert,
+}
 
 
 def eval(expression: str, _dict: dict[str, Any] = {}, **kw: Any) -> Any:
@@ -244,7 +249,7 @@ def eval(expression: str, _dict: dict[str, Any] = {}, **kw: Any) -> Any:
     """
 
     # build execution namespace
-    args = ops.copy()
+    args: dict[str, Any] = ops.copy()
     for k in list(_dict.keys()) + list(kw.keys()):
         if "__" in k or hasattr(builtins, k):
             msg = f"'{k}' not allowed"


Test patch: