| 
									
										
										
										
											2024-09-29 19:23:19 +03:00
										 |  |  | package core | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-12-04 16:23:56 +02:00
										 |  |  | 	"database/sql" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2023-12-03 20:54:48 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/pocketbase/dbx" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 22:20:19 +02:00
										 |  |  | // default retries intervals (in ms) | 
					
						
							| 
									
										
										
										
											2024-09-29 19:23:19 +03:00
										 |  |  | var defaultRetryIntervals = []int{50, 100, 150, 200, 300, 400, 500, 700, 1000} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // default max retry attempts | 
					
						
							|  |  |  | const defaultMaxLockRetries = 12 | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 22:20:19 +02:00
										 |  |  | func execLockRetry(timeout time.Duration, maxRetries int) dbx.ExecHookFunc { | 
					
						
							|  |  |  | 	return func(q *dbx.Query, op func() error) error { | 
					
						
							|  |  |  | 		if q.Context() == nil { | 
					
						
							|  |  |  | 			cancelCtx, cancel := context.WithTimeout(context.Background(), timeout) | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 			defer func() { | 
					
						
							|  |  |  | 				cancel() | 
					
						
							| 
									
										
										
										
											2023-02-23 21:51:42 +02:00
										 |  |  | 				//nolint:staticcheck | 
					
						
							| 
									
										
										
										
											2023-02-22 22:20:19 +02:00
										 |  |  | 				q.WithContext(nil) // reset | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 			}() | 
					
						
							| 
									
										
										
										
											2023-02-22 22:20:19 +02:00
										 |  |  | 			q.WithContext(cancelCtx) | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-03 20:54:48 +02:00
										 |  |  | 		execErr := baseLockRetry(func(attempt int) error { | 
					
						
							| 
									
										
										
										
											2023-02-22 22:20:19 +02:00
										 |  |  | 			return op() | 
					
						
							|  |  |  | 		}, maxRetries) | 
					
						
							| 
									
										
										
										
											2023-12-04 16:23:56 +02:00
										 |  |  | 		if execErr != nil && !errors.Is(execErr, sql.ErrNoRows) { | 
					
						
							|  |  |  | 			execErr = fmt.Errorf("%w; failed query: %s", execErr, q.SQL()) | 
					
						
							| 
									
										
										
										
											2023-12-03 20:54:48 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-04 16:23:56 +02:00
										 |  |  | 		return execErr | 
					
						
							| 
									
										
										
										
											2023-02-22 22:20:19 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func baseLockRetry(op func(attempt int) error, maxRetries int) error { | 
					
						
							|  |  |  | 	attempt := 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Retry: | 
					
						
							|  |  |  | 	err := op(attempt) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 07:45:27 +02:00
										 |  |  | 	if err != nil && attempt <= maxRetries { | 
					
						
							|  |  |  | 		errStr := err.Error() | 
					
						
							|  |  |  | 		// we are checking the error against the plain error texts since the codes could vary between drivers | 
					
						
							|  |  |  | 		if strings.Contains(errStr, "database is locked") || | 
					
						
							|  |  |  | 			strings.Contains(errStr, "table is locked") { | 
					
						
							|  |  |  | 			// wait and retry | 
					
						
							|  |  |  | 			time.Sleep(getDefaultRetryInterval(attempt)) | 
					
						
							|  |  |  | 			attempt++ | 
					
						
							|  |  |  | 			goto Retry | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-02-21 16:38:12 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func getDefaultRetryInterval(attempt int) time.Duration { | 
					
						
							|  |  |  | 	if attempt < 0 || attempt > len(defaultRetryIntervals)-1 { | 
					
						
							|  |  |  | 		return time.Duration(defaultRetryIntervals[len(defaultRetryIntervals)-1]) * time.Millisecond | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return time.Duration(defaultRetryIntervals[attempt]) * time.Millisecond | 
					
						
							|  |  |  | } |