#14711: Memleak when creating QuadraticField
-------------------------------------------------+-------------------------
       Reporter:  jpflori                        |        Owner:
           Type:  defect                         |  davidloeffler
       Priority:  critical                       |       Status:  new
      Component:  number fields                  |    Milestone:  sage-5.13
       Keywords:  memleak, number field,         |   Resolution:
  QuadraticField                                 |    Merged in:
        Authors:                                 |    Reviewers:
Report Upstream:  N/A                            |  Work issues:
         Branch:  u/SimonKing/ticket/14711       |       Commit:
   Dependencies:                                 |     Stopgaps:
-------------------------------------------------+-------------------------

Comment (by SimonKing):

 With my current not yet published code, I get errors only in one file,
 namely with scheme morphisms. And this actually isn't a surprise, since
 scheme morphisms almost completely ignore the existing methods they
 inherit from `sage.categories.map.Map`. They have a custom `__init__`
 doing more or less the same what the ''old'' version of `Map.__init__`
 used to do, and they even override `domain()` and `codomain()`.

 So, this should be fixable, and I suppose I will be able to post a commit
 so that all tests pass, later today.

 Replying to [comment:83 nbruin]:
 > Replying to [comment:82 SimonKing]:
 > > - Maps registered by `P.register_coercion(mor)` are the backbone of
 the discover_coercion algorithm. Hence, they need to be kept healthy as
 long as `P` lives.
 >
 > It's not clear to me this is the case. If `mor` is a map from S to P
 then `P.register_coercion(mor)` ensures the coercion system knows how to
 map an element from S into P. Why is it important to maintain this
 information for the lifetime of P? If S ceases to exist, then one would
 not need to map elements from S to P.

 One does! Namely, suppose that you first do `P.register_coercion(mor)`,
 then you allow `S` to become garbage collected, and then you create a new
 parent `Q` with an embedding into what looks like `S` (of course, it now
 is a replica of the original now garbage collected `S`).

 You would want that `Q` coerces into `P` via `S`, by transitivity of
 coercions. But you collected `mor.domain()` and thus you will fail to find
 this coercion.

 Another attempt to explain why I think the explicitly registered maps need
 to be kept:

 Think of the coercions as a digraph.
 - The vertices correspond to parents.
 - The arrows (oriented edges) of the digraph correspond to those maps that
 are declared as coercions by `.register_coercion(...)`.
 - In addition to the arrows created with `register_coercion`, the coercion
 system may find short-cuts by calling `._coerce_map_from_(...)`. For
 efficiency reasons, these short-cuts are stored in a cache.
 - The coercion system then tries to find directed paths between two given
 vertices, including short-cuts. For efficiency reasons, it stores the
 resulting composed maps in a cache.

 How does the coercion system find "directed paths with short-cuts" from Q
 to P?
 - Of course, it would be impossible to just guess a parent S such that Q
 has a short-cut into S and S has a short-cut into P.
 - One could search through all paths alpha (paths without shortcuts, I
 mean) starting at Q and simultaneously through all paths beta ending at P,
 and then test whether there is a short-cut from the endpoint of alpha to
 the startpoint of beta. It would work, but I guess it would be very time
 consuming.
 - The current coercion system will therefore just search (by backtracking)
 through all shortcut-free paths beta ending at P, end then test whether
 there is a short-cut from Q to the startpoint of beta. In addition, the
 coerce embedding registered for Q will be considered in forward direction.

 __Remark__

 Perhaps in the long run, we could actually use a proper digraph (with a
 lightning fast backend) to keep track of coercions.

 Now for garbage collection:

 > Why do we need to ensure S keeps existing? Shouldn't P have a reference
 to S independent of the coercion system if P's health depends on the
 existence of S? I think the coercion system is there to keep track of
 relations between existing objects, not to keep objects in existence
 (although this might be a side-effect of keeping track of relations
 between other objects).

 I think that in many cases it would indeed be the case that P should
 reference S independent of coercions (say, if S is the base ring of P).
 However, it is conceivable (and I think I've met such cases in failing
 doctests) that P can remain perfectly healthy without S, but still we
 would like to find coercions from Q to P via S.

 So, the coercion system will not care for the health of P, but it must
 care for the connectivity of the coercion graph. And in the current
 algorithm sketched above, it is of vital importance to keep arrows leading
 to P alive, since otherwise we have to live with shortcuts only.

 > > - If P has a coerce embedding then this embedding needs to be kept
 healthy as long as P lives.
 >
 > I presume this is the function of the `_embedding` coercion attribute?
 So it looks like we already have that. What if we want multiple
 embeddings?

 Then we look into the code and see that multiple embeddings are explicitly
 excluded. A parent has precisely one embedding that is taken into account
 by the coercion search ''in forward direction''. You may of course
 register further embeddings as coercions, by
 `emb.codomain().register_coercion(emb)`, but they would only be used for
 search in backward direction.

 > > - `P.register_coercion(mor)` keeps a strong reference to
 `mor.domain()` in a new attribute `P._registered_domains` (which is a
 list). All other maps in the coercion system will be weakened.
 >
 > So what means are there available to add a coercion without tying the
 lifespan of one to the other?

 Note that after `P.register_coercion(mor)`, `mor.domain()` will live at
 least as long as P. But `mor.domain()` would not be enough to keep P
 alive.

 And there surely is a means available to add a coercion that doesn't tie
 the lifespan of two parents too closely: Implement
 `P._coerce_map_from_(Q)`, which can return a map or simply "True" (in the
 latter case, conversion is used to coerce Q into P). The result is cached
 in `P._coerce_from_hash`, but not in `P._coerce_from_list`.

 >  Shouldn't it be possible to specify such things too?

 Perhaps in the long run? I wouldn't like to do those things (such as:
 Rewrite the coercion system to use a proper digraph backend) here.

--
Ticket URL: <http://trac.sagemath.org/ticket/14711#comment:84>
Sage <http://www.sagemath.org>
Sage: Creating a Viable Open Source Alternative to Magma, Maple, Mathematica, 
and MATLAB

-- 
You received this message because you are subscribed to the Google Groups 
"sage-trac" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sage-trac.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to