Skip to content

Commit

Permalink
Move startup notification to a task.
Browse files Browse the repository at this point in the history
Address the long-standing TODO that this should be done outside of the
profiler. Also:
- Remove the need to check if the profile saver is started to allocate
  full dex cache array. Now the notification that startup has completed
  will always be sent
- Fix 1002-notify-startup test by creating the class loader hierarching,
  making app image loading possible, which was the intention of the
  code.
- Rewrite 1002-notify-startup to wait for the runtime to say startup has
  completed, given it's an async task now reporting it.

Test: 1002-notify-startup
Bug: 260557058
Change-Id: I1fc0fc6b99de798e69af6ac4d57e8235d22c72e3
  • Loading branch information
Nicolas Geoffray committed Feb 15, 2023
1 parent bd8aef8 commit 8cc1c0f
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 138 deletions.
1 change: 1 addition & 0 deletions runtime/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ libart_cc_defaults {
"signal_catcher.cc",
"stack.cc",
"stack_map.cc",
"startup_completed_task.cc",
"string_builder_append.cc",
"thread.cc",
"thread_list.cc",
Expand Down
3 changes: 0 additions & 3 deletions runtime/jit/profile_saver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,6 @@ void ProfileSaver::Run() {
}
total_ms_of_sleep_ += sleep_time;
}
// Tell the runtime that startup is completed if it has not already been notified.
// TODO: We should use another thread to do this in case the profile saver is not running.
Runtime::Current()->NotifyStartupCompleted();

FetchAndCacheResolvedClassesAndMethods(/*startup=*/ true);

Expand Down
7 changes: 0 additions & 7 deletions runtime/mirror/dex_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,6 @@ bool DexCache::ShouldAllocateFullArrayAtStartup() {
return false;
}

if (!ProfileSaver::IsStarted()) {
// Only allocate full arrays if the profile saver is running: if the app
// does not call `reportFullyDrawn`, then only the profile saver will notify
// that the app has eventually started.
return false;
}

return true;
}

Expand Down
3 changes: 2 additions & 1 deletion runtime/native/dalvik_system_VMRuntime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ extern "C" void android_set_application_target_sdk_version(uint32_t version);
#include "runtime.h"
#include "scoped_fast_native_object_access-inl.h"
#include "scoped_thread_state_change-inl.h"
#include "startup_completed_task.h"
#include "string_array_utils.h"
#include "thread-inl.h"
#include "thread_list.h"
Expand Down Expand Up @@ -323,7 +324,7 @@ static void VMRuntime_updateProcessState(JNIEnv*, jobject, jint process_state) {
}

static void VMRuntime_notifyStartupCompleted(JNIEnv*, jobject) {
Runtime::Current()->NotifyStartupCompleted();
Runtime::Current()->GetHeap()->AddHeapTask(new StartupCompletedTask(NanoTime()));
}

