blob: e332fc6c6e0ce0497912eeaa4da7c8ac5cf43170 [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.
*/
#include "port_atomic.h"
#include "port_malloc.h"
#include "port_thread.h"
#include "port_thread_internal.h"
/* Forward declarations */
static int suspend_init_lock();
static port_thread_info_t* init_susres_list_item();
static port_thread_info_t* suspend_add_thread(osthread_t thread);
static void suspend_remove_thread(osthread_t thread);
static port_thread_info_t* suspend_find_thread(osthread_t thread);
typedef unsigned (__stdcall *beginthread_func_t)(void*);
typedef struct
{
port_threadfunc_t fun;
void* arg;
size_t stack_size;
} thread_start_struct_t;
static unsigned __stdcall thread_start_func(void* arg)
{
int err, result;
port_tls_data_t* tlsdata;
thread_start_struct_t* ptr = (thread_start_struct_t*)arg;
port_threadfunc_t fun = ptr->fun;
size_t stack_size = ptr->stack_size;
arg = ptr->arg;
STD_FREE(ptr);
if (port_shared_data)
{
tlsdata = (port_tls_data_t*)STD_ALLOCA(sizeof(port_tls_data_t));
err = port_thread_attach_local(tlsdata, FALSE, FALSE, stack_size);
assert(err == 0);
}
result = (int)fun(arg);
port_thread_detach();
return result;
}
int port_thread_create(/* out */osthread_t* phandle, size_t stacksize, int priority,
port_threadfunc_t func, void *data)
{
uintptr_t handle;
thread_start_struct_t* startstr;
int res;
if (!port_shared_data)
{
res = init_port_shared_data();
/* assert(res); */
/* It's OK to have an error here when Port shared library
is not available yet; only signals/crash handling will
not be available for the thread */
/* return res; */
}
if (!func)
return -1;
startstr =
(thread_start_struct_t*)STD_MALLOC(sizeof(thread_start_struct_t));
if (!startstr)
return -1;
if (stacksize != 0)
{
if (port_shared_data)
{
size_t min_stacksize =
/* Let's get alt stack size for normal stack and add guard page size */
((2*port_shared_data->guard_stack_size + port_shared_data->guard_page_size)
/* Roung up to alt stack size */
+ port_shared_data->guard_stack_size - 1) & ~(port_shared_data->guard_stack_size - 1);
if (stacksize < min_stacksize)
stacksize = min_stacksize;
}
}
startstr->fun = func;
startstr->arg = data;
startstr->stack_size = stacksize;
handle = _beginthreadex(NULL, (int)stacksize, thread_start_func,
startstr, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (handle != (uintptr_t)-1L)
{
*phandle = (HANDLE)handle;
if (priority)
SetThreadPriority(*phandle, priority);
return 0;
}
STD_FREE(startstr);
return res;
}
static int set_guard_page(port_tls_data_t* tlsdata, Boolean set)
{
DWORD oldProtect;
void* guard_addr;
size_t guard_size;
if (!tlsdata)
tlsdata = get_private_tls_data();
if (!tlsdata)
return -1;
if (!tlsdata->guard_page_addr)
return 0;
if ((set && tlsdata->guard_page_set) ||
(!set && !tlsdata->guard_page_set))
return 0; // Already in needed state
#ifdef HYX86_64
/* Windows x86_64 protects both guard page and guard stack area
specified by SetThreadStackGuarantee() with PAGE_GUARD flag */
guard_addr = tlsdata->guard_stack_addr;
guard_size = tlsdata->guard_stack_size + tlsdata->guard_page_size;
#else
guard_addr = tlsdata->guard_page_addr;
guard_size = tlsdata->guard_page_size;
#endif
if (set)
{
if ((size_t)&set - PSD->mem_protect_size
< (size_t)tlsdata->guard_page_addr + tlsdata->guard_page_size)
return -1;
if (!VirtualProtect(guard_addr, guard_size,
PAGE_GUARD | PAGE_READWRITE, &oldProtect))
// should be successful always
return -1;
}
else
{
if ((size_t)&set < (size_t)tlsdata->guard_page_addr + tlsdata->guard_page_size)
return -1;
if (!VirtualProtect(guard_addr, guard_size,
PAGE_READWRITE, &oldProtect))
// should be successful always
return -1;
}
tlsdata->guard_page_set = set;
return 0;
}
int port_thread_restore_guard_page()
{
return set_guard_page(NULL, TRUE);
}
int port_thread_clear_guard_page()
{
return set_guard_page(NULL, FALSE);
}
void port_thread_postpone_guard_page()
{
port_tls_data_t* tlsdata = get_private_tls_data();
if (!tlsdata || !tlsdata->guard_page_addr)
return;
tlsdata->restore_guard_page = FALSE;
}
void* port_thread_get_stack_address()
{
port_tls_data_t* tlsdata = get_private_tls_data();
return tlsdata ? tlsdata->stack_addr : NULL;
}
size_t port_thread_get_stack_size()
{
port_tls_data_t* tlsdata = get_private_tls_data();
return tlsdata ? tlsdata->stack_size : 0;
}
size_t port_thread_get_effective_stack_size()
{
port_tls_data_t* tlsdata = get_private_tls_data();
if (!tlsdata)
return 0;
if (!tlsdata->guard_page_addr || !tlsdata->guard_page_set)
return tlsdata->stack_size
- PSD->guard_page_size - PSD->mem_protect_size;
return tlsdata->stack_size - 2*PSD->guard_page_size
- PSD->guard_stack_size - PSD->mem_protect_size;
}
static int setup_stack(port_tls_data_t* tlsdata)
{
#ifdef HYX86_64
ULONG guard_stack_size_param;
#endif
if (!port_shared_data)
return -1;
#ifdef HYX86_64
/* this code in future should be used on both platforms x86-32 and x86-64 */
guard_stack_size_param = (ULONG)PSD->guard_stack_size;
if (!SetThreadStackGuarantee(&guard_stack_size_param))
/* should be successful always */
return -1;
#endif
if ((size_t)&tlsdata - PSD->mem_protect_size
< (size_t)tlsdata->guard_page_addr + tlsdata->guard_page_size)
return -1;
tlsdata->guard_page_set = TRUE; // GUARD_PAGE is set by default
return 0;
}
static int find_stack_addr_size(void** paddr, size_t* psize)
{
size_t reg_size;
MEMORY_BASIC_INFORMATION memory_information;
VirtualQuery(&memory_information, &memory_information, sizeof(memory_information));
reg_size = memory_information.RegionSize;
*paddr = (void*)((size_t)memory_information.BaseAddress + reg_size);
*psize = (size_t)*paddr - (size_t)memory_information.AllocationBase;
return 0;
}
static int init_stack(port_tls_data_t* tlsdata, size_t stack_size, Boolean temp)
{
int err;
size_t stack_begin;
if (!port_shared_data)
return -1;
err = find_stack_addr_size(&tlsdata->stack_addr, &tlsdata->stack_size);
if (err != 0) return err;
if (stack_size)
tlsdata->stack_size = stack_size;
tlsdata->guard_page_size = PSD->guard_page_size;
tlsdata->guard_stack_size = PSD->guard_stack_size;
tlsdata->mem_protect_size = PSD->mem_protect_size;
if (temp)
return 0;
stack_begin = (size_t)tlsdata->stack_addr - tlsdata->stack_size;
tlsdata->guard_stack_addr = (void*)(stack_begin + tlsdata->guard_page_size);
tlsdata->guard_page_addr =
(void*)((size_t)tlsdata->guard_stack_addr + tlsdata->guard_stack_size);
return setup_stack(tlsdata);
}
int port_thread_attach_local(port_tls_data_t* tlsdata, Boolean temp,
Boolean foreign, size_t stack_size)
{
int res;
memset(tlsdata, 0, sizeof(port_tls_data_t));
tlsdata->foreign = foreign;
res = init_stack(tlsdata, stack_size, temp);
if (res != 0) return res;
return set_private_tls_data(tlsdata);
}
int port_thread_attach()
{
int res;
port_tls_data_t* tlsdata;
if (!port_shared_data && (res = init_port_shared_data()) != 0)
return res;
if (get_private_tls_data())
return 0;
tlsdata = (port_tls_data_t*)STD_MALLOC(sizeof(port_tls_data_t));
if (!tlsdata) return ENOMEM;
res = port_thread_attach_local(tlsdata, FALSE, TRUE, 0);
if (res != 0)
STD_FREE(tlsdata);
return res;
}
int port_thread_detach_temporary()
{
port_tls_data_t* tlsdata = get_private_tls_data();
if (!tlsdata || tlsdata->guard_page_addr)
return -1;
return set_private_tls_data(NULL);
}
int port_thread_detach()
{
port_tls_data_t* tlsdata;
int res;
if (!port_shared_data && (res = init_port_shared_data()) != 0)
return res;
tlsdata = get_private_tls_data();
if (!tlsdata)
return 0;
if (port_thread_detach_temporary() == 0)
return 0;
if (tlsdata->foreign)
STD_FREE(tlsdata);
return set_private_tls_data(NULL);
}
int port_thread_set_priority(osthread_t os_thread, int priority)
{
if (SetThreadPriority(os_thread, (int)priority)) {
return 0;
} else {
return GetLastError();
}
}
osthread_t port_thread_current()
{
HANDLE hproc = GetCurrentProcess();
HANDLE hthread = GetCurrentThread();
if (!DuplicateHandle(hproc, hthread,
hproc, &hthread, 0, FALSE,
DUPLICATE_SAME_ACCESS)) {
return NULL;
}
return hthread;
}
int port_thread_free_handle(osthread_t os_thread)
{
BOOL r = CloseHandle(os_thread);
return !r;
}
int port_thread_join(osthread_t os_thread)
{
int error = 0;
DWORD r;
r = WaitForSingleObject(os_thread, INFINITE);
if (r == WAIT_OBJECT_0 || r == WAIT_ABANDONED)
r = 0;
else
r = GetLastError();
CloseHandle(os_thread);
return r;
}
int port_thread_cancel(osthread_t os_thread)
{
port_thread_info_t* pinfo;
int status = TM_ERROR_NONE;
if (!suspend_init_lock())
return TM_ERROR_INTERNAL;
pinfo = suspend_find_thread(os_thread);
if (pinfo)
suspend_remove_thread(os_thread);
if (!TerminateThread(os_thread, 0))
status = (int)GetLastError();
LeaveCriticalSection(&PSD->crit_section);
return status;
}
void port_thread_exit(int status)
{
ExitThread(status);
}
int port_get_thread_times(osthread_t os_thread, int64* pkernel, int64* puser)
{
FILETIME creation_time;
FILETIME exit_time;
FILETIME kernel_time;
FILETIME user_time;
int r;
r = GetThreadTimes(os_thread,
&creation_time, &exit_time, &kernel_time, &user_time);
if (r) {
// according to MSDN, time is counted in 100 ns units, so we need to multiply by 100
*pkernel = 100 *
(((int64)kernel_time.dwHighDateTime << 32)
| kernel_time.dwLowDateTime);
*puser = 100 *
(((int64)user_time.dwHighDateTime << 32)
| user_time.dwLowDateTime);
return 0;
} else
return GetLastError();
}
void port_thread_yield_other(osthread_t os_thread)
{
port_thread_info_t* pinfo;
/*
* Synchronization is needed to avoid cyclic (mutual) suspension problem.
* Accordingly to MSDN, it is possible on multiprocessor box that
* 2 threads suspend each other and become deadlocked.
*/
if (!suspend_init_lock()) // Initializes and enters a critical section
return;
pinfo = suspend_find_thread(os_thread);
if (pinfo && pinfo->suspend_count > 0) {
LeaveCriticalSection(&PSD->crit_section);
return;
}
if (SuspendThread(os_thread) != -1) {
/* suspended successfully, so resume it back. */
ResumeThread(os_thread);
}
LeaveCriticalSection(&PSD->crit_section);
}
int port_thread_suspend(osthread_t thread)
{
port_thread_info_t* pinfo;
DWORD old_count;
if (!thread)
return TM_ERROR_NULL_POINTER;
if (!suspend_init_lock())
return TM_ERROR_INTERNAL;
pinfo = suspend_find_thread(thread);
if (!pinfo)
pinfo = suspend_add_thread(thread);
if (!pinfo)
{
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_OUT_OF_MEMORY;
}
if (pinfo->suspend_count > 0)
{
++pinfo->suspend_count;
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_NONE;
}
old_count = SuspendThread(thread);
if (old_count == (DWORD)-1)
{
int status = (int)GetLastError();
LeaveCriticalSection(&PSD->crit_section);
return status;
}
++pinfo->suspend_count;
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_NONE;
}
int port_thread_resume(osthread_t thread)
{
port_thread_info_t* pinfo;
DWORD old_count;
if (!thread)
return TM_ERROR_NULL_POINTER;
if (!suspend_init_lock())
return TM_ERROR_INTERNAL;
pinfo = suspend_find_thread(thread);
if (!pinfo)
{
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_UNATTACHED_THREAD;
}
if (pinfo->suspend_count > 1)
{
--pinfo->suspend_count;
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_NONE;
}
old_count = ResumeThread(thread);
if (old_count == (DWORD)-1)
{
int status = (int)GetLastError();
LeaveCriticalSection(&PSD->crit_section);
return status;
}
if (--pinfo->suspend_count == 0)
suspend_remove_thread(thread);
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_NONE;
}
int port_thread_get_suspend_count(osthread_t thread)
{
port_thread_info_t* pinfo;
int suspend_count;
if (!thread)
return -1;
if (!suspend_init_lock())
return -1;
pinfo = suspend_find_thread(thread);
suspend_count = pinfo ? pinfo->suspend_count : 0;
LeaveCriticalSection(&PSD->crit_section);
return suspend_count;
}
int port_thread_get_context(osthread_t thread, thread_context_t *context)
{
port_thread_info_t* pinfo;
CONTEXT local_context;
if (!thread || !context)
return TM_ERROR_NULL_POINTER;
if (!suspend_init_lock())
return TM_ERROR_INTERNAL;
pinfo = suspend_find_thread(thread);
if (!pinfo)
{
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_UNATTACHED_THREAD;
}
#ifdef CONTEXT_ALL
local_context.ContextFlags = CONTEXT_ALL;
#else
local_context.ContextFlags = CONTEXT_FULL;
#endif
if (!GetThreadContext(thread, &local_context))
{
int status = (int)GetLastError();
LeaveCriticalSection(&PSD->crit_section);
return status;
}
pinfo->context = local_context;
*context = local_context;
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_NONE;
}
int port_thread_set_context(osthread_t thread, thread_context_t *context)
{
port_thread_info_t* pinfo;
if (!thread || !context)
return -1;
if (!suspend_init_lock())
return -2;
pinfo = suspend_find_thread(thread);
if (!pinfo)
{
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_UNATTACHED_THREAD;
}
if (!SetThreadContext(thread, context))
{
int status = (int)GetLastError();
LeaveCriticalSection(&PSD->crit_section);
return status;
}
pinfo->context = *context;
LeaveCriticalSection(&PSD->crit_section);
return TM_ERROR_NONE;
}
static int suspend_init_lock()
{
static uint16 initialized = 0;
if (!initialized)
{
// Critical section should be initialized only once,
// do nothing in case someone else already initialized it.
if (port_atomic_cas16((volatile uint16*)&initialized, 1, 0) == 0)
InitializeCriticalSectionAndSpinCount(&PSD->crit_section, 400);
}
EnterCriticalSection(&PSD->crit_section);
return 1;
}
static port_thread_info_t* init_susres_list_item()
{
port_thread_info_t* pinfo =
(port_thread_info_t*)malloc(sizeof(port_thread_info_t));
if (pinfo)
pinfo->suspend_count = 0;
return pinfo;
}
static port_thread_info_t* suspend_add_thread(osthread_t thread)
{
port_thread_info_t* pinfo = init_susres_list_item();
if (!pinfo)
return NULL;
pinfo->thread = thread;
pinfo->next = PSD->suspended_list;
PSD->suspended_list = pinfo;
return pinfo;
}
static void suspend_remove_thread(osthread_t thread)
{
port_thread_info_t** pprev = &PSD->suspended_list;
port_thread_info_t* pinfo;
for (pinfo = PSD->suspended_list; pinfo; pinfo = pinfo->next)
{
if (pinfo->thread == thread)
break;
pprev = &pinfo->next;
}
if (pinfo)
{
*pprev = pinfo->next;
free(pinfo);
}
}
static port_thread_info_t* suspend_find_thread(osthread_t thread)
{
port_thread_info_t* pinfo;
for (pinfo = PSD->suspended_list; pinfo; pinfo = pinfo->next)
{
if (pinfo->thread == thread)
break;
}
return pinfo;
}