◐ Shell
reader mode source ↗
Skip to content

attempt at a proper fix for the BiVariableMap memory leak#9452

Draft
matthias-fratz-bsz wants to merge 2 commits into
jruby:masterfrom
matthias-fratz-bsz:bvm-refactoring
Draft

attempt at a proper fix for the BiVariableMap memory leak#9452
matthias-fratz-bsz wants to merge 2 commits into
jruby:masterfrom
matthias-fratz-bsz:bvm-refactoring

Conversation

@matthias-fratz-bsz

Copy link
Copy Markdown
Contributor

This is an attempt at refactoring the BiVariableMap interface so it no longer keeps a copy of every single Ruby variable. It does still have to store all local variables, but for everything else it can retrieve them from either the specified receiver object or the Ruby runtime's global state.

This means instance varibles are no longer stored in BiVariableMap, thus they can no longer cause the #8527 memory leak. It should also improve performance somewhat because it avoids a lot of copying before and after every evaluation.

It does however introduce 2 minor behavior changes:

  • Since the "java type" of variables set via the RedBridge API can also no longer be stored, it is no longer preserved across the set/evaluate/get cycle. That is, a variable that has been set to a String[] will now come back as a RubyArray object (which can be used as a List<String>, but not String[]) when retrieved after evaluation. This is undocumented behavior, but it's a change from the previous behavior where reading the variable after evaluation would always try to convert its value back to the same type that was used for setting it.
  • Variables are no longer snapshotted after evaluation. For LocalContextProviders that use the global runtime, i.e. ConcurrentLocalContextProvider and obviously SingletonLocalContextProvider, this means that other threads can change the value of a global variable, constant, class variable, etc. between the evaluation and when the variable is read. As far as I can tell, evaluation and retrieval of (global etc.) variables has never been an atomic operation to begin with, but the timing window for getting race-y behavior is now somewhat larger.

@matthias-fratz-bsz matthias-fratz-bsz marked this pull request as draft May 20, 2026 13:49
This essentially ties each BiVariableMap instance to a particular thread
in the context-per-thread providers. Using it from a different thread then
behaves consistently as long as both threads aren't running concurrently.
(Using a BiVariableMap concurrently from multiple threads is as race-y as
before.)

This commit also moves some global runtime logic into a LocalContext
subclass called GlobalContext and refactors the various LocalContextProvider
classes to delegate almost everything to LocalContext.
This introduces some minor behavior changes:
- The "java type" of variables set via the RedBridge API is no longer stored.
  Thus a variable that has been set to `String[]` will now come back as a
  `RubyArray` object which can be used as a `List<String>`. The undocumented
  previous behavior was to convert the value back to the same type it was
  originally.
- BiVariableMap no longer holds a copy of all variables; it only actually
  stores local varibles. All other types of variable are simply retrieved
  from either the runtime's global state, or from the given receiver object,
  if and when the variable is accessed.
- As a corollary, variables can now change value between the evaluation and
  the moment when they are accessed via BiVariableMap, if some other threads
  modifies them concurrently. This mostly affects LocalContextProvider that
  use the global runtime, i.e. ConcurrentLocalContextProvider and obviously
  SingletonLocalContextProvider. Such usage has always been race-y, but the
  detail on what constitutes the critical section has changed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant