Files
go-sip/internal/dispatcher/sip_reload.go
T

82 lines
3.3 KiB
Go

package dispatcher
import (
"context"
"errors"
"fmt"
"git.ipao.vip/rogee/go-sip/internal/configread"
)
// refreshSIP processes only a durable sip.config notification. A pending
// revision stays fenced until existing calls drain and Agent confirms loading.
func (r *Runtime) refreshSIP(ctx context.Context, approvedSIP *configread.SIP) error {
if r == nil || approvedSIP == nil || r.Bootstrap.Client == nil || r.Bootstrap.Store == nil || r.Bootstrap.VerifySIP == nil || r.Logger == nil {
return errors.New("SIP refresh requires durable state and applied-revision verifier")
}
_, pending, err := r.Bootstrap.Store.SIPState(r.Bootstrap.DispatcherID)
if err != nil {
return err
}
if pending == 0 {
return nil
}
occupied, err := r.Bootstrap.Store.OccupiedCalls(r.Bootstrap.DispatcherID)
if err != nil {
return err
}
if occupied != 0 {
r.Logger.Debug("SIP reload waits for confirmed old-call drain", "dispatcher_id", r.Bootstrap.DispatcherID, "pending_revision", pending, "occupied_calls", occupied)
return nil
}
current, err := r.Bootstrap.Client.ReadSIP(ctx)
if err != nil {
return r.closeSIPAdmission(fmt.Errorf("read approved SIP full snapshot: %w", err))
}
current, err = r.Bootstrap.Store.PrepareSIP(current)
if err != nil {
return r.closeSIPAdmission(fmt.Errorf("persist local SIP load generation: %w", err))
}
if r.Bootstrap.ApplySIP != nil {
r.Logger.Info("applying pending SIP snapshot after calls drained", "dispatcher_id", r.Bootstrap.DispatcherID, "revision", current.Revision)
if err := r.Bootstrap.ApplySIP(ctx, current); err != nil {
r.Logger.Warn("approved SIP update remains fenced until native reload confirms", "dispatcher_id", r.Bootstrap.DispatcherID, "revision", current.Revision, "error", err)
return nil
}
}
if err := r.Bootstrap.VerifySIP(ctx, current); err != nil {
r.Logger.Debug("SIP reload waits for actual Agent/Asterisk revision", "dispatcher_id", r.Bootstrap.DispatcherID, "pending_revision", pending, "error", err)
return nil
}
r.gate.Lock()
defer r.gate.Unlock()
_, latestPending, err := r.Bootstrap.Store.SIPState(r.Bootstrap.DispatcherID)
if err != nil {
return err
}
if latestPending > current.Revision {
r.Logger.Info("newer SIP notification arrived during reload; admission remains closed", "dispatcher_id", r.Bootstrap.DispatcherID, "pending_revision", latestPending)
return nil
}
if err := r.Bootstrap.Store.RebindSIP(r.Bootstrap.DispatcherID, current); err != nil {
return r.closeSIPAdmission(fmt.Errorf("bind approved SIP to existing task snapshots: %w", err))
}
// This transaction rechecks drain, pending revision and every binding.
if err := r.Bootstrap.Store.MarkReadyForSIP(r.Bootstrap.DispatcherID, current.Revision); err != nil {
return r.closeSIPAdmission(fmt.Errorf("verify and commit reloaded SIP revision: %w", err))
}
*approvedSIP = current
if r.Bootstrap.SIP != nil {
*r.Bootstrap.SIP = current
}
r.Logger.Info("approved SIP revision is applied after confirmed drain", "dispatcher_id", r.Bootstrap.DispatcherID, "revision", current.Revision)
return nil
}
func (r *Runtime) closeSIPAdmission(cause error) error {
if err := r.Bootstrap.Store.CloseAdmission(r.Bootstrap.DispatcherID); err != nil {
return errors.Join(cause, fmt.Errorf("close admission after SIP refresh failure: %w", err))
}
return cause
}