static void VMRuntime_trimHeap(JNIEnv* env, jobject) {
Expand Down
7 changes: 7 additions & 0 deletions runtime/native/dalvik_system_ZygoteHooks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "oat_file_manager.h"
#include "scoped_thread_state_change-inl.h"
#include "stack.h"
#include "startup_completed_task.h"
#include "thread-current-inl.h"
#include "thread_list.h"
#include "trace.h"
Expand Down Expand Up @@ -344,6 +345,12 @@ static void ZygoteHooks_nativePostForkChild(JNIEnv* env,
}

runtime->GetHeap()->PostForkChildAction(thread);

// Setup an app startup complete task in case the app doesn't notify it
// through VMRuntime::notifyStartupCompleted.
static constexpr uint64_t kMaxAppStartupTimeNs = MsToNs(5000); // 5 seconds
runtime->GetHeap()->AddHeapTask(new StartupCompletedTask(kMaxAppStartupTimeNs));

if (runtime->GetJit() != nullptr) {
if (!is_system_server) {
// System server already called the JIT cache post fork action in `nativePostForkSystemServer`.
Expand Down
122 changes: 3 additions & 119 deletions runtime/runtime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3357,138 +3357,22 @@ void Runtime::ResetStartupCompleted() {
startup_completed_.store(false, std::memory_order_seq_cst);
}

class CollectStartupDexCacheVisitor : public DexCacheVisitor {
public:
explicit CollectStartupDexCacheVisitor(VariableSizedHandleScope& handles) : handles_(handles) {}

void Visit(ObjPtr<mirror::DexCache> dex_cache)
REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
handles_.NewHandle(dex_cache);
}

private:
VariableSizedHandleScope& handles_;
};

class UnlinkVisitor {
public:
UnlinkVisitor() {}

void VisitRootIfNonNull(StackReference<mirror::Object>* ref)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (!ref->IsNull()) {
ref->AsMirrorPtr()->AsDexCache()->UnlinkStartupCaches();
}
}
};

class Runtime::NotifyStartupCompletedTask : public gc::HeapTask {
public:
NotifyStartupCompletedTask() : gc::HeapTask(/*target_run_time=*/ NanoTime()) {}

void Run(Thread* self) override {
VLOG(startup) << "NotifyStartupCompletedTask running";
Runtime* const runtime = Runtime::Current();
{
std::string compiler_filter;
std::string compilation_reason;
runtime->GetAppInfo()->GetPrimaryApkOptimizationStatus(&compiler_filter, &compilation_reason);
CompilerFilter::Filter filter;
if (CompilerFilter::ParseCompilerFilter(compiler_filter.c_str(), &filter) &&
!CompilerFilter::IsAotCompilationEnabled(filter)) {
std::string error_msg;
if (!RuntimeImage::WriteImageToDisk(&error_msg)) {
LOG(DEBUG) << "Could not write temporary image to disk " << error_msg;
}
}
}
// Fetch the startup linear alloc before the checkpoint to play nice with
// 1002-notify-startup test which resets the startup state.
std::unique_ptr<LinearAlloc> startup_linear_alloc(runtime->ReleaseStartupLinearAlloc());
{
ScopedTrace trace("Releasing dex caches and app image spaces metadata");
ScopedObjectAccess soa(Thread::Current());

// Collect dex caches that were allocated with the startup linear alloc.
VariableSizedHandleScope handles(soa.Self());
{
CollectStartupDexCacheVisitor visitor(handles);
ReaderMutexLock mu(self, *Locks::dex_lock_);
runtime->GetClassLinker()->VisitDexCaches(&visitor);
}

// Request empty checkpoints to make sure no threads are:
// - accessing the image space metadata section when we madvise it
// - accessing dex caches when we free them
//
// Use GC exclusion to prevent deadlocks that may happen if
// multiple threads are attempting to run empty checkpoints at the same time.
{
// Avoid using ScopedGCCriticalSection since that does not allow thread suspension. This is
// not allowed to prevent allocations, but it's still safe to suspend temporarily for the
// checkpoint.
gc::ScopedInterruptibleGCCriticalSection sigcs(self,
gc::kGcCauseRunEmptyCheckpoint,
gc::kCollectorTypeCriticalSection);
// Do the unlinking of dex cache arrays in the GC critical section to
// avoid GC not seeing these arrays. We do it before the checkpoint so
// we know after the checkpoint, no thread is holding onto the array.
UnlinkVisitor visitor;
handles.VisitRoots(visitor);

runtime->GetThreadList()->RunEmptyCheckpoint();
}

for (gc::space::ContinuousSpace* space : runtime->GetHeap()->GetContinuousSpaces()) {
if (space->IsImageSpace()) {
gc::space::ImageSpace* image_space = space->AsImageSpace();
if (image_space->GetImageHeader().IsAppImage()) {
image_space->ReleaseMetadata();
}
}
}
}

{
// Delete the thread pool used for app image loading since startup is assumed to be completed.
ScopedTrace trace2("Delete thread pool");
runtime->DeleteThreadPool();
}

if (startup_linear_alloc != nullptr) {
// We know that after the checkpoint, there is no thread that can hold
// the startup linear alloc, so it's safe to delete it now.
ScopedTrace trace2("Delete startup linear alloc");
ArenaPool* arena_pool = startup_linear_alloc->GetArenaPool();
startup_linear_alloc.reset();
arena_pool->TrimMaps();
}
}
};

void Runtime::NotifyStartupCompleted() {
bool Runtime::NotifyStartupCompleted() {
bool expected = false;
if (!startup_completed_.compare_exchange_strong(expected, true, std::memory_order_seq_cst)) {
// Right now NotifyStartupCompleted will be called up to twice, once from profiler and up to
// once externally. For this reason there are no asserts.
return;
return false;
}

VLOG(startup) << app_info_;

VLOG(startup) << "Adding NotifyStartupCompleted task";
// Use the heap task processor since we want to be exclusive with the GC and we don't want to
// block the caller if the GC is running.
if (!GetHeap()->AddHeapTask(new NotifyStartupCompletedTask)) {
VLOG(startup) << "Failed to add NotifyStartupCompletedTask";
}

// Notify the profiler saver that startup is now completed.
ProfileSaver::NotifyStartupCompleted();

if (metrics_reporter_ != nullptr) {
metrics_reporter_->NotifyStartupCompleted();
}
return true;
}

void Runtime::NotifyDexFileLoaded() {
Expand Down
5 changes: 2 additions & 3 deletions runtime/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -1091,8 +1091,8 @@ class Runtime {
void ResetStartupCompleted();

// Notify the runtime that application startup is considered completed. Only has effect for the
// first call.
void NotifyStartupCompleted();
// first call. Returns whether this was the first call.
bool NotifyStartupCompleted();

// Notify the runtime that the application finished loading some dex/odex files. This is
// called everytime we load a set of dex files in a class loader.
Expand Down Expand Up @@ -1614,7 +1614,6 @@ class Runtime {
friend class Dex2oatImageTest;
friend class ScopedThreadPoolUsage;
friend class OatFileAssistantTest;
class NotifyStartupCompletedTask;
class SetupLinearAllocForZygoteFork;

DISALLOW_COPY_AND_ASSIGN(Runtime);
Expand Down
147 changes: 147 additions & 0 deletions runtime/startup_completed_task.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "startup_completed_task.h"

#include "base/systrace.h"
#include "class_linker.h"
#include "gc/heap.h"
#include "gc/scoped_gc_critical_section.h"
#include "gc/space/image_space.h"
#include "gc/space/space-inl.h"
#include "handle_scope-inl.h"
#include "linear_alloc-inl.h"
#include "mirror/dex_cache.h"
#include "mirror/object-inl.h"
#include "obj_ptr.h"
#include "runtime_image.h"
#include "scoped_thread_state_change-inl.h"
#include "thread.h"
#include "thread_list.h"

namespace art {

class CollectStartupDexCacheVisitor : public DexCacheVisitor {
public:
explicit CollectStartupDexCacheVisitor(VariableSizedHandleScope& handles) : handles_(handles) {}

void Visit(ObjPtr<mirror::DexCache> dex_cache)
REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
handles_.NewHandle(dex_cache);
}

private:
VariableSizedHandleScope& handles_;
};

class UnlinkVisitor {
public:
UnlinkVisitor() {}

void VisitRootIfNonNull(StackReference<mirror::Object>* ref)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (!ref->IsNull()) {
ref->AsMirrorPtr()->AsDexCache()->UnlinkStartupCaches();
}
}
};

void StartupCompletedTask::Run(Thread* self) {
VLOG(startup) << "StartupCompletedTask running";
Runtime* const runtime = Runtime::Current();
if (!runtime->NotifyStartupCompleted()) {
return;
}

// Maybe generate a runtime app image.
{
std::string compiler_filter;
std::string compilation_reason;
runtime->GetAppInfo()->GetPrimaryApkOptimizationStatus(&compiler_filter, &compilation_reason);
CompilerFilter::Filter filter;
if (CompilerFilter::ParseCompilerFilter(compiler_filter.c_str(), &filter) &&
!CompilerFilter::IsAotCompilationEnabled(filter)) {
std::string error_msg;
if (!RuntimeImage::WriteImageToDisk(&error_msg)) {
LOG(DEBUG) << "Could not write temporary image to disk " << error_msg;
}
}
}

// Fetch the startup linear alloc before the checkpoint to play nice with
// 1002-notify-startup test which resets the startup state.
std::unique_ptr<LinearAlloc> startup_linear_alloc(runtime->ReleaseStartupLinearAlloc());
{
ScopedTrace trace("Releasing dex caches and app image spaces metadata");
ScopedObjectAccess soa(Thread::Current());

// Collect dex caches that were allocated with the startup linear alloc.
VariableSizedHandleScope handles(soa.Self());
{
CollectStartupDexCacheVisitor visitor(handles);
ReaderMutexLock mu(self, *Locks::dex_lock_);
runtime->GetClassLinker()->VisitDexCaches(&visitor);
}

// Request empty checkpoints to make sure no threads are:
// - accessing the image space metadata section when we madvise it
// - accessing dex caches when we free them
//
// Use GC exclusion to prevent deadlocks that may happen if
// multiple threads are attempting to run empty checkpoints at the same time.
{
// Avoid using ScopedGCCriticalSection since that does not allow thread suspension. This is
// not allowed to prevent allocations, but it's still safe to suspend temporarily for the
// checkpoint.
gc::ScopedInterruptibleGCCriticalSection sigcs(self,
gc::kGcCauseRunEmptyCheckpoint,
gc::kCollectorTypeCriticalSection);
// Do the unlinking of dex cache arrays in the GC critical section to
// avoid GC not seeing these arrays. We do it before the checkpoint so
// we know after the checkpoint, no thread is holding onto the array.
UnlinkVisitor visitor;
handles.VisitRoots(visitor);

runtime->GetThreadList()->RunEmptyCheckpoint();
}

for (gc::space::ContinuousSpace* space : runtime->GetHeap()->GetContinuousSpaces()) {
if (space->IsImageSpace()) {
gc::space::ImageSpace* image_space = space->AsImageSpace();
if (image_space->GetImageHeader().IsAppImage()) {
image_space->ReleaseMetadata();
}
}
}
}

{
// Delete the thread pool used for app image loading since startup is assumed to be completed.
ScopedTrace trace2("Delete thread pool");
runtime->DeleteThreadPool();
}

if (startup_linear_alloc != nullptr) {
// We know that after the checkpoint, there is no thread that can hold
// the startup linear alloc, so it's safe to delete it now.
ScopedTrace trace2("Delete startup linear alloc");
ArenaPool* arena_pool = startup_linear_alloc->GetArenaPool();
startup_linear_alloc.reset();
arena_pool->TrimMaps();
}
}

} // namespace art
Loading

0 comments on commit 8cc1c0f

Please sign in to comment.