@@ -16,12 +16,13 @@ package async
16
16
17
17
import (
18
18
"context"
19
- "fmt "
19
+ "errors "
20
20
"log/slog"
21
21
"time"
22
22
23
23
v1 "github.com/google/go-containerregistry/pkg/v1"
24
24
"github.com/puzpuzpuz/xsync/v3"
25
+ "golang.org/x/sync/errgroup"
25
26
26
27
"github.com/cenkalti/backoff/v4"
27
28
"github.com/seqeralabs/staticreg/pkg/observability/logger"
@@ -31,22 +32,35 @@ import (
31
32
const imageInfoRequestsBufSize = 10
32
33
const tagRequestBufferSize = 10
33
34
35
+ var (
36
+ ErrNoTagsFound = errors .New ("no tags found" )
37
+ ErrImageInfoNotFound = errors .New ("image info not found" )
38
+ )
39
+
34
40
// Async is a struct that wraps an underlying registry.Client
35
41
// to provide asynchronous methods for interacting with a container registry.
36
42
// It continuously syncs data from the registry in a separate goroutine.
37
43
type Async struct {
38
44
// underlying is the actual registry client that does the registry operations, remember this is just a wrapper!
39
45
underlying registry.Client
40
-
46
+ // refreshInterval represents the time to wait to synchronize repositories again after a successful synchronization
41
47
refreshInterval time.Duration
42
48
49
+ // repos is an in memory list of all the repository names in the registry
43
50
repos []string
44
51
52
+ // repositoryTags represents the list of tags for each repository
45
53
repositoryTags * xsync.MapOf [string , []string ]
46
54
55
+ // imageInfo contains the image information indexed by repo name and tag
47
56
imageInfo * xsync.MapOf [imageInfoKey , imageInfo ]
48
57
58
+ // repositoryRequestBuffer generates requests for the `handleRepositoryRequest`
59
+ // handler that is responsible for retrieving the tags for a given image and
60
+ // scheduling new jobs on `imageInfoRequestsBuffer`
49
61
repositoryRequestBuffer chan repositoryRequest
62
+ // imageInfoRequestsBuffer is responsible for feeding `handleImageInfoRequest`
63
+ // so that image info is retrieved for each <repo,tag> combination
50
64
imageInfoRequestsBuffer chan imageInfoRequest
51
65
}
52
66
@@ -68,52 +82,51 @@ type imageInfo struct {
68
82
reference string
69
83
}
70
84
85
+ func (c * Async ) Stop (ctx context.Context ) {
86
+ close (c .imageInfoRequestsBuffer )
87
+ close (c .repositoryRequestBuffer )
88
+ }
89
+
71
90
func (c * Async ) Start (ctx context.Context ) error {
72
- // TODO(fntlnz): maybe instead of errCh use a backoff and retry ops
73
- errCh := make (chan error , 1 )
91
+ g , ctx := errgroup .WithContext (ctx )
74
92
75
- go func () {
93
+ g . Go ( func () error {
76
94
for {
77
95
err := backoff .Retry (func () error {
78
96
return c .synchronizeRepositories (ctx )
79
97
}, backoff .WithContext (newExponentialBackoff (), ctx ))
80
98
81
99
if err != nil {
82
- errCh <- err
100
+ return err
83
101
}
84
102
85
103
time .Sleep (c .refreshInterval )
86
104
}
87
- }( )
105
+ })
88
106
89
- go func () {
107
+ g . Go ( func () error {
90
108
for {
91
109
select {
92
110
case <- ctx .Done ():
93
- return
111
+ return ctx . Err ()
94
112
case req := <- c .repositoryRequestBuffer :
95
113
c .handleRepositoryRequest (ctx , req )
96
114
}
97
115
}
98
- }( )
116
+ })
99
117
100
- go func () {
118
+ g . Go ( func () error {
101
119
for {
102
120
select {
103
121
case <- ctx .Done ():
104
- return
122
+ return ctx . Err ()
105
123
case req := <- c .imageInfoRequestsBuffer :
106
124
c .handleImageInfoRequest (ctx , req )
107
125
}
108
126
}
109
- }( )
127
+ })
110
128
111
- select {
112
- case <- ctx .Done ():
113
- return nil
114
- case err := <- errCh :
115
- return err
116
- }
129
+ return g .Wait ()
117
130
}
118
131
119
132
func (c * Async ) synchronizeRepositories (ctx context.Context ) error {
@@ -126,7 +139,14 @@ func (c *Async) synchronizeRepositories(ctx context.Context) error {
126
139
c .repos = repos
127
140
128
141
for _ , r := range repos {
129
- c .repositoryRequestBuffer <- repositoryRequest {repo : r }
142
+ if err := ctx .Err (); err != nil {
143
+ return nil
144
+ }
145
+ select {
146
+ case c .repositoryRequestBuffer <- repositoryRequest {repo : r }:
147
+ default :
148
+ return nil
149
+ }
130
150
}
131
151
132
152
return nil
@@ -146,13 +166,19 @@ func (c *Async) handleRepositoryRequest(ctx context.Context, req repositoryReque
146
166
c .repositoryTags .Store (req .repo , tags )
147
167
148
168
for _ , t := range tags {
149
- c .imageInfoRequestsBuffer <- imageInfoRequest {
169
+ if ctx .Err () != nil {
170
+ return
171
+ }
172
+
173
+ select {
174
+ case c .imageInfoRequestsBuffer <- imageInfoRequest {
150
175
repo : req .repo ,
151
176
tag : t ,
177
+ }:
178
+ default :
179
+ return
152
180
}
153
181
}
154
-
155
- return
156
182
}
157
183
158
184
func (c * Async ) handleImageInfoRequest (ctx context.Context , req imageInfoRequest ) {
@@ -176,10 +202,11 @@ func (c *Async) RepoList(ctx context.Context) ([]string, error) {
176
202
return c .repos , nil
177
203
}
178
204
205
+ // TagList contains
179
206
func (c * Async ) TagList (ctx context.Context , repo string ) ([]string , error ) {
180
207
tags , ok := c .repositoryTags .Load (repo )
181
208
if ! ok {
182
- return nil , fmt . Errorf ( "no tags found" ) // TODO(fntlnz): make an error var
209
+ return nil , ErrNoTagsFound
183
210
}
184
211
return tags , nil
185
212
}
@@ -191,7 +218,7 @@ func (c *Async) ImageInfo(ctx context.Context, repo string, tag string) (image v
191
218
}
192
219
info , ok := c .imageInfo .Load (key )
193
220
if ! ok {
194
- return nil , "" , fmt . Errorf ( "image info not found" ) // TODO(fntlnz): make an error var
221
+ return nil , "" , ErrImageInfoNotFound
195
222
}
196
223
return info .image , info .reference , nil
197
224
}
0 commit comments