[Oberon] When is it safe to unload a module?

Andreas Pirklbauer andreas_pirklbauer at yahoo.com
Sat Aug 4 05:24:06 CEST 2018


     > > 1. Client reference
     > > 2. Type (tag) references
     > > 3. Procedure (variable) references

  > I suspect I don't need to worry too much about 2 and 3 affecting me but I can’t be sure. 

Well, you should! One important class of issues is the case where a structure rooted
in a variable of base type T declared in a base module M contains elements of
an extension T’ defined in a client module M' which is then unloaded.

For example, imagine you develop your own editor MyEdit, which defines MyFrame
as an extension to Viewers.Frame. Then you open a document using THAT extension,
then unload MyEdit without closing the open window. Original Oberon lets you do
that, but in general you can kiss the system good bye at that point (as the data
structure rooted in the global variable Viewers.root will contain dangling pointers
to extensions defined in modules that are no longer loaded).

   > Consequently, I'd be interested to see a simple *minimal* test
   > application for each of the use cases 2 or 3 

Below are such *minimal* examples, covering all possible cases.

In each case, the difference in output between Original Oberon (which
does not address the issue at all) and Experimental Oberon (which fully
solves this issue, including unloading of a *group* of module which only
contains references among themselves) is shown.

PS: You can literally paste the text below into an Oberon text and then
simply click through the compilations commands listed in M3.Mod.

-ap

------------------------------------------------------------------------

MODULE M0;
  TYPE Proc* = PROCEDURE;
    Ptr* = POINTER TO Rec;
    Rec* = RECORD x*: INTEGER;
      lproc*: Proc;
      lptr*, next: Ptr;
    END;

  VAR root: Ptr;

  PROCEDURE Insert*(p: Ptr); BEGIN p.next := root; root := p END Insert;
  PROCEDURE Init*; BEGIN root := NIL END Init;

BEGIN Init
END M0.

------------------------------------------------------------------------

MODULE M1;
  IMPORT M0;

  TYPE Ptr* = POINTER TO Rec;
    Rec* = RECORD (M0.Rec) END ;  (*extension of M0.Rec*)

  VAR gptr*: Ptr;
    gproc*: M0.Proc;

  PROCEDURE P*; BEGIN END P;

  PROCEDURE SetGPtr*(q: M0.Ptr); BEGIN gptr := q(Ptr) END SetGPtr;
  PROCEDURE SetLPtr*(q: M0.Ptr); BEGIN gptr.lptr := q(Ptr) END SetLPtr;

  PROCEDURE SetGProc*(q: M0.Proc); BEGIN gproc := q END SetGProc;
  PROCEDURE SetLProc*(q: M0.Proc); BEGIN gptr.lproc := q END SetLProc;

  PROCEDURE Init*; BEGIN NEW(gptr) END Init;

BEGIN Init
END M1.

------------------------------------------------------------------------

MODULE M2; (*identical to M1*)
  IMPORT M0;

  TYPE Ptr* = POINTER TO Rec;
    Rec* = RECORD (M0.Rec) END ;  (*extension of M0.Rec*)

  VAR gptr*: Ptr;
    gproc*: M0.Proc;

  PROCEDURE P*; BEGIN END P;

  PROCEDURE SetGPtr*(q: M0.Ptr); BEGIN gptr := q(Ptr) END SetGPtr;
  PROCEDURE SetLPtr*(q: M0.Ptr); BEGIN gptr.lptr := q(Ptr) END SetLPtr;

  PROCEDURE SetGProc*(q: M0.Proc); BEGIN gproc := q END SetGProc;
  PROCEDURE SetLProc*(q: M0.Proc); BEGIN gptr.lproc := q END SetLProc;

  PROCEDURE Init*; BEGIN NEW(gptr) END Init;

BEGIN Init
END M2.

------------------------------------------------------------------------

MODULE M3;
  IMPORT M0, M1, M2;

  PROCEDURE SetGProc1*; BEGIN M1.SetGProc(M2.P) END SetGProc1;
  PROCEDURE UnSetGProc1*; BEGIN M1.SetGProc(M1.P) END UnSetGProc1;

  PROCEDURE SetLProc1*; BEGIN M1.SetLProc(M2.P) END SetLProc1;
  PROCEDURE UnSetLProc1*; BEGIN M1.SetLProc(M1.P) END UnSetLProc1;

  PROCEDURE SetPtr1*;
    VAR p1: M1.Ptr; (*extension (declared in M1) of a base type declared in M0*)
  BEGIN  (*insert p1 into a structure rooted in a variable of a base type declared in M0*)
    NEW(p1); M0.Insert(p1)
  END SetPtr1;

  PROCEDURE Init*; BEGIN END Init;
