type AsyncTracker … // Start should be called prior to processing each key which is part of the // initial list. func (t *AsyncTracker[T]) Start(key T) { … } // Finished should be called when finished processing a key which was part of // the initial list. Since keys are tracked individually, nothing bad happens // if you call Finished without a corresponding call to Start. This makes it // easier to use this in combination with e.g. queues which don't make it easy // to plumb through the isInInitialList boolean. func (t *AsyncTracker[T]) Finished(key T) { … } // HasSynced returns true if the source is synced and every key present in the // initial list has been processed. This relies on the source not considering // itself synced until *after* it has delivered the notification for the last // key, and that notification handler must have called Start. func (t *AsyncTracker[T]) HasSynced() bool { … } type SingleFileTracker … // Start should be called prior to processing each key which is part of the // initial list. func (t *SingleFileTracker) Start() { … } // Finished should be called when finished processing a key which was part of // the initial list. You must never call Finished() before (or without) its // corresponding Start(), that is a logic error that could cause HasSynced to // return a wrong value. To help you notice this should it happen, Finished() // will panic if the internal counter goes negative. func (t *SingleFileTracker) Finished() { … } // HasSynced returns true if the source is synced and every key present in the // initial list has been processed. This relies on the source not considering // itself synced until *after* it has delivered the notification for the last // key, and that notification handler must have called Start. func (t *SingleFileTracker) HasSynced() bool { … }