gh-117657: Add TSAN suppressions for the free-threaded build (#117736)

Additionally, reduce the iterations for a few weakref tests that would
otherwise take a prohibitively long amount of time (> 1 hour) when TSAN
is enabled and the GIL is disabled.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9e23653..e1a2a62 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -492,6 +492,7 @@
     with:
       config_hash: ${{ needs.check_source.outputs.config_hash }}
       options: ./configure --config-cache --with-thread-sanitizer --with-pydebug
+      suppressions_path: Tools/tsan/supressions.txt
 
   build_tsan_free_threading:
     name: 'Thread sanitizer (free-threading)'
@@ -501,6 +502,7 @@
     with:
       config_hash: ${{ needs.check_source.outputs.config_hash }}
       options: ./configure --config-cache --disable-gil --with-thread-sanitizer --with-pydebug
+      suppressions_path: Tools/tsan/suppressions_free_threading.txt
 
   # CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
   cifuzz:
diff --git a/.github/workflows/reusable-tsan.yml b/.github/workflows/reusable-tsan.yml
index 96a9c1b..8ddb3b3 100644
--- a/.github/workflows/reusable-tsan.yml
+++ b/.github/workflows/reusable-tsan.yml
@@ -7,6 +7,10 @@
       options:
         required: true
         type: string
+      suppressions_path:
+        description: 'A repo relative path to the suppressions file'
+        required: true
+        type: string
 
 jobs:
   build_tsan_reusable:
@@ -30,7 +34,7 @@
         sudo sysctl -w vm.mmap_rnd_bits=28
     - name: TSAN Option Setup
       run: |
-        echo "TSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/Tools/tsan/supressions.txt" >> $GITHUB_ENV
+        echo "TSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/${{ inputs.suppressions_path }}" >> $GITHUB_ENV
         echo "CC=clang" >> $GITHUB_ENV
         echo "CXX=clang++" >> $GITHUB_ENV
     - name: Add ccache to PATH
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index d0e8df4..499ba77 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -1255,6 +1255,12 @@
 
     COUNT = 10
 
+    if support.check_sanitizer(thread=True) and support.Py_GIL_DISABLED:
+        # Reduce iteration count to get acceptable latency
+        NUM_THREADED_ITERATIONS = 1000
+    else:
+        NUM_THREADED_ITERATIONS = 100000
+
     def check_len_cycles(self, dict_type, cons):
         N = 20
         items = [RefCycle() for i in range(N)]
@@ -1880,7 +1886,7 @@
     def test_threaded_weak_valued_setdefault(self):
         d = weakref.WeakValueDictionary()
         with collect_in_thread():
-            for i in range(100000):
+            for i in range(self.NUM_THREADED_ITERATIONS):
                 x = d.setdefault(10, RefCycle())
                 self.assertIsNot(x, None)  # we never put None in there!
                 del x
@@ -1889,7 +1895,7 @@
     def test_threaded_weak_valued_pop(self):
         d = weakref.WeakValueDictionary()
         with collect_in_thread():
-            for i in range(100000):
+            for i in range(self.NUM_THREADED_ITERATIONS):
                 d[10] = RefCycle()
                 x = d.pop(10, 10)
                 self.assertIsNot(x, None)  # we never put None in there!
@@ -1900,7 +1906,7 @@
         # WeakValueDictionary when collecting from another thread.
         d = weakref.WeakValueDictionary()
         with collect_in_thread():
-            for i in range(200000):
+            for i in range(2 * self.NUM_THREADED_ITERATIONS):
                 o = RefCycle()
                 d[10] = o
                 # o is still alive, so the dict can't be empty
diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt
new file mode 100644
index 0000000..889b62e
--- /dev/null
+++ b/Tools/tsan/suppressions_free_threading.txt
@@ -0,0 +1,51 @@
+# This file contains suppressions for the free-threaded build. It contains the
+# suppressions for the default build and additional suppressions needed only in
+# the free-threaded build.
+#
+# reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
+
+## Default build suppresssions
+
+race:get_allocator_unlocked
+race:set_allocator_unlocked
+
+## Free-threaded suppressions
+
+race:_add_to_weak_set
+race:_in_weak_set
+race:_mi_heap_delayed_free_partial
+race:_Py_IsImmortal
+race:_Py_IsOwnedByCurrentThread
+race:_PyEval_EvalFrameDefault
+race:_PyFunction_SetVersion
+race:_PyImport_AcquireLock
+race:_PyImport_ReleaseLock
+race:_PyInterpreterState_SetNotRunningMain
+race:_PyInterpreterState_IsRunningMain
+race:_PyObject_GC_IS_SHARED
+race:_PyObject_GC_SET_SHARED
+race:_PyObject_GC_TRACK
+race:_PyType_HasFeature
+race:_PyType_Lookup
+race:assign_version_tag
+race:compare_unicode_unicode
+race:delitem_common
+race:dictkeys_decref
+race:dictkeys_incref
+race:dictresize
+race:gc_collect_main
+race:gc_restore_tid
+race:initialize_new_array
+race:insertdict
+race:lookup_tp_dict
+race:mi_heap_visit_pages
+race:PyMember_GetOne
+race:PyMember_SetOne
+race:new_reference
+race:set_contains_key
+race:set_inheritable
+race:start_the_world
+race:tstate_set_detached
+race:unicode_hash
+race:update_cache
+race:update_cache_gil_disabled
diff --git a/Tools/tsan/supressions.txt b/Tools/tsan/supressions.txt
index 448dfac..c778c79 100644
--- a/Tools/tsan/supressions.txt
+++ b/Tools/tsan/supressions.txt
@@ -1,5 +1,4 @@
-## reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
+# This file contains suppressions for the default (with GIL) build.
+# reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions
 race:get_allocator_unlocked
 race:set_allocator_unlocked
-race:mi_heap_visit_pages
-race:_mi_heap_delayed_free_partial