1 /*
   2  * Copyright (c) 2018, Red Hat, Inc. and/or its affiliates.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.
   7  *
   8  * This code is distributed in the hope that it will be useful, but WITHOUT
   9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11  * version 2 for more details (a copy is included in the LICENSE file that
  12  * accompanied this code).
  13  *
  14  * You should have received a copy of the GNU General Public License version
  15  * 2 along with this work; if not, write to the Free Software Foundation,
  16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  17  *
  18  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  19  * or visit www.oracle.com if you need additional information or have any
  20  * questions.
  21  *
  22  */
  23 
  24 #include "precompiled.hpp"
  25 
  26 #include "gc_implementation/shenandoah/shenandoahHeap.hpp"
  27 #include "gc_implementation/shenandoah/shenandoahUtils.hpp"
  28 #include "gc_implementation/shenandoah/shenandoahEvacOOMHandler.hpp"
  29 
  30 const jint ShenandoahEvacOOMHandler::OOM_MARKER_MASK = 0x80000000;
  31 
  32 ShenandoahEvacOOMHandler::ShenandoahEvacOOMHandler() :
  33   _threads_in_evac(0) {
  34 }
  35 
  36 void ShenandoahEvacOOMHandler::wait_for_no_evac_threads() {
  37   while ((OrderAccess::load_acquire(&_threads_in_evac) & ~OOM_MARKER_MASK) != 0) {
  38     os::naked_short_sleep(1);
  39   }
  40   // At this point we are sure that no threads can evacuate anything. Raise
  41   // the thread-local oom_during_evac flag to indicate that any attempt
  42   // to evacuate should simply return the forwarding pointer instead (which is safe now).
  43   Thread::current()->set_oom_during_evac(true);
  44 }
  45 
  46 void ShenandoahEvacOOMHandler::enter_evacuation() {
  47   jint threads_in_evac = OrderAccess::load_acquire(&_threads_in_evac);
  48 
  49   assert(!Thread::current()->is_evac_allowed(), "sanity");
  50   assert(!Thread::current()->is_oom_during_evac(), "TL oom-during-evac must not be set");
  51 
  52   if ((threads_in_evac & OOM_MARKER_MASK) != 0) {
  53     wait_for_no_evac_threads();
  54     return;
  55   }
  56 
  57   while (true) {
  58     jint other = Atomic::cmpxchg(threads_in_evac + 1, &_threads_in_evac, threads_in_evac);
  59     if (other == threads_in_evac) {
  60       // Success: caller may safely enter evacuation
  61       DEBUG_ONLY(Thread::current()->set_evac_allowed(true));
  62       return;
  63     } else {
  64       // Failure:
  65       //  - if offender has OOM_MARKER_MASK, then loop until no more threads in evac
  66       //  - otherwise re-try CAS
  67       if ((other & OOM_MARKER_MASK) != 0) {
  68         wait_for_no_evac_threads();
  69         return;
  70       }
  71       threads_in_evac = other;
  72     }
  73   }
  74 }
  75 
  76 void ShenandoahEvacOOMHandler::leave_evacuation() {
  77   if (!Thread::current()->is_oom_during_evac()) {
  78     assert((OrderAccess::load_acquire(&_threads_in_evac) & ~OOM_MARKER_MASK) > 0, "sanity");
  79     // NOTE: It's ok to simply decrement, even with mask set, because unmasked value is positive.
  80     Atomic::dec(&_threads_in_evac);
  81   } else {
  82     // If we get here, the current thread has already gone through the
  83     // OOM-during-evac protocol and has thus either never entered or successfully left
  84     // the evacuation region. Simply flip its TL oom-during-evac flag back off.
  85     Thread::current()->set_oom_during_evac(false);
  86   }
  87   DEBUG_ONLY(Thread::current()->set_evac_allowed(false));
  88   assert(!Thread::current()->is_oom_during_evac(), "TL oom-during-evac must be turned off");
  89 }
  90 
  91 void ShenandoahEvacOOMHandler::handle_out_of_memory_during_evacuation() {
  92   assert(Thread::current()->is_evac_allowed(), "sanity");
  93   assert(!Thread::current()->is_oom_during_evac(), "TL oom-during-evac must not be set");
  94 
  95   jint threads_in_evac = OrderAccess::load_acquire(&_threads_in_evac);
  96   while (true) {
  97     jint other = Atomic::cmpxchg((threads_in_evac - 1) | OOM_MARKER_MASK,
  98                                   &_threads_in_evac, threads_in_evac);
  99     if (other == threads_in_evac) {
 100       // Success: wait for other threads to get out of the protocol and return.
 101       wait_for_no_evac_threads();
 102       return;
 103     } else {
 104       // Failure: try again with updated new value.
 105       threads_in_evac = other;
 106     }
 107   }
 108 }
 109 
 110 void ShenandoahEvacOOMHandler::clear() {
 111   assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at a safepoint");
 112   assert((OrderAccess::load_acquire(&_threads_in_evac) & ~OOM_MARKER_MASK) == 0, "sanity");
 113   OrderAccess::release_store_fence(&_threads_in_evac, 0);
 114 }
 115 
 116 ShenandoahEvacOOMScope::ShenandoahEvacOOMScope() {
 117   ShenandoahHeap::heap()->enter_evacuation();
 118 }
 119 
 120 ShenandoahEvacOOMScope::~ShenandoahEvacOOMScope() {
 121   ShenandoahHeap::heap()->leave_evacuation();
 122 }
 123 
 124 ShenandoahEvacOOMScopeLeaver::ShenandoahEvacOOMScopeLeaver() {
 125   ShenandoahHeap::heap()->leave_evacuation();
 126 }
 127 
 128 ShenandoahEvacOOMScopeLeaver::~ShenandoahEvacOOMScopeLeaver() {
 129   ShenandoahHeap::heap()->enter_evacuation();
 130 }