We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Custom cache directory to be thread/process safe
**Is your feature request related to a problem? Please describe.**

The cache files are not thread/process safe. I've checked multiple projects simultaneously in different processes but get permission errors when one process is writing to the cache while another is reading from it. On linux the fix is pretty easy: set a different temp directory for each process using the ``XDG_CACHE_HOME`` environment variable but there is no equivalent for Windows (there's somewhat an equivalent but you need a rather [specific setup](https://github.com/platformdirs/platformdirs/blob/main/src/platformdirs/windows.py#L157-L165) to use the environment variable).

**Describe the solution you'd like**

The solution that will work for my use case (and I think is the least amount of changes) is to create a custom environment variable that overrides using platformdirs to get the cache directory. Think this could look like:
```python
CACHE_DIR = Path(os.environ.get("BLACK_CACHE_DIR", user_cache_dir("black", version=__version__)))
if not CACHE_DIR.exists():
    raise RuntimeError(f"{CACHE_DIR} does not exist")
```

**Describe alternatives you've considered**

1. Add the command line option to turn off cacheing (as this ticket as #248 asked for) (guess is this  more work)
2. Add command line option to set the cache directory (guess is this is more work but not a lot)
3. Make the cache dir thread/process safe (guess is this is more work)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/black/cache.py`
Content:
```
1 """Caching of formatted files with feature-based invalidation."""
2 
3 import os
4 import pickle
5 from pathlib import Path
6 import tempfile
7 from typing import Dict, Iterable, Set, Tuple
8 
9 from platformdirs import user_cache_dir
10 
11 from black.mode import Mode
12 
13 from _black_version import version as __version__
14 
15 
16 # types
17 Timestamp = float
18 FileSize = int
19 CacheInfo = Tuple[Timestamp, FileSize]
20 Cache = Dict[str, CacheInfo]
21 
22 
23 CACHE_DIR = Path(user_cache_dir("black", version=__version__))
24 
25 
26 def read_cache(mode: Mode) -> Cache:
27     """Read the cache if it exists and is well formed.
28 
29     If it is not well formed, the call to write_cache later should resolve the issue.
30     """
31     cache_file = get_cache_file(mode)
32     if not cache_file.exists():
33         return {}
34 
35     with cache_file.open("rb") as fobj:
36         try:
37             cache: Cache = pickle.load(fobj)
38         except (pickle.UnpicklingError, ValueError, IndexError):
39             return {}
40 
41     return cache
42 
43 
44 def get_cache_file(mode: Mode) -> Path:
45     return CACHE_DIR / f"cache.{mode.get_cache_key()}.pickle"
46 
47 
48 def get_cache_info(path: Path) -> CacheInfo:
49     """Return the information used to check if a file is already formatted or not."""
50     stat = path.stat()
51     return stat.st_mtime, stat.st_size
52 
53 
54 def filter_cached(cache: Cache, sources: Iterable[Path]) -> Tuple[Set[Path], Set[Path]]:
55     """Split an iterable of paths in `sources` into two sets.
56 
57     The first contains paths of files that modified on disk or are not in the
58     cache. The other contains paths to non-modified files.
59     """
60     todo, done = set(), set()
61     for src in sources:
62         res_src = src.resolve()
63         if cache.get(str(res_src)) != get_cache_info(res_src):
64             todo.add(src)
65         else:
66             done.add(src)
67     return todo, done
68 
69 
70 def write_cache(cache: Cache, sources: Iterable[Path], mode: Mode) -> None:
71     """Update the cache file."""
72     cache_file = get_cache_file(mode)
73     try:
74         CACHE_DIR.mkdir(parents=True, exist_ok=True)
75         new_cache = {
76             **cache,
77             **{str(src.resolve()): get_cache_info(src) for src in sources},
78         }
79         with tempfile.NamedTemporaryFile(dir=str(cache_file.parent), delete=False) as f:
80             pickle.dump(new_cache, f, protocol=4)
81         os.replace(f.name, cache_file)
82     except OSError:
83         pass
84
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/black/cache.py b/src/black/cache.py
--- a/src/black/cache.py
+++ b/src/black/cache.py
@@ -20,7 +20,23 @@
 Cache = Dict[str, CacheInfo]
 
 
-CACHE_DIR = Path(user_cache_dir("black", version=__version__))
+def get_cache_dir() -> Path:
+    """Get the cache directory used by black.
+
+    Users can customize this directory on all systems using `BLACK_CACHE_DIR`
+    environment variable. By default, the cache directory is the user cache directory
+    under the black application.
+
+    This result is immediately set to a constant `black.cache.CACHE_DIR` as to avoid
+    repeated calls.
+    """
+    # NOTE: Function mostly exists as a clean way to test getting the cache directory.
+    default_cache_dir = user_cache_dir("black", version=__version__)
+    cache_dir = Path(os.environ.get("BLACK_CACHE_DIR", default_cache_dir))
+    return cache_dir
+
+
+CACHE_DIR = get_cache_dir()
 
 
 def read_cache(mode: Mode) -> Cache:

{"golden_diff": "diff --git a/src/black/cache.py b/src/black/cache.py\n--- a/src/black/cache.py\n+++ b/src/black/cache.py\n@@ -20,7 +20,23 @@\n Cache = Dict[str, CacheInfo]\n \n \n-CACHE_DIR = Path(user_cache_dir(\"black\", version=__version__))\n+def get_cache_dir() -> Path:\n+    \"\"\"Get the cache directory used by black.\n+\n+    Users can customize this directory on all systems using `BLACK_CACHE_DIR`\n+    environment variable. By default, the cache directory is the user cache directory\n+    under the black application.\n+\n+    This result is immediately set to a constant `black.cache.CACHE_DIR` as to avoid\n+    repeated calls.\n+    \"\"\"\n+    # NOTE: Function mostly exists as a clean way to test getting the cache directory.\n+    default_cache_dir = user_cache_dir(\"black\", version=__version__)\n+    cache_dir = Path(os.environ.get(\"BLACK_CACHE_DIR\", default_cache_dir))\n+    return cache_dir\n+\n+\n+CACHE_DIR = get_cache_dir()\n \n \n def read_cache(mode: Mode) -> Cache:\n", "issue": "Custom cache directory to be thread/process safe\n**Is your feature request related to a problem? Please describe.**\r\n\r\nThe cache files are not thread/process safe. I've checked multiple projects simultaneously in different processes but get permission errors when one process is writing to the cache while another is reading from it. On linux the fix is pretty easy: set a different temp directory for each process using the ``XDG_CACHE_HOME`` environment variable but there is no equivalent for Windows (there's somewhat an equivalent but you need a rather [specific setup](https://github.com/platformdirs/platformdirs/blob/main/src/platformdirs/windows.py#L157-L165) to use the environment variable).\r\n\r\n**Describe the solution you'd like**\r\n\r\nThe solution that will work for my use case (and I think is the least amount of changes) is to create a custom environment variable that overrides using platformdirs to get the cache directory. Think this could look like:\r\n```python\r\nCACHE_DIR = Path(os.environ.get(\"BLACK_CACHE_DIR\", user_cache_dir(\"black\", version=__version__)))\r\nif not CACHE_DIR.exists():\r\n    raise RuntimeError(f\"{CACHE_DIR} does not exist\")\r\n```\r\n\r\n**Describe alternatives you've considered**\r\n\r\n1. Add the command line option to turn off cacheing (as this ticket as #248 asked for) (guess is this  more work)\r\n2. Add command line option to set the cache directory (guess is this is more work but not a lot)\r\n3. Make the cache dir thread/process safe (guess is this is more work)\n", "before_files": [{"content": "\"\"\"Caching of formatted files with feature-based invalidation.\"\"\"\n\nimport os\nimport pickle\nfrom pathlib import Path\nimport tempfile\nfrom typing import Dict, Iterable, Set, Tuple\n\nfrom platformdirs import user_cache_dir\n\nfrom black.mode import Mode\n\nfrom _black_version import version as __version__\n\n\n# types\nTimestamp = float\nFileSize = int\nCacheInfo = Tuple[Timestamp, FileSize]\nCache = Dict[str, CacheInfo]\n\n\nCACHE_DIR = Path(user_cache_dir(\"black\", version=__version__))\n\n\ndef read_cache(mode: Mode) -> Cache:\n    \"\"\"Read the cache if it exists and is well formed.\n\n    If it is not well formed, the call to write_cache later should resolve the issue.\n    \"\"\"\n    cache_file = get_cache_file(mode)\n    if not cache_file.exists():\n        return {}\n\n    with cache_file.open(\"rb\") as fobj:\n        try:\n            cache: Cache = pickle.load(fobj)\n        except (pickle.UnpicklingError, ValueError, IndexError):\n            return {}\n\n    return cache\n\n\ndef get_cache_file(mode: Mode) -> Path:\n    return CACHE_DIR / f\"cache.{mode.get_cache_key()}.pickle\"\n\n\ndef get_cache_info(path: Path) -> CacheInfo:\n    \"\"\"Return the information used to check if a file is already formatted or not.\"\"\"\n    stat = path.stat()\n    return stat.st_mtime, stat.st_size\n\n\ndef filter_cached(cache: Cache, sources: Iterable[Path]) -> Tuple[Set[Path], Set[Path]]:\n    \"\"\"Split an iterable of paths in `sources` into two sets.\n\n    The first contains paths of files that modified on disk or are not in the\n    cache. The other contains paths to non-modified files.\n    \"\"\"\n    todo, done = set(), set()\n    for src in sources:\n        res_src = src.resolve()\n        if cache.get(str(res_src)) != get_cache_info(res_src):\n            todo.add(src)\n        else:\n            done.add(src)\n    return todo, done\n\n\ndef write_cache(cache: Cache, sources: Iterable[Path], mode: Mode) -> None:\n    \"\"\"Update the cache file.\"\"\"\n    cache_file = get_cache_file(mode)\n    try:\n        CACHE_DIR.mkdir(parents=True, exist_ok=True)\n        new_cache = {\n            **cache,\n            **{str(src.resolve()): get_cache_info(src) for src in sources},\n        }\n        with tempfile.NamedTemporaryFile(dir=str(cache_file.parent), delete=False) as f:\n            pickle.dump(new_cache, f, protocol=4)\n        os.replace(f.name, cache_file)\n    except OSError:\n        pass\n", "path": "src/black/cache.py"}], "after_files": [{"content": "\"\"\"Caching of formatted files with feature-based invalidation.\"\"\"\n\nimport os\nimport pickle\nfrom pathlib import Path\nimport tempfile\nfrom typing import Dict, Iterable, Set, Tuple\n\nfrom platformdirs import user_cache_dir\n\nfrom black.mode import Mode\n\nfrom _black_version import version as __version__\n\n\n# types\nTimestamp = float\nFileSize = int\nCacheInfo = Tuple[Timestamp, FileSize]\nCache = Dict[str, CacheInfo]\n\n\ndef get_cache_dir() -> Path:\n    \"\"\"Get the cache directory used by black.\n\n    Users can customize this directory on all systems using `BLACK_CACHE_DIR`\n    environment variable. By default, the cache directory is the user cache directory\n    under the black application.\n\n    This result is immediately set to a constant `black.cache.CACHE_DIR` as to avoid\n    repeated calls.\n    \"\"\"\n    # NOTE: Function mostly exists as a clean way to test getting the cache directory.\n    default_cache_dir = user_cache_dir(\"black\", version=__version__)\n    cache_dir = Path(os.environ.get(\"BLACK_CACHE_DIR\", default_cache_dir))\n    return cache_dir\n\n\nCACHE_DIR = get_cache_dir()\n\n\ndef read_cache(mode: Mode) -> Cache:\n    \"\"\"Read the cache if it exists and is well formed.\n\n    If it is not well formed, the call to write_cache later should resolve the issue.\n    \"\"\"\n    cache_file = get_cache_file(mode)\n    if not cache_file.exists():\n        return {}\n\n    with cache_file.open(\"rb\") as fobj:\n        try:\n            cache: Cache = pickle.load(fobj)\n        except (pickle.UnpicklingError, ValueError, IndexError):\n            return {}\n\n    return cache\n\n\ndef get_cache_file(mode: Mode) -> Path:\n    return CACHE_DIR / f\"cache.{mode.get_cache_key()}.pickle\"\n\n\ndef get_cache_info(path: Path) -> CacheInfo:\n    \"\"\"Return the information used to check if a file is already formatted or not.\"\"\"\n    stat = path.stat()\n    return stat.st_mtime, stat.st_size\n\n\ndef filter_cached(cache: Cache, sources: Iterable[Path]) -> Tuple[Set[Path], Set[Path]]:\n    \"\"\"Split an iterable of paths in `sources` into two sets.\n\n    The first contains paths of files that modified on disk or are not in the\n    cache. The other contains paths to non-modified files.\n    \"\"\"\n    todo, done = set(), set()\n    for src in sources:\n        res_src = src.resolve()\n        if cache.get(str(res_src)) != get_cache_info(res_src):\n            todo.add(src)\n        else:\n            done.add(src)\n    return todo, done\n\n\ndef write_cache(cache: Cache, sources: Iterable[Path], mode: Mode) -> None:\n    \"\"\"Update the cache file.\"\"\"\n    cache_file = get_cache_file(mode)\n    try:\n        CACHE_DIR.mkdir(parents=True, exist_ok=True)\n        new_cache = {\n            **cache,\n            **{str(src.resolve()): get_cache_info(src) for src in sources},\n        }\n        with tempfile.NamedTemporaryFile(dir=str(cache_file.parent), delete=False) as f:\n            pickle.dump(new_cache, f, protocol=4)\n        os.replace(f.name, cache_file)\n    except OSError:\n        pass\n", "path": "src/black/cache.py"}]}