Files
scylladb/core/thread.cc
Avi Kivity edcd346d83 thread: fix thread-created-within-thread
The thread switching code assumed that we will always switch out of a
thread due to being blocked on an unavailable future.  This allows
the core to store the blocked thread's context in the synthetic
continuation chained to that future (which switched back to that thread).

That assumption failed in one place: when we create a thread from within a
thread.  In that case we switch to the new thread immediately, but forget
all about the old thread.  We never come back to the old thread, and anything
that depends on it hangs.

Fix by creating a linked list of active thread contexts.  These are all
threads that have been "preempted" by the act of creating a new thread,
terminated by the main, unthreaded, reactor context.  This gives us a place
to store those threads and we come back to them and continue where we left
off.

Reported by Pekka.
2015-06-16 17:04:02 +03:00

125 lines
3.1 KiB
C++

/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. 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.
*/
/*
* Copyright (C) 2015 Cloudius Systems, Ltd.
*/
#include "thread.hh"
#include "posix.hh"
#include <ucontext.h>
/// \cond internal
namespace seastar {
thread_local jmp_buf_link g_unthreaded_context;
thread_local jmp_buf_link* g_current_context;
thread_context::thread_context(std::function<void ()> func)
: _func(std::move(func)) {
setup();
}
void
thread_context::setup() {
// use setcontext() for the initial jump, as it allows us
// to set up a stack, but continue with longjmp() as it's
// much faster.
ucontext_t initial_context;
auto q = uint64_t(reinterpret_cast<uintptr_t>(this));
auto main = reinterpret_cast<void (*)()>(&thread_context::s_main);
auto r = getcontext(&initial_context);
throw_system_error_on(r == -1);
initial_context.uc_stack.ss_sp = _stack.get();
initial_context.uc_stack.ss_size = _stack_size;
initial_context.uc_link = nullptr;
makecontext(&initial_context, main, 2, int(q), int(q >> 32));
auto prev = g_current_context;
_context.link = prev;
_context.thread = this;
g_current_context = &_context;
if (setjmp(prev->jmpbuf) == 0) {
setcontext(&initial_context);
}
}
void
thread_context::switch_in() {
// FIXME: use setjmp/longjmp after initial_switch_in, much faster
auto prev = g_current_context;
g_current_context = &_context;
_context.link = prev;
if (setjmp(prev->jmpbuf) == 0) {
longjmp(_context.jmpbuf, 1);
}
}
void
thread_context::switch_out() {
g_current_context = _context.link;
if (setjmp(_context.jmpbuf) == 0) {
longjmp(g_current_context->jmpbuf, 1);
}
}
void
thread_context::s_main(unsigned int lo, unsigned int hi) {
uintptr_t q = lo | (uint64_t(hi) << 32);
reinterpret_cast<thread_context*>(q)->main();
}
void
thread_context::main() {
try {
_func();
_done.set_value();
} catch (...) {
_done.set_exception(std::current_exception());
}
g_current_context = _context.link;
longjmp(g_current_context->jmpbuf, 1);
}
namespace thread_impl {
thread_context* get() {
return g_current_context->thread;
}
void switch_in(thread_context* to) {
to->switch_in();
}
void switch_out(thread_context* from) {
from->switch_out();
}
void init() {
g_unthreaded_context.link = nullptr;
g_unthreaded_context.thread = nullptr;
g_current_context = &g_unthreaded_context;
}
}
}
/// \endcond