ominfra 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev149__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. ominfra/clouds/aws/cli.py +1 -1
  2. ominfra/configs.py +30 -5
  3. ominfra/journald/messages.py +1 -1
  4. ominfra/manage/commands/base.py +5 -5
  5. ominfra/manage/commands/execution.py +2 -2
  6. ominfra/manage/commands/inject.py +1 -1
  7. ominfra/manage/commands/interp.py +2 -2
  8. ominfra/manage/commands/subprocess.py +22 -14
  9. ominfra/manage/deploy/command.py +1 -1
  10. ominfra/manage/deploy/paths.py +2 -2
  11. ominfra/manage/main.py +48 -32
  12. ominfra/manage/remote/_main.py +172 -0
  13. ominfra/manage/remote/channel.py +41 -16
  14. ominfra/manage/remote/config.py +10 -0
  15. ominfra/manage/remote/connection.py +106 -0
  16. ominfra/manage/remote/execution.py +244 -155
  17. ominfra/manage/remote/inject.py +7 -3
  18. ominfra/manage/remote/spawning.py +51 -33
  19. ominfra/pyremote.py +28 -3
  20. ominfra/scripts/journald2aws.py +195 -91
  21. ominfra/scripts/manage.py +1366 -486
  22. ominfra/scripts/supervisor.py +533 -479
  23. ominfra/supervisor/dispatchers.py +1 -1
  24. ominfra/supervisor/http.py +2 -2
  25. ominfra/supervisor/inject.py +4 -4
  26. ominfra/supervisor/io.py +1 -1
  27. ominfra/supervisor/spawningimpl.py +1 -1
  28. ominfra/supervisor/supervisor.py +1 -1
  29. ominfra/supervisor/types.py +1 -1
  30. ominfra/tailscale/cli.py +1 -1
  31. {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev149.dist-info}/METADATA +3 -3
  32. {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev149.dist-info}/RECORD +36 -34
  33. {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev149.dist-info}/LICENSE +0 -0
  34. {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev149.dist-info}/WHEEL +0 -0
  35. {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev149.dist-info}/entry_points.txt +0 -0
  36. {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev149.dist-info}/top_level.txt +0 -0
ominfra/clouds/aws/cli.py CHANGED
@@ -1,4 +1,4 @@
1
- from omlish import argparse as ap
1
+ from omlish.argparse import all as ap
2
2
  from omlish.formats import json
3
3
 
4
4
  from . import metadata
ominfra/configs.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  # @omlish-lite
3
3
  import json
4
+ import os.path
4
5
  import typing as ta
5
6
 
6
7
  from omdev.toml.parser import toml_loads
@@ -14,6 +15,33 @@ T = ta.TypeVar('T')
14
15
  ConfigMapping = ta.Mapping[str, ta.Any]
15
16
 
16
17
 
