Repository: syncthing/syncthing (language: go)

Issue:
Data race in connection management
Probably I screwed this up in the multi-con stuff

```
=== RUN   TestConnCloseOnRestart
==================
WARNING: DATA RACE
Read at 0x00c00a2d7eb0 by goroutine 11372:
  github.com/syncthing/syncthing/lib/protocol.(*rawConnection).internalClose.func1()
      /home/runner/work/syncthing/syncthing/lib/protocol/protocol.go:982 +0x8ca
  sync.(*Once).doSlow()
      /opt/hostedtoolcache/go/1.20.8/x64/src/sync/once.go:74 +0x101
  sync.(*Once).Do()
      /opt/hostedtoolcache/go/1.20.8/x64/src/sync/once.go:65 +0x46
  github.com/syncthing/syncthing/lib/protocol.(*rawConnection).internalClose()
      /home/runner/work/syncthing/syncthing/lib/protocol/protocol.go:966 +0x74
  github.com/syncthing/syncthing/lib/protocol.(*rawConnection).Close.func2()
      /home/runner/work/syncthing/syncthing/lib/protocol/protocol.go:961 +0x58

Previous write at 0x00c00a2d7eb0 by goroutine 11328:
  github.com/syncthing/syncthing/lib/protocol.(*rawConnection).Start()
      /home/runner/work/syncthing/syncthing/lib/protocol/protocol.go:320 +0x2cc
  github.com/syncthing/syncthing/lib/protocol.encryptedConnection.Start()
      /home/runner/work/syncthing/syncthing/lib/protocol/encryption.go:181 +0x70
  github.com/syncthing/syncthing/lib/protocol.(*encryptedConnection).Start()
      <autogenerated>:1 +0x25
  github.com/syncthing/syncthing/lib/protocol.(*wireFormatConnection).Start()
      <autogenerated>:1 +0x4c
  github.com/syncthing/syncthing/lib/model.(*model).promoteConnections()
      /home/runner/work/syncthing/syncthing/lib/model/model.go:2426 +0x863
  github.com/syncthing/syncthing/lib/model.(*model).serve()
      /home/runner/work/syncthing/syncthing/lib/model/model.go:288 +0x418
  github.com/syncthing/syncthing/lib/model.(*model).serve-fm()
      <autogenerated>:1 +0x4d
  github.com/syncthing/syncthing/lib/svcutil.(*service).Serve()
      /home/runner/work/syncthing/syncthing/lib/svcutil/svcutil.go:130 +0xed
  github.com/thejerf/suture/v4.(*Supervisor).runService.func2()
      /home/runner/go/pkg/mod/github.com/thejerf/suture/v4@v4.0.2/supervisor.go:565 +0x1c8

Goroutine 11372 (running) created at:
  github.com/syncthing/syncthing/lib/protocol.(*rawConnection).Close()
      /home/runner/work/syncthing/syncthing/lib/protocol/protocol.go:961 +0x12a
  github.com/syncthing/syncthing/lib/protocol.encryptedConnection.Close()
      /home/runner/work/syncthing/syncthing/lib/protocol/encryption.go:254 +0x8b
  github.com/syncthing/syncthing/lib/protocol.(*encryptedConnection).Close()
      <autogenerated>:1 +0x29
  github.com/syncthing/syncthing/lib/protocol.(*wireFormatConnection).Close()
      <autogenerated>:1 +0x63
  github.com/syncthing/syncthing/lib/model.(*model).CommitConfiguration.func2()
      /home/runner/work/syncthing/syncthing/lib/model/model.go:3123 +0x67

Goroutine 11328 (running) created at:
  github.com/thejerf/suture/v4.(*Supervisor).runService()
      /home/runner/go/pkg/mod/github.com/thejerf/suture/v4@v4.0.2/supervisor.go:539 +0x32c
  github.com/thejerf/suture/v4.(*Supervisor).Serve()
      /home/runner/go/pkg/mod/github.com/thejerf/suture/v4@v4.0.2/supervisor.go:348 +0x409
  github.com/syncthing/syncthing/lib/model.(*testModel).ServeBackground.func1()
      /home/runner/work/syncthing/syncthing/lib/model/testutils_test.go:171 +0x73
==================
    testing.go:1446: race detected during execution of test
--- FAIL: TestConnCloseOnRestart (0.03s)
```


Discussion / hints:

Fix patch:
diff --git a/lib/protocol/protocol.go b/lib/protocol/protocol.go
index 8e5f653d9f8..a22c157bb59 100644
--- a/lib/protocol/protocol.go
+++ b/lib/protocol/protocol.go
@@ -208,6 +208,7 @@ type rawConnection struct {
 	closeOnce             sync.Once
 	sendCloseOnce         sync.Once
 	compression           Compression
+	startStopMut          sync.Mutex // start and stop must be serialized
 
 	loopWG sync.WaitGroup // Need to ensure no leftover routines in testing
 }
@@ -295,6 +296,8 @@ func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, clo
 // Start creates the goroutines for sending and receiving of messages. It must
 // be called exactly once after creating a connection.
 func (c *rawConnection) Start() {
+	c.startStopMut.Lock()
+	defer c.startStopMut.Unlock()
 	c.loopWG.Add(5)
 	go func() {
 		c.readerLoop()
@@ -963,6 +966,8 @@ func (c *rawConnection) Close(err error) {
 
 // internalClose is called if there is an unexpected error during normal operation.
 func (c *rawConnection) internalClose(err error) {
+	c.startStopMut.Lock()
+	defer c.startStopMut.Unlock()
 	c.closeOnce.Do(func() {
 		l.Debugf("close connection to %s at %s due to %v", c.deviceID.Short(), c.ConnectionInfo, err)
 		if cerr := c.closer.Close(); cerr != nil {


Test patch: