Python allows multiple threads to concurrently access file descriptors through [files](https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files) and [sockets](https://docs.python.org/3/library/socket.html). Race conditions at the Python level can lead to unexpected behavior, even with the global interpreter lock. Thread sanitizer reports these [races](https://github.com/google/sanitizers/wiki/ThreadSanitizerPopularDataRaces#race-on-a-file-descriptor) in some of our tests that exercise this behavior. We cannot fix these potential race conditions without introducing potential deadlocks. For example, consider: ```python import threading secrets = open("secrets.txt", "w"); def thread1(): secrets.write("a secret") def thread2(): secrets.close() def thread3(): with open("log.txt", "w") as log: log.write("log message") threading.Thread(target=thread1).start() threading.Thread(target=thread2).start() threading.Thread(target=thread3).start() ``` If you are particularly unlucky, then the file descriptor for `secrets` may be closed by `thread2` and **reused** as the file descriptor for `log` just before `thread1` writes to it. In other words, `thread1` may write "a secret" to a completely different file or socket than it intended. This can happen, **even with the GIL**, because the GIL is released around [`write()`](https://github.com/python/cpython/blob/9c08f40a613d9aee78de4ce4ec3e125d1496d148/Python/fileutils.c#L1951-L1972) and [`close()`](https://github.com/python/cpython/blob/9c08f40a613d9aee78de4ce4ec3e125d1496d148/Modules/_io/fileio.c#L122-L128). In other words, the following can happen: 1. The `secrets.write()` call releases the GIL, but before it actually calls the C `write()` function on the file descriptor.... 2. The `secrets.close()` call closes the file descriptor 3. The `open("log.txt", "w")` re-uses the same file descriptor number 4. The `secrets.write()` call now `write()` on the wrong file descriptor Note that we must release the GIL before calling `write()` or `close()` because these functions potentially block.