Waiting for all the child/sub-tasks to die out after `deinit`

actor life cycles seem simple enough on the surface… you set things up in the init and then kill them off in a Task launched from the deinit...

right now i have a ConnectionManager that starts up child/nested/sub-tasks on itself every now and then:

actor ConnectionManager
{
    ...
}

extension ConnectionManager
{
    // called whenever there is something new to monitor.
    nonisolated
    func monitor(_ host:Host)
    {
        let _:Task<Void, Never> = .init
        {
            await self.monitor(host)
        }
    }
    // loops forever on an `AsyncStream`. the `ConnectionManager`
    // keeps a copy of its continuation, so it can terminate them
    // when needed.
    private
    func monitor(_ host:Host) async
    {
        ...
    }
}

since the sub-tasks run on self, it’s not possible to get them to stop from ConnectionManager’s own deinit, so there is a larger object SessionPool that sends the ‘stop iteration’ signals to ConnectionManager on SessionPool’s deinit:

actor SessionPool
{
    nonisolated
    let connectionManager:ConnectionManager

    deinit
    {
        let _:Task<Void, Never> = .init
        {
            [connectionManager] in

            await connectionManager.stopAllMonitors()
        }
    }
}

but the Tasks created by ConnectionManager are unstructured tasks, so there is no way to know for sure when they have all completed, only that they have been put in a state where they are no longer looping forever.

and now a problem i have discovered is that these unstructured tasks can outlive the SessionPool for a while, which means when i try to create a new SessionPool after the previous one died, sometimes it fails because the old connections are still lingering around, and the SessionPool lifecycle loop needs to sleep for a second or two before the previous generation of Tasks finally dies off.

i thought about setting up a TaskGroup that waits on an AsyncStream that ConnectionManager yields to in order to request a ConnectionManager.monitor(_:) task to be scheduled, but sometimes the ConnectionManager.monitor(_:) tasks can die on their own, and that means the TaskGroup would just accumulate child tasks forever that never get awaited on until the SessionManager.deinit tells the task group to shut down.

any ideas?

1 Like