Skip to content

Commit 6f5acc3

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 b0fe189 commit 6f5acc3

5 files changed

Lines changed: 169 additions & 47 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
@@ -403,6 +403,7 @@ cf_cc_library(
403403
"//cuttlefish/host/commands/cvd/cli:types",
404404
"//cuttlefish/host/commands/cvd/cli:utils",
405405
"//cuttlefish/host/commands/cvd/cli/commands:command_handler",
406+
"//cuttlefish/host/commands/cvd/cli/commands/monitor",
406407
"//cuttlefish/host/commands/cvd/cli/commands:host_tool_target",
407408
"//cuttlefish/host/commands/cvd/cli/selector",
408409
"//cuttlefish/host/commands/cvd/fetch:substitute",

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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
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+
Result<void> MonitorLogs(const LocalInstance& instance, SharedFD stop_eventfd);
30+
2631
} // namespace cuttlefish

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

Lines changed: 123 additions & 25 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,30 @@
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"
42+
#include "absl/strings/match.h"
4143
#include "absl/strings/str_join.h"
4244
#include "absl/strings/str_split.h"
45+
#include "fmt/core.h"
46+
#include "fmt/format.h"
4347

48+
#include "cuttlefish/common/libs/fs/shared_buf.h"
4449
#include "cuttlefish/common/libs/fs/shared_fd.h"
4550
#include "cuttlefish/common/libs/utils/contains.h"
4651
#include "cuttlefish/common/libs/utils/files.h"
47-
#include "cuttlefish/flag_parser/flag.h"
48-
#include "cuttlefish/flag_parser/gflags_compat.h"
4952
#include "cuttlefish/common/libs/utils/json.h"
5053
#include "cuttlefish/common/libs/utils/subprocess.h"
5154
#include "cuttlefish/common/libs/utils/users.h"
55+
#include "cuttlefish/flag_parser/flag.h"
56+
#include "cuttlefish/flag_parser/gflags_compat.h"
5257
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
5358
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
5459
#include "cuttlefish/host/commands/cvd/cli/commands/host_tool_target.h"
60+
#include "cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.h"
5561
#include "cuttlefish/host/commands/cvd/cli/help_format.h"
5662
#include "cuttlefish/host/commands/cvd/cli/selector/selector.h"
5763
#include "cuttlefish/host/commands/cvd/cli/types.h"
@@ -279,7 +285,8 @@ Result<Command> ConstructCvdNonHelpCommand(const std::string& bin_file,
279285
const LocalInstanceGroup& group,
280286
const cvd_common::Args& args,
281287
const cvd_common::Envs& envs,
282-
const CommandRequest& request) {
288+
const CommandRequest& request,
289+
SharedFD output_fd = SharedFD()) {
283290
auto bin_path = group.HostArtifactsPath();
284291
CF_EXPECTF(PotentiallyHostArtifactsPath(bin_path),
285292
"ANDROID_HOST_OUT, \"{}\" is not a tool directory", bin_path);
@@ -292,10 +299,17 @@ Result<Command> ConstructCvdNonHelpCommand(const std::string& bin_file,
292299
.working_dir = CurrentDirectory(),
293300
.command_name = bin_file};
294301
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);
302+
if (output_fd->IsOpen()) {
303+
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
304+
output_fd);
305+
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr,
306+
output_fd);
307+
} else {
308+
// Print everything to stderr, cvd needs to print JSON to stdout which
309+
// would be unparseable with the subcommand's output.
310+
non_help_command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
311+
Subprocess::StdIOChannel::kStdErr);
312+
}
299313
return non_help_command;
300314
}
301315

@@ -317,16 +331,11 @@ CvdStartCommandHandler::CvdStartCommandHandler(
317331
InstanceManager& instance_manager)
318332
: instance_manager_(instance_manager) {}
319333

320-
static Result<void> ConsumeDaemonModeFlag(cvd_common::Args& args) {
334+
static Result<bool> ConsumeDaemonModeFlag(cvd_common::Args& args) {
321335
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-
});
336+
Flag flag = GflagsCompatFlag("daemon", daemon);
328337
CF_EXPECT(ConsumeFlags({flag}, args));
329-
return {};
338+
return daemon;
330339
}
331340

332341
static bool HasUnsafeFlagsForBypass(const std::vector<std::string>& args) {
@@ -370,8 +379,24 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
370379
}
371380
}
372381

373-
CF_EXPECT(ConsumeDaemonModeFlag(subcmd_args));
382+
bool is_daemon = CF_EXPECT(ConsumeDaemonModeFlag(subcmd_args));
374383
subcmd_args.push_back("--daemon=true");
384+
if (!is_daemon) {
385+
bool has_stats_flag = false;
386+
for (const auto& arg : subcmd_args) {
387+
if (absl::StartsWith(arg, "--report_anonymous_usage_stats") ||
388+
absl::StartsWith(arg, "-report_anonymous_usage_stats")) {
389+
has_stats_flag = true;
390+
break;
391+
}
392+
}
393+
if (!has_stats_flag) {
394+
// If the user did not specify the anonymous usage stats flag, default it
395+
// to 'n' to ensure that cvd_internal_start does not block waiting for
396+
// input on stdin.
397+
subcmd_args.push_back("--report_anonymous_usage_stats=n");
398+
}
399+
}
375400

