Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do fewer session refreshes. Fixes #468 #469

Merged
merged 2 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Release 0.22.0

- Add SessionRefreshInterval to DialOptions, with a default of 1 hour.

# Release 0.21.0

- New `ListenOptions` field: `WaitForNEstablishedListeners`. Allows specifying that you want at least N listeners to be established before the `Listen` method returns. Defaults to 0.
Expand Down
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.21
0.22
15 changes: 13 additions & 2 deletions ziti/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,23 @@ const (
ServiceAdded ServiceEventType = "Added"
ServiceRemoved ServiceEventType = "Removed"
ServiceChanged ServiceEventType = "Changed"

DefaultServiceRefreshInterval = 5 * time.Minute
DefaultSessionRefreshInterval = time.Hour
)

type serviceCB func(eventType ServiceEventType, service *rest_model.ServiceDetail)

type Options struct {
// Service refresh interval. May not be less than 1 second
RefreshInterval time.Duration

// Edge session refresh interval. Edge session only need to be refreshed if the list of available
// edge routers has changed. This should be a relatively rare occurrence. If a dial fails, the
// edge session will be refreshed regardless.
// May not be less than 1 second
SessionRefreshInterval time.Duration

// Deprecated: OnContextReady is a callback that is invoked after the first successful authentication request. It
// does not delineate between fully and partially authenticated API Sessions. Use context.AddListener() with the events
// EventAuthenticationStateFull, EventAuthenticationStatePartial, EventAuthenticationStateUnAuthenticated instead.
Expand All @@ -34,8 +44,9 @@ func (self *Options) isEdgeRouterUrlAccepted(url string) bool {
}

var DefaultOptions = &Options{
RefreshInterval: 5 * time.Minute,
OnServiceUpdate: nil,
RefreshInterval: DefaultServiceRefreshInterval,
SessionRefreshInterval: DefaultSessionRefreshInterval,
OnServiceUpdate: nil,
}

type DialOptions struct {
Expand Down
61 changes: 42 additions & 19 deletions ziti/ziti.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,10 +710,29 @@ func (context *ContextImpl) RefreshService(serviceName string) (*rest_model.Serv
return serviceDetail, nil
}

func (context *ContextImpl) runSessionRefresh() {
func (context *ContextImpl) runRefreshes() {
log := pfxlog.Logger()
svcUpdateTick := time.NewTicker(context.options.RefreshInterval)
defer svcUpdateTick.Stop()
svcRefreshInterval := context.options.RefreshInterval

if svcRefreshInterval == 0 {
svcRefreshInterval = DefaultServiceRefreshInterval
}
if svcRefreshInterval < time.Second {
svcRefreshInterval = time.Second
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is too aggressive for a default (when the caller probably made a mistake)? Also a minRefreshInterval constant to cover session and service refreshes might make sense.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that a developer might want to set it this low. So if the dev forgot to set it at all, it will get set to the default value, but if they have set it to something low, we'll cap it at 1 second.

I'll add a constant for minRefreshInterval

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constant added

}
svcRefreshTick := time.NewTicker(svcRefreshInterval)
defer svcRefreshTick.Stop()

sessionRefreshInterval := context.options.SessionRefreshInterval
if sessionRefreshInterval == 0 {
sessionRefreshInterval = DefaultSessionRefreshInterval
}
if sessionRefreshInterval < time.Second {
sessionRefreshInterval = time.Second
}

sessionRefreshTick := time.NewTicker(sessionRefreshInterval)
defer sessionRefreshTick.Stop()

refreshAt := time.Now().Add(30 * time.Second)
if currentApiSession := context.CtrlClt.GetCurrentApiSession(); currentApiSession != nil && currentApiSession.ExpiresAt != nil {
Expand All @@ -736,13 +755,15 @@ func (context *ContextImpl) runSessionRefresh() {
log.Debugf("apiSession refreshed, new expiration[%s]", *exp)
}

case <-svcUpdateTick.C:
case <-svcRefreshTick.C:
log.Debug("refreshing services")
if err := context.refreshServices(false); err != nil {
log.WithError(err).Error("failed to load service updates")
} else {
context.refreshSessions()
}

case <-sessionRefreshTick.C:
log.Debug("refreshing sessions")
context.refreshSessions()
}
}
}
Expand Down Expand Up @@ -853,7 +874,7 @@ func (context *ContextImpl) onFullAuth(apiSession *rest_model.CurrentAPISessionD
if context.options.OnContextReady != nil {
context.options.OnContextReady(context)
}
go context.runSessionRefresh()
go context.runRefreshes()

metricsTags := map[string]string{
"srcId": apiSession.Identity.ID,
Expand Down Expand Up @@ -1110,20 +1131,22 @@ func (context *ContextImpl) listenSession(service *rest_model.ServiceDetail, opt
func (context *ContextImpl) getEdgeRouterConn(session *rest_model.SessionDetail, options edge.ConnOptions) (edge.RouterConn, error) {
logger := pfxlog.Logger().WithField("sessionId", *session.ID)

if refreshedSession, err := context.refreshSession(*session.ID); err != nil {
target := &rest_session.DetailSessionNotFound{}
if errors.As(err, &target) {
sessionKey := fmt.Sprintf("%s:%s", session.Service.ID, *session.Type)
context.sessions.Remove(sessionKey)
}
if len(session.EdgeRouters) == 0 {
if refreshedSession, err := context.refreshSession(*session.ID); err != nil {
target := &rest_session.DetailSessionNotFound{}
if errors.As(err, &target) {
sessionKey := fmt.Sprintf("%s:%s", session.Service.ID, *session.Type)
context.sessions.Remove(sessionKey)
}

return nil, fmt.Errorf("no edge routers available, refresh errored: %v", err)
} else {
if len(refreshedSession.EdgeRouters) == 0 {
return nil, errors.New("no edge routers available, refresh yielded no new edge routers")
}
return nil, fmt.Errorf("no edge routers available, refresh errored: %v", err)
} else {
if len(refreshedSession.EdgeRouters) == 0 {
return nil, errors.New("no edge routers available, refresh yielded no new edge routers")
}

session = refreshedSession
session = refreshedSession
}
}

// go through connected routers first
Expand Down
Loading