00:00:15.599
Hello everyone! As Matt mentioned, I’m a software engineer at Rookout. I have a background in low-level programming, and I really enjoy learning and using various programming languages. In my role, I mainly work with Ruby, Java, and Go, but I also dabble in C#, Python, JavaScript, C++, and Swift at home. It's a satisfying blend of languages, and I love it!
00:00:44.580
At Rookout, we are developing a debugger designed for production environments. Unlike traditional debuggers, which pause your code at breakpoints, our debugger allows your application to keep running. You can inspect the state of your code at any moment without stopping it, which is incredibly useful for debugging in production or other environments beyond local development.
00:01:14.520
One day, my boss asked me to add support for JRuby in one of our debuggers. I didn’t know much about JRuby at that point, so I went online to do some research. According to the JRuby Wiki, JRuby is a Java implementation of the Ruby programming language, meaning it runs Ruby on top of the Java Virtual Machine (JVM). This is a pretty exciting concept! I started with a simple tutorial app to experiment with JRuby. My app was a basic to-do list where I could add and remove tasks. I wanted to ensure it worked on JRuby, so I used RVM (Ruby Version Manager) for managing my Ruby versions. Luckily, RVM supports JRuby right out of the box. Initially, I needed to install JRuby using RVM, but after that, it was straightforward.
00:05:05.160
The first time I ran my app, I had to execute `bundle install`. Back then, I encountered various errors that prevented my app from running smoothly. I spent three days trying to resolve these issues, researching and reinstalling JRuby and RVM multiple times without success. Eventually, I discovered that I had set the `force_ruby_platform` option to `true`, which caused Bundler to forcibly utilize native extensions. However, JRuby does not support native extensions, leading to the conflicts I was facing. Once I disabled this option, everything worked perfectly. My to-do app was now functioning on JRuby, which was pretty exciting!
00:10:41.400
Now, I needed to implement JRuby support within one of Rookout's debuggers. Rookout's Java debugger can support any language built on top of the JVM, including JRuby. I had to decide whether to enhance the Java debugger for JRuby support or the Ruby debugger. In the Java debugger, we used the JVM API, which required a deeper understanding of how JRuby operates. By contrast, the Ruby API only needed to implement specific functions in JRuby, which made it easier. However, using the JVM API would potentially allow debugging not just JRuby code but also external Java libraries. On the other side, the Java implementation involved more boilerplate and necessitated a deeper exploration of JRuby's internals due to potential discrepancies.
00:19:52.920
I opted to start with the Ruby debugger, as it seemed more straightforward. I broke the task into smaller parts: first, I needed to run an app with the debugger attached, then add a breakpoint, and finally trigger that breakpoint to collect data. Attaching our debugger required simply adding the Rookout gem and initializing it—however, I initially encountered exceptions indicating unknown events related to trace points. Understanding trace points was key, as they allow us to subscribe to events in Ruby code. Various events trigger callbacks for method calls and returns, crucial for developing an effective debugger.
00:25:22.120
I discovered that I needed to invoke the 'script compiled' event to collect information on newly compiled methods, which included the instruction sequence. This enabled me to set breakpoints dynamically based on the instruction sequences for each file. However, I found that JRuby lagged behind in this area of the trace point API, making it difficult to utilize certain functionality necessary for my debugging. I had the option to enable trace points without parameters, but this would significantly impact performance. Instead, I pivoted back to the Java debugger because of those advantages it offered.
00:28:52.500
With the Java debugger, I implemented a Java agent that gets executed along with the main JVM application. This agent has access to the Instrumentation API, which allows us to manipulate classes on the fly. Implementing this required sending the agent as a JVM option to JRuby. Once connected, I verified that each app was correctly hooked up to the debugger, allowing me to further develop the debugging capabilities with breakpoints and data collection. It was exciting because as I continued to explore how JRuby worked, I realized that its compilation settings directly influenced performance. JRuby has three compilation modes: interpretation, compilation, and JIT (just-in-time). Understanding these allowed me to manipulate it effectively.
00:36:04.200
By exploring these modes, I learned how to influence the JVM's behavior. For example, I can force JRuby to compile everything at startup, enhancing performance. However, I had to balance this with the startup time, as compiling libraries on the first load takes longer. I worked through these challenges, finding that once I applied these techniques, my debugger could successfully collect data during execution. I also discovered I could place breakpoints and gather local variables efficiently, illuminating how JRuby handles variable scoping and execution flow.
00:38:39.960
Throughout this process, I learned to embrace setbacks and frustrations. While building a JRuby debugger seemed straightforward, it involved significant complexity that I had underestimated. After weeks of effort, I realized I was the first person to create a Java-based JRuby debugger, which made the challenges worth it! I also learned the importance of communication while working on such projects. Engaging with the community and developers behind tools can facilitate solutions and make debugging smoother.