@@ -21,6 +21,7 @@ import (
21
21
"path/filepath"
22
22
"regexp"
23
23
"strconv"
24
+ "sync"
24
25
"time"
25
26
26
27
"github.com/tarantool/go-tarantool/v2"
@@ -75,6 +76,17 @@ type StartOpts struct {
75
76
Dialer tarantool.Dialer
76
77
}
77
78
79
+ type statusInstance struct {
80
+ result error
81
+ isStopping bool
82
+ isDone bool
83
+ //! wg sync.WaitGroup
84
+ // waitMutex is used to prevent several invokes of the "Wait"
85
+ // for the same process.
86
+ // https://github.com/golang/go/issues/28461
87
+ waitMutex sync.Mutex
88
+ }
89
+
78
90
// TarantoolInstance is a data for instance graceful shutdown and cleanup.
79
91
type TarantoolInstance struct {
80
92
// Cmd is a Tarantool command. Used to kill Tarantool process.
@@ -86,38 +98,60 @@ type TarantoolInstance struct {
86
98
// Dialer to check that connection established.
87
99
Dialer tarantool.Dialer
88
100
89
- done chan error
90
- is_done bool
91
- result error
92
- is_stopping bool
101
+ st * statusInstance
93
102
}
94
103
95
- // Status checks if Tarantool instance is still running.
96
- // Return true if it is running, false if it is not.
97
- // If instance was exit and error is nil - process completed success with zero status code.
98
- func (t * TarantoolInstance ) Status () (bool , error ) {
99
- if t .is_done {
100
- return false , t .result
101
- }
104
+ func newTarantoolInstance () TarantoolInstance {
105
+ return TarantoolInstance {st : & statusInstance {}}
106
+ }
102
107
103
- select {
104
- case t .result = <- t .done :
105
- t .is_done = true
106
- return false , t .result
107
- default :
108
- return true , nil
108
+ func (t * TarantoolInstance ) checkDone () {
109
+ if t .st == nil {
110
+ panic ("TarantoolInstance is not initialized" )
111
+ }
112
+ // t.st.wg.Add(1)
113
+ go func () {
114
+ // defer t.st.wg.Done()
115
+ t .st .waitMutex .Lock ()
116
+ defer t .st .waitMutex .Unlock ()
117
+ if t .st .isDone {
118
+ return
119
+ }
120
+ t .st .result = t .Cmd .Wait ()
121
+ t .st .isDone = true
122
+ if ! t .st .isStopping {
123
+ log .Printf ("Tarantool %q was unexpected terminated: %v" , t .Opts .Listen , t .st .result )
124
+ }
125
+ }()
126
+ }
127
+
128
+ func (t * TarantoolInstance ) Wait () error {
129
+ if t .st == nil {
130
+ panic ("TarantoolInstance is not initialized" )
109
131
}
132
+ //! t.st.wg.Wait()
133
+ t .st .waitMutex .Lock ()
134
+ defer t .st .waitMutex .Unlock ()
135
+ return t .st .result
110
136
}
111
137
112
- func (t * TarantoolInstance ) checkDone () {
113
- t .is_done = false
114
- t .is_stopping = false
115
- t .done = make (chan error , 1 )
116
- t .done <- t .Cmd .Wait ()
117
- if ! t .is_stopping {
118
- _ , err := t .Status ()
119
- log .Printf ("Tarantool was unexpected terminated: %s" , err )
138
+ func (t * TarantoolInstance ) Stop () error {
139
+ log .Printf ("Stopping Tarantool instance %q" , t .Opts .Listen )
140
+ t .st .isStopping = true
141
+ if t .st .isDone {
142
+ log .Printf ("Already stopped instance %q with result: %v" , t .Opts .Listen , t .st .result )
143
+ return nil
144
+ }
145
+ if t .Cmd != nil && t .Cmd .Process != nil {
146
+ log .Printf ("Killing Tarantool %q (pid %d)" , t .Opts .Listen , t .Cmd .Process .Pid )
147
+ if err := t .Cmd .Process .Kill (); err != nil && ! t .st .isDone {
148
+ return fmt .Errorf ("failed to kill tarantool %q (pid %d), got %s" ,
149
+ t .Opts .Listen , t .Cmd .Process .Pid , err )
150
+ }
151
+ t .Wait ()
152
+ t .Cmd .Process = nil
120
153
}
154
+ return nil
121
155
}
122
156
123
157
func isReady (dialer tarantool.Dialer , opts * tarantool.Opts ) error {
@@ -232,49 +266,81 @@ func IsTarantoolEE() (bool, error) {
232
266
func RestartTarantool (inst * TarantoolInstance ) error {
233
267
startedInst , err := StartTarantool (inst .Opts )
234
268
inst .Cmd .Process = startedInst .Cmd .Process
269
+ inst .st = startedInst .st
235
270
return err
236
271
}
237
272
273
+ func removeByMask (dir string , masks ... string ) error {
274
+ for _ , mask := range masks {
275
+ files , err := filepath .Glob (filepath .Join (dir , mask ))
276
+ if err != nil {
277
+ return err
278
+ }
279
+ for _ , f := range files {
280
+ if err = os .Remove (f ); err != nil {
281
+ return err
282
+ }
283
+ }
284
+ }
285
+ return nil
286
+ }
287
+
288
+ func prepareDir (workDir string ) (string , error ) {
289
+ if workDir == "" {
290
+ dir , err := os .MkdirTemp ("" , "work_dir" )
291
+ if err != nil {
292
+ return "" , err
293
+ }
294
+ return dir , nil
295
+ }
296
+ // Create work_dir.
297
+ err := os .MkdirAll (workDir , 0755 )
298
+ if err != nil {
299
+ return "" , err
300
+ }
301
+
302
+ // Clean up existing work_dir.
303
+ // TODO: Ensure that nested files will be removed.
304
+ err = removeByMask (workDir , "*.snap" , "*.xlog" )
305
+ if err != nil {
306
+ return "" , err
307
+ }
308
+ return workDir , nil
309
+ }
310
+
238
311
// StartTarantool starts a tarantool instance for tests
239
312
// with specifies parameters (refer to StartOpts).
240
313
// Process must be stopped with StopTarantool.
241
314
func StartTarantool (startOpts StartOpts ) (TarantoolInstance , error ) {
242
315
// Prepare tarantool command.
243
- var inst TarantoolInstance
244
- var dir string
316
+ inst := newTarantoolInstance ()
245
317
var err error
246
318
247
319
inst .Dialer = startOpts .Dialer
248
-
249
- if startOpts .WorkDir == "" {
250
- dir , err = os .MkdirTemp ("" , "work_dir" )
251
- if err != nil {
252
- return inst , err
253
- }
254
- startOpts .WorkDir = dir
255
- } else {
256
- // Clean up existing work_dir.
257
- err = os .RemoveAll (startOpts .WorkDir )
258
- if err != nil {
259
- return inst , err
260
- }
261
-
262
- // Create work_dir.
263
- err = os .Mkdir (startOpts .WorkDir , 0755 )
264
- if err != nil {
265
- return inst , err
266
- }
320
+ startOpts .WorkDir , err = prepareDir (startOpts .WorkDir )
321
+ if err != nil {
322
+ return inst , fmt .Errorf ("failed prepare working dir %q: %w" , startOpts .WorkDir , err )
267
323
}
268
- args := []string {}
269
324
325
+ args := []string {}
270
326
if startOpts .InitScript != "" {
327
+ if ! filepath .IsAbs (startOpts .InitScript ) {
328
+ cwd , err := os .Getwd ()
329
+ if err != nil {
330
+ return inst , fmt .Errorf ("failed to get current working directory: %w" , err )
331
+ }
332
+ startOpts .InitScript = filepath .Join (cwd , startOpts .InitScript )
333
+ }
271
334
args = append (args , startOpts .InitScript )
272
335
}
273
336
if startOpts .ConfigFile != "" && startOpts .InstanceName != "" {
274
337
args = append (args , "--config" , startOpts .ConfigFile )
275
338
args = append (args , "--name" , startOpts .InstanceName )
276
339
}
277
340
inst .Cmd = exec .Command (getTarantoolExec (), args ... )
341
+ inst .Cmd .Dir = startOpts .WorkDir
342
+ inst .Cmd .Stdout = os .Stderr //! DEBUG: remove
343
+ inst .Cmd .Stderr = os .Stderr //! DEBUG: remove
278
344
279
345
inst .Cmd .Env = append (
280
346
os .Environ (),
@@ -306,7 +372,7 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
306
372
// see https://github.com/tarantool/go-tarantool/issues/136
307
373
time .Sleep (startOpts .WaitStart )
308
374
309
- go inst .checkDone ()
375
+ inst .checkDone ()
310
376
311
377
opts := tarantool.Opts {
312
378
Timeout : 500 * time .Millisecond ,
@@ -327,15 +393,16 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
327
393
}
328
394
}
329
395
330
- working , err_st := inst .Status ()
331
- if ! working || err_st != nil {
396
+ if inst .st .isDone && inst .st .result != nil {
332
397
StopTarantool (inst )
333
- return TarantoolInstance {}, fmt .Errorf ("unexpected terminated Tarantool: %w" , err_st )
398
+ return TarantoolInstance {}, fmt .Errorf ("unexpected terminated Tarantool %q: %w" ,
399
+ inst .Opts .Listen , inst .st .result )
334
400
}
335
401
336
402
if err != nil {
337
403
StopTarantool (inst )
338
- return TarantoolInstance {}, fmt .Errorf ("failed to connect Tarantool: %w" , err )
404
+ return TarantoolInstance {}, fmt .Errorf ("failed to connect Tarantool %q: %w" ,
405
+ inst .Opts .Listen , err )
339
406
}
340
407
341
408
return inst , nil
@@ -345,25 +412,9 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
345
412
// with StartTarantool. Waits until any resources
346
413
// associated with the process is released. If something went wrong, fails.
347
414
func StopTarantool (inst TarantoolInstance ) {
348
- log .Printf ("Stopping Tarantool instance" )
349
- inst .is_stopping = true
350
- if inst .Cmd != nil && inst .Cmd .Process != nil {
351
- if err := inst .Cmd .Process .Kill (); err != nil {
352
- is_running , _ := inst .Status ()
353
- if is_running {
354
- log .Fatalf ("Failed to kill tarantool (pid %d), got %s" , inst .Cmd .Process .Pid , err )
355
- }
356
- }
357
-
358
- // Wait releases any resources associated with the Process.
359
- if _ , err := inst .Cmd .Process .Wait (); err != nil {
360
- is_running , _ := inst .Status ()
361
- if is_running {
362
- log .Fatalf ("Failed to wait for Tarantool process to exit, got %s" , err )
363
- }
364
- }
365
-
366
- inst .Cmd .Process = nil
415
+ err := inst .Stop ()
416
+ if err != nil {
417
+ log .Fatal (err )
367
418
}
368
419
}
369
420
0 commit comments