END M3.

------------------------------------------------------------------------

ORP.Compile M0.Mod/s M1.Mod/s M2.Mod/s M3.Mod/s ~

Call sequence #1:  (no reference)

   M3.Init             # load M0, M1, M2, M3
   System.Free M3 ~    # unload M3 successful
   System.Free M2 ~    # unload M2 successful
   System.Free M1 ~    # unload M1 successful
   System.Free M0 ~    # unload M0 successful
   System.ShowModules

Call sequence #2:  (procedure variable reference from a global procedure variable)

   M3.Init             # load M0, M1, M2, M3
   M3.SetGProc1        # set M1.gproc to M2.P (global procedure variable reference)
   System.Free M3 ~    # unload M3 successful
   System.Free M2 ~    # unload M2 SHOULD NOT be successful (as ref from M1 to M2 exists)
   M3.UnSetGProc1      # set M1.gproc to M1.P (this removes the ref from M1 to M2)
   System.Free M3 ~    # unload M3 successful
   System.Free M2 ~    # unload M2 now successful (as ref from M1 to M2 no longer exists)

Call sequence #3:  (procedure variable reference from the heap)

   M3.Init             # load M0, M1, M2, M3
   M3.SetLProc1        # set M1.gptr.lproc to M2.P (dynamic procedure variable reference)
   System.Free M3 ~    # unload M3 successful
   System.Free M2 ~    # unload M2 SHOULD NOT be successful (as ref from M1 to M2 exists)
   M3.UnSetLProc1      # set M1.gptr.lproc to M1.P (this removes the ref from M1 to M2)
   System.Free M3 ~    # unload M3 successful
   System.Free M2 ~    # unload M2 now successful (as ref from M1 to M2 no longer exists)

Call sequence #4:  (pointer reference from a pointer variable in the heap)

   M3.Init             # load M0, M1, M2, M3
   M3.SetPtr1          # insert a variable of type M1.ptr into a data structure rooted in in M0
   System.Free M3 ~    # unload M3 successful
   System.Free M1 ~    # unload M1 SHOULD NOT be successful (as ref from M0 to M1 exists)
   M0.Init             # clear the data structure in M0
   System.Free M3 ~    # unload M3 successful
   System.Free M1 ~    # unload M1 now successful (as ref from M0 to M1 no longer exists)

Call sequence #5:  (unloading of modules as a group)

   M3.Init             # load M0, M1, M2, M3
   M3.SetGProc1        # set M1.gproc to M2.P (global procedure variable reference)
   System.Free M3 ~    # unload M3 successful
   System.Free M1 M2 ~ # unloading of M1 and M2 as a group

Comparison of output between Original Oberon and Experimental Oberon:

--------------------------
Original Oberon output
--------------------------

Call sequence #1:

System.Free M3
  M3 unloading
System.Free M2
  M2 unloading
System.Free M1
  M1 unloading
System.Free M0
  M0 unloading

Call sequence #2:

System.Free M3
  M3 unloading
System.Free M2
  M2 unloading
System.Free M3
  M3 unloading
System.Free M2
  M2 unloading

Call sequence #3:

System.Free M3
  M3 unloading
System.Free M2
  M2 unloading
System.Free M3
  M3 unloading
System.Free M2
  M2 unloading

Call sequence #4:

System.Free M3
  M3 unloading
System.Free M1
  M1 unloading
System.Free M3
  M3 unloading
System.Free M1
  M1 unloading

Call sequence #5:

System.Free M3
  M3 unloading
System.Free M1 M2
  M1 unloading
  M2 unloading

--------------------------
Experimental Oberon output
--------------------------

Call sequence #1:

System.Free M3
  M3 unloading
System.Free M2
  M2 unloading
System.Free M1
  M1 unloading
System.Free M0
  M0 unloading

Call sequence #2:

System.Free M3
  M3 unloading
System.Free M2
  M2 unloading failed (references exist)
  procedures of M2 in use in global procedures variables of M1
System.Free M3
  M3 unloading
System.Free M2
  M2 unloading

Call sequence #3:

System.Free M3
  M3 unloading
System.Free M2
  M2 unloading failed (references exist)
  procedures of M2 in use in dynamic objects reachable by M1
System.Free M3
  M3 unloading
System.Free M2
  M2 unloading

Call sequence #4:

System.Free M3
  M3 unloading
System.Free M1
  M1 unloading failed (references exist)
  types of M1 in use in dynamic objects reachable by M0
System.Free M3
System.Free M1
  M1 unloading

Call sequence #5:

System.Free M3
  M3 unloading
System.Free M1 M2
  M2 M1 unloading   <-- CAN unload AS A GROUP, but not individually










More information about the Oberon mailing list