chzyer / readline

Readline is a pure go(golang) implementation for GNU-Readline kind library
MIT License
2.1k stars 280 forks source link

Data race between `Operation.ioloop()` and `Operation.SetConfig()` #196

Open rutsky opened 2 years ago

rutsky commented 2 years ago

If Operation.SetConfig() is called on already active readline instance (that e.g. read some lines), this may lead to data race between updating Operation.history in ioloop() and resetting history in SetConfig(), as access to Operation.history is not protected by any mutex.

Example trace from gotsan detecting such race condition:

==================
WARNING: DATA RACE
Write at 0x00c000842d60 by goroutine 123:
  root/third_party/golang/readline/readline.(*Operation).SetHistoryPath()
      third_party/golang/readline/operation.go:445 +0x23e
  root/third_party/golang/readline/readline.(*Operation).SetConfig()
      third_party/golang/readline/operation.go:469 +0x2d0
  root/third_party/golang/readline/readline.(*Instance).SetConfig()
      third_party/golang/readline/readline.go:309 +0xaa
  root/script/cli/multiline.setAutoCompleter()
      script/cli/multiline.go:18 +0x195
  root/script/cli/multiline.ReadSingleOptionalValue.func1()
      script/cli/multiline.go:24 +0x3d
  runtime.deferreturn()
      third_party/go/gc/src/runtime/panic.go:436 +0x32
  root/script/cli/commands/retain.(*Command).read()
      script/cli/commands/retain.go:156 +0xfa
  root/script/cli/commands/retain.(*Command).run()
      script/cli/commands/retain.go:308 +0x1531
  root/script/cli/commands/retain.TestInteractive.func1()
      script/cli/commands/retain_test.go:321 +0x35c
  testing.tRunner()
      third_party/go/gc/src/testing/testing.go:1410 +0x213
  testing.(*T).Run.func1()
      third_party/go/gc/src/testing/testing.go:1457 +0x47

Previous read at 0x00c000842d60 by goroutine 126:
  root/third_party/golang/readline/readline.(*Operation).ioloop()
      third_party/golang/readline/operation.go:257 +0x19c4
  root/third_party/golang/readline/readline.NewOperation.func2()
      third_party/golang/readline/operation.go:88 +0x39

Goroutine 123 (running) created at:
  testing.(*T).Run()
      third_party/go/gc/src/testing/testing.go:1457 +0x724
  root/script/cli/commands/retain.TestInteractive()
      script/cli/commands/retain_test.go:301 +0x130c
  testing.tRunner()
      third_party/go/gc/src/testing/testing.go:1410 +0x213
  testing.(*T).Run.func1()
      third_party/go/gc/src/testing/testing.go:1457 +0x47

Goroutine 126 (running) created at:
  root/third_party/golang/readline/readline.NewOperation()
      third_party/golang/readline/operation.go:88 +0x8bd
  root/third_party/golang/readline/readline.(*Terminal).Readline()
      third_party/golang/readline/terminal.go:96 +0x5b
  root/third_party/golang/readline/readline.NewEx()
      third_party/golang/readline/readline.go:167 +0x41
  root/script/cli/commands/retain.(*Command).run()
      script/cli/commands/retain.go:221 +0x1a9
  root/script/cli/commands/retain.TestInteractive.func1()
      script/cli/commands/retain_test.go:321 +0x35c
  testing.tRunner()
      third_party/go/gc/src/testing/testing.go:1410 +0x213
  testing.(*T).Run.func1()
      third_party/go/gc/src/testing/testing.go:1457 +0x47
==================