Skip to content

Application Entry Point#

In Go, the main() function is the entry point of the binary application and is defined in the main.go file of our application. As in the case of Bond-assisted development, we perform the following same steps

  • handling the application's version
  • setting up the logger
  • creating the context and appending app metadata

Exit Handler#

Here is the first part that we have to manually implement when not using Bond.

In the context of the NDK application life cycle the exit handler is a function that is called when the application receives Interrupt or SIGTERM signals. The exit handler is a good place to perform cleanup actions like closing the open connections, releasing resources, etc.

We execute exitHandler function passing it the cancel function of the context:

func exitHandler(cancel context.CancelFunc) {
    // handle CTRL-C signal
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-sig

        cancel()
    }()
}

This function is non-blocking as it spawns a goroutine that waits for the registered signals and then execute the cancel function of the context. This will propagate the cancellation signal to all the child contexts and our application reacts to it.

greeter/app.go
func (a *App) Start(ctx context.Context) {
    go a.receiveConfigNotifications(ctx)

    for {
        select {
        case <-a.configReceivedCh:
            a.logger.Info().Msg("Received full config")

            a.processConfig(ctx)

            a.updateState(ctx)

        case <-ctx.Done():
            a.stop()
            return
        }
    }
}

We will cover the func (a *App) Start() function properly when we get there, but for now, it is important to highlight how cancellation of the main context is intercepted in this function and leading to a.stop() call.

The a.stop() function is responsible to perform the graceful shutdown of the application.

greeter/app.go
func (a *App) stop() {
    a.logger.Info().Msg("Got a signal to exit, unregistering greeter agent, bye!")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    ctx = metadata.AppendToOutgoingContext(ctx, "agent_name", AppName)
    defer cancel()

    // unregister agent
    r, err := a.SDKMgrServiceClient.AgentUnRegister(ctx, &ndk.AgentRegistrationRequest{})
    if err != nil || r.Status != ndk.SdkMgrStatus_kSdkMgrSuccess {
        a.logger.Error().
            Err(err).
            Str("status", r.GetStatus().String()).
            Msgf("Agent unregistration failed %s", r.GetErrorStr())

        return
    }

    err = a.gRPCConn.Close()
    if err != nil {
        a.logger.Error().Err(err).Msg("Closing gRPC connection to NDK server failed")
    }

    err = a.gNMITarget.Close()
    if err != nil {
        a.logger.Error().Err(err).Msg("Closing gNMI connection failed")
    }

    a.logger.Info().Msg("Greeter unregistered successfully!")
}

Following the Graceful Exit section we first unregister the agent with the NDK manager and then closing all connections that our app had opened.

Initializing the Application#

In the end, we initializa the app the same way:

main.go
    app := greeter.NewApp(ctx, &logger)
    app.Start(ctx)

This is where the application logic starts to kick in. Let's turn the page and start digging into it in the next chapter.

Comments