-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
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
tmrobject 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
tmrobject may interpretstartas a no-op if there is no callback registered, and so may skip installing itself into the Lua registry. -
a
tlsornetconnection may tear itself down from the established state if it has noreceive,sent, ordisconnectcallback. If the user has retained a reference, they are free to reuse the connection (and it will re-register itself with the registry). -
a
tlsornetconnection object is permitted to disconnect if, upon connection establishment, it finds noconnected,receive,sent, ordisconnectedcallback. -
a
tlsornetconnection object is permitted to fail aconnectrequest if there are no callbacks registered. (and so the user must writeskt: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.