@@ -21,6 +21,7 @@ import (
2121 "path/filepath"
2222 "regexp"
2323 "strconv"
24+ "sync"
2425 "time"
2526
2627 "github.com/tarantool/go-tarantool/v2"
@@ -75,6 +76,17 @@ type StartOpts struct {
7576 Dialer tarantool.Dialer
7677}
7778
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+
7890// TarantoolInstance is a data for instance graceful shutdown and cleanup.
7991type TarantoolInstance struct {
8092 // Cmd is a Tarantool command. Used to kill Tarantool process.
@@ -86,38 +98,60 @@ type TarantoolInstance struct {
8698 // Dialer to check that connection established.
8799 Dialer tarantool.Dialer
88100
89- done chan error
90- is_done bool
91- result error
92- is_stopping bool
101+ st * statusInstance
93102}
94103
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+ }
102107
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" )
109131 }
132+ //! t.st.wg.Wait()
133+ t .st .waitMutex .Lock ()
134+ defer t .st .waitMutex .Unlock ()
135+ return t .st .result
110136}
111137
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
120153 }
154+ return nil
121155}
122156
123157func isReady (dialer tarantool.Dialer , opts * tarantool.Opts ) error {
@@ -232,49 +266,81 @@ func IsTarantoolEE() (bool, error) {
232266func RestartTarantool (inst * TarantoolInstance ) error {
233267 startedInst , err := StartTarantool (inst .Opts )
234268 inst .Cmd .Process = startedInst .Cmd .Process
269+ inst .st = startedInst .st
235270 return err
236271}
237272
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+
238311// StartTarantool starts a tarantool instance for tests
239312// with specifies parameters (refer to StartOpts).
240313// Process must be stopped with StopTarantool.
241314func StartTarantool (startOpts StartOpts ) (TarantoolInstance , error ) {
242315 // Prepare tarantool command.
243- var inst TarantoolInstance
244- var dir string
316+ inst := newTarantoolInstance ()
245317 var err error
246318
247319 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 )
267323 }
268- args := []string {}
269324
325+ args := []string {}
270326 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+ }
271334 args = append (args , startOpts .InitScript )
272335 }
273336 if startOpts .ConfigFile != "" && startOpts .InstanceName != "" {
274337 args = append (args , "--config" , startOpts .ConfigFile )
275338 args = append (args , "--name" , startOpts .InstanceName )
276339 }
277340 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
278344
279345 inst .Cmd .Env = append (
280346 os .Environ (),
@@ -306,7 +372,7 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
306372 // see https://github.com/tarantool/go-tarantool/issues/136
307373 time .Sleep (startOpts .WaitStart )
308374
309- go inst .checkDone ()
375+ inst .checkDone ()
310376
311377 opts := tarantool.Opts {
312378 Timeout : 500 * time .Millisecond ,
@@ -327,15 +393,16 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
327393 }
328394 }
329395
330- working , err_st := inst .Status ()
331- if ! working || err_st != nil {
396+ if inst .st .isDone && inst .st .result != nil {
332397 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 )
334400 }
335401
336402 if err != nil {
337403 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 )
339406 }
340407
341408 return inst , nil
@@ -345,25 +412,9 @@ func StartTarantool(startOpts StartOpts) (TarantoolInstance, error) {
345412// with StartTarantool. Waits until any resources
346413// associated with the process is released. If something went wrong, fails.
347414func 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 )
367418 }
368419}
369420
0 commit comments