1515# specific language governing permissions and limitations
1616# under the License.
1717
18+ import multiprocessing
1819import os
19- from concurrent .futures import ThreadPoolExecutor
20- from typing import Dict , Optional
20+ from concurrent .futures import ProcessPoolExecutor , ThreadPoolExecutor
21+ from typing import Dict , Generator , Optional
2122from unittest import mock
2223
2324import pytest
2930INVALID_ENV = {"PYICEBERG_MAX_WORKERS" : "invalid" }
3031
3132
33+ @pytest .fixture
34+ def fork_process () -> Generator [None , None , None ]:
35+ original = multiprocessing .get_start_method ()
36+ allowed = multiprocessing .get_all_start_methods ()
37+
38+ assert "fork" in allowed
39+
40+ multiprocessing .set_start_method ("fork" , force = True )
41+
42+ yield
43+
44+ multiprocessing .set_start_method (original , force = True )
45+
46+
47+ @pytest .fixture
48+ def spawn_process () -> Generator [None , None , None ]:
49+ original = multiprocessing .get_start_method ()
50+ allowed = multiprocessing .get_all_start_methods ()
51+
52+ assert "spawn" in allowed
53+
54+ multiprocessing .set_start_method ("spawn" , force = True )
55+
56+ yield
57+
58+ multiprocessing .set_start_method (original , force = True )
59+
60+
61+ def _use_executor_to_return (value : int ) -> int :
62+ # Module level function to enabling pickling for use with ProcessPoolExecutor.
63+ executor = ExecutorFactory .get_or_create ()
64+ future = executor .submit (lambda : value )
65+ return future .result ()
66+
67+
3268def test_create_reused () -> None :
3369 first = ExecutorFactory .get_or_create ()
3470 second = ExecutorFactory .get_or_create ()
@@ -50,3 +86,38 @@ def test_max_workers() -> None:
5086def test_max_workers_invalid () -> None :
5187 with pytest .raises (ValueError ):
5288 ExecutorFactory .max_workers ()
89+
90+
91+ @pytest .mark .parametrize (
92+ "fixture_name" ,
93+ [
94+ pytest .param (
95+ "fork_process" ,
96+ marks = pytest .mark .skipif (
97+ "fork" not in multiprocessing .get_all_start_methods (), reason = "Fork start method is not available"
98+ ),
99+ ),
100+ pytest .param (
101+ "spawn_process" ,
102+ marks = pytest .mark .skipif (
103+ "spawn" not in multiprocessing .get_all_start_methods (), reason = "Spawn start method is not available"
104+ ),
105+ ),
106+ ],
107+ )
108+ def test_use_executor_in_different_process (fixture_name : str , request : pytest .FixtureRequest ) -> None :
109+ # Use the fixture, which sets up fork or spawn process start method.
110+ request .getfixturevalue (fixture_name )
111+
112+ # Use executor in main process to ensure the singleton is initialized.
113+ main_value = _use_executor_to_return (10 )
114+
115+ # Use two separate ProcessPoolExecutors to ensure different processes are used.
116+ with ProcessPoolExecutor () as process_executor :
117+ future1 = process_executor .submit (_use_executor_to_return , 20 )
118+ with ProcessPoolExecutor () as process_executor :
119+ future2 = process_executor .submit (_use_executor_to_return , 30 )
120+
121+ assert main_value == 10
122+ assert future1 .result () == 20
123+ assert future2 .result () == 30
0 commit comments