@@ -11,6 +11,8 @@ import (
11
11
"path"
12
12
"strconv"
13
13
"strings"
14
+ "sync"
15
+ "sync/atomic"
14
16
"time"
15
17
16
18
"github.com/docker/docker/api/types"
@@ -72,17 +74,18 @@ type provisionModeLocal struct {
72
74
provision
73
75
dockerClient * client.Client
74
76
runner runners.Runner
77
+ mu * sync.Mutex
75
78
ports []bool
76
- sessionCounter uint
79
+ sessionCounter uint32
77
80
thinCloneManager thinclones.Manager
78
81
}
79
82
80
83
// NewProvisionModeLocal creates a new Provision instance of ModeLocal.
81
84
func NewProvisionModeLocal (ctx context.Context , config Config , dockerClient * client.Client ) (Provision , error ) {
82
85
p := & provisionModeLocal {
83
- runner : runners .NewLocalRunner (config .ModeLocal .UseSudo ),
84
- sessionCounter : 0 ,
85
- dockerClient : dockerClient ,
86
+ runner : runners .NewLocalRunner (config .ModeLocal .UseSudo ),
87
+ mu : & sync. Mutex {} ,
88
+ dockerClient : dockerClient ,
86
89
provision : provision {
87
90
config : config ,
88
91
ctx : ctx ,
@@ -196,8 +199,7 @@ func (j *provisionModeLocal) StartSession(username, password, snapshotID string)
196
199
return nil , errors .Wrap (err , "failed to get snapshots" )
197
200
}
198
201
199
- // TODO(anatoly): Synchronization or port allocation statuses.
200
- port , err := j .getFreePort ()
202
+ port , err := j .allocatePort ()
201
203
if err != nil {
202
204
return nil , errors .New ("failed to get a free port" )
203
205
}
@@ -209,6 +211,10 @@ func (j *provisionModeLocal) StartSession(username, password, snapshotID string)
209
211
defer func () {
210
212
if err != nil {
211
213
j .revertSession (name )
214
+
215
+ if portErr := j .freePort (port ); portErr != nil {
216
+ log .Err (portErr )
217
+ }
212
218
}
213
219
}()
214
220
@@ -227,12 +233,7 @@ func (j *provisionModeLocal) StartSession(username, password, snapshotID string)
227
233
return nil , errors .Wrap (err , "failed to prepare a database" )
228
234
}
229
235
230
- err = j .setPort (port , true )
231
- if err != nil {
232
- return nil , errors .Wrap (err , "failed to set a port" )
233
- }
234
-
235
- j .sessionCounter ++
236
+ atomic .AddUint32 (& j .sessionCounter , 1 )
236
237
237
238
appConfig := j .getAppConfig (name , port )
238
239
@@ -263,7 +264,7 @@ func (j *provisionModeLocal) StopSession(session *resources.Session) error {
263
264
return errors .Wrap (err , "failed to destroy a clone" )
264
265
}
265
266
266
- err = j .setPort (session .Port , false )
267
+ err = j .freePort (session .Port )
267
268
if err != nil {
268
269
return errors .Wrap (err , "failed to unbind a port" )
269
270
}
@@ -372,20 +373,39 @@ func (j *provisionModeLocal) initPortPool() error {
372
373
return nil
373
374
}
374
375
375
- func (j * provisionModeLocal ) getFreePort () (uint , error ) {
376
+ // allocatePort tries to find a free port and occupy it.
377
+ func (j * provisionModeLocal ) allocatePort () (uint , error ) {
376
378
portOpts := j .config .ModeLocal .PortPool
377
379
380
+ j .mu .Lock ()
381
+ defer j .mu .Unlock ()
382
+
378
383
for index , binded := range j .ports {
379
384
if ! binded {
380
385
port := portOpts .From + uint (index )
386
+
387
+ if err := j .setPortStatus (port , true ); err != nil {
388
+ return 0 , errors .Wrap (err , "failed to set port status" )
389
+ }
390
+
381
391
return port , nil
382
392
}
383
393
}
384
394
385
395
return 0 , errors .WithStack (NewNoRoomError ("no available ports" ))
386
396
}
387
397
388
- func (j * provisionModeLocal ) setPort (port uint , bind bool ) error {
398
+ // freePort marks the port as free.
399
+ func (j * provisionModeLocal ) freePort (port uint ) error {
400
+ j .mu .Lock ()
401
+ defer j .mu .Unlock ()
402
+
403
+ return j .setPortStatus (port , false )
404
+ }
405
+
406
+ // setPortStatus updates the port status.
407
+ // It's not safe to invoke without ports mutex locking. Use allocatePort and freePort methods.
408
+ func (j * provisionModeLocal ) setPortStatus (port uint , bind bool ) error {
389
409
portOpts := j .config .ModeLocal .PortPool
390
410
391
411
if port < portOpts .From || port >= portOpts .To {
@@ -399,14 +419,14 @@ func (j *provisionModeLocal) setPort(port uint, bind bool) error {
399
419
}
400
420
401
421
func (j * provisionModeLocal ) stopAllSessions () error {
402
- insts , err := postgres .List (j .runner , ClonePrefix )
422
+ instances , err := postgres .List (j .runner , ClonePrefix )
403
423
if err != nil {
404
424
return errors .Wrap (err , "failed to list containers" )
405
425
}
406
426
407
- log .Dbg ("Containers running:" , insts )
427
+ log .Dbg ("Containers running:" , instances )
408
428
409
- for _ , inst := range insts {
429
+ for _ , inst := range instances {
410
430
log .Dbg ("Stopping container:" , inst )
411
431
412
432
if err = postgres .Stop (j .runner , j .getAppConfig (inst , 0 )); err != nil {
0 commit comments