ominfra 0.0.0.dev127__py3-none-any.whl → 0.0.0.dev129__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. ominfra/deploy/_executor.py +24 -0
  2. ominfra/pyremote/_runcommands.py +24 -0
  3. ominfra/scripts/journald2aws.py +24 -0
  4. ominfra/scripts/supervisor.py +1320 -1225
  5. ominfra/supervisor/configs.py +34 -11
  6. ominfra/supervisor/dispatchers.py +7 -6
  7. ominfra/supervisor/dispatchersimpl.py +29 -22
  8. ominfra/supervisor/groups.py +1 -1
  9. ominfra/supervisor/groupsimpl.py +2 -2
  10. ominfra/supervisor/inject.py +22 -17
  11. ominfra/supervisor/io.py +82 -0
  12. ominfra/supervisor/main.py +6 -7
  13. ominfra/supervisor/pipes.py +15 -13
  14. ominfra/supervisor/poller.py +36 -35
  15. ominfra/supervisor/{processes.py → process.py} +2 -1
  16. ominfra/supervisor/{processesimpl.py → processimpl.py} +42 -54
  17. ominfra/supervisor/setup.py +1 -1
  18. ominfra/supervisor/setupimpl.py +4 -3
  19. ominfra/supervisor/signals.py +56 -50
  20. ominfra/supervisor/spawning.py +2 -1
  21. ominfra/supervisor/spawningimpl.py +24 -21
  22. ominfra/supervisor/supervisor.py +72 -134
  23. ominfra/supervisor/types.py +45 -34
  24. ominfra/supervisor/utils/__init__.py +0 -0
  25. ominfra/supervisor/utils/diag.py +31 -0
  26. ominfra/supervisor/utils/fds.py +46 -0
  27. ominfra/supervisor/utils/fs.py +47 -0
  28. ominfra/supervisor/utils/os.py +45 -0
  29. ominfra/supervisor/utils/ostypes.py +9 -0
  30. ominfra/supervisor/utils/signals.py +60 -0
  31. ominfra/supervisor/utils/strings.py +105 -0
  32. ominfra/supervisor/{users.py → utils/users.py} +11 -8
  33. {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/METADATA +3 -3
  34. {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/RECORD +39 -33
  35. ominfra/supervisor/context.py +0 -84
  36. ominfra/supervisor/datatypes.py +0 -113
  37. ominfra/supervisor/utils.py +0 -206
  38. /ominfra/supervisor/{collections.py → utils/collections.py} +0 -0
  39. {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/LICENSE +0 -0
  40. {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/WHEEL +0 -0
  41. {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/entry_points.txt +0 -0
  42. {ominfra-0.0.0.dev127.dist-info → ominfra-0.0.0.dev129.dist-info}/top_level.txt +0 -0
@@ -7,11 +7,21 @@ import typing as ta
7
7
 
8
8
  from ..configs import ConfigMapping
9
9
  from ..configs import build_config_named_children
10
- from .datatypes import byte_size
11
- from .datatypes import existing_directory
12
- from .datatypes import existing_dirpath
13
- from .datatypes import logging_level
14
- from .datatypes import octal_type
10
+ from .utils.fs import check_existing_dir
11
+ from .utils.fs import check_path_with_existing_dir
12
+ from .utils.strings import parse_bytes_size
13
+ from .utils.strings import parse_octal
14
+
15
+
16
+ ##
17
+
18
+
19
+ class RestartWhenExitUnexpected:
20
+ pass
21
+
22
+
23
+ class RestartUnconditionally:
24
+ pass
15
25
 
16
26
 
17
27
  ##
@@ -104,12 +114,12 @@ class ServerConfig:
104
114
  **kwargs: ta.Any,
105
115
  ) -> 'ServerConfig':
106
116
  return cls(
107
- umask=octal_type(umask),
108
- directory=existing_directory(directory) if directory is not None else None,
109
- logfile=existing_dirpath(logfile),
110
- logfile_maxbytes=byte_size(logfile_maxbytes),
111
- loglevel=logging_level(loglevel),
112
- pidfile=existing_dirpath(pidfile),
117
+ umask=parse_octal(umask),
118
+ directory=check_existing_dir(directory) if directory is not None else None,
119
+ logfile=check_path_with_existing_dir(logfile),
120
+ logfile_maxbytes=parse_bytes_size(logfile_maxbytes),
121
+ loglevel=parse_logging_level(loglevel),
122
+ pidfile=check_path_with_existing_dir(pidfile),
113
123
  child_logdir=child_logdir if child_logdir else tempfile.gettempdir(),
114
124
  **kwargs,
115
125
  )
@@ -129,3 +139,16 @@ def prepare_server_config(dct: ta.Mapping[str, ta.Any]) -> ta.Mapping[str, ta.An
129
139
  group_dcts = build_config_named_children(out.get('groups'))
130
140
  out['groups'] = [prepare_process_group_config(group_dct) for group_dct in group_dcts or []]
131
141
  return out
142
+
143
+
144
+ ##
145
+
146
+
147
+ def parse_logging_level(value: ta.Union[str, int]) -> int:
148
+ if isinstance(value, int):
149
+ return value
150
+ s = str(value).lower()
151
+ level = logging.getLevelNamesMapping().get(s.upper())
152
+ if level is None:
153
+ raise ValueError(f'bad logging level name {value!r}')
154
+ return level
@@ -1,11 +1,12 @@
1
1
  # ruff: noqa: UP006 UP007
2
- from .collections import KeyedCollection
3
2
  from .types import Dispatcher
4
- from .types import OutputDispatcher
3
+ from .types import ProcessOutputDispatcher
4
+ from .utils.collections import KeyedCollection
5
+ from .utils.ostypes import Fd
5
6
 
6
7
 
7
- class Dispatchers(KeyedCollection[int, Dispatcher]):
8
- def _key(self, v: Dispatcher) -> int:
8
+ class Dispatchers(KeyedCollection[Fd, Dispatcher]):
9
+ def _key(self, v: Dispatcher) -> Fd:
9
10
  return v.fd
10
11
 
11
12
  #
@@ -23,10 +24,10 @@ class Dispatchers(KeyedCollection[int, Dispatcher]):
23
24
 
24
25
  def remove_logs(self) -> None:
25
26
  for d in self:
26
- if isinstance(d, OutputDispatcher):
27
+ if isinstance(d, ProcessOutputDispatcher):
27
28
  d.remove_logs()
28
29
 
29
30
  def reopen_logs(self) -> None:
30
31
  for d in self:
31
- if isinstance(d, OutputDispatcher):
32
+ if isinstance(d, ProcessOutputDispatcher):
32
33
  d.reopen_logs()
@@ -8,29 +8,32 @@ import typing as ta
8
8
  from omlish.lite.logs import log
9
9
 
10
10
  from .configs import ProcessConfig
11
+ from .configs import ServerConfig
11
12
  from .events import EventCallbacks
12
13
  from .events import ProcessCommunicationEvent
13
14
  from .events import ProcessLogStderrEvent
14
15
  from .events import ProcessLogStdoutEvent
15
- from .types import Dispatcher
16
- from .types import InputDispatcher
17
- from .types import OutputDispatcher
18
16
  from .types import Process
19
- from .utils import as_bytes
20
- from .utils import compact_traceback
21
- from .utils import find_prefix_at_end
22
- from .utils import read_fd
23
- from .utils import strip_escapes
24
-
25
-
26
- class BaseDispatcherImpl(Dispatcher, abc.ABC):
17
+ from .types import ProcessDispatcher
18
+ from .types import ProcessInputDispatcher
19
+ from .types import ProcessOutputDispatcher
20
+ from .utils.diag import compact_traceback
21
+ from .utils.fds import read_fd
22
+ from .utils.ostypes import Fd
23
+ from .utils.strings import as_bytes
24
+ from .utils.strings import find_prefix_at_end
25
+ from .utils.strings import strip_escapes
26
+
27
+
28
+ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
27
29
  def __init__(
28
30
  self,
29
31
  process: Process,
30
32
  channel: str,
31
- fd: int,
33
+ fd: Fd,
32
34
  *,
33
35
  event_callbacks: EventCallbacks,
36
+ server_config: ServerConfig,
34
37
  ) -> None:
35
38
  super().__init__()
36
39
 
@@ -38,6 +41,7 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
38
41
  self._channel = channel # 'stderr' or 'stdout'
39
42
  self._fd = fd
40
43
  self._event_callbacks = event_callbacks
44
+ self._server_config = server_config
41
45
 
42
46
  self._closed = False # True if close() has been called
43
47
 
@@ -57,7 +61,7 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
57
61
  return self._channel
58
62
 
59
63
  @property
60
- def fd(self) -> int:
64
+ def fd(self) -> Fd:
61
65
  return self._fd
62
66
 
63
67
  @property
@@ -78,7 +82,7 @@ class BaseDispatcherImpl(Dispatcher, abc.ABC):
78
82
  self.close()
79
83
 
80
84
 
81
- class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
85
+ class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispatcher):
82
86
  """
83
87
  Dispatcher for one channel (stdout or stderr) of one process. Serves several purposes:
84
88
 
@@ -91,15 +95,17 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
91
95
  self,
92
96
  process: Process,
93
97
  event_type: ta.Type[ProcessCommunicationEvent],
94
- fd: int,
98
+ fd: Fd,
95
99
  *,
96
100
  event_callbacks: EventCallbacks,
101
+ server_config: ServerConfig,
97
102
  ) -> None:
98
103
  super().__init__(
99
104
  process,
100
105
  event_type.channel,
101
106
  fd,
102
107
  event_callbacks=event_callbacks,
108
+ server_config=server_config,
103
109
  )
104
110
 
105
111
  self._event_type = event_type
@@ -123,11 +129,10 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
123
129
 
124
130
  self._main_log_level = logging.DEBUG
125
131
 
126
- self._log_to_main_log = process.context.config.loglevel <= self._main_log_level
132
+ self._log_to_main_log = self._server_config.loglevel <= self._main_log_level
127
133
 
128
- config = self._process.config
129
- self._stdout_events_enabled = config.stdout.events_enabled
130
- self._stderr_events_enabled = config.stderr.events_enabled
134
+ self._stdout_events_enabled = self._process.config.stdout.events_enabled
135
+ self._stderr_events_enabled = self._process.config.stderr.events_enabled
131
136
 
132
137
  _child_log: ta.Optional[logging.Logger] = None # the current logger (normal_log or capture_log)
133
138
  _normal_log: ta.Optional[logging.Logger] = None # the "normal" (non-capture) logger
@@ -198,7 +203,7 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
198
203
  if not data:
199
204
  return
200
205
 
201
- if self._process.context.config.strip_ansi:
206
+ if self._server_config.strip_ansi:
202
207
  data = strip_escapes(as_bytes(data))
203
208
 
204
209
  if self._child_log:
@@ -296,20 +301,22 @@ class OutputDispatcherImpl(BaseDispatcherImpl, OutputDispatcher):
296
301
  self.close()
297
302
 
298
303
 
299
- class InputDispatcherImpl(BaseDispatcherImpl, InputDispatcher):
304
+ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatcher):
300
305
  def __init__(
301
306
  self,
302
307
  process: Process,
303
308
  channel: str,
304
- fd: int,
309
+ fd: Fd,
305
310
  *,
306
311
  event_callbacks: EventCallbacks,
312
+ server_config: ServerConfig,
307
313
  ) -> None:
308
314
  super().__init__(
309
315
  process,
310
316
  channel,
311
317
  fd,
312
318
  event_callbacks=event_callbacks,
319
+ server_config=server_config,
313
320
  )
314
321
 
315
322
  self._input_buffer = b''
@@ -1,13 +1,13 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import typing as ta
3
3
 
4
- from .collections import KeyedCollectionAccessors
5
4
  from .configs import ProcessGroupConfig
6
5
  from .events import EventCallbacks
7
6
  from .events import ProcessGroupAddedEvent
8
7
  from .events import ProcessGroupRemovedEvent
9
8
  from .types import Process
10
9
  from .types import ProcessGroup
10
+ from .utils.collections import KeyedCollectionAccessors
11
11
 
12
12
 
13
13
  class ProcessGroupManager(KeyedCollectionAccessors[str, ProcessGroup]):
@@ -61,7 +61,7 @@ class ProcessGroupImpl(ProcessGroup):
61
61
  #
62
62
 
63
63
  def get_unstopped_processes(self) -> ta.List[Process]:
64
- return [x for x in self if not x.get_state().stopped]
64
+ return [x for x in self if not x.state.stopped]
65
65
 
66
66
  def stop_all(self) -> None:
67
67
  processes = list(self._by_name.values())
@@ -69,7 +69,7 @@ class ProcessGroupImpl(ProcessGroup):
69
69
  processes.reverse() # stop in desc priority order
70
70
 
71
71
  for proc in processes:
72
- state = proc.get_state()
72
+ state = proc.state
73
73
  if state == ProcessState.RUNNING:
74
74
  # RUNNING -> STOPPING
75
75
  proc.stop()
@@ -6,34 +6,35 @@ from omlish.lite.inject import InjectorBindings
6
6
  from omlish.lite.inject import inj
7
7
 
8
8
  from .configs import ServerConfig
9
- from .context import ServerContextImpl
10
- from .dispatchersimpl import InputDispatcherImpl
11
- from .dispatchersimpl import OutputDispatcherImpl
9
+ from .dispatchersimpl import ProcessInputDispatcherImpl
10
+ from .dispatchersimpl import ProcessOutputDispatcherImpl
12
11
  from .events import EventCallbacks
13
12
  from .groups import ProcessGroupManager
14
13
  from .groupsimpl import ProcessFactory
15
14
  from .groupsimpl import ProcessGroupImpl
15
+ from .io import IoManager
16
16
  from .poller import Poller
17
17
  from .poller import get_poller_impl
18
- from .processes import PidHistory
19
- from .processesimpl import ProcessImpl
20
- from .processesimpl import ProcessSpawningFactory
18
+ from .process import PidHistory
19
+ from .processimpl import ProcessImpl
20
+ from .processimpl import ProcessSpawningFactory
21
21
  from .setup import DaemonizeListener
22
22
  from .setup import DaemonizeListeners
23
23
  from .setup import SupervisorUser
24
24
  from .setupimpl import SupervisorSetup
25
25
  from .setupimpl import SupervisorSetupImpl
26
- from .signals import SignalReceiver
26
+ from .signals import SignalHandler
27
27
  from .spawningimpl import InheritedFds
28
- from .spawningimpl import InputDispatcherFactory
29
- from .spawningimpl import OutputDispatcherFactory
28
+ from .spawningimpl import ProcessInputDispatcherFactory
29
+ from .spawningimpl import ProcessOutputDispatcherFactory
30
30
  from .spawningimpl import ProcessSpawningImpl
31
31
  from .supervisor import ProcessGroupFactory
32
- from .supervisor import SignalHandler
33
32
  from .supervisor import Supervisor
34
- from .types import ServerContext
33
+ from .supervisor import SupervisorStateManagerImpl
35
34
  from .types import ServerEpoch
36
- from .users import get_user
35
+ from .types import SupervisorStateManager
36
+ from .utils.signals import SignalReceiver
37
+ from .utils.users import get_user
37
38
 
38
39
 
39
40
  def bind_server(
@@ -52,17 +53,21 @@ def bind_server(
52
53
 
53
54
  inj.bind(DaemonizeListener, array=True, to_key=Poller),
54
55
 
55
- inj.bind(ServerContextImpl, singleton=True),
56
- inj.bind(ServerContext, to_key=ServerContextImpl),
57
-
58
56
  inj.bind(EventCallbacks, singleton=True),
59
57
 
60
58
  inj.bind(SignalReceiver, singleton=True),
61
59
 
60
+ inj.bind(IoManager, singleton=True),
61
+
62
62
  inj.bind(SignalHandler, singleton=True),
63
+
63
64
  inj.bind(ProcessGroupManager, singleton=True),
65
+
64
66
  inj.bind(Supervisor, singleton=True),
65
67
 
68
+ inj.bind(SupervisorStateManagerImpl, singleton=True),
69
+ inj.bind(SupervisorStateManager, to_key=SupervisorStateManagerImpl),
70
+
66
71
  inj.bind(PidHistory()),
67
72
 
68
73
  inj.bind_factory(ProcessGroupImpl, ProcessGroupFactory),
@@ -70,8 +75,8 @@ def bind_server(
70
75
 
71
76
  inj.bind_factory(ProcessSpawningImpl, ProcessSpawningFactory),
72
77
 
73
- inj.bind_factory(OutputDispatcherImpl, OutputDispatcherFactory),
74
- inj.bind_factory(InputDispatcherImpl, InputDispatcherFactory),
78
+ inj.bind_factory(ProcessOutputDispatcherImpl, ProcessOutputDispatcherFactory),
79
+ inj.bind_factory(ProcessInputDispatcherImpl, ProcessInputDispatcherFactory),
75
80
  ]
76
81
 
77
82
  #
@@ -0,0 +1,82 @@
1
+ # ruff: noqa: UP006 UP007
2
+ from omlish.lite.logs import log
3
+
4
+ from .dispatchers import Dispatchers
5
+ from .groups import ProcessGroupManager
6
+ from .poller import Poller
7
+ from .types import ExitNow
8
+
9
+
10
+ ##
11
+
12
+
13
+ class IoManager:
14
+ def __init__(
15
+ self,
16
+ *,
17
+ poller: Poller,
18
+ process_groups: ProcessGroupManager,
19
+ ) -> None:
20
+ super().__init__()
21
+
22
+ self._poller = poller
23
+ self._process_groups = process_groups
24
+
25
+ def get_dispatchers(self) -> Dispatchers:
26
+ return Dispatchers(
27
+ d
28
+ for p in self._process_groups.all_processes()
29
+ for d in p.get_dispatchers()
30
+ )
31
+
32
+ def poll(self) -> None:
33
+ dispatchers = self.get_dispatchers()
34
+
35
+ for fd, dispatcher in dispatchers.items():
36
+ if dispatcher.readable():
37
+ self._poller.register_readable(fd)
38
+ if dispatcher.writable():
39
+ self._poller.register_writable(fd)
40
+
41
+ timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
42
+ r, w = self._poller.poll(timeout)
43
+
44
+ for fd in r:
45
+ if fd in dispatchers:
46
+ try:
47
+ dispatcher = dispatchers[fd]
48
+ log.debug('read event caused by %r', dispatcher)
49
+ dispatcher.handle_read_event()
50
+ if not dispatcher.readable():
51
+ self._poller.unregister_readable(fd)
52
+ except ExitNow:
53
+ raise
54
+ except Exception: # noqa
55
+ dispatchers[fd].handle_error()
56
+ else:
57
+ # if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
58
+ # time, which may cause 100% cpu usage
59
+ log.debug('unexpected read event from fd %r', fd)
60
+ try:
61
+ self._poller.unregister_readable(fd)
62
+ except Exception: # noqa
63
+ pass
64
+
65
+ for fd in w:
66
+ if fd in dispatchers:
67
+ try:
68
+ dispatcher = dispatchers[fd]
69
+ log.debug('write event caused by %r', dispatcher)
70
+ dispatcher.handle_write_event()
71
+ if not dispatcher.writable():
72
+ self._poller.unregister_writable(fd)
73
+ except ExitNow:
74
+ raise
75
+ except Exception: # noqa
76
+ dispatchers[fd].handle_error()
77
+ else:
78
+ log.debug('unexpected write event from fd %r', fd)
79
+ try:
80
+ self._poller.unregister_writable(fd)
81
+ except Exception: # noqa
82
+ pass
@@ -37,18 +37,18 @@ from omlish.lite.http.coroserver import CoroHttpServer
37
37
  from omlish.lite.inject import inj
38
38
  from omlish.lite.journald import journald_log_handler_factory
39
39
  from omlish.lite.logs import configure_standard_logging
40
+ from omlish.lite.runtime import is_debugger_attached
40
41
 
41
42
  from ..configs import read_config_file
42
43
  from .configs import ServerConfig
43
44
  from .configs import prepare_server_config
44
- from .context import ServerContextImpl
45
- from .context import ServerEpoch
46
45
  from .inject import bind_server
47
46
  from .spawningimpl import InheritedFds
48
47
  from .states import SupervisorState
49
48
  from .supervisor import Supervisor
50
- from .utils import ExitNow
51
- from .utils import get_open_fds
49
+ from .types import ExitNow
50
+ from .types import ServerEpoch
51
+ from .utils.fds import get_open_fds
52
52
 
53
53
 
54
54
  ##
@@ -79,7 +79,7 @@ def main(
79
79
  if not no_logging:
80
80
  configure_standard_logging(
81
81
  'INFO',
82
- handler_factory=journald_log_handler_factory if not args.no_journald else None,
82
+ handler_factory=journald_log_handler_factory if not (args.no_journald or is_debugger_attached()) else None,
83
83
  )
84
84
 
85
85
  #
@@ -102,7 +102,6 @@ def main(
102
102
  inherited_fds=inherited_fds,
103
103
  ))
104
104
 
105
- context = injector[ServerContextImpl]
106
105
  supervisor = injector[Supervisor]
107
106
 
108
107
  try:
@@ -110,7 +109,7 @@ def main(
110
109
  except ExitNow:
111
110
  pass
112
111
 
113
- if context.state < SupervisorState.RESTARTING:
112
+ if supervisor.state < SupervisorState.RESTARTING:
114
113
  break
115
114
 
116
115
 
@@ -4,24 +4,26 @@ import fcntl
4
4
  import os
5
5
  import typing as ta
6
6
 
7
- from .utils import close_fd
7
+ from .utils.fds import close_fd
8
+ from .utils.fds import make_pipe
9
+ from .utils.ostypes import Fd
8
10
 
9
11
 
10
12
  @dc.dataclass(frozen=True)
11
13
  class ProcessPipes:
12
- child_stdin: ta.Optional[int] = None
13
- stdin: ta.Optional[int] = None
14
+ child_stdin: ta.Optional[Fd] = None
15
+ stdin: ta.Optional[Fd] = None
14
16
 
15
- stdout: ta.Optional[int] = None
16
- child_stdout: ta.Optional[int] = None
17
+ stdout: ta.Optional[Fd] = None
18
+ child_stdout: ta.Optional[Fd] = None
17
19
 
18
- stderr: ta.Optional[int] = None
19
- child_stderr: ta.Optional[int] = None
20
+ stderr: ta.Optional[Fd] = None
21
+ child_stderr: ta.Optional[Fd] = None
20
22
 
21
- def child_fds(self) -> ta.List[int]:
23
+ def child_fds(self) -> ta.List[Fd]:
22
24
  return [fd for fd in [self.child_stdin, self.child_stdout, self.child_stderr] if fd is not None]
23
25
 
24
- def parent_fds(self) -> ta.List[int]:
26
+ def parent_fds(self) -> ta.List[Fd]:
25
27
  return [fd for fd in [self.stdin, self.stdout, self.stderr] if fd is not None]
26
28
 
27
29
 
@@ -31,7 +33,7 @@ def make_process_pipes(stderr=True) -> ProcessPipes:
31
33
  read them in the mainloop without blocking. If stderr is False, don't create a pipe for stderr.
32
34
  """
33
35
 
34
- pipes: ta.Dict[str, ta.Optional[int]] = {
36
+ pipes: ta.Dict[str, ta.Optional[Fd]] = {
35
37
  'child_stdin': None,
36
38
  'stdin': None,
37
39
 
@@ -43,11 +45,11 @@ def make_process_pipes(stderr=True) -> ProcessPipes:
43
45
  }
44
46
 
45
47
  try:
46
- pipes['child_stdin'], pipes['stdin'] = os.pipe()
47
- pipes['stdout'], pipes['child_stdout'] = os.pipe()
48
+ pipes['child_stdin'], pipes['stdin'] = make_pipe()
49
+ pipes['stdout'], pipes['child_stdout'] = make_pipe()
48
50
 
49
51
  if stderr:
50
- pipes['stderr'], pipes['child_stderr'] = os.pipe()
52
+ pipes['stderr'], pipes['child_stderr'] = make_pipe()
51
53
 
52
54
  for fd in (
53
55
  pipes['stdout'],