18
+ def parse_config_file(
19
+ name: str,
20
+ f: ta.TextIO,
21
+ ) -> ConfigMapping:
22
+ if name.endswith('.toml'):
23
+ return toml_loads(f.read())
24
+
25
+ elif any(name.endswith(e) for e in ('.yml', '.yaml')):
26
+ yaml = __import__('yaml')
27
+ return yaml.safe_load(f)
28
+
29
+ elif name.endswith('.ini'):
30
+ import configparser
31
+ cp = configparser.ConfigParser()
32
+ cp.read_file(f)
33
+ config_dct: ta.Dict[str, ta.Any] = {}
34
+ for sec in cp.sections():
35
+ cd = config_dct
36
+ for k in sec.split('.'):
37
+ cd = cd.setdefault(k, {})
38
+ cd.update(cp.items(sec))
39
+ return config_dct
40
+
41
+ else:
42
+ return json.loads(f.read())
43
+
44
+
17
45
  def read_config_file(
18
46
  path: str,
19
47
  cls: ta.Type[T],
@@ -21,13 +49,10 @@ def read_config_file(
21
49
  prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
22
50
  ) -> T:
23
51
  with open(path) as cf:
24
- if path.endswith('.toml'):
25
- config_dct = toml_loads(cf.read())
26
- else:
27
- config_dct = json.loads(cf.read())
52
+ config_dct = parse_config_file(os.path.basename(path), cf)
28
53
 
29
54
  if prepare is not None:
30
- config_dct = prepare(config_dct) # type: ignore
55
+ config_dct = prepare(config_dct)
31
56
 
32
57
  return unmarshal_obj(config_dct, cls)
33
58
 
@@ -4,8 +4,8 @@ import dataclasses as dc
4
4
  import json
5
5
  import typing as ta
6
6
 
7
+ from omlish.io.buffers import DelimitingBuffer
7
8
  from omlish.lite.check import check_isinstance
8
- from omlish.lite.io import DelimitingBuffer
9
9
  from omlish.lite.logs import log
10
10
 
11
11
 
@@ -23,8 +23,8 @@ class Command(abc.ABC, ta.Generic[CommandOutputT]):
23
23
  pass
24
24
 
25
25
  @ta.final
26
- def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
27
- return check_isinstance(executor.execute(self), self.Output) # type: ignore[return-value]
26
+ async def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
27
+ return check_isinstance(await executor.execute(self), self.Output) # type: ignore[return-value]
28
28
 
29
29
 
30
30
  ##
@@ -85,10 +85,10 @@ class CommandOutputOrExceptionData(CommandOutputOrException):
85
85
 
86
86
  class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
87
87
  @abc.abstractmethod
88
- def execute(self, cmd: CommandT) -> CommandOutputT:
88
+ def execute(self, cmd: CommandT) -> ta.Awaitable[CommandOutputT]:
89
89
  raise NotImplementedError
90
90
 
91
- def try_execute(
91
+ async def try_execute(
92
92
  self,
93
93
  cmd: CommandT,
94
94
  *,
@@ -96,7 +96,7 @@ class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
96
96
  omit_exc_object: bool = False,
97
97
  ) -> CommandOutputOrException[CommandOutputT]:
98
98
  try:
99
- o = self.execute(cmd)
99
+ o = await self.execute(cmd)
100
100
 
101
101
  except Exception as e: # noqa
102
102
  if log is not None:
@@ -18,6 +18,6 @@ class LocalCommandExecutor(CommandExecutor):
18
18
 
19
19
  self._command_executors = command_executors
20
20
 
21
- def execute(self, cmd: Command) -> Command.Output:
21
+ async def execute(self, cmd: Command) -> Command.Output:
22
22
  ce: CommandExecutor = self._command_executors[type(cmd)]
23
- return ce.execute(cmd)
23
+ return await ce.execute(cmd)
@@ -54,7 +54,7 @@ def bind_command(
54
54
  class _FactoryCommandExecutor(CommandExecutor):
55
55
  factory: ta.Callable[[], CommandExecutor]
56
56
 
57
- def execute(self, i: Command) -> Command.Output:
57
+ def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
58
58
  return self.factory().execute(i)
59
59
 
60
60
 
@@ -29,9 +29,9 @@ class InterpCommand(Command['InterpCommand.Output']):
29
29
 
30
30
 
31
31
  class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
32
- def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
32
+ async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
33
33
  i = InterpSpecifier.parse(check_not_none(cmd.spec))
34
- o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
34
+ o = check_not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
35
35
  return InterpCommand.Output(
36
36
  exe=o.exe,
37
37
  version=str(o.version.version),
@@ -1,11 +1,15 @@
1
- # ruff: noqa: UP006 UP007
1
+ # ruff: noqa: TC003 UP006 UP007
2
+ import asyncio.subprocess
2
3
  import dataclasses as dc
3
4
  import os
4
5
  import subprocess
5
6
  import time
6
7
  import typing as ta
7
8
 
9
+ from omlish.lite.asyncio.subprocesses import asyncio_subprocess_communicate
10
+ from omlish.lite.asyncio.subprocesses import asyncio_subprocess_popen
8
11
  from omlish.lite.check import check_not_isinstance
12
+ from omlish.lite.check import check_not_none
9
13
  from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
10
14
  from omlish.lite.subprocesses import SubprocessChannelOption
11
15
  from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
@@ -49,27 +53,31 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
49
53
 
50
54
 
51
55
  class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
52
- def execute(self, inp: SubprocessCommand) -> SubprocessCommand.Output:
53
- with subprocess.Popen(
54
- subprocess_maybe_shell_wrap_exec(*inp.cmd),
56
+ async def execute(self, cmd: SubprocessCommand) -> SubprocessCommand.Output:
57
+ proc: asyncio.subprocess.Process
58
+ async with asyncio_subprocess_popen(
59
+ *subprocess_maybe_shell_wrap_exec(*cmd.cmd),
55
60
 
56
- shell=inp.shell,
57
- cwd=inp.cwd,
58
- env={**os.environ, **(inp.env or {})},
61
+ shell=cmd.shell,
62
+ cwd=cmd.cwd,
63
+ env={**os.environ, **(cmd.env or {})},
59
64
 
60
- stdin=subprocess.PIPE if inp.input is not None else None,
61
- stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, inp.stdout)],
62
- stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, inp.stderr)],
65
+ stdin=subprocess.PIPE if cmd.input is not None else None,
66
+ stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, cmd.stdout)],
67
+ stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, cmd.stderr)],
68
+
69
+ timeout=cmd.timeout,
63
70
  ) as proc:
