From 7796cbc3b23a5e20f8394c999d8b7512b29094bb Mon Sep 17 00:00:00 2001 From: "yunyao.zxl" Date: Wed, 22 Jul 2020 17:31:08 +0800 Subject: [PATCH] [Coroutine] Bug fixes for JKU coroutine Summary: Bug fixes for JKU coroutine The JKU patch has several flaws: 1. When using JDI to debug coroutine, it may crash because the _switchTo intrinsic entry will change to the interpreter one, which is a SHOULD_NOT_REACH_HERE(). 2. Change miscellaneous thread-local data structures to coroutine-local level during _switchTo phase. 3. Disable making zombies of _switchTo alike intrinsic things. 4. Fix multiple mem leak problems for coroutine data structures. 5. Create an empty JNIHandleBlock for the first Java call. 6. The stack layout of Coroutine doesn't align with 16 bytes on x86_64 which will cause some float point instructions to throw an sig fault. 7. etc. Test Plan: Coroutine tests Reviewed-by: yuleil, shiyuexw, sanhong Issue: https://github.com/alibaba/dragonwell8/issues/113 --- src/cpu/x86/vm/interp_masm_x86_64.cpp | 14 ++- src/cpu/x86/vm/sharedRuntime_x86_32.cpp | 13 +- src/cpu/x86/vm/sharedRuntime_x86_64.cpp | 9 ++ src/share/vm/code/nmethod.cpp | 13 ++ src/share/vm/prims/jvmtiThreadState.cpp | 3 + src/share/vm/prims/unsafe.cpp | 11 +- src/share/vm/runtime/coroutine.cpp | 48 ++++++- src/share/vm/runtime/coroutine.hpp | 15 ++- src/share/vm/runtime/handles.hpp | 1 + src/share/vm/runtime/thread.cpp | 6 + src/share/vm/runtime/thread.hpp | 5 + test/runtime/coroutine/Issue11230146.java | 117 ++++++++++++++++++ test/runtime/coroutine/SimpleWispTest.java | 34 +++++ .../TestAvoidDeoptCoroutineMethod.java | 52 ++++++++ test/runtime/coroutine/TestMemLeak.java | 112 +++++++++++++++++ .../coroutineBreakpointSwitchToTest.c | 69 +++++++++++ .../coroutineBreakpointSwitchToTest.sh | 66 ++++++++++ test/runtime/coroutine/logCompilationTest.sh | 54 ++++++++ 18 files changed, 630 insertions(+), 12 deletions(-) create mode 100644 test/runtime/coroutine/Issue11230146.java create mode 100644 test/runtime/coroutine/SimpleWispTest.java create mode 100644 test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java create mode 100644 test/runtime/coroutine/TestMemLeak.java create mode 100644 test/runtime/coroutine/coroutineBreakpointSwitchToTest.c create mode 100644 test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh create mode 100644 test/runtime/coroutine/logCompilationTest.sh diff --git a/src/cpu/x86/vm/interp_masm_x86_64.cpp b/src/cpu/x86/vm/interp_masm_x86_64.cpp index 727207ec1..507d302d8 100644 --- a/src/cpu/x86/vm/interp_masm_x86_64.cpp +++ b/src/cpu/x86/vm/interp_masm_x86_64.cpp @@ -444,15 +444,27 @@ void InterpreterMacroAssembler::jump_from_interpreted(Register method, Register if (JvmtiExport::can_post_interpreter_events()) { Label run_compiled_code; + Label coroutine_skip_interpret; // JVMTI events, such as single-stepping, are implemented partly by avoiding running // compiled code in threads for which the event is enabled. Check here for // interp_only_mode if these events CAN be enabled. // interp_only is an int, on little endian it is sufficient to test the byte only // Is a cmpl faster? cmpb(Address(r15_thread, JavaThread::interp_only_mode_offset()), 0); - jccb(Assembler::zero, run_compiled_code); + jcc(Assembler::zero, run_compiled_code); + if (EnableCoroutine) { + cmpw(Address(method, Method::intrinsic_id_offset_in_bytes()), vmIntrinsics::_switchTo); + jcc(Assembler::zero, coroutine_skip_interpret); + cmpw(Address(method, Method::intrinsic_id_offset_in_bytes()), vmIntrinsics::_switchToAndExit); + jcc(Assembler::zero, coroutine_skip_interpret); + cmpw(Address(method, Method::intrinsic_id_offset_in_bytes()), vmIntrinsics::_switchToAndTerminate); + jcc(Assembler::zero, coroutine_skip_interpret); + } jmp(Address(method, Method::interpreter_entry_offset())); bind(run_compiled_code); + if (EnableCoroutine) { + bind(coroutine_skip_interpret); + } } jmp(Address(method, Method::from_interpreted_offset())); diff --git a/src/cpu/x86/vm/sharedRuntime_x86_32.cpp b/src/cpu/x86/vm/sharedRuntime_x86_32.cpp index 50e870f95..ae86d70b6 100644 --- a/src/cpu/x86/vm/sharedRuntime_x86_32.cpp +++ b/src/cpu/x86/vm/sharedRuntime_x86_32.cpp @@ -3636,6 +3636,10 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(Address(old_coroutine, Coroutine::resource_area_offset()), temp); __ movptr(temp, Address(thread, Thread::last_handle_mark_offset())); __ movptr(Address(old_coroutine, Coroutine::last_handle_mark_offset()), temp); + __ movptr(temp, Address(thread, Thread::active_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::active_handles_offset()), temp); + __ movptr(temp, Address(thread, Thread::metadata_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::metadata_handles_offset()), temp); // push the current IP and frame pointer onto the stack __ push(rbp); @@ -3658,7 +3662,8 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma Register temp2 = rdi; { Register thread = rax; - __ get_thread(rax); + __ get_thread(thread); + __ movptr(Address(thread, JavaThread::current_coroutine_offset()), target_coroutine); // set new handle and resource areas __ movptr(temp, Address(target_coroutine, Coroutine::handle_area_offset())); __ movptr(Address(target_coroutine, Coroutine::handle_area_offset()), (intptr_t)NULL_WORD); // TODO is this really needed? @@ -3669,6 +3674,12 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(temp, Address(target_coroutine, Coroutine::last_handle_mark_offset())); __ movptr(Address(target_coroutine, Coroutine::last_handle_mark_offset()), (intptr_t)NULL_WORD); // TODO is this really needed? __ movptr(Address(thread, Thread::last_handle_mark_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::active_handles_offset())); + __ movptr(Address(target_coroutine, Coroutine::active_handles_offset()), (intptr_t)NULL_WORD); + __ movptr(Address(thread, Thread::active_handles_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::metadata_handles_offset())); + __ movptr(Address(target_coroutine, Coroutine::metadata_handles_offset()), (intptr_t)NULL_WORD); + __ movptr(Address(thread, Thread::metadata_handles_offset()), temp); // update the thread's stack base and size __ movptr(temp, Address(target_stack, CoroutineStack::stack_base_offset())); diff --git a/src/cpu/x86/vm/sharedRuntime_x86_64.cpp b/src/cpu/x86/vm/sharedRuntime_x86_64.cpp index e87a1e3b2..1ec5816ff 100644 --- a/src/cpu/x86/vm/sharedRuntime_x86_64.cpp +++ b/src/cpu/x86/vm/sharedRuntime_x86_64.cpp @@ -4470,6 +4470,10 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(Address(old_coroutine, Coroutine::resource_area_offset()), temp); __ movptr(temp, Address(thread, Thread::last_handle_mark_offset())); __ movptr(Address(old_coroutine, Coroutine::last_handle_mark_offset()), temp); + __ movptr(temp, Address(thread, Thread::active_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::active_handles_offset()), temp); + __ movptr(temp, Address(thread, Thread::metadata_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::metadata_handles_offset()), temp); #ifdef ASSERT __ movl(temp, Address(thread, JavaThread::java_call_counter_offset())); __ movl(Address(old_coroutine, Coroutine::java_call_counter_offset()), temp); @@ -4492,6 +4496,7 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma Register temp2 = r9; { Register thread = r15; + __ movptr(Address(thread, JavaThread::current_coroutine_offset()), target_coroutine); // set new handle and resource areas __ movptr(temp, Address(target_coroutine, Coroutine::handle_area_offset())); __ movptr(Address(thread, Thread::handle_area_offset()), temp); @@ -4499,6 +4504,10 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(Address(thread, Thread::resource_area_offset()), temp); __ movptr(temp, Address(target_coroutine, Coroutine::last_handle_mark_offset())); __ movptr(Address(thread, Thread::last_handle_mark_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::active_handles_offset())); + __ movptr(Address(thread, Thread::active_handles_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::metadata_handles_offset())); + __ movptr(Address(thread, Thread::metadata_handles_offset()), temp); #ifdef ASSERT __ movl(temp, Address(target_coroutine, Coroutine::java_call_counter_offset())); diff --git a/src/share/vm/code/nmethod.cpp b/src/share/vm/code/nmethod.cpp index e230419a3..ecbc45a5d 100644 --- a/src/share/vm/code/nmethod.cpp +++ b/src/share/vm/code/nmethod.cpp @@ -1438,6 +1438,19 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) { assert(state == zombie || state == not_entrant, "must be zombie or not_entrant"); assert(!is_zombie(), "should not already be a zombie"); + if (EnableCoroutine && method() != NULL) { + // do not deal with intrinsic methods of coroutine klass + assert(!is_unloaded(), "wrong state"); + vmIntrinsics::ID intrinsic_id = method()->intrinsic_id(); + if (intrinsic_id == vmIntrinsics::_switchTo || + intrinsic_id == vmIntrinsics::_switchToAndExit || + intrinsic_id == vmIntrinsics::_switchToAndTerminate) { + assert(method()->constants()->pool_holder() == SystemDictionary::java_dyn_CoroutineSupport_klass(), "wrong method!"); + assert(state == not_entrant, "wrong state"); + return false; + } + } + // Make sure neither the nmethod nor the method is flushed in case of a safepoint in code below. nmethodLocker nml(this); methodHandle the_method(method()); diff --git a/src/share/vm/prims/jvmtiThreadState.cpp b/src/share/vm/prims/jvmtiThreadState.cpp index 34b5bdc8f..a4116e27a 100644 --- a/src/share/vm/prims/jvmtiThreadState.cpp +++ b/src/share/vm/prims/jvmtiThreadState.cpp @@ -288,6 +288,9 @@ int JvmtiThreadState::cur_stack_depth() { if (!is_interp_only_mode() || _cur_stack_depth == UNKNOWN_STACK_DEPTH) { _cur_stack_depth = count_frames(); } else { + if (EnableCoroutine) { + _cur_stack_depth = count_frames(); // update debug stack depth, for switchToAndExit + } // heavy weight assert assert(_cur_stack_depth == count_frames(), "cur_stack_depth out of sync"); diff --git a/src/share/vm/prims/unsafe.cpp b/src/share/vm/prims/unsafe.cpp index ea8f32a4e..806359d30 100644 --- a/src/share/vm/prims/unsafe.cpp +++ b/src/share/vm/prims/unsafe.cpp @@ -1394,7 +1394,7 @@ void CoroutineSupport_switchToAndTerminate(JNIEnv* env, jclass klass, jobject ol } else { CoroutineStack::free_stack(stack, THREAD); } - Coroutine::free_coroutine(coro, THREAD); + delete coro; } void CoroutineSupport_switchToAndExit(JNIEnv* env, jclass klass, jobject old_coroutine, jobject target_coroutine) { @@ -1450,7 +1450,14 @@ jboolean CoroutineSupport_isDisposable(JNIEnv* env, jclass klass, jlong coroutin assert(coro != NULL, "cannot free NULL coroutine"); assert(!coro->is_thread_coroutine(), "cannot free thread coroutine"); - return coro->is_disposable(); + jboolean is_disposable = coro->is_disposable(); + if (is_disposable) { + CoroutineStack* stack = coro->stack(); + stack->remove_from_list(THREAD->coroutine_stack_list()); + CoroutineStack::free_stack(stack, THREAD); + delete coro; + } + return is_disposable; } jobject CoroutineSupport_cleanupCoroutine(JNIEnv* env, jclass klass) { diff --git a/src/share/vm/runtime/coroutine.cpp b/src/share/vm/runtime/coroutine.cpp index 121891ffa..a388653b6 100644 --- a/src/share/vm/runtime/coroutine.cpp +++ b/src/share/vm/runtime/coroutine.cpp @@ -77,6 +77,7 @@ void Coroutine::run(jobject coroutine) { _thread->set_resource_area(new (mtThread) ResourceArea(32)); _thread->set_handle_area(new (mtThread) HandleArea(NULL, 32)); + _thread->set_metadata_handles(new (ResourceObj::C_HEAP, mtClass) GrowableArray(30, true)); { HandleMark hm(_thread); @@ -105,6 +106,8 @@ Coroutine* Coroutine::create_thread_coroutine(JavaThread* thread, CoroutineStack coro->_resource_area = NULL; coro->_handle_area = NULL; coro->_last_handle_mark = NULL; + coro->_active_handles = thread->active_handles(); + coro->_metadata_handles = NULL; #ifdef ASSERT coro->_java_call_counter = 0; #endif @@ -114,6 +117,12 @@ Coroutine* Coroutine::create_thread_coroutine(JavaThread* thread, CoroutineStack return coro; } +/** + * The initial value for corountine' active handles, which will be replaced with a real one + * when the coroutine invokes call_virtual (before running any real logic). + */ +static JNIHandleBlock* shared_empty_JNIHandleBlock = JNIHandleBlock::allocate_block(); + Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack, oop coroutineObj) { Coroutine* coro = new Coroutine(); if (coro == NULL) { @@ -121,6 +130,8 @@ Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack } intptr_t** d = (intptr_t**)stack->stack_base(); + // Make the 16 bytes(original is 8*5=40 bytes) alignation which is required by some instructions like movaps otherwise we will incur a crash. + *(--d) = NULL; *(--d) = NULL; jobject obj = JNIHandles::make_global(coroutineObj); *(--d) = (intptr_t*)obj; @@ -131,13 +142,15 @@ Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack stack->set_last_sp((address) d); - coro->_state = _onstack; + coro->_state = _created; coro->_is_thread_coroutine = false; coro->_thread = thread; coro->_stack = stack; coro->_resource_area = NULL; coro->_handle_area = NULL; coro->_last_handle_mark = NULL; + coro->_active_handles = shared_empty_JNIHandleBlock; + coro->_metadata_handles = NULL; #ifdef ASSERT coro->_java_call_counter = 0; #endif @@ -147,13 +160,25 @@ Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack return coro; } -void Coroutine::free_coroutine(Coroutine* coroutine, JavaThread* thread) { - coroutine->remove_from_list(thread->coroutine_list()); - delete coroutine; +Coroutine::~Coroutine() { + remove_from_list(_thread->coroutine_list()); + if (!_is_thread_coroutine && _state != Coroutine::_created) { + assert(_resource_area != NULL, "_resource_area is NULL"); + assert(_handle_area != NULL, "_handle_area is NULL"); + assert(_metadata_handles != NULL, "_metadata_handles is NULL"); + delete _resource_area; + delete _handle_area; + delete _metadata_handles; + JNIHandleBlock::release_block(active_handles(), _thread); + //allocated from JavaCalls::call_virtual during coroutine's first running + } } void Coroutine::frames_do(FrameClosure* fc) { switch (_state) { + case Coroutine::_created: + // the coroutine has never been run + break; case Coroutine::_current: // the contents of this coroutine have already been visited break; @@ -182,6 +207,7 @@ void Coroutine::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) { if (_state == _onstack &&_handle_area != NULL) { DEBUG_CORO_ONLY(tty->print_cr("collecting handle area %08x", _handle_area)); _handle_area->oops_do(f); + _active_handles->oops_do(f); } } @@ -207,6 +233,11 @@ public: }; void Coroutine::metadata_do(void f(Metadata*)) { + if (metadata_handles() != NULL) { + for (int i = 0; i< metadata_handles()->length(); i++) { + f(metadata_handles()->at(i)); + } + } metadata_do_Closure fc(f); frames_do(&fc); } @@ -225,7 +256,9 @@ void Coroutine::frames_do(void f(frame*, const RegisterMap* map)) { } bool Coroutine::is_disposable() { - return false; + //_handle_area == NULL indicates this coroutine has not been initialized, + //we should delete it directly. + return _handle_area == NULL; } @@ -290,7 +323,10 @@ CoroutineStack* CoroutineStack::create_stack(JavaThread* thread, intptr_t size/* } void CoroutineStack::free_stack(CoroutineStack* stack, JavaThread* thread) { - guarantee(!stack->is_thread_stack(), "cannot free thread stack"); + if (stack->is_thread_stack()) { + delete stack; + return; + } ThreadLocalStorage::remove_coroutine_stack(thread, stack->stack_base(), stack->stack_size()); if (stack->_reserved_space.size() > 0) { diff --git a/src/share/vm/runtime/coroutine.hpp b/src/share/vm/runtime/coroutine.hpp index cfd2f3eb6..944e5c5ab 100644 --- a/src/share/vm/runtime/coroutine.hpp +++ b/src/share/vm/runtime/coroutine.hpp @@ -79,6 +79,7 @@ public: class Coroutine: public CHeapObj, public DoublyLinkedList { public: enum CoroutineState { + _created = 0x00000000, // not inited _onstack = 0x00000001, _current = 0x00000002, _dead = 0x00000003, // TODO is this really needed? @@ -96,6 +97,8 @@ private: ResourceArea* _resource_area; HandleArea* _handle_area; HandleMark* _last_handle_mark; + JNIHandleBlock* _active_handles; + GrowableArray* _metadata_handles; #ifdef ASSERT int _java_call_counter; #endif @@ -106,16 +109,16 @@ private: // objects of this type can only be created via static functions Coroutine() { } - virtual ~Coroutine() { } void frames_do(FrameClosure* fc); public: + virtual ~Coroutine(); + void run(jobject coroutine); static Coroutine* create_thread_coroutine(JavaThread* thread, CoroutineStack* stack); static Coroutine* create_coroutine(JavaThread* thread, CoroutineStack* stack, oop coroutineObj); - static void free_coroutine(Coroutine* coroutine, JavaThread* thread); CoroutineState state() const { return _state; } void set_state(CoroutineState x) { _state = x; } @@ -136,6 +139,12 @@ public: HandleMark* last_handle_mark() const { return _last_handle_mark; } void set_last_handle_mark(HandleMark* x){ _last_handle_mark = x; } + JNIHandleBlock* active_handles() const { return _active_handles; } + void set_active_handles(JNIHandleBlock* block) { _active_handles = block; } + + GrowableArray* metadata_handles() const { return _metadata_handles; } + void set_metadata_handles(GrowableArray* handles){ _metadata_handles = handles; } + #ifdef ASSERT int java_call_counter() const { return _java_call_counter; } void set_java_call_counter(int x) { _java_call_counter = x; } @@ -155,6 +164,8 @@ public: static ByteSize resource_area_offset() { return byte_offset_of(Coroutine, _resource_area); } static ByteSize handle_area_offset() { return byte_offset_of(Coroutine, _handle_area); } static ByteSize last_handle_mark_offset() { return byte_offset_of(Coroutine, _last_handle_mark); } + static ByteSize active_handles_offset() { return byte_offset_of(Coroutine, _active_handles); } + static ByteSize metadata_handles_offset() { return byte_offset_of(Coroutine, _metadata_handles); } #ifdef ASSERT static ByteSize java_call_counter_offset() { return byte_offset_of(Coroutine, _java_call_counter); } #endif diff --git a/src/share/vm/runtime/handles.hpp b/src/share/vm/runtime/handles.hpp index 97d51da79..f41b03350 100644 --- a/src/share/vm/runtime/handles.hpp +++ b/src/share/vm/runtime/handles.hpp @@ -233,6 +233,7 @@ class HandleArea: public Arena { _prev = prev; } + // Only coroutine uses this constructor HandleArea(HandleArea* prev, size_t init_size) : Arena(mtThread, init_size) { assert(EnableCoroutine, "EnableCoroutine is off"); debug_only(_handle_mark_nesting = 0); diff --git a/src/share/vm/runtime/thread.cpp b/src/share/vm/runtime/thread.cpp index 1996b0743..edc6adf0e 100644 --- a/src/share/vm/runtime/thread.cpp +++ b/src/share/vm/runtime/thread.cpp @@ -1500,6 +1500,7 @@ void JavaThread::initialize() { _coroutine_stack_cache_size = 0; _coroutine_stack_list = NULL; _coroutine_list = NULL; + _current_coroutine = NULL; _thread_stat = NULL; _thread_stat = new ThreadStatistics(); @@ -1643,6 +1644,11 @@ JavaThread::~JavaThread() { CoroutineStack::free_stack(stack, this); } + while (EnableCoroutine && coroutine_list() != NULL) { + CoroutineStack::free_stack(coroutine_list()->stack(), this); + delete coroutine_list(); + } + if (TraceThreadEvents) { tty->print_cr("terminate thread %p", this); } diff --git a/src/share/vm/runtime/thread.hpp b/src/share/vm/runtime/thread.hpp index 6f49aa6a3..cf2eaa014 100644 --- a/src/share/vm/runtime/thread.hpp +++ b/src/share/vm/runtime/thread.hpp @@ -639,6 +639,7 @@ protected: static ByteSize exception_file_offset() { return byte_offset_of(Thread, _exception_file ); } static ByteSize exception_line_offset() { return byte_offset_of(Thread, _exception_line ); } static ByteSize active_handles_offset() { return byte_offset_of(Thread, _active_handles ); } + static ByteSize metadata_handles_offset() { return byte_offset_of(Thread, _metadata_handles); } static ByteSize stack_base_offset() { return byte_offset_of(Thread, _stack_base ); } static ByteSize stack_size_offset() { return byte_offset_of(Thread, _stack_size ); } @@ -980,6 +981,7 @@ class JavaThread: public Thread { uintx _coroutine_stack_cache_size; CoroutineStack* _coroutine_stack_list; Coroutine* _coroutine_list; + Coroutine* _current_coroutine; intptr_t _coroutine_temp; @@ -988,9 +990,12 @@ class JavaThread: public Thread { uintx& coroutine_stack_cache_size() { return _coroutine_stack_cache_size; } CoroutineStack*& coroutine_stack_list() { return _coroutine_stack_list; } Coroutine*& coroutine_list() { return _coroutine_list; } + Coroutine* current_coroutine() { return _current_coroutine; } static ByteSize coroutine_temp_offset() { return byte_offset_of(JavaThread, _coroutine_temp); } + static ByteSize current_coroutine_offset() { return byte_offset_of(JavaThread, _current_coroutine); } + void initialize_coroutine_support(); private: diff --git a/test/runtime/coroutine/Issue11230146.java b/test/runtime/coroutine/Issue11230146.java new file mode 100644 index 000000000..e5f412f7c --- /dev/null +++ b/test/runtime/coroutine/Issue11230146.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * @test Issue11230146 + * @library /testlibrary /testlibrary/whitebox + * @build Issue11230146 + * @run main ClassFileInstaller sun.hotspot.WhiteBox + * @run main/othervm -XX:-Inline -XX:+EnableCoroutine -Xmx10m -Xms10m -XX:ReservedCodeCacheSize=3m -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI Issue11230146 + * @summary Issue11230146, JTreg test for D181275 + */ + +import sun.hotspot.WhiteBox; +import java.dyn.Coroutine; +import java.io.*; +import java.lang.reflect.Method; +import java.util.*; + +public class Issue11230146 { + public static void main(String[] args) throws Exception { + for (int i = 0; i < 100; i++) { + // trigger method state translate: + // in_use -> not_entrant -> zombie -> unload -> zombie + // ^ + // | + // +-- crash happens here (described in issue11230146) + doTest(); + } + } + + public static void doTest() throws Exception { + WhiteBox whiteBox = WhiteBox.getWhiteBox(); + MyClassloader loader = new MyClassloader(); + try { + Class c = loader.loadClass("ClassTmp"); + Method fooMethod = c.getMethod("foo"); + Object fooObject = c.newInstance(); + for (int i = 0; i < 10000; i++) { + fooMethod.invoke(fooObject); + } + } catch (Exception e) { + e.printStackTrace(); + } + System.gc(); + // deoptimize methods + whiteBox.deoptimizeAll(); + } + + /* + * class file contet: + * + * public class ClassTmp { + * public String a; + * + * public void foo() { + * if (a != null) { + * System.out.println("foo..."); + * } + * } + * } + * + */ + public static String classContent = + "yv66vgAAADQAIQoABwASCQAGABMJABQAFQgAFgoAFwAYBwAZBwAaAQABYQEAEkxqYXZhL2xhbmcv" + + "U3RyaW5nOwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAANmb28BAA1T" + + "dGFja01hcFRhYmxlAQAKU291cmNlRmlsZQEADUNsYXNzVG1wLmphdmEMAAoACwwACAAJBwAbDAAc" + + "AB0BAAZmb28uLi4HAB4MAB8AIAEACENsYXNzVG1wAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEv" + + "bGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50" + + "U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAYABwAAAAEAAQAIAAkA" + + "AAACAAEACgALAAEADAAAAB0AAQABAAAABSq3AAGxAAAAAQANAAAABgABAAAAAQABAA4ACwABAAwA" + + "AAA5AAIAAQAAABAqtAACxgALsgADEgS2AAWxAAAAAgANAAAADgADAAAABQAHAAYADwAIAA8AAAAD" + + "AAEPAAEAEAAAAAIAEQ=="; + + /** + * decode Base64 + */ + public static byte[] decodeBase64(String str) throws Exception { + byte[] bt = null; + sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); + bt = decoder.decodeBuffer(str); + return bt; + } + + public static class MyClassloader extends ClassLoader { + public Class loadClass(String name) throws ClassNotFoundException { + if ("ClassTmp".equals(name)) { + try { + byte[] bs = decodeBase64(classContent); + return defineClass(name, bs, 0, bs.length); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } else { + return super.loadClass(name); + } + } + } +} diff --git a/test/runtime/coroutine/SimpleWispTest.java b/test/runtime/coroutine/SimpleWispTest.java new file mode 100644 index 000000000..b2154610e --- /dev/null +++ b/test/runtime/coroutine/SimpleWispTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import com.alibaba.wisp.engine.WispEngine; + +public class SimpleWispTest { + + public static void main(String[] args) { + + WispEngine.dispatch(() -> { + + }); + + } + +} diff --git a/test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java b/test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java new file mode 100644 index 000000000..a9cb1844e --- /dev/null +++ b/test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * @test TestAvoidDeoptCoroutineMethod + * @library /testlibrary /testlibrary/whitebox + * @build TestAvoidDeoptCoroutineMethod + * @run main ClassFileInstaller sun.hotspot.WhiteBox + * @run main/othervm -XX:+EnableCoroutine -Xmx10m -Xms10m -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestAvoidDeoptCoroutineMethod + * @summary test avoid coroutine intrinsic method to be deoptimized + */ + +import sun.hotspot.WhiteBox; +import java.dyn.Coroutine; +import java.io.*; + +public class TestAvoidDeoptCoroutineMethod { + public static void main(String[] args) throws Exception { + WhiteBox whiteBox = WhiteBox.getWhiteBox(); + runSomeCoroutines(); + // deoptimize all + whiteBox.deoptimizeAll(); + // if intrinsic methods of coroutine have been deoptimized, it will crash here + runSomeCoroutines(); + } + + private static void runSomeCoroutines() throws Exception { + for (int i = 0; i < 10000; i++) { + new Coroutine(() -> {}); + Coroutine.yield(); // switch to new created coroutine and let it die + } + System.out.println("end of run"); + } +} diff --git a/test/runtime/coroutine/TestMemLeak.java b/test/runtime/coroutine/TestMemLeak.java new file mode 100644 index 000000000..625111285 --- /dev/null +++ b/test/runtime/coroutine/TestMemLeak.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * @test + * @summary test of memory leak while creating and destroying coroutine/thread + * @run main/othervm -XX:+EnableCoroutine -Xmx10m -Xms10m TestMemLeak + */ + +import java.dyn.Coroutine; +import java.io.*; + +public class TestMemLeak { + private final static Runnable r = () -> {}; + + public static void main(String[] args) throws Exception { + testThreadCoroutineLeak(); + testUserCreatedCoroutineLeak(); + } + + + /** + * Before fix: 35128kB -> 40124kB + * After fix : 28368kB -> 28424kB + */ + private static void testThreadCoroutineLeak() throws Exception { + // occupy rss + for (int i = 0; i < 20000; i++) { + Thread t = new Thread(r); + t.start(); + t.join(); + } + + int rss0 = getRssInKb(); + System.out.println(rss0); + + for (int i = 0; i < 20000; i++) { + Thread t = new Thread(r); + t.start(); + t.join(); + } + + int rss1 = getRssInKb(); + System.out.println(rss1); + + if (rss1 - rss0 > 1024) { // 1M + throw new Error("thread coroutine mem leak"); + } + } + + /** + * Before fix: 152892kB -> 280904kB + * After fix : 25436kB -> 25572kB + */ + private static void testUserCreatedCoroutineLeak() throws Exception { + // occupy rss + for (int i = 0; i < 200000; i++) { + new Coroutine(r); + Coroutine.yield(); // switch to new created coroutine and let it die + } + + int rss0 = getRssInKb(); + System.out.println(rss0); + + for (int i = 0; i < 200000; i++) { + new Coroutine(r); + Coroutine.yield(); + } + + int rss1 = getRssInKb(); + System.out.println(rss1); + if (rss1 - rss0 > 1024) { // 1M + throw new Error("user created coroutine mem leak"); + } + } + + + private static int getRssInKb() throws IOException { + try (BufferedReader br = new BufferedReader(new FileReader("/proc/self/status"))) { + int rss = -1; + String line; + while ((line = br.readLine()) != null) { + //i.e. VmRSS: 360 kB + if (line.trim().startsWith("VmRSS:")) { + int numEnd = line.length() - 3; + int numBegin = line.lastIndexOf(" ", numEnd - 1) + 1; + rss = Integer.parseInt(line.substring(numBegin, numEnd)); + break; + } + } + return rss; + } + } +} diff --git a/test/runtime/coroutine/coroutineBreakpointSwitchToTest.c b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.c new file mode 100644 index 000000000..0df1c19bd --- /dev/null +++ b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +void Jvmti_Error(int errcode, const char *msg) { + if (errcode != JVMTI_ERROR_NONE) { + printf("%s, error code: [%d]\n", errcode, msg); + exit(1); + } +} + +#define JVMTI_CHECK(x, str) (Jvmti_Error(x, str)) + +void JNICALL +SingleStepCallBack(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jmethodID method, + jlocation location) +{ +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + + // 1. get the jvmti env + jvmtiEnv *jvmti = NULL; + JVMTI_CHECK((*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1), "env get error"); + + // 2. grant the ability to the jvmti: can single step + jvmtiCapabilities noryoku; + memset((void *)&noryoku, 0, sizeof(jvmtiCapabilities)); + noryoku.can_generate_single_step_events = 1; + JVMTI_CHECK((*jvmti)->AddCapabilities(jvmti, &noryoku), "add capability error"); + + // 3. set: every single step, we can get a notification. + JVMTI_CHECK((*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, NULL), "set notification failed"); + + // 4. when a notification happens, we will get a callback! This callback will booooom. + jvmtiEventCallbacks callbacks; + memset((void *)&callbacks, 0, sizeof(callbacks)); + callbacks.SingleStep = &SingleStepCallBack; + JVMTI_CHECK((*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks)), "set callback failed"); + + return 0; +} diff --git a/test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh new file mode 100644 index 000000000..b2ea7061f --- /dev/null +++ b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# +# Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Alibaba designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# +# @test +# @library /testlibrary +# @compile SimpleWispTest.java +# +# @summary test Coroutine SwitchTo() crash problem +# @run shell coroutineBreakpointSwitchToTest.sh +# + +OS=`uname -s` +case "$OS" in + AIX | Darwin | Linux | SunOS ) + FS="/" + ;; + Windows_* ) + FS="\\" + ;; + CYGWIN_* ) + FS="/" + ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + +JAVA=${TESTJAVA}${FS}bin${FS}java + +export LD_LIBRARY_PATH=.:${TESTJAVA}${FS}jre${FS}lib${FS}amd64${FS}server:${FS}usr${FS}lib:${LD_LIBRARY_PATH} + +gcc_cmd=`which gcc` +if [ "x${gcc_cmd}" = "x" ]; then + echo "WARNING: gcc not found. Cannot execute test." 2>&1 + exit 0 +fi + +gcc -DLINUX -fPIC -shared -o libtest.so \ + -I${COMPILEJAVA}/include -I${COMPILEJAVA}/include/linux \ + ${TESTSRC}/coroutineBreakpointSwitchToTest.c + +${JAVA} -agentpath:libtest.so -XX:-UseBiasedLocking -XX:+EnableCoroutine -XX:+UseWispMonitor -Dcom.alibaba.transparentAsync=true -cp ${TESTCLASSES} SimpleWispTest + +exit $? diff --git a/test/runtime/coroutine/logCompilationTest.sh b/test/runtime/coroutine/logCompilationTest.sh new file mode 100644 index 000000000..0b8316082 --- /dev/null +++ b/test/runtime/coroutine/logCompilationTest.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +# +# Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Alibaba designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# +# @test +# @library /testlibrary +# @compile SimpleWispTest.java +# +# @summary test coroutine and -XX:+LogCompilation could work together +# @run shell logCompilationTest.sh +# + +OS=`uname -s` +case "$OS" in + AIX | Darwin | Linux | SunOS ) + FS="/" + ;; + Windows_* ) + FS="\\" + ;; + CYGWIN_* ) + FS="/" + ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + +JAVA=${TESTJAVA}${FS}bin${FS}java + +${JAVA} -XX:+UnlockDiagnosticVMOptions -XX:+EnableCoroutine -XX:+UseWispMonitor -Dcom.alibaba.transparentAsync=true -XX:+LogCompilation -Xcomp -cp ${TESTCLASSES} SimpleWispTest + +exit $? \ No newline at end of file -- GitLab