Skip to content

Commit 81ee2ab

Browse files
psergeeoleg-jukovec
authored andcommitted
log: fix crash on removing log dir
Fix double closing the chanel on log dirs removing.
1 parent 0c4094e commit 81ee2ab

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

cli/cmd/log.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"os/signal"
9+
"sync"
910

1011
"github.com/spf13/cobra"
1112
"github.com/tarantool/tt/cli/cmd/internal"
@@ -74,10 +75,12 @@ func follow(instances []running.InstanceCtx, n int) error {
7475
const logLinesChannelCapacity = 64
7576
logLines := make(chan string, logLinesChannelCapacity)
7677
tailRoutinesStarted := 0
78+
// Wait group to wait for completion of all log reading routines to close the channel once.
79+
var wg sync.WaitGroup
7780
for _, inst := range instances {
7881
if err := tail.Follow(ctx, logLines,
7982
tail.NewLogFormatter(running.GetAppInstanceName(inst)+": ", color),
80-
inst.Log, n); err != nil {
83+
inst.Log, n, &wg); err != nil {
8184
if errors.Is(err, os.ErrNotExist) {
8285
continue
8386
}
@@ -89,6 +92,10 @@ func follow(instances []running.InstanceCtx, n int) error {
8992
}
9093

9194
if tailRoutinesStarted > 0 {
95+
go func() {
96+
wg.Wait()
97+
close(logLines)
98+
}()
9299
return printLines(ctx, logLines)
93100
}
94101
return nil

cli/tail/tail.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io"
99
"os"
1010
"strings"
11+
"sync"
1112

1213
"github.com/apex/log"
1314
"github.com/fatih/color"
@@ -131,7 +132,7 @@ func TailN(ctx context.Context, logFormatter LogFormatter, fileName string,
131132

132133
// Follow sends to the channel each new line from the file as it grows.
133134
func Follow(ctx context.Context, out chan<- string, logFormatter LogFormatter, fileName string,
134-
n int) error {
135+
n int, wg *sync.WaitGroup) error {
135136
file, err := os.Open(fileName)
136137
if err != nil {
137138
return fmt.Errorf("cannot open %q: %w", fileName, err)
@@ -157,7 +158,9 @@ func Follow(ctx context.Context, out chan<- string, logFormatter LogFormatter, f
157158
return err
158159
}
159160

161+
wg.Add(1)
160162
go func() {
163+
defer wg.Done()
161164
for {
162165
select {
163166
case <-ctx.Done():
@@ -172,7 +175,6 @@ func Follow(ctx context.Context, out chan<- string, logFormatter LogFormatter, f
172175
} else {
173176
log.Errorf("The log file %q is unavailable for reading. Exiting.")
174177
}
175-
close(out)
176178
return
177179
}
178180
out <- logFormatter(line.Text)

cli/tail/tail_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"os"
7+
"sync"
78
"testing"
89
"time"
910

@@ -299,7 +300,8 @@ func TestFollow(t *testing.T) {
299300
defer stop()
300301
in := make(chan string)
301302
err = Follow(ctx, in,
302-
func(str string) string { return str }, outFile.Name(), tt.nLines)
303+
func(str string) string { return str }, outFile.Name(), tt.nLines,
304+
&sync.WaitGroup{})
303305
require.NoError(t, err)
304306

305307
if tt.nLines > 0 && len(tt.expectedLastLines) > 0 {

test/integration/log/test_log.py

+35-2
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def test_log_output_default_follow_want_zero_last(tt_cmd, mock_env_dir):
256256
assert 'app1:inst0' not in output
257257

258258

259-
def test_log__dir_removed_after_follow(tt_cmd, mock_env_dir):
259+
def test_log_dir_removed_after_follow(tt_cmd, mock_env_dir):
260260
cmd = [tt_cmd, 'log', '-f']
261261
process = subprocess.Popen(
262262
cmd,
@@ -270,7 +270,40 @@ def test_log__dir_removed_after_follow(tt_cmd, mock_env_dir):
270270
['app0:inst0: line 19', 'app1:inst2: line 19',
271271
'app0:inst1: line 19', 'app1:inst1: line 19'])
272272

273-
var_dir = os.path.join(mock_env_dir, 'ie', 'app0', 'var')
273+
var_dir = os.path.join(mock_env_dir, 'ie')
274+
assert os.path.exists(var_dir)
275+
shutil.rmtree(var_dir)
276+
277+
assert process.wait(2) == 0
278+
assert "Failed to detect creation of" in process.stdout.read()
279+
280+
281+
# There are two apps in this test: app0 and app1. After removing app0 dirs,
282+
# tt log -f is still able to monitor the app1 log files, so there should be no issue.
283+
def test_log_dir_partially_removed_after_follow(tt_cmd, mock_env_dir):
284+
cmd = [tt_cmd, 'log', '-f']
285+
process = subprocess.Popen(
286+
cmd,
287+
cwd=mock_env_dir,
288+
stderr=subprocess.STDOUT,
289+
stdout=subprocess.PIPE,
290+
text=True,
291+
)
292+
293+
wait_for_lines_in_output(process.stdout,
294+
['app0:inst0: line 19', 'app1:inst2: line 19',
295+
'app0:inst1: line 19', 'app1:inst1: line 19'])
296+
297+
# Remove one app log dir.
298+
var_dir = os.path.join(mock_env_dir, 'ie', 'app0', 'var', 'log')
299+
assert os.path.exists(var_dir)
300+
shutil.rmtree(var_dir)
301+
302+
wait_for_lines_in_output(process.stdout, ['Failed to detect creation of'])
303+
assert process.poll() is None # Still running.
304+
305+
# Remove app1 log dir.
306+
var_dir = os.path.join(mock_env_dir, 'ie', 'app1')
274307
assert os.path.exists(var_dir)
275308
shutil.rmtree(var_dir)
276309

0 commit comments

Comments
 (0)