|
1 | 1 | import sys |
2 | 2 | import tarfile |
| 3 | +import threading |
| 4 | +from concurrent.futures import ThreadPoolExecutor |
3 | 5 |
|
4 | 6 | import psutil |
5 | 7 | import pytest |
@@ -1036,3 +1038,39 @@ def test_append_handles_arcnames_of_type_str( |
1036 | 1038 | with tarfile.open(archive_path, read_mode) as archive: |
1037 | 1039 | assert archive.getnames() == ["nested/file.txt"] |
1038 | 1040 | assert archive.getmember("nested/file.txt").isfile() |
| 1041 | + |
| 1042 | + |
| 1043 | +def test_multithreaded_append( |
| 1044 | + source_path, target_path, archive_path, write_mode, read_mode |
| 1045 | +): |
| 1046 | + def worker(barrier, writer, target_path, lock, thread_index): |
| 1047 | + barrier.wait() |
| 1048 | + thread_target_path = target_path / f"thread_{thread_index}" |
| 1049 | + thread_target_path.touch() |
| 1050 | + # PyO3 ensures only one mutable borrow is allowed at a time so we use a |
| 1051 | + # lock. Calling append concurrently will raise "RuntimeError: already |
| 1052 | + # borrowed" here. |
| 1053 | + with lock: |
| 1054 | + writer.append(thread_target_path) |
| 1055 | + |
| 1056 | + num_workers = 4 |
| 1057 | + barrier = threading.Barrier(num_workers) |
| 1058 | + lock = threading.Lock() |
| 1059 | + |
| 1060 | + with ArchiveWriter.open(archive_path, write_mode) as writer: |
| 1061 | + with ThreadPoolExecutor(max_workers=num_workers) as tpe: |
| 1062 | + try: |
| 1063 | + futures = [] |
| 1064 | + for i in range(num_workers): |
| 1065 | + futures.append( |
| 1066 | + tpe.submit(worker, barrier, writer, target_path, lock, i) |
| 1067 | + ) |
| 1068 | + finally: |
| 1069 | + # avoid deadlocks if any threads failed to spawn |
| 1070 | + if len(futures) < num_workers: |
| 1071 | + barrier.abort() |
| 1072 | + # join spawned threads |
| 1073 | + [f.result() for f in futures] |
| 1074 | + |
| 1075 | + with tarfile.open(archive_path, read_mode) as archive: |
| 1076 | + assert sorted(archive.getnames()) == [f"thread_{i}" for i in range(num_workers)] |
0 commit comments