Skip to content

Fix memory leak across files caused by session reuse.

Han-Wen Nienhuys requested to merge dev/hanwen/drop-reexport into master

This fixes a memory leak across .ly files.

Modules have both an internal namespace and an external namespace. Here are the relevant operations:

  • define (built-in): Scheme's primitive 'define'

  • define-private (boot-9.scm), creates a module-internal symbol

  • export (boot-9.scm): makes an internal definition public

  • define-public (boot-9.scm): define-private followed by export

After loading these definitions, the define operation is overwritten so it points to define-private.

A module is a struct with a hash table (called obarray) holding the symbol => variable(containing a value) mapping, representing the (private) symbols defined in the module.

The public interface is implemented by creating another module, stored in the %module-public-interface private variable of a module. Exporting a symbol copies the variable from the module's obarray to the obarray of the %module-public-interface.

LilyPond implements variables in .ly files using GUILE modules: each namespace is implemented as a module. Inheritance is implemented by importing other modules, eg.

f.ly:

#(define a 1)

% displays the module holding the binding for a #(display (current-module))

% in preparation for \layout, all symbols are exported

\layout {

% shows a new module
#(display (current-module))

% current module has done 'use-module' on the outer module,
% so `a` resolves
x = #(+ a 2)

% this shows 3.
#(display x)

}

All symbols in .ly files are public, and this is achieved by exporting all symbols explicitly when a new scope comes into play.

The session mechanism (introduced in commit 9eb1eba8 ("Issue 2872: Provide define-session and define-session-public commands", Oct 1, 2012) stores the state of initialization .scm and .ly files, just before starting the parse of the user-supplied .ly files. This provides protection against modifying definitions in .scm files, and saves the effort of parsing init .ly files for each .ly on the command line over and over.

The session mechanism achieves this by

  1. for first .ly file: storing the contents of the current module (ie. the scope in the parser) in the variable lilypond-declarations, which is private to the module for lily.scm

  2. for second and further .ly files: copying variables from lilypond-declarations into the parser scope.

Unfortunately, it also stored the %module-public-interface from the first .ly's scope into the scopes for subsequent files.

As a result, the toplevel scope for each .ly file got the public interface from the first file. Since the public interface was shared, settings would leak across files, eg.

file1.ly x = "hello!" #(display x)

file2.ly

\layout {
  #(display x)
}

would successfully display "hello!" twice when running

lilypond file1.ly file2.ly

For correctly written files, this is normally not a problem, as you'd have to define each symbol before use (thereby overwriting previous settings), but in a multi-file settings (such as the regtest), the definitions would retain memory across files, leading to "Parsed object should be dead" messages.

Fixes issue #6080 (closed)

Edited by Han-Wen Nienhuys

Merge request reports