Ruby

Keynote: Ruby & JVM: A (JRuby) Love Story

Keynote: Ruby & JVM: A (JRuby) Love Story

by Yarden Laifenfeld

In the keynote titled "Ruby & JVM: A (JRuby) Love Story," delivered at RubyConf TH 2022 by speaker Yarden Laifenfeld, the presentation explores the integration of JRuby—an implementation of Ruby that runs on the Java Virtual Machine (JVM)—into debugging environments for production applications. Laifenfeld shares insights from his experience at Rookout, where he works on a unique debugger that operates without halting code execution. Key points from the talk include:

  • Background and Role: Laifenfeld, a software engineer, describes his diverse programming background, predominantly in Ruby, Java, and Go, and introduces Rookout's production debugger. Unlike traditional debuggers, Rookout's allows continuous application performance while inspecting the code's state.
  • Understanding JRuby: He discusses his initial unfamiliarity with JRuby and the research he conducted to grasp its operation atop the JVM, along with a personal experience of developing a to-do list application to understand JRuby's functionalities.
  • Building JRuby Support: The speaker outlines his process of enhancing Rookout's debugger for JRuby integration. He weighs the pros and cons of using the Java debugger versus the Ruby debugger, eventually opting for the simpler Ruby debugger implementation while highlighting key components like attaching the debugger and managing trace points.
  • Challenges Faced: Laifenfeld elaborates on the initial challenges he faced, including configuration issues with Bundler due to JRuby's lack of support for native extensions. He also expresses the complexity of implementing features that allow the debugging of JRuby applications efficiently.
  • Performance Insights: He explains different JRuby compilation modes (interpretation, compilation, JIT), emphasizing how he manipulated these modes to optimize performance, especially concerning startup times and data collection during execution.
  • Reflection and Community Engagement: Throughout the project, he reflects on the importance of overcoming setbacks and learning from community interactions. Ultimately, he finds value in the challenges of being the first to develop a Java-based JRuby debugger.
  • Final Thoughts: Laifenfeld concludes that the choice between JRuby and CRuby should be based on application needs and highlights the advantages of each, particularly recommending JRuby for long-running applications while acknowledging CRuby's broader community support.

This engaging talk emphasizes the potential of JRuby within the Ruby ecosystem, encouraging developers to explore its capabilities and consider it for future projects.

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.