MCPcopy
hub / github.com/larksuite/cli / refreshWithLock

Function refreshWithLock

internal/auth/uat_client.go:106–171  ·  view source on GitHub ↗

refreshWithLock acquires a file lock before attempting to refresh the token.

(httpClient *http.Client, opts UATCallOptions, stored *StoredUAToken)

Source from the content-addressed store, hash-verified

104
105// refreshWithLock acquires a file lock before attempting to refresh the token.
106func refreshWithLock(httpClient *http.Client, opts UATCallOptions, stored *StoredUAToken) (*StoredUAToken, error) {
107 key := fmt.Sprintf("%s:%s", opts.AppId, opts.UserOpenId)
108
109 // 1. Process-level lock (prevents multiple goroutines in the same process)
110 done := make(chan struct{})
111 if existing, loaded := refreshLocks.LoadOrStore(key, done); loaded {
112 // Another goroutine is already refreshing; wait for it
113 if ch, ok := existing.(chan struct{}); ok {
114 <-ch
115 } else {
116 // fallback in case of unexpected type
117 refreshLocks.Delete(key)
118 }
119 return GetStoredToken(opts.AppId, opts.UserOpenId), nil
120 }
121
122 // We own the process lock; done is the channel stored in the map
123 defer func() {
124 close(done)
125 refreshLocks.Delete(key)
126 }()
127
128 // 2. Cross-process lock using flock
129 // We use the same underlying storage directory resolution as keychain_other.go
130 // to ensure locks are isolated properly alongside other sensitive data.
131 configDir := core.GetConfigDir()
132
133 lockDir := filepath.Join(configDir, "locks")
134 if err := vfs.MkdirAll(lockDir, 0700); err != nil {
135 return nil, fmt.Errorf("failed to create lock directory: %w", err)
136 }
137
138 safeAppId := sanitizeID(opts.AppId)
139 safeUserOpenId := sanitizeID(opts.UserOpenId)
140 lockFile := filepath.Join(lockDir, fmt.Sprintf("refresh_%s_%s.lock", safeAppId, safeUserOpenId))
141 fileLock := flock.New(lockFile)
142
143 // Try to acquire the lock, wait if necessary
144 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
145 defer cancel()
146
147 locked, err := fileLock.TryLockContext(ctx, 500*time.Millisecond)
148 if err != nil {
149 return nil, fmt.Errorf("failed to acquire cross-process lock: %w", err)
150 }
151 if !locked {
152 return nil, fmt.Errorf("timeout waiting for cross-process lock")
153 }
154 defer fileLock.Unlock()
155
156 // 3. Double-checked locking: Check if another process has already refreshed the token
157 freshStored := GetStoredToken(opts.AppId, opts.UserOpenId)
158 if freshStored != nil {
159 status := TokenStatus(freshStored)
160 if status == "valid" {
161 // Another process refreshed it, we can just use the new token
162 if opts.ErrOut != nil {
163 fmt.Fprintf(opts.ErrOut, "[lark-cli] uat-client: token already refreshed by another process\n")

Callers 1

GetValidAccessTokenFunction · 0.85

Calls 8

GetConfigDirFunction · 0.92
MkdirAllFunction · 0.92
GetStoredTokenFunction · 0.85
sanitizeIDFunction · 0.85
TokenStatusFunction · 0.85
doRefreshTokenFunction · 0.85
UnlockMethod · 0.80
DeleteMethod · 0.65

Tested by

no test coverage detected