|
7 | 7 | import time |
8 | 8 | from abc import ABC, abstractmethod |
9 | 9 | from types import FrameType |
10 | | -from typing import Optional, Dict, List, Type, Tuple |
| 10 | +from typing import Optional, Dict, List, Tuple, Type, cast |
11 | 11 |
|
12 | 12 | import psutil |
13 | 13 |
|
14 | 14 | from psij import InvalidJobException, SubmitException, Launcher |
15 | 15 | from psij import Job, JobSpec, JobExecutorConfig, JobState, JobStatus |
16 | 16 | from psij import JobExecutor |
| 17 | +from psij.utils import SingletonThread |
17 | 18 |
|
18 | 19 | logger = logging.getLogger(__name__) |
19 | 20 |
|
@@ -117,18 +118,11 @@ def _get_env(spec: JobSpec) -> Optional[Dict[str, str]]: |
117 | 118 | return spec.environment |
118 | 119 |
|
119 | 120 |
|
120 | | -class _ProcessReaper(threading.Thread): |
121 | | - _instance: Optional['_ProcessReaper'] = None |
122 | | - _lock = threading.RLock() |
| 121 | +class _ProcessReaper(SingletonThread): |
123 | 122 |
|
124 | 123 | @classmethod |
125 | 124 | def get_instance(cls: Type['_ProcessReaper']) -> '_ProcessReaper': |
126 | | - with cls._lock: |
127 | | - if cls._instance is None: |
128 | | - cls._instance = _ProcessReaper() |
129 | | - cls._instance.start() |
130 | | - signal.signal(signal.SIGCHLD, _handle_sigchld) |
131 | | - return cls._instance |
| 125 | + return cast('_ProcessReaper', super().get_instance()) |
132 | 126 |
|
133 | 127 | def __init__(self) -> None: |
134 | 128 | super().__init__(name='Local Executor Process Reaper', daemon=True) |
@@ -205,8 +199,15 @@ class LocalJobExecutor(JobExecutor): |
205 | 199 | This job executor is intended to be used when there is no resource manager, only |
206 | 200 | the operating system. Or when there is a resource manager, but it should be ignored. |
207 | 201 |
|
208 | | - Limitations: in Linux, attached jobs always appear to complete with a zero exit code regardless |
| 202 | + Limitations: |
| 203 | + - In Linux, attached jobs always appear to complete with a zero exit code regardless |
209 | 204 | of the actual exit code. |
| 205 | + - Instantiation of a local executor from both parent process and a `fork()`-ed process |
| 206 | + is not guaranteed to work. In general, using `fork()` and multi-threading in Linux is unsafe, |
| 207 | + as suggested by the `fork()` man page. While PSI/J attempts to minimize problems that can |
| 208 | + arise when `fork()` is combined with threads (which are used by PSI/J), no guarantees can be |
| 209 | + made and the chances of unexpected behavior are high. Please do not use PSI/J with `fork()`. |
| 210 | + If you do, please be mindful that support for using PSI/J with `fork()` will be limited. |
210 | 211 | """ |
211 | 212 |
|
212 | 213 | def __init__(self, url: Optional[str] = None, |
|
0 commit comments