blob: a037a08a5ce9808555711a8f1e4f638a799c1163 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* @file thread_native_suspend.c
* @brief Hythread suspend/resume related functions
*/
#define LOG_DOMAIN "tm.suspend"
#include <open/hythread_ext.h>
#include <apr_atomic.h>
#include "port_barriers.h"
#include "port_mutex.h"
#include "thread_private.h"
static void thread_safe_point_impl(hythread_t thread);
/** @name Safe suspension support
*/
//@{
/**
* Denotes a single point where safe exception throwing is possible.
*/
void VMCALL hythread_exception_safe_point()
{
hythread_t self = tm_self_tls;
int gc_disable_count;
hythread_event_callback_proc callback_func;
if (!self->safepoint_callback) {
return;
}
/**
* There is a contract between disable_count value and Java code:
* during Java code execution disable_count should be equal to 1.
* It means that garbage collection while in native code can only
* take place when the native code makes disable_count equal to zero.
* To do this, the native code must guarantee that the GC is able to
* see all live references the native code is working with. And that
* the native code will not read/write live references while disable_count
* is equal to zero. This happens within suspension safe points or
* within VM helpers safe regions.
*
* Safepoint callback function could call Java code. If this happens,
* disable_count must be set to 1 before callback function execution.
*
* Field disable_count cannot be changed by another thread. Its value
* changes only by the same thread. So there is no race condition or
* separate usage in setting disable_count to 1.
*/
gc_disable_count = self->disable_count;
self->disable_count = 1;
// Clear callback (this is one-time event)
callback_func = self->safepoint_callback;
self->safepoint_callback = NULL;
// remove safe point callback request
apr_atomic_dec32(&self->request);
callback_func();
// restore disable_count
self->disable_count = gc_disable_count;
return;
}
/**
* Denotes a single point where safe suspension is possible.
*
* If there was a suspension request set for this thread, this method notifies
* the requesting thread and then blocks until someone calls the
* hythread_resume() function for this thread.
* <p>
* A thread marks itself with functions hythread_suspend_enable()
* and hythread_suspend_disable() in order to denote a safe region of code.
* A thread may also call hythread_safe_point() method to denote a selected
* point where safe suspension is possible.
*/
void VMCALL hythread_safe_point()
{
thread_safe_point_impl(tm_self_tls);
}
/**
* Same as hythread_safe_point, but inserts safe point for given thread
* other thread.
*/
void VMCALL hythread_safe_point_other(hythread_t thread)
{
thread_safe_point_impl(thread);
}
/**
* Denotes a single point where safe suspension is
* possible for a given thread.
*/
static void thread_safe_point_impl(hythread_t thread)
{
int gc_disable_count;
if (!thread->suspend_count) {
return;
}
// the thread is entering to the safe region,
// set disable count to 0 (safe region value)
gc_disable_count = thread->disable_count;
thread->disable_count = 0;
port_rw_barrier();
do {
CTRACE(("safe point enter: thread: %p, suspend_count: %d, request: %d",
thread, thread->suspend_count, thread->request));
// wait for resume event
hysem_wait(thread->resume_event);
CTRACE(("safe point exit: thread: %p, suspend_count: %d, request: %d",
thread, thread->suspend_count, thread->request));
} while (thread->suspend_count);
// restore disable_count value
hythread_set_suspend_disable(gc_disable_count);
return;
} // thread_safe_point_impl
/**
* Starts suspension of a given thread.
* The method increases suspend_count and sets request.
* Calling wait_safe_region_event() could be called
* to wait for safe region or safe point.
*/
void VMCALL hythread_send_suspend_request(hythread_t thread)
{
// increment suspend count
apr_atomic_inc32(&thread->suspend_count);
apr_atomic_inc32(&thread->request);
if (thread != hythread_self()) {
// notify target thread about suspend request change
hythread_yield_other(thread);
}
}
/**
* Waits until a given thread reaches a safe region.
* The method is called after hythread_send_suspend_request() function
* as the second part of suspension.
*/
static IDATA wait_safe_region_event(hythread_t thread)
{
hythread_t self = tm_self_tls;
assert(thread->suspend_count);
assert(thread != self);
// We need to wait for notification only in case the thread is in
// the unsafe/disable region. All threads should be stoped in
// safepoints or be in safe region.
while (thread->disable_count != 0) {
// HIT cyclic suspend
if (self->request) {
return TM_ERROR_EBUSY;
}
hythread_yield();
}
CTRACE(("suspend wait exit safe region thread: "
"%p, suspend_count: %d, request: %d",
thread, thread->suspend_count, thread->request));
port_mutex_lock(&thread->mutex);
thread->state |= TM_THREAD_STATE_SUSPENDED;
port_mutex_unlock(&thread->mutex);
return TM_ERROR_NONE;
}
/**
* Suspends the current thread.
*
* Stop the current thread from executing until it is resumed.
*
* @see hythread_resume
*/
void VMCALL hythread_suspend()
{
hythread_t self = tm_self_tls;
hythread_send_suspend_request(self);
port_mutex_lock(&self->mutex);
self->state |= TM_THREAD_STATE_SUSPENDED;
port_mutex_unlock(&self->mutex);
thread_safe_point_impl(self);
}
/**
* Safely suspends the <code>thread</code> execution.
*
* The safe suspension acts as follows:
* <ul>
* <li>
* If the <code>thread</code> is currently running in safe code region, this
* method immediately returns back.
* The <code>thread</code> itself runs until it reaches the end of safe region
* and then blocks until someone calls hythread_resume() for it.
* <li>
* If the <code>thread</code> is currently in unsafe region, this
* method blocks until the <code>thread</code> either reaches the beginning
* of a safe region, or reaches a safe point.
* Once reached safe point or end of safe region, the<code>thread</code> blocks
* until someone calls hythread_resume() for it.
* </ul>
* A thread marks itself with functions hythread_suspend_enable()
* and hythread_suspend_disable() in order to denote a safe region of code.
* A thread may also call hythread_safe_point() method to denote a selected
* point where safe suspension is possible.
*
* @param[in] thread thread to be suspended
* @return TM_ERROR_EBUSY if deadlock, TM_ERROR_NONE if OK
*/
IDATA VMCALL hythread_suspend_other(hythread_t thread)
{
hythread_t self = tm_self_tls;
CTRACE(("suspend enter, self: %p, thread: %p, "
"suspend_count: %d, request: %d", self, thread,
thread->suspend_count, thread->request));
if (self == thread) {
// suspend self
hythread_suspend();
return TM_ERROR_NONE;
}
// suspend another thread
hythread_send_suspend_request(thread);
if (wait_safe_region_event(thread) != TM_ERROR_NONE) {
hythread_resume(thread);
return TM_ERROR_EBUSY;
}
CTRACE(("suspend exit, self: %p, thread: %p, "
"suspend_count: %d, request: %d", self, thread,
thread->suspend_count, thread->request));
return TM_ERROR_NONE;
}
/**
* Resumes a thread.
*
* Take a threads out of the suspended state.
* If the thread is not suspended, no action is taken.
*
* @param[in] thread a thread to be resumed
*
* @see hythread_create, hythread_suspend
*/
void VMCALL hythread_resume(hythread_t thread)
{
int count;
hythread_t self = tm_self_tls;
CTRACE(("start resume, self: %p, thread: %p, "
"suspend_count: %d, request: %d", self, thread,
thread->suspend_count, thread->request));
do {
count = thread->suspend_count;
if (!count) {
return;
}
} while (apr_atomic_cas32(&thread->suspend_count, count - 1, count) != count);
// If there was request for suspension,
// decrease the suspend counter and remove request
apr_atomic_dec32(&thread->request);
if (thread->suspend_count) {
return;
}
// change thread state
port_mutex_lock(&thread->mutex);
thread->state &= ~TM_THREAD_STATE_SUSPENDED;
port_mutex_unlock(&thread->mutex);
CTRACE(("sent resume, self: %p, thread: %p, "
"suspend_count: %d, request: %d", self, thread,
thread->suspend_count, thread->request));
// notify the thread that it may wake up now
hysem_post(thread->resume_event);
return;
}
/**
* Sets safepoint callback function.
*
* Callback function is executed at safepoint in case there was
* a safepoint callback request.
*
* @param[in] thread thread where callback needs to be executed
* @param[in] callback callback function
*/
IDATA hythread_set_safepoint_callback(hythread_t thread,
tm_thread_event_callback_proc callback)
{
hythread_t self = tm_self_tls;
while (apr_atomic_casptr((volatile void **) &thread->safepoint_callback,
callback, NULL) != NULL)
{
thread_safe_point_impl(self);
hythread_yield();
}
// set safe point callback request
apr_atomic_inc32(&thread->request);
if (self == thread) {
hythread_exception_safe_point();
} else {
// sent an interupt signal for waiting threads
if (thread->current_condition || thread->waited_monitor) {
hythread_interrupt(thread);
}
}
return TM_ERROR_NONE;
}
/**
* Helps to safely suspend the threads in the selected group.
*
* The method sets a suspend request for the every thread in the group
* and then returns the iterator that can be used to traverse through
* the suspended threads.
* Each invocation of the hythread_iterator_next() method on the iterator
* will return the next suspended thread.
*
* @param[out] iter_ptr iterator
* @param[in] group thread group to be suspended
*/
IDATA VMCALL hythread_suspend_all(hythread_iterator_t* iter_ptr,
hythread_group_t group)
{
hythread_t self = tm_self_tls;
hythread_t next;
hythread_iterator_t iter;
CTRACE(("suspend all threads"));
assert(hythread_is_suspend_enabled());
// try to prevent cyclic suspend dead-lock
thread_safe_point_impl(self);
// get global lock to prevent new thread creation
hythread_global_lock();
// send suspend requests to all threads
CTRACE(("send suspend requests"));
iter = hythread_iterator_create(group);
while ((next = hythread_iterator_next(&iter)) != NULL) {
if (next != self) {
hythread_send_suspend_request(next);
}
}
hythread_iterator_reset(&iter);
// all threads should be stoped in safepoints or be in safe region.
CTRACE(("wait suspend responses"));
while ((next = hythread_iterator_next(&iter)) != NULL) {
if (next == self) {
continue;
}
while (wait_safe_region_event(next) != TM_ERROR_NONE) {
thread_safe_point_impl(self);
hythread_yield();
}
}
hythread_iterator_reset(&iter);
hythread_iterator_release(&iter);
if (iter_ptr) {
*iter_ptr = iter;
}
return TM_ERROR_NONE;
}
/**
* Resumes all threads in the selected group.
*
* @param[in] group thread group to be resumed
*/
IDATA VMCALL hythread_resume_all(hythread_group_t group)
{
hythread_t self = tm_self_tls;
hythread_t next;
hythread_iterator_t iter;
CTRACE(("resume all threads"));
// send resume requests to all threads
iter = hythread_iterator_create(group);
while ((next = hythread_iterator_next(&iter)) != NULL) {
if (next != self) {
hythread_resume(next);
}
}
hythread_iterator_release(&iter);
// release global lock which was got in hythread_suspend_all()
hythread_global_unlock();
return TM_ERROR_NONE;
}
/**
* Reset disable_count for currect thread.
* The method begins suspension safe region.
* Field disable_count is restored in hythread_set_suspend_disable() function.
*/
int VMCALL hythread_reset_suspend_disable()
{
hythread_t self = tm_self_tls;
int disable_count = self->disable_count;
self->disable_count = 0;
return disable_count;
}
/**
* Restores disable_count for current thread,
* which was reset in hythread_reset_suspend_disable() function
* If restored value ends suspension safe region
* and there was a suspension request set for this thread,
* the method invokes hythread_safe_point() function.
*/
void VMCALL hythread_set_suspend_disable(int count)
{
hythread_t self = tm_self_tls;
assert(count >= 0);
self->disable_count = count;
port_rw_barrier();
if (count && self->suspend_count) {
thread_safe_point_impl(self);
}
}
//@}