376401
LocalInstanceGroup group =
377402
CF_EXPECT(selector::SelectGroup(instance_manager_, request),
@@ -393,12 +418,25 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
393418
CF_EXPECT(HostPackageSubstitution(group.HostArtifactsPath(),
394419
own_flags_.host_substitutions));
395420

396-
Command command = CF_EXPECT(
397-
ConstructCvdNonHelpCommand(bin, group, subcmd_args, envs, request));
421+
SharedFD memfd;
422+
SharedFD stop_eventfd;
423+
if (!is_daemon) {
424+
memfd = SharedFD::MemfdCreate("cvd_internal_start_output");
425+
CF_EXPECT(memfd->IsOpen(), "Failed to create memfd for subprocess output");
426+
427+
stop_eventfd = SharedFD::Event();
428+
CF_EXPECT(stop_eventfd->IsOpen(),
429+
"Failed to create eventfd for stopping monitor");
430+
}
398431

399-
// The instance database needs to be updated if an interrupt is received.
400-
auto handle_res = PushInterruptListener([this, &group](int signal) {
432+
Command command = CF_EXPECT(ConstructCvdNonHelpCommand(
433+
bin, group, subcmd_args, envs, request, memfd));
434+
435+
auto handle_res = PushInterruptListener([this, &group, stop_eventfd](int signal) {
401436
LOG(WARNING) << strsignal(signal) << " signal received, cleanning up";
437+
if (stop_eventfd->IsOpen()) {
438+
stop_eventfd->EventfdWrite(1);
439+
}
402440
auto interrupt_res = subprocess_waiter_.Interrupt();
403441
if (!interrupt_res.ok()) {
404442
LOG(ERROR) << "Failed to stop subprocesses: " << interrupt_res.error();
@@ -417,17 +455,71 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
417455

418456
std::abort();
419457
});
458+
420459
auto listener_handle = CF_EXPECT(std::move(handle_res));
421460
group.SetAllStates(cvd::INSTANCE_STATE_STARTING);
422461
group.SetStartTime(CvdServerClock::now());
423462
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
424-
CF_EXPECT(
425-
LaunchDeviceInterruptible(std::move(command), group, envs, request));
463+
464+
LOG(INFO)
465+
<< "By using this Android Virtual Device, you agree to Google Terms of "
466+
"Service (https://policies.google.com/terms). The Google Privacy "
467+
"Policy (https://policies.google.com/privacy) describes how Google "
468+
"handles information generated as you use Google services.";
469+
470+
std::thread monitor_thread;
471+
Result<void> monitor_res;
472+
473+
if (!is_daemon) {
474+
monitor_thread = std::thread([&group, stop_eventfd, &monitor_res]() {
475+
const auto& first_instance = *group.Instances().begin();
476+
monitor_res = MonitorLogs(first_instance, stop_eventfd);
477+
});
478+
}
479+
480+
auto launch_res =
481+
LaunchDeviceInterruptible(std::move(command), group, envs, request);
482+
483+
if (!launch_res.ok()) {
484+
if (!is_daemon) {
485+
if (stop_eventfd->IsOpen()) {
486+
stop_eventfd->EventfdWrite(1);
487+
}
488+
if (monitor_thread.joinable()) {
489+
monitor_thread.join();
490+
}
491+
memfd->LSeek(0, SEEK_SET);
492+
std::string full_output;
493+
ReadAll(memfd, &full_output);
494+
std::cout << full_output << std::flush;
495+
}
496+
return launch_res;
497+
}
498+
426499
group.SetAllStates(cvd::INSTANCE_STATE_RUNNING);
427500
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
501+
502+
if (!is_daemon) {
503+
listener_handle.reset();
504+
auto stop_listener =
505+
CF_EXPECT(PushInterruptListener([stop_eventfd](int) {
506+
stop_eventfd->EventfdWrite(1);
507+
}));
508+
509+
if (monitor_thread.joinable()) {
510+
monitor_thread.join();
511+
}
512+
stop_listener.reset();
513+
514+
LOG(INFO) << "Stopping device...";
515+
CF_EXPECT(instance_manager_.StopInstanceGroup(
516+
group, std::chrono::seconds(5), InstanceDirActionOnStop::Keep, {}));
517+
LOG(INFO) << "Device stopped.";
518+
return monitor_res;
519+
}
520+
428521
listener_handle.reset();
429522

430-
// show user a more succinct output
431523
if (isatty(0)) {
432524
std::vector<std::string> instance_names;
433525
for (const auto& instance : group.Instances()) {
@@ -549,6 +641,12 @@ Result<void> CvdStartCommandHandler::LaunchSingleInstance(
549641
}
550642
};
551643

644+
LOG(INFO)
645+
<< "By using this Android Virtual Device, you agree to Google Terms of "
646+
"Service (https://policies.google.com/terms). The Google Privacy "
647+
"Policy (https://policies.google.com/privacy) describes how Google "
648+
"handles information generated as you use Google services.";
649+
552650
set_instance_state(cvd::INSTANCE_STATE_STARTING);
553651
group.SetStartTime(CvdServerClock::now());
554652
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
#include <memory>
2020
#include <string>
2121

22-
#include "cuttlefish/flag_parser/flag.h"
2322
#include "cuttlefish/common/libs/utils/subprocess.h"
23+
#include "cuttlefish/flag_parser/flag.h"
2424
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
2525
#include "cuttlefish/host/commands/cvd/instances/instance_manager.h"
2626
#include "cuttlefish/host/commands/cvd/instances/local_instance_group.h"
@@ -47,14 +47,13 @@ class CvdStartCommandHandler : public CvdCommandHandler {
4747
LocalInstanceGroup& group,
4848
const CommandRequest& request);
4949

50-
Result<void> LaunchDevice(Command command, LocalInstanceGroup& group,
51-
const cvd_common::Envs& envs,
52-
const CommandRequest& request);
50+
Result<void> LaunchDevice(
51+
Command command, LocalInstanceGroup& group, const cvd_common::Envs& envs,
52+
const CommandRequest& request);
5353

54-
Result<void> LaunchDeviceInterruptible(Command command,
55-
LocalInstanceGroup& group,
56-
const cvd_common::Envs& envs,
57-
const CommandRequest& request);
54+
Result<void> LaunchDeviceInterruptible(
55+
Command command, LocalInstanceGroup& group, const cvd_common::Envs& envs,
56+
const CommandRequest& request);
5857

5958
// Flags handled by `cvd start` itself, not cvd_internal_start.
6059
std::vector<Flag> BuildOwnFlags();

0 commit comments

Comments
 (0)