64
71
  start_time = time.time()
65
- stdout, stderr = proc.communicate(
66
- input=inp.input,
67
- timeout=inp.timeout,
72
+ stdout, stderr = await asyncio_subprocess_communicate(
73
+ proc,
74
+ input=cmd.input,
75
+ timeout=cmd.timeout,
68
76
  )
69
77
  end_time = time.time()
70
78
 
71
79
  return SubprocessCommand.Output(
72
- rc=proc.returncode,
80
+ rc=check_not_none(proc.returncode),
73
81
  pid=proc.pid,
74
82
 
75
83
  elapsed_s=end_time - start_time,
@@ -21,7 +21,7 @@ class DeployCommand(Command['DeployCommand.Output']):
21
21
 
22
22
 
23
23
  class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
24
- def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
24
+ async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
25
25
  log.info('Deploying!')
26
26
 
27
27
  return DeployCommand.Output()
@@ -37,8 +37,8 @@ import os.path
37
37
  import typing as ta
38
38
 
39
39
  from omlish.lite.check import check_equal
40
- from omlish.lite.check import check_non_empty
41
40
  from omlish.lite.check import check_non_empty_str
41
+ from omlish.lite.check import check_not_empty
42
42
  from omlish.lite.check import check_not_in
43
43
 
44
44
 
@@ -151,7 +151,7 @@ class DeployPath:
151
151
  parts: ta.Sequence[DeployPathPart]
152
152
 
153
153
  def __post_init__(self) -> None:
154
- check_non_empty(self.parts)
154
+ check_not_empty(self.parts)
155
155
  for p in self.parts[:-1]:
156
156
  check_equal(p.kind, 'dir')
157
157
 
ominfra/manage/main.py CHANGED
@@ -5,6 +5,7 @@
5
5
  manage.py -s 'docker run -i python:3.12'
6
6
  manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
7
7
  """
8
+ import asyncio
8
9
  import contextlib
9
10
  import json
10
11
  import typing as ta
@@ -21,38 +22,14 @@ from .commands.base import CommandExecutor
21
22
  from .commands.execution import LocalCommandExecutor
22
23
  from .config import MainConfig
23
24
  from .remote.config import RemoteConfig
24
- from .remote.execution import RemoteExecution
25
+ from .remote.connection import RemoteExecutionConnector
25
26
  from .remote.spawning import RemoteSpawning
26
27
 
27
28
 
28
29
  ##
29
30
 
30
31
 
31
- def _main() -> None:
32
- import argparse
33
-
34
- parser = argparse.ArgumentParser()
35
-
36
- parser.add_argument('--_payload-file')
37
-
38
- parser.add_argument('-s', '--shell')
39
- parser.add_argument('-q', '--shell-quote', action='store_true')
40
- parser.add_argument('--python', default='python3')
41
-
42
- parser.add_argument('--pycharm-debug-port', type=int)
43
- parser.add_argument('--pycharm-debug-host')
44
- parser.add_argument('--pycharm-debug-version')
45
-
46
- parser.add_argument('--debug', action='store_true')
47
-
48
- parser.add_argument('--local', action='store_true')
49
-
50
- parser.add_argument('command', nargs='+')
51
-
52
- args = parser.parse_args()
53
-
54
- #
55
-
32
+ async def _async_main(args: ta.Any) -> None:
56
33
  bs = MainBootstrap(
57
34
  main_config=MainConfig(
58
35
  log_level='DEBUG' if args.debug else 'INFO',
@@ -65,12 +42,16 @@ def _main() -> None:
65
42
 
66
43
  pycharm_remote_debug=PycharmRemoteDebug(
67
44
  port=args.pycharm_debug_port,
68
- host=args.pycharm_debug_host,
45
+ **(dict(host=args.pycharm_debug_host) if args.pycharm_debug_host is not None else {}),
69
46
  install_version=args.pycharm_debug_version,
70
47
  ) if args.pycharm_debug_port is not None else None,
48
+
49
+ timebomb_delay_s=args.remote_timebomb_delay_s,
71
50
  ),
72
51
  )
73
52
 
53
+ #
54
+
74
55
  injector = main_bootstrap(
75
56
  bs,
76
57
  )
@@ -89,7 +70,7 @@ def _main() -> None:
89
70
 
90
71
  #
91
72
 
92
- with contextlib.ExitStack() as es:
73
+ async with contextlib.AsyncExitStack() as es:
93
74
  ce: CommandExecutor
94
75
 
95
76
  if args.local:
@@ -102,16 +83,51 @@ def _main() -> None:
102
83
  python=args.python,
103
84
  )
104
85
 
105
- ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
86
+ ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs)) # noqa
106
87
 
107
- for cmd in cmds:
108
- r = ce.try_execute(
88
+ async def run_command(cmd: Command) -> None:
89
+ res = await ce.try_execute(
109
90
  cmd,
110
91
  log=log,
111
92
  omit_exc_object=True,
112
93
  )
113
94
 
114
- print(msh.marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
95
+ print(msh.marshal_obj(res, opts=ObjMarshalOptions(raw_bytes=True)))
96
+
97
+ await asyncio.gather(*[
98
+ run_command(cmd)
99
+ for cmd in cmds
100
+ ])
101
+
102
+
103
+ def _main() -> None:
104
+ import argparse
105
+
106
+ parser = argparse.ArgumentParser()
107
+
108
+ parser.add_argument('--_payload-file')
109
+
110
+ parser.add_argument('-s', '--shell')
111
+ parser.add_argument('-q', '--shell-quote', action='store_true')
112
+ parser.add_argument('--python', default='python3')
113
+
114
+ parser.add_argument('--pycharm-debug-port', type=int)
115
+ parser.add_argument('--pycharm-debug-host')
116
+ parser.add_argument('--pycharm-debug-version')
117
+
118
+ parser.add_argument('--remote-timebomb-delay-s', type=float)
119
+
120
+ parser.add_argument('--debug', action='store_true')
121
+
122
+ parser.add_argument('--local', action='store_true')
123
+
124
+ parser.add_argument('command', nargs='+')
125
+
126
+ args = parser.parse_args()
127
+
128
+ #
129
+
130
+ asyncio.run(_async_main(args))
115
131
 
116
132
 
117
133
  if __name__ == '__main__':
@@ -0,0 +1,172 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import asyncio
3
+ import functools
4
+ import logging
5
+ import os
6
+ import signal
7
+ import threading
8
+ import time
9
+ import typing as ta
10
+
11
+ from omlish.lite.asyncio.asyncio import asyncio_open_stream_reader
12
+ from omlish.lite.asyncio.asyncio import asyncio_open_stream_writer
13
+ from omlish.lite.cached import cached_nullary
14
+ from omlish.lite.check import check_none
15
+ from omlish.lite.check import check_not_none
16
+ from omlish.lite.deathsig import set_process_deathsig
17
+ from omlish.lite.inject import Injector
18
+ from omlish.lite.logs import log
19
+ from omlish.lite.marshal import ObjMarshalerManager
20
+ from omlish.lite.pycharm import pycharm_debug_connect
21
+
22
+ from ...pyremote import pyremote_bootstrap_finalize
23
+ from ..bootstrap import MainBootstrap
24
+ from ..commands.execution import LocalCommandExecutor
25
+ from .channel import RemoteChannel
26
+ from .channel import RemoteChannelImpl
27
+ from .execution import _RemoteCommandHandler
28
+ from .execution import _RemoteLogHandler
29
+
30
+
31
+ if ta.TYPE_CHECKING:
32
+ from ..bootstrap_ import main_bootstrap
33
+ else:
34
+ main_bootstrap: ta.Any = None
35
+
36
+
37
+ ##
38
+
39
+
40
+ class _RemoteExecutionLogHandler(logging.Handler):
41
+ def __init__(self, fn: ta.Callable[[str], None]) -> None:
42
+ super().__init__()
43
+ self._fn = fn
44
+
45
+ def emit(self, record):
46
+ msg = self.format(record)
47
+ self._fn(msg)
48
+
49
+
50
+ ##
51
+
52
+
53
+ class _RemoteExecutionMain:
54
+ def __init__(
55
+ self,
56
+ chan: RemoteChannel,
57
+ ) -> None:
58
+ super().__init__()
59
+
60
+ self._chan = chan
61
+
62
+ self.__bootstrap: ta.Optional[MainBootstrap] = None
63
+ self.__injector: ta.Optional[Injector] = None
64
+
65
+ @property
66
+ def _bootstrap(self) -> MainBootstrap:
67
+ return check_not_none(self.__bootstrap)
68
+
69
+ @property
70
+ def _injector(self) -> Injector:
71
+ return check_not_none(self.__injector)
72
+
73
+ #
74
+
75
+ def _timebomb_main(
76
+ self,
77
+ delay_s: float,
78
+ *,
79
+ sig: int = signal.SIGINT,
80
+ code: int = 1,
81
+ ) -> None:
82
+ time.sleep(delay_s)
83
+
84
+ if (pgid := os.getpgid(0)) == os.getpid():
85
+ os.killpg(pgid, sig)
86
+
87
+ os._exit(code) # noqa
88
+
89
+ @cached_nullary
90
+ def _timebomb_thread(self) -> ta.Optional[threading.Thread]:
91
+ if (tbd := self._bootstrap.remote_config.timebomb_delay_s) is None:
92
+ return None
93
+
94
+ thr = threading.Thread(
95
+ target=functools.partial(self._timebomb_main, tbd),
96
+ name=f'{self.__class__.__name__}.timebomb',
97
+ daemon=True,
98
+ )
99
+
100
+ thr.start()
101
+
102
+ log.debug('Started timebomb thread: %r', thr)
103
+
104
+ return thr
105
+
106
+ #
107
+
108
+ @cached_nullary
109
+ def _log_handler(self) -> _RemoteLogHandler:
110
+ return _RemoteLogHandler(self._chan)
111
+
112
+ #
113
+
114
+ async def _setup(self) -> None:
115
+ check_none(self.__bootstrap)
116
+ check_none(self.__injector)
117
+
118
+ # Bootstrap
119
+
120
+ self.__bootstrap = check_not_none(await self._chan.recv_obj(MainBootstrap))
121
+
122
+ if (prd := self._bootstrap.remote_config.pycharm_remote_debug) is not None:
123
+ pycharm_debug_connect(prd)
124
+
125
+ self.__injector = main_bootstrap(self._bootstrap)
126
+
127
+ self._chan.set_marshaler(self._injector[ObjMarshalerManager])
128
+
129
+ # Post-bootstrap
130
+
131
+ if self._bootstrap.remote_config.set_pgid:
132
+ if os.getpgid(0) != os.getpid():
133
+ log.debug('Setting pgid')
134
+ os.setpgid(0, 0)
135
+
136
+ if (ds := self._bootstrap.remote_config.deathsig) is not None:
137
+ log.debug('Setting deathsig: %s', ds)
138
+ set_process_deathsig(int(signal.Signals[f'SIG{ds.upper()}']))
139
+
140
+ self._timebomb_thread()
141
+
142
+ if self._bootstrap.remote_config.forward_logging:
143
+ log.debug('Installing log forwarder')
144
+ logging.root.addHandler(self._log_handler())
145
+
146
+ #
147
+
148
+ async def run(self) -> None:
149
+ await self._setup()
150
+
151
+ executor = self._injector[LocalCommandExecutor]
152
+
153
+ handler = _RemoteCommandHandler(self._chan, executor)
154
+
155
+ await handler.run()
156
+
157
+
158
+ def _remote_execution_main() -> None:
159
+ rt = pyremote_bootstrap_finalize() # noqa
160
+
161
+ async def inner() -> None:
162
+ input = await asyncio_open_stream_reader(rt.input) # noqa
163
+ output = await asyncio_open_stream_writer(rt.output)
164
+
165
+ chan = RemoteChannelImpl(
166
+ input,
167
+ output,
168
+ )
169
+
170
+ await _RemoteExecutionMain(chan).run()
171
+
172
+ asyncio.run(inner())
@@ -1,7 +1,8 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import asyncio
2
4
  import json
3
5
  import struct
4
- import threading
5
6
  import typing as ta
6
7
 
7
8
  from omlish.lite.json import json_dumps_compact
@@ -12,11 +13,30 @@ from omlish.lite.marshal import ObjMarshalerManager
12
13
  T = ta.TypeVar('T')
13
14
 
14
15
 
15
- class RemoteChannel:
16
+ ##
17
+
18
+
19
+ class RemoteChannel(abc.ABC):
20
+ @abc.abstractmethod
21
+ def send_obj(self, o: ta.Any, ty: ta.Any = None) -> ta.Awaitable[None]:
22
+ raise NotImplementedError
23
+
24
+ @abc.abstractmethod
25
+ def recv_obj(self, ty: ta.Type[T]) -> ta.Awaitable[ta.Optional[T]]:
26
+ raise NotImplementedError
27
+
28
+ def set_marshaler(self, msh: ObjMarshalerManager) -> None: # noqa
29
+ pass
30
+
31
+
32
+ ##
33
+
34
+
35
+ class RemoteChannelImpl(RemoteChannel):
16
36
  def __init__(
17
37
  self,
18
- input: ta.IO, # noqa
19
- output: ta.IO,
38
+ input: asyncio.StreamReader, # noqa
39
+ output: asyncio.StreamWriter,
20
40
  *,
21
41
  msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
22
42
  ) -> None:
@@ -26,38 +46,43 @@ class RemoteChannel:
26
46
  self._output = output
27
47
  self._msh = msh
28
48
 
29
- self._lock = threading.RLock()
49
+ self._input_lock = asyncio.Lock()
50
+ self._output_lock = asyncio.Lock()
30
51
 
31
52
  def set_marshaler(self, msh: ObjMarshalerManager) -> None:
32
53
  self._msh = msh
33
54
 
34
- def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
55
+ #
56
+
57
+ async def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
35
58
  j = json_dumps_compact(self._msh.marshal_obj(o, ty))
36
59
  d = j.encode('utf-8')
37
60
 
38
61
  self._output.write(struct.pack('<I', len(d)))
39
62
  self._output.write(d)
40
- self._output.flush()
63
+ await self._output.drain()
64
+
65
+ async def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
66
+ async with self._output_lock:
67
+ return await self._send_obj(o, ty)
41
68
 
42
- def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
43
- with self._lock:
44
- return self._send_obj(o, ty)
69
+ #
45
70
 
46
- def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
47
- d = self._input.read(4)
71
+ async def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
72
+ d = await self._input.read(4)
48
73
  if not d:
49
74
  return None
50
75
  if len(d) != 4:
51
76
  raise EOFError
52
77
 
53
78
  sz = struct.unpack('<I', d)[0]
54
- d = self._input.read(sz)
79
+ d = await self._input.read(sz)
55
80
  if len(d) != sz:
56
81
  raise EOFError
57
82
 
58
83
  j = json.loads(d.decode('utf-8'))
59
84
  return self._msh.unmarshal_obj(j, ty)
60
85
 
61
- def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
62
- with self._lock:
63
- return self._recv_obj(ty)
86
+ async def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
87
+ async with self._input_lock:
88
+ return await self._recv_obj(ty)
@@ -9,4 +9,14 @@ from omlish.lite.pycharm import PycharmRemoteDebug
9
9
  class RemoteConfig:
10
10
  payload_file: ta.Optional[str] = None
11
11
 
12
+ set_pgid: bool = True
13
+
14
+ deathsig: ta.Optional[str] = 'KILL'
15
+
12
16
  pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None
17
+
18
+ forward_logging: bool = True
19
+
20
+ timebomb_delay_s: ta.Optional[float] = 60 * 60.
21
+
22
+ heartbeat_interval_s: float = 3.