Skip to content

C objects beyond their Lua lifetime, or: a new callback-registration invariant? #3062

@nwf

Description

@nwf

The Problem

We have a number of modules (notably, tmr, net, tls) that appear to be intended to function without the Lua application retaining reference to any particular object: it's OK to tmr.create():alarm(...) and similarly drop a socket object after calling :on() to establish callbacks.

This creates the unfortunate possibility that some application unregisters all callbacks and so leaves the underlying C object pinned in the registry, forever occupying resources and yet unreachable from the Lua object graph. It is difficult to detect this happening, since the hold from the registry prevents garbage collection, and so we cannot tell if the user is otherwise retaining reference or not.

New invariant proposal

Therefore, I propose that we introduce, document, and gradually enforce the following invariant:

Any callback-driven API object must have, at all times, least one callback for a possible eventuality from its current state registered before being told to initiate events; such an object is permitted to abort at any point in the future if this is not upheld.

This licenses the following kinds of behavior in lieu of resource leaks:

  • a tmr object may stop itself, and release itself from the Lua registry, if, when a timer tick arrives, there is no callback registered. If the user has retained a reference, they may register a new callback and restart the timer via that reference (and it will re-register itself with the registry).

  • a tmr object may interpret start as a no-op if there is no callback registered, and so may skip installing itself into the Lua registry.

  • a tls or net connection may tear itself down from the established state if it has no receive, sent, or disconnect callback. If the user has retained a reference, they are free to reuse the connection (and it will re-register itself with the registry).

  • a tls or net connection object is permitted to disconnect if, upon connection establishment, it finds no connected, receive, sent, or disconnected callback.

  • a tls or net connection object is permitted to fail a connect request if there are no callbacks registered. (and so the user must write skt:on("connect", ...) skt:connect(...), not the other way around).

New requirement proposal

Alternatively, we could require that the state-machine objects (tmr objects, tls connections, ...) be held by the user and not pin them in the registry. This is attractive for its simplicity but has, I think, an unpleasant side-effect. Because Lua does not easily offer two-stage finalization (that is, while __gc metamethods can resurrect objects, there is no indication that this has taken place), these objects' __gc metamethods would unconditionally unregister the object's Lua callbacks, making resurrection challenging from an application perspective.

ETA: The reason to consider resurrection is that it would be most friendly if our __gc metamethods on, for example, sockets, attempted to call back on the "disconnected" callback, if there were one registered. If we aren't worried about being that friendly in the event of breaking this "must-be-held" invariant, then I think we don't need to worry about resurrection and it becomes the simplest proposal but with far-reaching code-breaking potential.

Call for help

Any and all feedback, including alternate proposals for dealing with these edge cases, is more than welcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions