diff options
-rw-r--r-- | runtime/Android.bp | 1 | ||||
-rw-r--r-- | runtime/jit/profile_saver.cc | 3 | ||||
-rw-r--r-- | runtime/mirror/dex_cache.cc | 7 | ||||
-rw-r--r-- | runtime/native/dalvik_system_VMRuntime.cc | 3 | ||||
-rw-r--r-- | runtime/native/dalvik_system_ZygoteHooks.cc | 7 | ||||
-rw-r--r-- | runtime/runtime.cc | 122 | ||||
-rw-r--r-- | runtime/runtime.h | 5 | ||||
-rw-r--r-- | runtime/startup_completed_task.cc | 147 | ||||
-rw-r--r-- | runtime/startup_completed_task.h | 35 | ||||
-rw-r--r-- | test/1002-notify-startup/expected-stdout.txt | 2 | ||||
-rw-r--r-- | test/1002-notify-startup/src-art/Main.java | 15 |
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); } |