summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/Android.bp1
-rw-r--r--runtime/jit/profile_saver.cc3
-rw-r--r--runtime/mirror/dex_cache.cc7
-rw-r--r--runtime/native/dalvik_system_VMRuntime.cc3
-rw-r--r--runtime/native/dalvik_system_ZygoteHooks.cc7
-rw-r--r--runtime/runtime.cc122
-rw-r--r--runtime/runtime.h5
-rw-r--r--runtime/startup_completed_task.cc147
-rw-r--r--runtime/startup_completed_task.h35
-rw-r--r--test/1002-notify-startup/expected-stdout.txt2
-rw-r--r--test/1002-notify-startup/src-art/Main.java15
11 files changed, 209 insertions, 138 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 640a5ca60c..93b1daf932 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -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",
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index d746adefa6..8faf6dbf78 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -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);
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index b0b60126d8..7cc11fc7e4 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -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;
}
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 74202fc5f1..9e2e8b9f80 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -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"
@@ -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) {
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 75506decef..31271637d2 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -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"
@@ -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`.
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 42522ee8eb..02cba22add 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -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() {
diff --git a/runtime/runtime.h b/runtime/runtime.h
index a0a36b92c3..88d1a2d253 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -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.
@@ -1614,7 +1614,6 @@ class Runtime {
friend class Dex2oatImageTest;
friend class ScopedThreadPoolUsage;
friend class OatFileAssistantTest;
- class NotifyStartupCompletedTask;
class SetupLinearAllocForZygoteFork;
DISALLOW_COPY_AND_ASSIGN(Runtime);
diff --git a/runtime/startup_completed_task.cc b/runtime/startup_completed_task.cc
new file mode 100644
index 0000000000..b82ab57860
--- /dev/null
+++ b/runtime/startup_completed_task.cc
@@ -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
diff --git a/runtime/startup_completed_task.h b/runtime/startup_completed_task.h
new file mode 100644
index 0000000000..54f3631842
--- /dev/null
+++ b/runtime/startup_completed_task.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_RUNTIME_STARTUP_COMPLETED_TASK_H_
+#define ART_RUNTIME_STARTUP_COMPLETED_TASK_H_
+
+#include "gc/task_processor.h"
+
+namespace art {
+
+class Thread;
+
+class StartupCompletedTask : public gc::HeapTask {
+ public:
+ explicit StartupCompletedTask(uint64_t target_run_time) : gc::HeapTask(target_run_time) {}
+
+ void Run(Thread* self) override;
+};
+
+} // namespace art
+
+#endif // ART_RUNTIME_STARTUP_COMPLETED_TASK_H_
diff --git a/test/1002-notify-startup/expected-stdout.txt b/test/1002-notify-startup/expected-stdout.txt
index a86b9aa949..6a5618ebc6 100644
--- a/test/1002-notify-startup/expected-stdout.txt
+++ b/test/1002-notify-startup/expected-stdout.txt
@@ -1,3 +1 @@
JNI_OnLoad called
-Startup completed: false
-Startup completed: true
diff --git a/test/1002-notify-startup/src-art/Main.java b/test/1002-notify-startup/src-art/Main.java
index 8951af8ab7..68033fa909 100644
--- a/test/1002-notify-startup/src-art/Main.java
+++ b/test/1002-notify-startup/src-art/Main.java
@@ -26,9 +26,16 @@ public class Main {
static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path");
static AtomicBoolean completed = new AtomicBoolean(false);
+ public static void assertFalse(boolean value) {
+ if (value) {
+ throw new Error("Expected false false");
+ }
+ }
+
public static void main(String[] args) {
System.loadLibrary(args[0]);
- System.out.println("Startup completed: " + hasStartupCompleted());
+ assertFalse(hasStartupCompleted());
+
Thread workerThread = new WorkerThread();
workerThread.start();
do {
@@ -41,7 +48,9 @@ public class Main {
} catch (Throwable e) {
System.err.println(e);
}
- System.out.println("Startup completed: " + hasStartupCompleted());
+ while (!hasStartupCompleted()) {
+ Thread.yield();
+ }
}
private static class WorkerThread extends Thread {
@@ -49,7 +58,7 @@ public class Main {
private WeakReference<Class<?>> $noinline$loadClassInLoader() throws Exception {
ClassLoader loader = new PathClassLoader(
- DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
+ DEX_FILE, LIBRARY_SEARCH_PATH, Object.class.getClassLoader());
Class ret = loader.loadClass("Main");
return new WeakReference(ret);
}