Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop & start the world (undocumented API) #14729

Merged
36 changes: 36 additions & 0 deletions src/crystal/system/thread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ module Crystal::System::Thread
# private def stack_address : Void*

# private def system_name=(String) : String

# def self.init_suspend_resume : Nil

# private def system_suspend : Nil

# private def system_wait_suspended : Nil

# private def system_resume : Nil
end

{% if flag?(:wasi) %}
Expand Down Expand Up @@ -66,6 +74,14 @@ class Thread
@@threads.try(&.unsafe_each { |thread| yield thread })
end

def self.lock : Nil
[email protected]
end

def self.unlock : Nil
[email protected]
end

# Creates and starts a new system thread.
def initialize(@name : String? = nil, &@func : Thread ->)
@system_handle = uninitialized Crystal::System::Thread::Handle
Expand Down Expand Up @@ -168,6 +184,26 @@ class Thread

# Holds the GC thread handler
property gc_thread_handler : Void* = Pointer(Void).null

def suspend : Nil
system_suspend
end

def wait_suspended : Nil
system_wait_suspended
end

def resume : Nil
system_resume
end
ysbaddaden marked this conversation as resolved.
Show resolved Hide resolved

def self.stop_world : Nil
GC.stop_world
end

def self.start_world : Nil
GC.start_world
end
end

require "./thread_linked_list"
Expand Down
75 changes: 75 additions & 0 deletions src/crystal/system/unix/pthread.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "c/pthread"
require "c/sched"
require "../panic"

module Crystal::System::Thread
alias Handle = LibC::PthreadT
Expand Down Expand Up @@ -153,6 +154,80 @@ module Crystal::System::Thread
{% end %}
name
end

@suspended = Atomic(Bool).new(false)

def self.init_suspend_resume : Nil
install_sig_suspend_signal_handler
install_sig_resume_signal_handler
end

private def self.install_sig_suspend_signal_handler
action = LibC::Sigaction.new
action.sa_flags = LibC::SA_SIGINFO
action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _|
# notify that the thread has been interrupted
[email protected](true)

# block all signals but SIG_RESUME
mask = LibC::SigsetT.new
LibC.sigfillset(pointerof(mask))
LibC.sigdelset(pointerof(mask), SIG_RESUME)

# suspend the thread until it receives the SIG_RESUME signal
LibC.sigsuspend(pointerof(mask))
end
LibC.sigemptyset(pointerof(action.@sa_mask))
LibC.sigaction(SIG_SUSPEND, pointerof(action), nil)
end

private def self.install_sig_resume_signal_handler
action = LibC::Sigaction.new
action.sa_flags = 0
action.sa_sigaction = LibC::SigactionHandlerT.new do |_, _, _|
# do nothing (a handler is still required to receive the signal)
end
LibC.sigemptyset(pointerof(action.@sa_mask))
LibC.sigaction(SIG_RESUME, pointerof(action), nil)
end

private def system_suspend : Nil
@suspended.set(false)

if LibC.pthread_kill(@system_handle, SIG_SUSPEND) == -1
System.panic("pthread_kill()", Errno.value)
end
end

private def system_wait_suspended : Nil
until @suspended.get
Thread.yield_current
end
end

private def system_resume : Nil
if LibC.pthread_kill(@system_handle, SIG_RESUME) == -1
System.panic("pthread_kill()", Errno.value)
end
end

# the suspend/resume signals follow BDWGC

private SIG_SUSPEND =
{% if flag?(:linux) %}
LibC::SIGPWR
{% elsif LibC.has_constant?(:SIGRTMIN) %}
LibC::SIGRTMIN + 6
{% else %}
LibC::SIGXFSZ
{% end %}

private SIG_RESUME =
{% if LibC.has_constant?(:SIGRTMIN) %}
LibC::SIGRTMIN + 5
{% else %}
LibC::SIGXCPU
{% end %}
end

# In musl (alpine) the calls to unwind API segfaults
Expand Down
15 changes: 15 additions & 0 deletions src/crystal/system/wasi/thread.cr
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,19 @@ module Crystal::System::Thread
# TODO: Implement
Pointer(Void).null
end

def self.init_suspend_resume : Nil
end

private def system_suspend : Nil
raise NotImplementedError.new("Crystal::System::Thread.system_suspend")
end

private def system_wait_suspended : Nil
raise NotImplementedError.new("Crystal::System::Thread.system_wait_suspended")
end

private def system_resume : Nil
raise NotImplementedError.new("Crystal::System::Thread.system_resume")
end
end
28 changes: 28 additions & 0 deletions src/crystal/system/win32/thread.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "c/processthreadsapi"
require "c/synchapi"
require "../panic"

module Crystal::System::Thread
alias Handle = LibC::HANDLE
Expand Down Expand Up @@ -87,4 +88,31 @@ module Crystal::System::Thread
{% end %}
name
end

def self.init_suspend_resume : Nil
end

private def system_suspend : Nil
if LibC.SuspendThread(@system_handle) == -1
Crystal::System.panic("SuspendThread()", WinError.value)
end
end

private def system_wait_suspended : Nil
# context must be aligned on 16 bytes but we lack a mean to force the
# alignment on the struct, so we overallocate then realign the pointer:
local = uninitialized UInt8[sizeof(Tuple(LibC::CONTEXT, UInt8[15]))]
thread_context = Pointer(LibC::CONTEXT).new(local.to_unsafe.address &+ 15_u64 & ~15_u64)
thread_context.value.contextFlags = LibC::CONTEXT_FULL

if LibC.GetThreadContext(@system_handle, thread_context) == -1
Crystal::System.panic("GetThreadContext()", WinError.value)
end
end

private def system_resume : Nil
if LibC.ResumeThread(@system_handle) == -1
Crystal::System.panic("ResumeThread()", WinError.value)
end
end
end
13 changes: 13 additions & 0 deletions src/gc/boehm.cr
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ lib LibGC
alias WarnProc = LibC::Char*, Word ->
fun set_warn_proc = GC_set_warn_proc(WarnProc)
$warn_proc = GC_current_warn_proc : WarnProc

fun stop_world_external = GC_stop_world_external
fun start_world_external = GC_start_world_external
end

module GC
Expand Down Expand Up @@ -470,4 +473,14 @@ module GC
GC.unlock_write
end
{% end %}

# :nodoc:
def self.stop_world : Nil
LibGC.stop_world_external
end

# :nodoc:
def self.start_world : Nil
LibGC.start_world_external
end
end
54 changes: 54 additions & 0 deletions src/gc/none.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require "crystal/tracing"

module GC
def self.init
Crystal::System::Thread.init_suspend_resume
end

# :nodoc:
Expand Down Expand Up @@ -136,4 +137,57 @@ module GC
# :nodoc:
def self.push_stack(stack_top, stack_bottom)
end

# Stop and start the world.
#
# This isn't a GC-safe stop-the-world implementation (it may allocate objects
# while stopping the world), but the guarantees are enough for the purpose of
# gc_none. It could be GC-safe if Thread::LinkedList(T) became a struct, and
# Thread::Mutex either became a struct or provide low level abstraction
# methods that directly interact with syscalls (without allocating).
#
# Thread safety is guaranteed by the mutex in Thread::LinkedList: either a
# thread is starting and hasn't added itself to the list (it will block until
# it can acquire the lock), or is currently adding itself (the current thread
# will block until it can acquire the lock).
#
# In both cases there can't be a deadlock since we won't suspend another
# thread until it has successfuly added (or removed) itself to (from) the
# linked list and released the lock, and the other thread won't progress until
# it can add (or remove) itself from the list.
#
# Finally, we lock the mutex and keep it locked until we resume the world, so
# any thread waiting on the mutex will only be resumed when the world is
# resumed.

# :nodoc:
def self.stop_world : Nil
current_thread = Thread.current

# grab the lock (and keep it until the world is restarted)
Thread.lock

# tell all threads to stop (async)
Thread.unsafe_each do |thread|
thread.suspend unless thread == current_thread
end

# wait for all threads to have stopped
Thread.unsafe_each do |thread|
thread.wait_suspended unless thread == current_thread
end
end

# :nodoc:
def self.start_world : Nil
current_thread = Thread.current

# tell all threads to resume
Thread.unsafe_each do |thread|
thread.resume unless thread == current_thread
end

# finally, we can release the lock
Thread.unlock
end
end
2 changes: 2 additions & 0 deletions src/lib_c/aarch64-android/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ lib LibC

fun kill(__pid : PidT, __signal : Int) : Int
fun pthread_sigmask(__how : Int, __new_set : SigsetT*, __old_set : SigsetT*) : Int
fun pthread_kill(__thread : PthreadT, __sig : Int) : Int
fun sigaction(__signal : Int, __new_action : Sigaction*, __old_action : Sigaction*) : Int
fun sigaltstack(__new_signal_stack : StackT*, __old_signal_stack : StackT*) : Int
{% if ANDROID_API >= 21 %}
Expand All @@ -89,5 +90,6 @@ lib LibC
fun sigaddset(__set : SigsetT*, __signal : Int) : Int
fun sigdelset(__set : SigsetT*, __signal : Int) : Int
fun sigismember(__set : SigsetT*, __signal : Int) : Int
fun sigsuspend(__mask : SigsetT*) : Int
{% end %}
end
2 changes: 2 additions & 0 deletions src/lib_c/aarch64-darwin/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ lib LibC

fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
Expand All @@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
fun sigsuspend(SigsetT*) : Int
end
2 changes: 2 additions & 0 deletions src/lib_c/aarch64-linux-gnu/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ lib LibC

fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
Expand All @@ -86,4 +87,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
fun sigsuspend(SigsetT*) : Int
end
2 changes: 2 additions & 0 deletions src/lib_c/aarch64-linux-musl/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ lib LibC

fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
Expand All @@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
fun sigsuspend(SigsetT*) : Int
end
2 changes: 2 additions & 0 deletions src/lib_c/arm-linux-gnueabihf/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ lib LibC

fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
Expand All @@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
fun sigsuspend(SigsetT*) : Int
end
2 changes: 2 additions & 0 deletions src/lib_c/i386-linux-gnu/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ lib LibC

fun kill(pid : PidT, sig : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
fun pthread_kill(PthreadT, Int) : Int
fun signal(sig : Int, handler : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
Expand All @@ -85,4 +86,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
fun sigsuspend(SigsetT*) : Int
end
2 changes: 2 additions & 0 deletions src/lib_c/i386-linux-musl/c/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ lib LibC

fun kill(x0 : PidT, x1 : Int) : Int
fun pthread_sigmask(Int, SigsetT*, SigsetT*) : Int
fun pthread_kill(PthreadT, Int) : Int
fun signal(x0 : Int, x1 : Int -> Void) : Int -> Void
fun sigaction(x0 : Int, x1 : Sigaction*, x2 : Sigaction*) : Int
fun sigaltstack(x0 : StackT*, x1 : StackT*) : Int
Expand All @@ -84,4 +85,5 @@ lib LibC
fun sigaddset(SigsetT*, Int) : Int
fun sigdelset(SigsetT*, Int) : Int
fun sigismember(SigsetT*, Int) : Int
fun sigsuspend(SigsetT*) : Int
end
Loading
Loading