@@ -32,6 +32,8 @@ const (
3232 backoffJitter = 0.2
3333)
3434
35+ var timeoutErr = errors .New ("timeout reached" )
36+
3537type RoomModel struct {
3638 ctx context.Context
3739 app * config.AppConfig
@@ -82,39 +84,32 @@ func (m *RoomModel) SetBreakoutRoomModel(bm *BreakoutRoomModel) {
8284 m .breakoutModel = bm
8385}
8486
85- func acquireRoomCreationLockWithRetry (ctx context.Context , rs * redisservice.RedisService , roomID string , log * logrus.Entry ) (string , error ) {
86- maxWaitTime := defaultRoomCreationMaxWaitTime
87- lockTTL := defaultRoomCreationLockTTL
87+ // performWithBackoff is a helper that executes a function with an exponential backoff retry strategy.
88+ func performWithBackoff (ctx context.Context , maxWaitTime time.Duration , log * logrus.Entry , action func () (bool , error )) error {
8889 currentInterval := backoffInitialInterval
89-
9090 loopStartTime := time .Now ()
91- log .Info ("attempting to acquire room creation lock" )
9291
9392 for {
9493 select {
9594 case <- ctx .Done ():
96- log .WithError (ctx .Err ()).Warn ("Context cancelled while waiting for room creation lock " )
97- return "" , fmt . Errorf ( "lock acquisition cancelled for room '%s': %w" , roomID , ctx .Err () )
95+ log .WithError (ctx .Err ()).Warn ("Context cancelled during backoff " )
96+ return ctx .Err ()
9897 default :
9998 }
10099
101- acquired , lockValue , errLock := rs . LockRoomCreation ( ctx , roomID , lockTTL )
102- if errLock != nil {
103- log .WithError (errLock ).Error ("Redis error while attempting to acquire room creation lock " )
104- return "" , fmt . Errorf ( "redis communication error for room '%s' lock: %w" , roomID , errLock )
100+ done , err := action ( )
101+ if err != nil {
102+ log .WithError (err ).Error ("Error during backoff action " )
103+ return err
105104 }
106105
107- if acquired {
108- log .WithFields (logrus.Fields {
109- "lockValue" : lockValue ,
110- "duration" : time .Since (loopStartTime ),
111- }).Info ("successfully acquired room creation lock" )
112- return lockValue , nil
106+ if done {
107+ return nil
113108 }
114109
115110 if time .Since (loopStartTime ) >= maxWaitTime {
116- log .WithField ("maxWaitTime" , maxWaitTime ).Warn ("Timeout while waiting for room creation lock " )
117- return "" , errors . New ( "timeout waiting to acquire lock for room " + roomID + ", operation is currently locked" )
111+ log .WithField ("maxWaitTime" , maxWaitTime ).Warn ("Timeout during backoff " )
112+ return timeoutErr
118113 }
119114
120115 // Calculate next interval with jitter
@@ -124,12 +119,12 @@ func acquireRoomCreationLockWithRetry(ctx context.Context, rs *redisservice.Redi
124119 log .WithFields (logrus.Fields {
125120 "waitDuration" : waitDuration ,
126121 "elapsed" : time .Since (loopStartTime ),
127- }).Debug ("Room creation lock not acquired . Waiting." )
122+ }).Debug ("Action not complete . Waiting." )
128123 select {
129124 case <- time .After (waitDuration ):
130125 case <- ctx .Done ():
131- log .WithError (ctx .Err ()).Warn ("Context cancelled while polling for room creation lock " )
132- return "" , fmt . Errorf ( "lock acquisition polling cancelled for room '%s': %w" , roomID , ctx .Err () )
126+ log .WithError (ctx .Err ()).Warn ("Context cancelled while waiting for next backoff attempt " )
127+ return ctx .Err ()
133128 }
134129 currentInterval = time .Duration (float64 (currentInterval ) * backoffMultiplier )
135130 if currentInterval > backoffMaxInterval {
@@ -138,52 +133,58 @@ func acquireRoomCreationLockWithRetry(ctx context.Context, rs *redisservice.Redi
138133 }
139134}
140135
141- // waitUntilRoomCreationCompletes waits until the room creation lock for the given roomID is released.
142- func waitUntilRoomCreationCompletes (ctx context.Context , rs * redisservice.RedisService , roomID string , log * logrus.Entry ) error {
143- maxWaitTime := defaultWaitForRoomCreationMaxWaitTime
144- currentInterval := backoffInitialInterval
145- loopStartTime := time .Now ()
136+ func acquireRoomCreationLockWithRetry (ctx context.Context , rs * redisservice.RedisService , roomID string , log * logrus.Entry ) (string , error ) {
137+ maxWaitTime := defaultRoomCreationMaxWaitTime
138+ lockTTL := defaultRoomCreationLockTTL
139+ var lockValue string
146140
147- for {
148- select {
149- case <- ctx .Done ():
150- log .WithError (ctx .Err ()).Warn ("Context cancelled while waiting for room creation to complete" )
151- return fmt .Errorf ("waiting for room creation to complete cancelled for room '%s': %w" , roomID , ctx .Err ())
152- default :
153- }
141+ log .Info ("Attempting to acquire room creation lock" )
154142
155- isLocked , errCheck := rs . IsRoomCreationLock ( ctx , roomID )
156- if errCheck != nil {
157- log . WithError ( errCheck ). Error ( "Redis error while checking room creation lock" )
158- return fmt .Errorf ("redis communication error while checking room '%s' creation lock: %w" , roomID , errCheck )
143+ action := func () ( bool , error ) {
144+ acquired , val , err := rs . LockRoomCreation ( ctx , roomID , lockTTL )
145+ if err != nil {
146+ return false , fmt .Errorf ("redis communication error for room '%s' lock: %w" , roomID , err )
159147 }
160-
161- if ! isLocked {
162- return nil
148+ if acquired {
149+ lockValue = val
150+ return true , nil
163151 }
152+ return false , nil
153+ }
164154
165- if time .Since (loopStartTime ) >= maxWaitTime {
166- log .WithField ("maxWaitTime" , maxWaitTime ).Warn ("Timeout while waiting for room creation to complete" )
167- return fmt .Errorf ("timeout waiting for room creation of room '%s' to complete" , roomID )
155+ err := performWithBackoff (ctx , maxWaitTime , log , action )
156+ if err != nil {
157+ if errors .Is (err , timeoutErr ) {
158+ return "" , errors .New ("timeout waiting to acquire lock for room " + roomID + ", operation is currently locked" )
168159 }
160+ return "" , fmt .Errorf ("lock acquisition cancelled for room '%s': %w" , roomID , err )
161+ }
169162
170- // Calculate next interval with jitter
171- jitter := time .Duration (rand .Float64 () * backoffJitter * float64 (currentInterval ))
172- waitDuration := currentInterval + jitter
163+ log .WithFields (logrus.Fields {
164+ "lockValue" : lockValue ,
165+ }).Info ("Successfully acquired room creation lock" )
166+ return lockValue , nil
167+ }
173168
174- log .WithFields (logrus.Fields {
175- "waitDuration" : waitDuration ,
176- "elapsed" : time .Since (loopStartTime ),
177- }).Debug ("Room creation is still in progress. Waiting." )
178- select {
179- case <- time .After (waitDuration ):
180- case <- ctx .Done ():
181- log .WithError (ctx .Err ()).Warn ("Context cancelled while polling for room creation to complete" )
182- return fmt .Errorf ("polling for room creation to complete cancelled for room '%s': %w" , roomID , ctx .Err ())
169+ // waitUntilRoomCreationCompletes waits until the room creation lock for the given roomID is released.
170+ func waitUntilRoomCreationCompletes (ctx context.Context , rs * redisservice.RedisService , roomID string , log * logrus.Entry ) error {
171+ maxWaitTime := defaultWaitForRoomCreationMaxWaitTime
172+
173+ action := func () (bool , error ) {
174+ isLocked , err := rs .IsRoomCreationLock (ctx , roomID )
175+ if err != nil {
176+ return false , fmt .Errorf ("redis communication error while checking room '%s' creation lock: %w" , roomID , err )
183177 }
184- currentInterval = time .Duration (float64 (currentInterval ) * backoffMultiplier )
185- if currentInterval > backoffMaxInterval {
186- currentInterval = backoffMaxInterval
178+ return ! isLocked , nil
179+ }
180+
181+ err := performWithBackoff (ctx , maxWaitTime , log , action )
182+ if err != nil {
183+ if errors .Is (err , timeoutErr ) {
184+ return fmt .Errorf ("timeout waiting for room creation of room '%s' to complete" , roomID )
187185 }
186+ return fmt .Errorf ("waiting for room creation to complete cancelled for room '%s': %w" , roomID , err )
188187 }
188+
189+ return nil
189190}
0 commit comments