sourceafIoc::RegistryShutdown.fan

using concurrent::Future
using afConcurrent::SynchronizedState

@NoDoc @Deprecated { msg="Use RegistryShutdown instead" }
const mixin RegistryShutdownHub {
    abstract Void addRegistryShutdownListener(Str id, Str[] constraints, |->| listener)
    abstract Void addRegistryWillShutdownListener(Str id, Str[] constraints, |->| listener)
}

** (Service) - Contribute functions to be executed on `Registry` shutdown. 
** All functions need to be immutable, which essentially means they can only reference 'const' classes.
** Example usage:
** 
** pre>
** class AppModule {
**     @Contribute { serviceType=RegistryShutdown# }
**     static Void contributeRegistryShutdown(OrderedConfig conf, MyService myService) {
**         conf.add |->| { myService.shutdown() }
**     }
** }
** <pre
** 
** If the shutdown method of your service depends on other services being available, add a constraint on 'afIoc.shutdown': 
** 
**   conf.addOrdered("MyServiceShutdown", |->| { myService.shutdown() }, ["BEFORE: afIoc.shutdown"])
** 
** Note that Errs thrown by shutdown functions will be logged and then swallowed.
** 
** @uses OrderedConfig of |->|
const mixin RegistryShutdown : RegistryShutdownHub {
    internal abstract Void shutdown()
}

internal const class RegistryShutdownImpl : RegistryShutdown {
    private const static Log        log         := Utils.getLog(RegistryShutdown#)
    private const OneShotLock       lock        := OneShotLock(IocMessages.registryShutdown)
    private const SynchronizedState conState
    private const |->|[]            shutdownFuncs
 
    new make(|->|[] shutdownFuncs,  ActorPools actorPools) {
        this.shutdownFuncs = shutdownFuncs
        conState = SynchronizedState(actorPools[IocConstants.systemActorPool], RegistryShutdownHubState#)
    }
    
    override Void shutdown() {
        lock.check
        lock.lock

        registryWillShutdown
        
        shutdownFuncs.each | |->| listener| {
            try {
                listener.call
            } catch (Err e) {
                log.err(IocMessages.shutdownListenerError(listener, e))
            }
        }
        
        registryHasShutdown
    }
    
    override Void addRegistryShutdownListener(Str id, Str[] constraints, |->| listener) {
        lock.check
        iHandler := listener.toImmutable
        conState.withState |RegistryShutdownHubState state| {
            state.listeners.addOrdered(id, iHandler, constraints)
        }.get
    }

    override Void addRegistryWillShutdownListener(Str id, Str[] constraints, |->| listener) {
        lock.check
        iHandler := listener.toImmutable
        conState.withState |RegistryShutdownHubState state| {
            state.preListeners.addOrdered(id, iHandler, constraints)
        }.get
    }

    private Void registryWillShutdown() {
        preListeners.each | |->| listener| {
            try {
                listener.call
            } catch (Err e) {
                log.err(IocMessages.shutdownListenerError(listener, e))
            }
        }
        conState.withState |RegistryShutdownHubState state| { state.preListeners.clear }
    }

    private Void registryHasShutdown() {
        listeners.each |listener| {
            try {
                listener()
            } catch (Err e) {
                log.err(IocMessages.shutdownListenerError(listener, e))
            }
        }
        conState.withState |RegistryShutdownHubState state| { state.listeners.clear }
    }
    
    private |->|[] preListeners() {
        conState.getState |RegistryShutdownHubState state -> Obj| { state.preListeners.toOrderedList.toImmutable }
    }

    private |->|[] listeners() {
        conState.getState |RegistryShutdownHubState state -> Obj| { state.listeners.toOrderedList.toImmutable }
    }
}

internal class RegistryShutdownHubState {
    Orderer preListeners    := Orderer()
    Orderer listeners       := Orderer()
}