Skip to content

Commit 48d3738

Browse files
committed
Support cvd start --daemon=false
Implements foreground mode for `cvd start`. When `--daemon=false` is requested, it continues to launch the device via `cvd_internal_start --daemon=true` but subsequently attaches the log monitor view (`cvd monitor`). Upon user interruption (Ctrl-C), it gracefully shuts down the instance group. Assisted-by: Jetski:Gemini-Next Bug: b/519304405 TAG=agy CONV=34ddc7b8-a695-4937-be99-b796087dd987
1 parent 285e149 commit 48d3738

5 files changed

Lines changed: 180 additions & 67 deletions

File tree

base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ cf_cc_library(
407407
"//cuttlefish/host/commands/cvd/cli:utils",
408408
"//cuttlefish/host/commands/cvd/cli/commands:command_handler",
409409
"//cuttlefish/host/commands/cvd/cli/commands:host_tool_target",
410+
"//cuttlefish/host/commands/cvd/cli/commands/monitor",
410411
"//cuttlefish/host/commands/cvd/cli/selector",
411412
"//cuttlefish/host/commands/cvd/fetch:substitute",
412413
"//cuttlefish/host/commands/cvd/instances",

base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.cc

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818

1919
#include <errno.h>
2020
#include <fcntl.h>
21-
#include <poll.h>
21+
#include <sys/eventfd.h>
2222
#include <sys/inotify.h>
23+
#include <sys/poll.h>
2324
#include <sys/types.h>
2425
#include <unistd.h>
2526

@@ -28,6 +29,7 @@
2829
#include <memory>
2930
#include <string>
3031
#include <thread>
32+
#include <vector>
3133

3234
#include "absl/strings/str_cat.h"
3335

@@ -40,16 +42,8 @@
4042
#include "cuttlefish/result/result.h"
4143

4244
namespace cuttlefish {
43-
4445
namespace {
4546

46-
void ClearLastNLines(int n) {
47-
if (n > 0) {
48-
// Move cursor up N lines and clear to end of screen
49-
std::cout << AnsiCursorUp(n) << kAnsiClearScreenAfterCursor << std::flush;
50-
}
51-
}
52-
5347
void UpdateFileAndWatch(const SharedFD& inotify_fd, const std::string& path,
5448
SharedFD& fd, int& watch) {
5549
if (!fd->IsOpen()) {
@@ -62,7 +56,17 @@ void UpdateFileAndWatch(const SharedFD& inotify_fd, const std::string& path,
6256

6357
} // namespace
6458

59+
void ClearLastNLines(int n) {
60+
if (n > 0) {
61+
// Move cursor up N lines and clear to end of screen
62+
std::cout << AnsiCursorUp(n) << kAnsiClearScreenAfterCursor << std::flush;
63+
}
64+
}
6565
Result<void> MonitorLogs(const LocalInstance& instance) {
66+
return MonitorLogs(instance, SharedFD());
67+
}
68+
69+
Result<void> MonitorLogs(const LocalInstance& instance, SharedFD stop_eventfd) {
6670
CF_EXPECT(isatty(0), "The monitor command requires an interactive terminal.");
6771

6872
const std::string kernel_log =
@@ -122,19 +126,34 @@ Result<void> MonitorLogs(const LocalInstance& instance) {
122126
}
123127
last_draw_time = std::chrono::steady_clock::now();
124128

129+
// Setup poll_fds
130+
std::vector<PollSharedFd> poll_fds = {
131+
PollSharedFd{.fd = inotify_fd, .events = POLLIN, .revents = 0},
132+
};
133+
if (stop_eventfd->IsOpen()) {
134+
poll_fds.push_back(
135+
PollSharedFd{.fd = stop_eventfd, .events = POLLIN, .revents = 0});
136+
}
137+
125138
// Block until file changes occur. If any watch failed or is missing, use
126139
// a 1-second fallback timeout to awake and retry.
127-
// NOLINTNEXTLINE(misc-include-cleaner)
128-
PollSharedFd poll_fd = {inotify_fd, POLLIN, 0};
129140
int timeout_ms = -1;
130141
if (dir_watch == -1 || kernel_watch == -1 || launcher_watch == -1 ||
131142
logcat_watch == -1) {
132143
timeout_ms = 1000;
133144
}
134145

135-
// NOLINTNEXTLINE(misc-include-cleaner)
136-
if (SharedFD::Poll(&poll_fd, 1, timeout_ms) > 0 &&
137-
(poll_fd.revents & POLLIN)) {
146+
int poll_res = SharedFD::Poll(poll_fds, timeout_ms);
147+
148+
if (stop_eventfd->IsOpen() && (poll_fds[1].revents & POLLIN)) {
149+
// Stop requested via eventfd
150+
eventfd_t val;
151+
stop_eventfd->EventfdRead(&val);
152+
ClearLastNLines(total_lines_drawn);
153+
return {};
154+
}
155+
156+
if (poll_res > 0 && (poll_fds[0].revents & POLLIN)) {
138157
// Exhaustively drain all available events from the non-blocking
139158
// descriptor to coalesce rapid file modifications.
140159
char buf[4096]

base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@
1616

1717
#pragma once
1818

19+
#include "cuttlefish/common/libs/fs/shared_fd.h"
1920
#include "cuttlefish/host/commands/cvd/instances/local_instance.h"
2021
#include "cuttlefish/result/result.h"
2122

2223
namespace cuttlefish {
2324

25+
void ClearLastNLines(int n);
26+
2427
Result<void> MonitorLogs(const LocalInstance& instance);
2528

29+
// The monitor will stop and return if `stop_eventfd` becomes readable (receives
30+
// an event).
31+
Result<void> MonitorLogs(const LocalInstance& instance, SharedFD stop_eventfd);
32+
2633
} // namespace cuttlefish

base/cvd/cuttlefish/host/commands/cvd/cli/commands/start.cpp

Lines changed: 135 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <unistd.h>
2525

2626
#include <algorithm>
27+
#include <chrono>
28+
#include <cstdio>
2729
#include <cstdlib>
2830
#include <cstring>
2931
#include <iostream>
@@ -32,26 +34,29 @@
3234
#include <optional>
3335
#include <set>
3436
#include <string>
37+
#include <thread>
3538
#include <utility>
3639
#include <vector>
3740

38-
#include <fmt/core.h>
39-
#include <fmt/format.h>
4041
#include "absl/log/log.h"
4142
#include "absl/strings/str_join.h"
4243
#include "absl/strings/str_split.h"
44+
#include "fmt/core.h"
45+
#include "fmt/format.h"
4346

47+
#include "cuttlefish/common/libs/fs/shared_buf.h"
4448
#include "cuttlefish/common/libs/fs/shared_fd.h"
4549
#include "cuttlefish/common/libs/utils/contains.h"
4650
#include "cuttlefish/common/libs/utils/files.h"
47-
#include "cuttlefish/flag_parser/flag.h"
48-
#include "cuttlefish/flag_parser/gflags_compat.h"
4951
#include "cuttlefish/common/libs/utils/json.h"
5052
#include "cuttlefish/common/libs/utils/subprocess.h"
5153
#include "cuttlefish/common/libs/utils/users.h"
54+
#include "cuttlefish/flag_parser/flag.h"
55+
#include "cuttlefish/flag_parser/gflags_compat.h"
5256
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
5357
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
5458
#include "cuttlefish/host/commands/cvd/cli/commands/host_tool_target.h"
59+
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.h"
5560
#include "cuttlefish/host/commands/cvd/cli/help_format.h"
5661
#include "cuttlefish/host/commands/cvd/cli/selector/selector.h"
5762
#include "cuttlefish/host/commands/cvd/cli/types.h"
@@ -279,7 +284,8 @@ Result<Command> ConstructCvdNonHelpCommand(const std::string& bin_file,
279284
const LocalInstanceGroup& group,
280285
const cvd_common::Args& args,
281286
const cvd_common::Envs& envs,
282-
const CommandRequest& request) {
287+
const CommandRequest& request,
288+
SharedFD output_fd = SharedFD()) {
283289
auto bin_path = group.HostArtifactsPath();
284290
CF_EXPECTF(PotentiallyHostArtifactsPath(bin_path),
285291
"ANDROID_HOST_OUT, \"{}\" is not a tool directory", bin_path);
@@ -292,10 +298,17 @@ Result<Command> ConstructCvdNonHelpCommand(const std::string& bin_file,
292298
.working_dir = CurrentDirectory(),
293299
.command_name = bin_file};
294300
Command non_help_command = CF_EXPECT(ConstructCommand(construct_cmd_param));
295-
// Print everything to stderr, cvd needs to print JSON to stdout which
296-
// would be unparseable with the subcommand's output.
297-
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
298-
Subprocess::StdIOChannel::kStdErr);
301+
if (output_fd->IsOpen()) {
302+
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
303+
output_fd);
304+
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
305+
output_fd);
306+
} else {
307+
// Print everything to stderr, cvd needs to print JSON to stdout which
308+
// would be unparseable with the subcommand's output.
309+
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
310+
Subprocess::StdIOChannel::kStdErr);
311+
}
299312
return non_help_command;
300313
}
301314

@@ -315,18 +328,8 @@ Result<std::vector<Flag>> GetCvdInternalStartFlags(
315328

316329
CvdStartCommandHandler::CvdStartCommandHandler(
317330
InstanceManager& instance_manager)
318-
: instance_manager_(instance_manager) {}
319-
320-
static Result<void> ConsumeDaemonModeFlag(cvd_common::Args& args) {
321-
bool daemon = true;
322-
Flag flag =
323-
GflagsCompatFlag("daemon", daemon)
324-
.AddValidator([&daemon]() -> Result<void> {
325-
CF_EXPECT(!!daemon, "`cvd start` must always run in daemon mode.");
326-
return {};
327-
});
328-
CF_EXPECT(ConsumeFlags({flag}, args));
329-
return {};
331+
: instance_manager_(instance_manager) {
332+
own_flags_.daemon = true;
330333
}
331334

332335
static bool HasUnsafeFlagsForBypass(const std::vector<std::string>& args) {
@@ -370,8 +373,17 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
370373
}
371374
}
372375

373-
CF_EXPECT(ConsumeDaemonModeFlag(subcmd_args));
376+
CF_EXPECT(ConsumeFlags(BuildOwnFlags(), subcmd_args));
374377
subcmd_args.push_back("--daemon=true");
378+
if (own_flags_.report_anonymous_usage_stats) {
379+
subcmd_args.push_back("--report_anonymous_usage_stats=" +
380+
*own_flags_.report_anonymous_usage_stats);
381+
} else if (!own_flags_.daemon) {
382+
// If the user did not specify the anonymous usage stats flag, default it
383+
// to 'n' to ensure that cvd_internal_start does not block waiting for
384+
// input on stdin.
385+
subcmd_args.push_back("--report_anonymous_usage_stats=n");
386+
}
375387

376388
LocalInstanceGroup group =
377389
CF_EXPECT(selector::SelectGroup(instance_manager_, request),
@@ -388,46 +400,109 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
388400
CF_EXPECT(UpdateEnvs(envs, group));
389401
const auto bin = CF_EXPECT(FindStartBin(group.HostArtifactsPath()));
390402

391-
CF_EXPECT(ConsumeFlags(BuildOwnFlags(), subcmd_args));
392-
393403
CF_EXPECT(HostPackageSubstitution(group.HostArtifactsPath(),
394404
own_flags_.host_substitutions));
395405

396-
Command command = CF_EXPECT(
397-
ConstructCvdNonHelpCommand(bin, group, subcmd_args, envs, request));
398-
399-
// The instance database needs to be updated if an interrupt is received.
400-
auto handle_res = PushInterruptListener([this, &group](int signal) {
401-
LOG(WARNING) << strsignal(signal) << " signal received, cleanning up";
402-
auto interrupt_res = subprocess_waiter_.Interrupt();
403-
if (!interrupt_res.ok()) {
404-
LOG(ERROR) << "Failed to stop subprocesses: " << interrupt_res.error();
405-
LOG(ERROR) << "Devices may still be executing in the background, run "
406-
"`cvd reset` to ensure a clean state";
407-
}
408-
409-
group.SetAllStates(cvd::INSTANCE_STATE_CANCELLED);
410-
auto update_res = instance_manager_.UpdateInstanceGroup(group);
411-
if (!update_res.ok()) {
412-
LOG(ERROR) << "Failed to update group status: " << update_res.error();
413-
}
414-
// It's technically possible for the group's state to be set to
415-
// "running" before abort has a chance to run, but that can only happen
416-
// if the instances are indeed running, so it's OK.
406+
SharedFD memfd;
407+
SharedFD stop_eventfd;
408+
if (!own_flags_.daemon) {
409+
memfd = SharedFD::MemfdCreate("cvd_internal_start_output");
410+
CF_EXPECT(memfd->IsOpen(), "Failed to create memfd for subprocess output: "
411+
<< memfd->StrError());
412+
413+
stop_eventfd = SharedFD::Event();
414+
CF_EXPECT(stop_eventfd->IsOpen(),
415+
"Failed to create eventfd for stopping monitor: "
416+
<< stop_eventfd->StrError());
417+
}
417418

418-
std::abort();
419-
});
420-
auto listener_handle = CF_EXPECT(std::move(handle_res));
419+
Command command = CF_EXPECT(ConstructCvdNonHelpCommand(
420+
bin, group, subcmd_args, envs, request, memfd));
421+
422+
Result<std::unique_ptr<InterruptListenerHandle>> handle_res =
423+
PushInterruptListener([this, &group, stop_eventfd](int signal) {
424+
LOG(WARNING) << strsignal(signal) << " signal received, cleanning up";
425+
if (stop_eventfd->IsOpen()) {
426+
stop_eventfd->EventfdWrite(1);
427+
}
428+
Result<void> interrupt_res = subprocess_waiter_.Interrupt();
429+
if (!interrupt_res.ok()) {
430+
LOG(ERROR) << "Failed to stop subprocesses: "
431+
<< interrupt_res.error();
432+
LOG(ERROR) << "Devices may still be executing in the background, "
433+
"run `cvd reset` to ensure a clean state";
434+
}
435+
436+
group.SetAllStates(cvd::INSTANCE_STATE_CANCELLED);
437+
Result<void> update_res = instance_manager_.UpdateInstanceGroup(group);
438+
if (!update_res.ok()) {
439+
LOG(ERROR) << "Failed to update group status: " << update_res.error();
440+
}
441+
// It's technically possible for the group's state to be set to
442+
// "running" before abort has a chance to run, but that can only happen
443+
// if the instances are indeed running, so it's OK.
444+
445+
std::abort();
446+
});
447+
448+
std::unique_ptr<InterruptListenerHandle> listener_handle =
449+
CF_EXPECT(std::move(handle_res));
421450
group.SetAllStates(cvd::INSTANCE_STATE_STARTING);
422451
group.SetStartTime(CvdServerClock::now());
423452
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
424-
CF_EXPECT(
425-
LaunchDeviceInterruptible(std::move(command), group, envs, request));
453+
454+
std::thread monitor_thread;
455+
Result<void> monitor_res;
456+
457+
if (!own_flags_.daemon) {
458+
monitor_thread = std::thread([&group, stop_eventfd, &monitor_res]() {
459+
const LocalInstance& first_instance = *group.Instances().begin();
460+
monitor_res = MonitorLogs(first_instance, stop_eventfd);
461+
});
462+
}
463+
464+
Result<void> launch_res =
465+
LaunchDeviceInterruptible(std::move(command), group, envs, request);
466+
467+
if (!launch_res.ok()) {
468+
if (!own_flags_.daemon) {
469+
if (stop_eventfd->IsOpen()) {
470+
stop_eventfd->EventfdWrite(1);
471+
}
472+
if (monitor_thread.joinable()) {
473+
monitor_thread.join();
474+
}
475+
memfd->LSeek(0, SEEK_SET);
476+
std::string full_output;
477+
ReadAll(memfd, &full_output);
478+
std::cout << full_output << std::flush;
479+
}
480+
return launch_res;
481+
}
482+
426483
group.SetAllStates(cvd::INSTANCE_STATE_RUNNING);
427484
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
485+
486+
if (!own_flags_.daemon) {
487+
listener_handle.reset();
488+
std::unique_ptr<InterruptListenerHandle> stop_listener =
489+
CF_EXPECT(PushInterruptListener(
490+
[stop_eventfd](int) { stop_eventfd->EventfdWrite(1); }));
491+
492+
if (monitor_thread.joinable()) {
493+
monitor_thread.join();
494+
}
495+
stop_listener.reset();
496+
497+
LOG(INFO) << "Stopping device...";
498+
CF_EXPECT(instance_manager_.StopInstanceGroup(
499+
group, std::chrono::seconds(5), InstanceDirActionOnStop::Keep, {}));
500+
LOG(INFO) << "Device stopped.";
501+
return monitor_res;
502+
}
503+
428504
listener_handle.reset();
429505

430-
// show user a more succinct output
431506
if (isatty(0)) {
432507
std::vector<std::string> instance_names;
433508
for (const auto& instance : group.Instances()) {
@@ -609,7 +684,15 @@ std::vector<Flag> CvdStartCommandHandler::BuildOwnFlags() {
609684
"cuttlefish-base package. The special value \"all\" causes "
610685
"it to replace everything it can, while with an empty it "
611686
"will replace what the android build specifies in the "
612-
"'debian_substitution_marker' file.")};
687+
"'debian_substitution_marker' file."),
688+
GflagsCompatFlag("daemon", own_flags_.daemon)
689+
.Help("Run the start command in the background as a daemon. "
690+
"If false, the command runs in the foreground and monitors "
691+
"logs."),
692+
GflagsCompatFlag("report_anonymous_usage_stats",
693+
own_flags_.report_anonymous_usage_stats)
694+
.Help("Report anonymous usage stats. In foreground mode, "
695+
"defaults to 'n' if not specified.")};
613696
}
614697

615698
std::unique_ptr<CvdCommandHandler> NewCvdStartCommandHandler(

0 commit comments

Comments
 (0)