Skip to content

feat: use the go garbage collector to close nodes and managers #225

@abjeni

Description

@abjeni

Managers have a way to add nodes, but not a way to remove them.

It is possible to attach callbacks to objects which gets called before they become freed by the go garbage collector, using runtime.AddCleanup (go 1.24) or runtime.SetFinalizer.
By using weak pointers it is possible for the manager to keep track of all nodes while also allowing the nodes to be reclaimed by the garbage collector. A cleanup function can be added to each node which closes the connection to the node and removes the node from the manager.
Configurations should not use weak pointers since you don't want any of the nodes in the configuration to close until the configuration itself is reclaimed.

Weak pointer problem

The nodes are currently accessible through the manager using node ids, making it possible to access a node without the node pointer as long as you have its node id. The garbage collector would clean up the node in this case and the node would be closed where it would otherwise be accessible.
This can be avoided if the manager does not expose its nodes, but that means that you have to store the nodes you need not to be closed somewhere outside the manager, and it also limit what you can do with node ids.

Example

func NewRawNode(addr string) (*RawNode, error) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return nil, err
	}
	h := fnv.New32a()
	_, _ = h.Write([]byte(tcpAddr.String()))

	node := &RawNode{
		id:   h.Sum32(),
		addr: tcpAddr.String(),
	}

	_ = runtime.AddCleanup(node, func(n *RawNode) {
		m := n.mgr
		if m == nil {
			n.close()
			return
		}
		m.mu.Lock()
		defer m.mu.Unlock()
		for i, n2 := range m.nodes {
			if n2 == n {
				m.nodes = append(m.nodes[:i], m.nodes[i+1:]...)
				n.close()
				return
			}
		}
		panic("node does not exist in manager")
	}, node)

	return node, nil
}

We don't need lookup, but need nodes for Close()

type RawManager struct {
	mu        sync.Mutex
	nodes     []weak.Pointer[RawNode]
	closeOnce sync.Once
	logger    *log.Logger
	opts      managerOptions
	nextMsgID uint64
}
func (m *RawManager) AddNode(node *RawNode) error {
	...

	m.mu.Lock()
	defer m.mu.Unlock()
	m.nodes = append(m.nodes, weak.Make(node))
	return nil
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions