platform_bionic/libc/bionic/pthread_atfork.cpp
Mingwei Shi f6a21bfac5 Eliminate deadlock in forked child due to delayed resetting mutex lock
For some program implementation, the pattern like below, calling 
pthread_atfork to register atfork interfaces.

    pthread_atfork(&atfork_prepare, &atfork_parent, &atfork_child);

When the program is expected to reopen the shared library's handle 
inherited from parent in child process. Maybe, dlclose is called in 
atfork_child to release the shared library handle before reopen it. 
Then, dlclose will indrectly call _cxa_finalize and finaly call 
__unregister_atfork when dso is not NULL.

    atfork_child() -> dlclose() -> __on_dlclose() 
    	-> __cxa_finalize() -> __unregister_atfork(dso)

In __unregister_atfork, firstly, it try to hold the g_atfork_list_mutex 
lock to operate the g_atfork_list. Due to the registered atfork_child is
executed before resetting g_atfork_list_mutex lock in child, the child 
process will be blocked here because of deadlock.

Test: bionic-unit-tests32 --gtest_filter=pthread.pthread_atfork_child_with_dlclose 
without the fixing, the test will be timeout.

Change-Id: I35d3001682c836e0955d6d681bc5f9297fad0c7b
Signed-off-by: Mingwei Shi <mingwei.shi@intel.com>
Signed-off-by: Qiming Shi <qiming.shi@intel.com>
Signed-off-by: Chao Xie <chao.xie@intel.com>
2016-11-07 03:32:57 +00:00

183 lines
4.9 KiB
C++

/*
* Copyright (C) 2008 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include "private/bionic_macros.h"
struct atfork_t {
atfork_t* next;
atfork_t* prev;
void (*prepare)(void);
void (*child)(void);
void (*parent)(void);
void* dso_handle;
};
class atfork_list_t {
public:
constexpr atfork_list_t() : first_(nullptr), last_(nullptr) {}
template<typename F>
void walk_forward(F f) {
for (atfork_t* it = first_; it != nullptr; it = it->next) {
f(it);
}
}
template<typename F>
void walk_backwards(F f) {
for (atfork_t* it = last_; it != nullptr; it = it->prev) {
f(it);
}
}
void push_back(atfork_t* entry) {
entry->next = nullptr;
entry->prev = last_;
if (entry->prev != nullptr) {
entry->prev->next = entry;
}
if (first_ == nullptr) {
first_ = entry;
}
last_ = entry;
}
template<typename F>
void remove_if(F predicate) {
atfork_t* it = first_;
while (it != nullptr) {
if (predicate(it)) {
atfork_t* entry = it;
it = it->next;
remove(entry);
} else {
it = it->next;
}
}
}
private:
void remove(atfork_t* entry) {
if (entry->prev != nullptr) {
entry->prev->next = entry->next;
} else {
first_ = entry->next;
}
if (entry->next != nullptr) {
entry->next->prev = entry->prev;
} else {
last_ = entry->prev;
}
free(entry);
}
atfork_t* first_;
atfork_t* last_;
DISALLOW_COPY_AND_ASSIGN(atfork_list_t);
};
static pthread_mutex_t g_atfork_list_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
static atfork_list_t g_atfork_list;
void __bionic_atfork_run_prepare() {
// We lock the atfork list here, unlock it in the parent, and reset it in the child.
// This ensures that nobody can modify the handler array between the calls
// to the prepare and parent/child handlers.
pthread_mutex_lock(&g_atfork_list_mutex);
// Call pthread_atfork() prepare handlers. POSIX states that the prepare
// handlers should be called in the reverse order of the parent/child
// handlers, so we iterate backwards.
g_atfork_list.walk_backwards([](atfork_t* it) {
if (it->prepare != nullptr) {
it->prepare();
}
});
}
void __bionic_atfork_run_child() {
g_atfork_list_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_lock(&g_atfork_list_mutex);
g_atfork_list.walk_forward([](atfork_t* it) {
if (it->child != nullptr) {
it->child();
}
});
pthread_mutex_unlock(&g_atfork_list_mutex);
}
void __bionic_atfork_run_parent() {
g_atfork_list.walk_forward([](atfork_t* it) {
if (it->parent != nullptr) {
it->parent();
}
});
pthread_mutex_unlock(&g_atfork_list_mutex);
}
// __register_atfork is the name used by glibc
extern "C" int __register_atfork(void (*prepare)(void), void (*parent)(void),
void(*child)(void), void* dso) {
atfork_t* entry = reinterpret_cast<atfork_t*>(malloc(sizeof(atfork_t)));
if (entry == nullptr) {
return ENOMEM;
}
entry->prepare = prepare;
entry->parent = parent;
entry->child = child;
entry->dso_handle = dso;
pthread_mutex_lock(&g_atfork_list_mutex);
g_atfork_list.push_back(entry);
pthread_mutex_unlock(&g_atfork_list_mutex);
return 0;
}
extern "C" __LIBC_HIDDEN__ void __unregister_atfork(void* dso) {
pthread_mutex_lock(&g_atfork_list_mutex);
g_atfork_list.remove_if([&](const atfork_t* entry) {
return entry->dso_handle == dso;
});
pthread_mutex_unlock(&g_atfork_list_mutex);
}