lorenzodonini / ocpp-go

Open Charge Point Protocol implementation in Go
MIT License
275 stars 126 forks source link

Workflows, e.g. BootNootification -> GetConfiguration #189

Open rogeralsing opened 1 year ago

rogeralsing commented 1 year ago

What is the proper way to model flows of events here? e.g. if you receive a BootNotificationReq, and then return a BootNotificationConf, and once that result has been returned, you want to send a GetConfigurationReq from the server.

I could ofc just start a go routine from the eventhandler, but what about race conditions, e.g. can I guarantee that the BootNotificationConf is is sent out before the GetConfigurationReq?

lorenzodonini commented 1 year ago

Concurrent request/response order cannot currently be guaranteed (especially on CSMS endpoints), since they run on two separate goroutines. Hence your follow-up request could indeed reach its destination before the response to the previous request. The protocol doesn't prohibit two endpoints from sending each other a request simultaneously.

Just out of curiosity, why could a GetConfigurationReq not reach its destination before a BootNotificationResp? The two messages are unrelated, so there isn't really a race condition the way I see it.

To the matter at hand: you have two options, but either way you'll have to work with goroutines.

Inline follow-up request

Simply send the follow-up request directly (as you suggested):

func (h *CentralSystemHandler) OnBootNotification(chargePointId string, request *core.BootNotificationRequest) (confirmation *core.BootNotificationConfirmation, err error) {
         // do what you have to
        followUpReq := core.NewGetConfigurationRequest()
        go centralSystem.SendRequestAsync(chargePointID, followUpReq, myCallback)
    return core.NewBootNotificationConfirmation(types.NewDateTime(time.Now()), defaultHeartbeatInterval, core.RegistrationStatusAccepted), nil
}

Note:

Task scheduling

Or you can schedule follow-up operations via channels on a separate goroutine. For example:

func (h *CentralSystemHandler) OnBootNotification(chargePointId string, request *core.BootNotificationRequest) (confirmation *core.BootNotificationConfirmation, err error) {
         // do what you have to
        h.myChan <- followUpTrigger // could be anything, from a signal to an entire payload
    return core.NewBootNotificationConfirmation(types.NewDateTime(time.Now()), defaultHeartbeatInterval, core.RegistrationStatusAccepted), nil
}

func (handler *CentralSystemHandler) MyTaskScheduler() {
    for {
        select {
        case followUpTrigger := <-h.myChan:
            // Process the trigger
            centralSystem.GetConfiguration("someID", myCallback)
        }
    }
}

Similarly to before, you technically cannot guarantee that your follow-up will arrive after the response to the "previous" message.

I hope this helps.