Use asyncio for 'eden start' systemd loop

Summary:
'eden start' polls for systemctl to exit, which is pretty gross. Use asyncio to avoid this explicit polling. (asyncio may still poll internally, though.)

We still poll the log file. We could use inotify in the future to avoid polling.

This refactor makes it easier to replace systemctl with direct D-Bus access in a future diff.

This diff should not change behavior.

Reviewed By: simpkins

Differential Revision: D13526245

fbshipit-source-id: a29b1e062c489d8f2c70759e61765f8dd8df6664
This commit is contained in:
Matt Glazar 2019-01-10 20:34:16 -08:00 committed by Facebook Github Bot
parent 444b73b83b
commit d83cb71731
2 changed files with 21 additions and 13 deletions

View File

@ -7,6 +7,7 @@
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
import asyncio
import errno
import os
import pathlib
@ -215,19 +216,20 @@ def start_systemd_service(
startup_log_path = service_config.startup_log_file_path
startup_log_path.write_bytes(b"")
with forward_log_file( # pyre-ignore (T37455202)
startup_log_path, sys.stderr.buffer
) as log_forwarder:
with subprocess.Popen(
["systemctl", "--user", "start", "--", service_name]
) as start_process:
while True:
with forward_log_file(startup_log_path, sys.stderr.buffer) as log_forwarder:
loop = asyncio.get_event_loop()
async def start_service_async():
start_process = await asyncio.create_subprocess_exec(
"systemctl", "--user", "start", "--", service_name
)
return await start_process.wait()
start_task = loop.create_task(start_service_async())
loop.create_task(log_forwarder.poll_forever_async())
loop.run_until_complete(start_task)
log_forwarder.poll()
exit_code = start_process.poll()
if exit_code is not None:
log_forwarder.poll()
return exit_code
time.sleep(0.1)
return start_task.result()
def _get_daemon_args(

View File

@ -6,6 +6,7 @@
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
import asyncio
import contextlib
import io
import pathlib
@ -36,6 +37,11 @@ class LogForwarder:
self.__output_file.write(self.__follower.poll())
self.__output_file.flush()
async def poll_forever_async(self) -> typing.NoReturn:
while True:
self.poll()
await asyncio.sleep(0.1)
@contextlib.contextmanager
def follow_log_file(file_path: pathlib.Path) -> typing.Iterator["LogFollower"]: