 Ruby_Grant_2014_Report.md

Threading Support for Byebug
Motivation
Having a fully featured stable debugger is important for most programming languages. It makes thelanguage more attractive for beginners and for users coming from other languages, because it's a very
adequate tool not only for bug fixing but also just for playing around with a language's features orstudying code not written by ourselves. With this in mind, the main purpose of Byebug since it wasstarted was to make it an atractive tool for beginners (I was actually a Ruby beginner during the initialdevelopment phase of Byebug so I was making heavy use of my own tool too).
The main features supported by Byebug are:Breaking. Pause the program at some event or specified instruction, to examine the current state.
Related commands: break , catch , condition , delete , enable , disable .
Analyzing. Studying program status at a certain point during its execution (including right aftertermination). Specifically, we can:Inspect and move around the backtrace ( backtrace , up , down and frame commands).
Have a basic REPL functionality, evaluating custom code ( eval , irb , pry , method , pp ,ps , putl , var commands).

Look and change the program's source code ( edit , list , info commands).
Stepping: Running your program one line or instruction at a time, or until specific points in the
program are reached. Related commands: step , next , continue , finish , kill , quit ,restart .

Tracking: Keeping track of the different values of your variables or the different lines executed byyour program. Related commands: display , undisplay , tracevar , untracevar , set
linetrace .

This features have been working very well as long as the debugged program would have no multiple
Ruby threads, but would just not work when the program would use different threads. Notice that thiswould affect developers making use of threads, but was also affecting users not necessarily knowing
anything about threads, because very well know libraries out there transparently make use of them (for

example, capybara-webkit or Ruby's stdlib Timeout module).
So Byebug needed a way to debug multithreaded programs that was both:
Reliable: no deadlock, no killed threads when they are not related to user's code.
Useful: allow debugging issues with multithreaded programs. To do that, we would need toprovide the user with the ability to stop/resume specific threads, list active threads and switch
between threads.
This is what this grant is about.

The feature
The addition of threading support to Byebug's debugger allows users to properly debug programs
making use of Ruby's threads. This includes listing active threads and their statuses, switchingexecution to specific threads and temporarily pausing/resuming threads.
To try out the feature, you might want to use a real application (a Rails app for example) using threads
or just follow the sample session about threads included in Byebug's Guide. See here for details.
The feature is also fully tested. You can clone byebug's repo and then run
bundle install # Install dependencies
rake compile # Compile the C-extension
ruby -w -Ilib test/test_helper.rb test/commands/thread_test.rb

This is the list of available commands and a short explanation of its usage:
thread list: Lists threads. This is equivalent to Ruby's Thread.list , but it has the following
format:A mark '+' for the current thread.
A mark '$' for a stopped thread.
An internal id for the thread, specific to Byebug.
Ruby's id and status for the thread, in the format #<Thread:0x0123456789ABCD(run|sleep)> . In Ruby 2.2.x, also the file and line number where the thread is defined areincluded. This feature is very useful to correctly identify threads, because otherwise the only
way to tell which thread is which is from the order they are defined.
Current file and line number location of the thread's execution, in the format file:line .
thread current: Shows the thread list entry for the current thread, just like the frame
command shows the current frame whereas the backtrace command shows the wholebacktrace.
thread stop: Allows the user to temporarily stop the execution of a thread. This is useful when wewant to focus on debugging specific threads and want to make sure some other thread stays
unchanged, or if we want our main thread to "wait for us" and don't finish until we tell it to.
thread resume: Allows resuming threads previously stopped with thread stop . It can be used toresume normal program execution, once we've introduced a change that could fix our issue, forexample.
thread switch: Switches current thread and context to another thread. After issuing this command,
execution will be stopped in a different place in the source code and we'll get a different
backtrace. The target thread can't be in the sleeping state so we might have to issue the threadresume command before running this command.

The implementation
The TracePoint API was not well suited for this feature. It includes a THREAD_BEGIN and a
THREAD_END events, but they are generated when the execution is first delegated to the thread and

not when the thread is created. We want the threads to be available to the user (in a "sleep" state)from their creation, so we need to resort to "other trickery".

Mantaining a global thread list
Byebug mantains a global hash table of active threads which is constructed dinamically as TracePoint
API events are received. Every time an event is processed, we look for a thread matching the current
thread in our threads table and we set up that context to be the current context (when we talk about"context" in Byebug we mean the program's state in a specific moment during its execution). If thethread is not found (first event of the thread), we create a new entry in the table. This is done by thethread_context_lookup method in threads.c .

Periodically, the table is cleaned up of dead threads, using the cleanup_dead_threads method inthreads.c . This method needs to make use of Ruby because the C-extension API does not seem tohave utilities to check for thread status. This might be a big performance penalty for programs using a
big number of threads, so at some point we might want to either have a rb_thread_status method
available to C-extensions, or add a workaround inside Byebug such as not cleaning the threads tablefor every event but only every "N" events, where "N" is big enough so this cleanup is not a
performance bottleneck anymore. Nevertheless, I've tried latest byebug with some Rails apps andhaven't noticed any performance issues.

Thread syncronization

The biggest challenge of implementing threading in Byebug has been this one. While our user is
stopping at his Byebug prompt, the scheduler can (and does) schedule different threads to be run, so
TracePoint API events are generated for other threads. We want everything halted while the user is incontrol so we need to lock the processing of this events until the user gives control back to the
debugger. To do that we've used a global lock in the C-extension, that ensures that a single
TracePoint API event is processed at the same time.
At the beginning of the processing of every event, we call the acquire_lock method that will either:
Obtain the global lock if it's free (or the current thread already has it because the previous event
was also from the same thread). In this case, the event is processed normally.
Go to sleep and pass execution on to another thread if the lock is currently being hold by anotherthread. Notice that we need to specifically call rb_thread_stop here because C-extensions are
not preemptive in the sense that the scheduler won't automatically switch thread execution while
in a C-extension just like it does when running "regular Ruby code". So if we don't call
rb_thread_stop , the execution will just deadlock here.

At the end of the processing of every event, we call the release_lock method that will release thelock and pass on the execution to another thread (that will probably be halted in acquire_lock andwill be able to pass through once the lock is released.

Specifics of some commands
thread list
We currently manually syncronize our thread list with the one given by Ruby ( Thread.list ) when
this command is executed, but once this feature is well tested we can probably get rid of that checkand just trust our table that should always be up to date and exactly the same as Thread.list .

thread stop
To implement this command, we needed to add some global flags. A function rb_thread_stop isavailable for C-extensions to stop the current thread, but when the user issues this command, thetarget thread is not the current thread so we can't directly use that method as it doesn't accept a target
thread argument. Instead, we set a global flag, CTX_FL_SUSPEND and check that in acquire_lock toprevent thread execution. So even if the global lock is free, we never delegate execution to thesuspended thread.

thread resume
The only specific comment about the implementation of this command is the CTX_FL_WAS_RUNNING
flag. This flag is used to remember the thread's status when a thread was suspended so the threadresume command can correctly restore it. It CTX_FL_WAS_RUNNING is set when we run threadresume we need to call rb_thread_wakeup to restore the "running" status.

thread switch
This command was a bit problematic. Users will probably expect nothing to be run when they issue
this command, just a "context change". However, we actually need to let program's execution to
succesfully achieve the context change, so that the new "current thread" is the one we are switching toand we can properly show backtrace and file location information for the new thread.
So the idea here is to force the scheduler to inmediately delegate execution control to the target threadso that the next TracePoint API event generated belongs to that thread and we can inmediately stop
execution again without running anything else. To achieve this, thread switch does the following:
Saves the target thread in a global next_thread variable.
Sets a breakpoint for the next event to be receive.
Releases the user prompt and gives control back to the debugger.
To make this work, we need to change the way we release_lock after every event has beenprocessed. We needn't just release the lock but also force the scheduler the give control to the thread
specified by next_thread . To implement this, we add a double linked list where we mantain the list of
threads whose execution is being hold by Byebug's global lock. Threads are added to this list in
acquire_lock and removed in release_lock . In the release_lock method we pop next_threadfrom the list if next_thread is set or any thread otherwise. Then we call rb_thread_run on thepopped thread to delegate control to that thread.

Byebug's REPL and threads
Byebug's debugger includes a REPL aside from it's built-in commands. Anything that's not recognized
as a Byebug command will be automatically evaluated as Ruby code. This has proven to be a very
useful feature for users to the point the some people consider Byebug as an irb or pry alternative.
The new threading feature would not play nice with the REPL when the command to be evaluatedincluded thread stuff. Users would get either a 'No threads alive. deadlock?' error or a proper
deadlock. For an example of this issues, have a look at here.
This would happen because byebug 's global lock wouldn't be released before evaluating stuff, so if
an evaluated command created new threads or switched to previously created threads, we would get adeadlock because those threads wouldn't be able to run because thread execution would be hold by
Byebug's current thread.
To solve this issues, we exposed to Ruby a couple of methods Byebug.lock and Byebug.unlock towould call acquire_lock and release_lock , and then implemented the following method:def allowing_other_threads
Byebug.unlock
res = yield
Byebug.lockresend

and call it before evaluating anything in Byebug's prompt. This solved issues when evaluating stuff
from the user's prompt.

The code
After the explanation of the current implementation, I think we've gone through every bit of code
relating to threads. I copy the relevant file threads.c in the C-extension for completeness.
#include <byebug.h>/* Threads table class */static VALUE cThreadsTable;/* If not Qnil, holds the next thread that must be run */VALUE next_thread = Qnil;/* To allow thread syncronization, we must stop threads when debugging */VALUE locker = Qnil;
static int
t_tbl_mark_keyvalue(st_data_t key, st_data_t value, st_data_t tbl){
UNUSED(tbl);
rb_gc_mark((VALUE) key);if (!value)
return ST_CONTINUE;
rb_gc_mark((VALUE) value);
return ST_CONTINUE;}
static void
t_tbl_mark(void *data){
threads_table_t *t_tbl = (threads_table_t *) data;
st_table *tbl = t_tbl->tbl;

st_foreach(tbl, t_tbl_mark_keyvalue, (st_data_t) tbl);}
static void
t_tbl_free(void *data){
threads_table_t *t_tbl = (threads_table_t *) data;
st_free_table(t_tbl->tbl);
xfree(t_tbl);}/** Creates a numeric hash whose keys are the currently active threads and* whose values are their associated contexts.
*/
VALUE
create_threads_table(void){
threads_table_t *t_tbl;
t_tbl = ALLOC(threads_table_t);t_tbl->tbl = st_init_numtable();return Data_Wrap_Struct(cThreadsTable, t_tbl_mark, t_tbl_free, t_tbl);}/** Checks a single entry in the threads table.
** If it has no associated context or the key doesn't correspond to a living* thread, the entry is removed from the thread's list.
*/
static int
check_thread_i(st_data_t key, st_data_t value, st_data_t data){
UNUSED(data);
if (!value)
return ST_DELETE;
if (!is_living_thread((VALUE) key))
return ST_DELETE;
return ST_CONTINUE;}/** Checks whether a thread is either in the running or sleeping state.
*/int
is_living_thread(VALUE thread){
VALUE status = rb_funcall(thread, rb_intern("status"), 0);
if (NIL_P(status) || status == Qfalse)
return 0;
if (rb_str_cmp(status, rb_str_new2("run")) == 0|| rb_str_cmp(status, rb_str_new2("sleep")) == 0)
return 1;
return 0;}/** Checks threads table for dead/finished threads.
*/
voidcleanup_dead_threads(void){
threads_table_t *t_tbl;
Data_Get_Struct(threads, threads_table_t, t_tbl);
st_foreach(t_tbl->tbl, check_thread_i, 0);}/** Looks up a context in the threads table. If not present, it creates it.
*/
voidthread_context_lookup(VALUE thread, VALUE * context){
threads_table_t *t_tbl;
Data_Get_Struct(threads, threads_table_t, t_tbl);
if (!st_lookup(t_tbl->tbl, thread, context) || !*context){*context = context_create(thread);st_insert(t_tbl->tbl, thread, *context);}}/** Holds thread execution while another thread is active.
** Thanks to this, all threads are "frozen" while the user is typing commands.
*/
void
acquire_lock(debug_context_t * dc){
while ((!NIL_P(locker) && locker != rb_thread_current())

|| CTX_FL_TEST(dc, CTX_FL_SUSPEND)){
add_to_locked(rb_thread_current());
rb_thread_stop();if (CTX_FL_TEST(dc, CTX_FL_SUSPEND))
CTX_FL_SET(dc, CTX_FL_WAS_RUNNING);}
locker = rb_thread_current();}/** Releases our global lock and passes execution on to another thread, either* the thread specified by +next_thread+ or any other thread if +next_thread+* is nil.
*/voidrelease_lock(void){
VALUE thread;cleanup_dead_threads();
locker = Qnil;
if (NIL_P(next_thread))thread = pop_from_locked();else{
remove_from_locked(next_thread);thread = next_thread;}if (thread == next_thread)
next_thread = Qnil;
if (!NIL_P(thread) && is_living_thread(thread))rb_thread_run(thread);}/** call-seq:*
Byebug.unlock -> nil** Unlocks global switch so other threads can run.
*/static VALUE
Unlock(VALUE self){
UNUSED(self);release_lock();
return locker;}/** call-seq:*
Byebug.lock -> Thread.current** Locks global switch to reserve execution to current thread exclusively.
*/
static VALUE
Lock(VALUE self){
debug_context_t *dc;
VALUE context;
UNUSED(self);if (!is_living_thread(rb_thread_current()))
rb_raise(rb_eRuntimeError, "Current thread is dead!");thread_context_lookup(rb_thread_current(), &context);
Data_Get_Struct(context, debug_context_t, dc);
acquire_lock(dc);return locker;}/***
Document-class: ThreadsTable**== Sumary**
Hash table holding currently active threads and their associated contexts*/
void
Init_threads_table(VALUE mByebug){
cThreadsTable = rb_define_class_under(mByebug, "ThreadsTable", rb_cObject);rb_define_module_function(mByebug, "unlock", Unlock, 0);
rb_define_module_function(mByebug, "lock", Lock, 0);}

Future work

With the tasks performed in this grant, threading support is finished. The next tasks will be to makesure the feature is working fine for our users and fix any issues that might come up.
Regarding Byebug as a whole, the idea is that the next major release will include a full rewrite / review
of remote debugging support, and will make sure that editor plugins or graphical debuggers can easily
use byebug under the hood.