Przeglądaj źródła

lib/ur: Implement crash (panic) reporting (fixes #959) (#5702)

* lib/ur: Implement crash (panic) reporting (fixes #959)

This implements a simple crash reporting method. It piggybacks on the
panic log files created by the monitor process, picking these up and
uploading them from the usage reporting routine.

A new config value points to the crash receiver base URL, which defaults
to "https://crash.syncthing.net/newcrash" (following the pattern of
"https://data.syncthing.net/newdata" for usage reports, but allowing us
to separate the service as required).
Jakob Borg 6 lat temu
rodzic
commit
42ce6be9b9

+ 1172 - 0
cmd/stcrashreceiver/_testdata/panic.log

@@ -0,0 +1,1172 @@
+09:12:41 INFO: syncthing v1.1.3+39-g62a6d619e-dirty "Erbium Earthworm" (go1.12.1 darwin-amd64) [email protected] 2019-05-08 15:38:08 UTC
+Panic at 2019-05-16T09:12:45+02:00
+panic: runtime error: invalid memory address or nil pointer dereference
+[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x457922a]
+
+goroutine 171 [running]:
+github.com/syncthing/syncthing/lib/connections.(*service).setConnectionStatus(0xc0001a80e0, 0xc001b01020, 0x1a, 0x4b2b0a0, 0xc000021270)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:689 +0x3a
+github.com/syncthing/syncthing/lib/connections.(*service).dialParallel.func1(0xc0001a80e0, 0xc0034bf3e0, 0xc003d18510, 0xc001b01020, 0x1a, 0x4b32080, 0xc003d98da0, 0xa, 0xc000b88c80, 0x303a58f8b941897c, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:838 +0xec
+created by github.com/syncthing/syncthing/lib/connections.(*service).dialParallel
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:836 +0x446
+
+goroutine 1 [chan receive]:
+main.(*exiter).waitForExit(...)
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:289
+main.syncthingMain(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4880b34, 0x1, 0x0, 0x0, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:928 +0x2213
+main.main()
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:430 +0x406
+
+goroutine 35 [select]:
+github.com/syncthing/syncthing/lib/events.(*Logger).Serve(0xc0000c0120)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:260 +0x126
+created by github.com/syncthing/syncthing/lib/events.init.1
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:234 +0x5b
+
+goroutine 19 [syscall]:
+os/signal.signal_recv(0x0)
+	/usr/local/go/src/runtime/sigqueue.go:139 +0x9f
+os/signal.loop()
+	/usr/local/go/src/os/signal/signal_unix.go:23 +0x22
+created by os/signal.init.0
+	/usr/local/go/src/os/signal/signal_unix.go:29 +0x41
+
+goroutine 20 [chan receive]:
+github.com/syncthing/notify.(*recursiveTree).dispatch(0xc00015e140)
+	/Users/jb/go/pkg/mod/github.com/syncthing/[email protected]/tree_recursive.go:125 +0xc7
+created by github.com/syncthing/notify.newRecursiveTree
+	/Users/jb/go/pkg/mod/github.com/syncthing/[email protected]/tree_recursive.go:119 +0x133
+
+goroutine 21 [syscall, locked to thread]:
+github.com/syncthing/notify._Cfunc_CFRunLoopRun()
+	_cgo_gotypes.go:224 +0x41
+github.com/syncthing/notify.init.1.func1()
+	/Users/jb/go/pkg/mod/github.com/syncthing/[email protected]/watcher_fsevents_cgo.go:72 +0x3c
+created by github.com/syncthing/notify.init.1
+	/Users/jb/go/pkg/mod/github.com/syncthing/[email protected]/watcher_fsevents_cgo.go:63 +0x4e
+
+goroutine 39 [chan receive]:
+github.com/syncthing/syncthing/lib/discover.(*globalClient).Serve(0xc000246150)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/global.go:190 +0x225
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470f00, 0xc000000002, 0x5788660, 0xc000246150)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 50 [chan receive]:
+main.setupSignalHandling.func2(0xc0002205a0)
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:959 +0x38
+created by main.setupSignalHandling
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:958 +0x160
+
+goroutine 26 [chan receive]:
+main.setupSignalHandling.func1(0xc000220540)
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:949 +0x38
+created by main.setupSignalHandling
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:948 +0xb2
+
+goroutine 51 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000268000)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+created by github.com/thejerf/suture.(*Supervisor).ServeBackground
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:407 +0x3f
+
+goroutine 53 [chan receive]:
+github.com/syncthing/syncthing/lib/events.(*bufferedSubscription).pollingLoop(0xc000282050)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:441 +0x8d
+created by github.com/syncthing/syncthing/lib/events.NewBufferedSubscription
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:436 +0x157
+
+goroutine 54 [chan receive]:
+github.com/syncthing/syncthing/lib/events.(*bufferedSubscription).pollingLoop(0xc0002820f0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:441 +0x8d
+created by github.com/syncthing/syncthing/lib/events.NewBufferedSubscription
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:436 +0x157
+
+goroutine 5 [select]:
+github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).drain(0xc0001a8000)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/util/buffer_pool.go:206 +0x121
+created by github.com/syndtr/goleveldb/leveldb/util.NewBufferPool
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/util/buffer_pool.go:237 +0x176
+
+goroutine 6 [select]:
+github.com/syndtr/goleveldb/leveldb.(*DB).compactionError(0xc0000f8680)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:90 +0xcd
+created by github.com/syndtr/goleveldb/leveldb.openDB
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db.go:141 +0x40f
+
+goroutine 7 [select]:
+github.com/syndtr/goleveldb/leveldb.(*DB).mpoolDrain(0xc0000f8680)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_state.go:101 +0xf6
+created by github.com/syndtr/goleveldb/leveldb.openDB
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db.go:142 +0x431
+
+goroutine 8 [runnable]:
+syscall.syscall(0x40b2bf0, 0x32, 0xc003a12000, 0xf68, 0xf68, 0x0, 0x0)
+	/usr/local/go/src/runtime/sys_darwin.go:63 +0x2e
+syscall.write(0x32, 0xc003a12000, 0xf68, 0x1592, 0x0, 0xc003a12c78, 0x2ec)
+	/usr/local/go/src/syscall/zsyscall_darwin_amd64.go:1621 +0x67
+syscall.Write(...)
+	/usr/local/go/src/syscall/syscall_unix.go:191
+internal/poll.(*FD).Write(0xc000081c20, 0xc003a12000, 0xf68, 0x1592, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:268 +0x179
+os.(*File).write(...)
+	/usr/local/go/src/os/file_unix.go:280
+os.(*File).Write(0xc0036aa768, 0xc003a12000, 0xf68, 0x1592, 0x0, 0x0, 0x0)
+	/usr/local/go/src/os/file.go:145 +0x76
+github.com/syndtr/goleveldb/leveldb/table.(*Writer).writeBlock(0xc000bd4f00, 0xc000bd4f58, 0x2, 0x80, 0xc000bd5184, 0xc000bd4f58, 0xc000bd1e80)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/writer.go:186 +0x1c3
+github.com/syndtr/goleveldb/leveldb/table.(*Writer).finishBlock(0xc000bd4f00, 0xc003e8ca80, 0x79)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/writer.go:221 +0x55
+github.com/syndtr/goleveldb/leveldb/table.(*Writer).Append(0xc000bd4f00, 0xc003e8ca80, 0x79, 0x80, 0xc003becf56, 0x300, 0x10a4, 0xc003e8ca80, 0x79)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/writer.go:254 +0x2e1
+github.com/syndtr/goleveldb/leveldb.(*tWriter).append(0xc0034bf500, 0xc003e8ca80, 0x79, 0x80, 0xc003becf56, 0x300, 0x10a4, 0xc003e8ca80, 0xc003e8ca80)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table.go:488 +0xe2
+github.com/syndtr/goleveldb/leveldb.(*tableCompactionBuilder).appendKV(0xc000774960, 0xc003e8ca80, 0x79, 0x80, 0xc003becf56, 0x300, 0x10a4, 0xffffffffffffffff, 0x0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:396 +0xa3
+github.com/syndtr/goleveldb/leveldb.(*tableCompactionBuilder).run(0xc000774960, 0xc003da5738, 0x0, 0x0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:514 +0x50b
+github.com/syndtr/goleveldb/leveldb.(*DB).compactionTransact(0xc0000f8680, 0x4887dc4, 0xb, 0x4b323c0, 0xc000774960)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:185 +0x161
+github.com/syndtr/goleveldb/leveldb.(*DB).tableCompaction(0xc0000f8680, 0xc000270f00, 0xc000283400)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:577 +0x62b
+github.com/syndtr/goleveldb/leveldb.(*DB).tableAutoCompaction(0xc0000f8680)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:633 +0x54
+github.com/syndtr/goleveldb/leveldb.(*DB).tCompaction(0xc0000f8680)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:824 +0x321
+created by github.com/syndtr/goleveldb/leveldb.openDB
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db.go:148 +0x585
+
+goroutine 9 [select]:
+github.com/syndtr/goleveldb/leveldb.(*DB).mCompaction(0xc0000f8680)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_compaction.go:751 +0x12e
+created by github.com/syndtr/goleveldb/leveldb.openDB
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db.go:149 +0x5a7
+
+goroutine 27 [sleep]:
+runtime.goparkunlock(...)
+	/usr/local/go/src/runtime/proc.go:307
+time.Sleep(0x45d964b800)
+	/usr/local/go/src/runtime/time.go:105 +0x159
+github.com/syncthing/syncthing/lib/model.(*deadlockDetector).Watch.func1(0xc0000a0d90, 0x570e6a0, 0xc000146800, 0x48817ae, 0x4)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/util.go:35 +0x58
+created by github.com/syncthing/syncthing/lib/model.(*deadlockDetector).Watch
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/util.go:33 +0xb2
+
+goroutine 28 [sleep]:
+runtime.goparkunlock(...)
+	/usr/local/go/src/runtime/proc.go:307
+time.Sleep(0x45d964b800)
+	/usr/local/go/src/runtime/time.go:105 +0x159
+github.com/syncthing/syncthing/lib/model.(*deadlockDetector).Watch.func1(0xc0000a0d90, 0x570e6a0, 0xc000146820, 0x48819ca, 0x4)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/util.go:35 +0x58
+created by github.com/syncthing/syncthing/lib/model.(*deadlockDetector).Watch
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/util.go:33 +0xb2
+
+goroutine 29 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000470b40)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000000, 0x570e770, 0xc0001a6160)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 30 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000470f00)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000001, 0x570e7b8, 0xc000020190)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 10 [select]:
+github.com/syncthing/syncthing/lib/model.(*ProgressEmitter).Serve(0xc00022e5a0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/progressemitter.go:59 +0x37c
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470b40, 0xc000000000, 0x4b321c0, 0xc00022e5a0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 11 [chan receive]:
+github.com/syncthing/syncthing/lib/model.(*folder).scanSubdirs(0xc00011a840, 0x0, 0x0, 0x0, 0x0, 0x0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder.go:402 +0x68f
+github.com/syncthing/syncthing/lib/model.(*folder).scanTimerFired(0xc00011a840)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder.go:545 +0x54
+github.com/syncthing/syncthing/lib/model.(*folder).Serve(0xc00011a840)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder.go:181 +0x7c9
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470b40, 0xc000000001, 0x570e6c8, 0xc00011a840)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 12 [select]:
+github.com/syncthing/syncthing/lib/model.(*folder).Serve(0xc000196840)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder.go:153 +0x47d
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470b40, 0xc000000002, 0x570e6c8, 0xc000196840)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 31 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000470ff0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000002, 0x4b32040, 0xc0001a80e0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 32 [chan receive]:
+github.com/syncthing/syncthing/lib/connections.(*service).dialParallel(0xc0001a80e0, 0x303a58f8b941897c, 0xe0c6153d5b38dadd, 0x6308f7c9a45b6725, 0x9c959a98f78344e6, 0xc0001e50e0, 0x1, 0x1, 0xc00000d567, 0x5, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:854 +0x4c9
+github.com/syncthing/syncthing/lib/connections.(*service).connect(0xc0001a80e0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:455 +0xee6
+github.com/syncthing/syncthing/lib/connections.serviceFunc.Serve(0xc0000a1010)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/structs.go:180 +0x25
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470ff0, 0xc000000000, 0x4b33700, 0xc0000a1010)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 33 [chan receive]:
+github.com/syncthing/syncthing/lib/connections.(*service).handle(0xc0001a80e0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:193 +0x7d
+github.com/syncthing/syncthing/lib/connections.serviceFunc.Serve(0xc0000a1020)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/structs.go:180 +0x25
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470ff0, 0xc000000001, 0x4b33700, 0xc0000a1020)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 66 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc0004710e0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470ff0, 0xc000000002, 0x4b32400, 0xc0004710e0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 67 [IO wait]:
+internal/poll.runtime_pollWait(0x5740f08, 0x72, 0x0)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc0002cc518, 0x72, 0x0, 0x0, 0x4882e05)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Accept(0xc0002cc500, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:384 +0x1ba
+net.(*netFD).accept(0xc0002cc500, 0x460f4bb75024, 0x3b9ac39a, 0xc000065ac8)
+	/usr/local/go/src/net/fd_unix.go:238 +0x42
+net.(*TCPListener).accept(0xc0000be0e0, 0xe9, 0x0, 0x0)
+	/usr/local/go/src/net/tcpsock_posix.go:139 +0x32
+net.(*TCPListener).Accept(0xc0000be0e0, 0xbf2f613b6ecc3d68, 0xe8b29287, 0x50bfa40, 0x0)
+	/usr/local/go/src/net/tcpsock.go:260 +0x48
+github.com/syncthing/syncthing/lib/connections.(*tcpListener).Serve(0xc00017a1b0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/tcp_listen.go:87 +0x728
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc0004710e0, 0xc000000000, 0x570e8a0, 0xc00017a1b0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 57 [select]:
+github.com/syncthing/syncthing/lib/discover.(*globalClient).Serve(0xc00015c3f0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/global.go:201 +0x177
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470f00, 0xc000000000, 0x5788660, 0xc00015c3f0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 58 [select]:
+github.com/syncthing/syncthing/lib/discover.(*globalClient).Serve(0xc00015c460)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/global.go:201 +0x177
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470f00, 0xc000000001, 0x5788660, 0xc00015c460)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 114 [chan receive]:
+github.com/rcrowley/go-metrics.(*meterArbiter).tick(0x50bf5c0)
+	/Users/jb/go/pkg/mod/github.com/rcrowley/[email protected]/meter.go:252 +0x31
+created by github.com/rcrowley/go-metrics.NewMeter
+	/Users/jb/go/pkg/mod/github.com/rcrowley/[email protected]/meter.go:44 +0x107
+
+goroutine 40 [chan receive]:
+github.com/syncthing/syncthing/lib/beacon.(*Broadcast).Recv(0xc0000cbda0, 0xc00016c1b8, 0x20, 0xc00046ee01, 0x2, 0x2)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/beacon/broadcast.go:67 +0x53
+github.com/syncthing/syncthing/lib/discover.(*localClient).recvAnnouncements(0xc00016c1b0, 0x4b3d600, 0xc0000cbda0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:164 +0xb1
+created by github.com/syncthing/syncthing/lib/discover.(*localClient).startLocalIPv4Broadcasts
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:89 +0xd5
+
+goroutine 41 [select]:
+github.com/syncthing/syncthing/lib/discover.(*localClient).sendLocalAnnouncements(0xc00016c1b0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:154 +0x123
+created by github.com/syncthing/syncthing/lib/discover.NewLocal
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:81 +0x2d9
+
+goroutine 42 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000268e10)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470f00, 0xc000000003, 0x5741588, 0xc00016c1b0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 43 [chan receive]:
+github.com/syncthing/syncthing/lib/beacon.(*Multicast).Recv(0xc0000cbf20, 0xc00016c3f8, 0x20, 0xc0000acb01, 0x2, 0x2)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/beacon/multicast.go:67 +0x53
+github.com/syncthing/syncthing/lib/discover.(*localClient).recvAnnouncements(0xc00016c3f0, 0x4b3d640, 0xc0000cbf20)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:164 +0xb1
+created by github.com/syncthing/syncthing/lib/discover.(*localClient).startLocalIPv6Multicasts
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:95 +0xdf
+
+goroutine 44 [select]:
+github.com/syncthing/syncthing/lib/discover.(*localClient).sendLocalAnnouncements(0xc00016c3f0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:154 +0x123
+created by github.com/syncthing/syncthing/lib/discover.NewLocal
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/discover/local.go:81 +0x2d9
+
+goroutine 45 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000268ff0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000470f00, 0xc000000004, 0x5741588, 0xc00016c3f0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 46 [select]:
+github.com/syncthing/syncthing/lib/ur.(*Service).Serve(0xc0002461c0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/ur/usage_report.go:399 +0x231
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000003, 0x4b32340, 0xc0002461c0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 47 [select]:
+main.(*cpuService).Serve(0xc0000ce860)
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/cpuusage.go:41 +0x170
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000004, 0x4b32780, 0xc0000ce860)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 14 [runnable]:
+syscall.syscall(0x40b2ca0, 0x33, 0xc00353d978, 0x0, 0x0, 0x0, 0x0)
+	/usr/local/go/src/runtime/sys_darwin.go:63 +0x2e
+syscall.Fstat(0x33, 0xc00353d978, 0xc00353d980, 0x4037cbe)
+	/usr/local/go/src/syscall/zsyscall_darwin_amd64.go:1823 +0x62
+os.newFile(0x33, 0xc0000b2510, 0x2e, 0x1, 0x33)
+	/usr/local/go/src/os/file_unix.go:150 +0x1f5
+os.openFileNolog(0xc0000b2510, 0x2e, 0x0, 0xc0000001ff, 0x12, 0xc000537e20, 0x1b)
+	/usr/local/go/src/os/file_unix.go:227 +0x1f9
+os.OpenFile(0xc0000b2510, 0x2e, 0x0, 0x1ff, 0xc0000b2510, 0x2e, 0x0)
+	/usr/local/go/src/os/file.go:284 +0x5f
+github.com/syncthing/syncthing/lib/fs.(*BasicFilesystem).DirNames(0xc0000c3b70, 0xc000537e20, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/basicfs.go:195 +0xa5
+github.com/syncthing/syncthing/lib/fs.(*walkFilesystem).walk(0xc0000c3b80, 0xc000537e20, 0x1b, 0x4b43d80, 0xc0014cf1b0, 0xc0001e4280, 0x0, 0x0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/walkfs.go:58 +0x1d7
+github.com/syncthing/syncthing/lib/fs.(*walkFilesystem).walk(0xc0000c3b80, 0xc0038b2f40, 0x16, 0x4b43d80, 0xc0011f6940, 0xc0001e4280, 0x0, 0x0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/walkfs.go:71 +0x41e
+github.com/syncthing/syncthing/lib/fs.(*walkFilesystem).walk(0xc0000c3b80, 0xc003dfd320, 0x14, 0x4b43d80, 0xc001016050, 0xc0001e4280, 0x0, 0x0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/walkfs.go:71 +0x41e
+github.com/syncthing/syncthing/lib/fs.(*walkFilesystem).walk(0xc0000c3b80, 0x4880b35, 0x1, 0x4b43d80, 0xc0000c3e30, 0xc0001e4280, 0x0, 0xc000237f48)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/walkfs.go:71 +0x41e
+github.com/syncthing/syncthing/lib/fs.(*walkFilesystem).Walk(0xc0000c3b80, 0x4880b35, 0x1, 0xc0001e4280, 0x50bfa40, 0xc0000c3d70)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/walkfs.go:93 +0xd3
+github.com/syncthing/syncthing/lib/scanner.(*walker).walk.func1(0xc00014e080, 0x4b3b4c0, 0xc0000b6cc0, 0xc00026afc0, 0xc00026b020)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/scanner/walk.go:111 +0x127
+created by github.com/syncthing/syncthing/lib/scanner.(*walker).walk
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/scanner/walk.go:108 +0x18c
+
+goroutine 15 [chan receive]:
+github.com/syncthing/syncthing/lib/scanner.(*walker).walk.func2(0xc00026afc0, 0x4b3b4c0, 0xc0000b6cc0, 0xc00014e080, 0xc00026b020, 0xc0001e4230)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/scanner/walk.go:145 +0xb3
+created by github.com/syncthing/syncthing/lib/scanner.(*walker).walk
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/scanner/walk.go:141 +0x216
+
+goroutine 48 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc0002691d0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000005, 0x5741778, 0xc0000f2960)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 16 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc0002690e0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268ff0, 0xc000000000, 0x5741610, 0xc0000cbf20)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 82 [IO wait]:
+internal/poll.runtime_pollWait(0x5740e38, 0x72, 0xc002ef2070)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc00014e118, 0x72, 0xc00014e100, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).RawRead(0xc00014e100, 0xc00030aab0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:534 +0x100
+net.(*rawConn).Read(0xc000266068, 0xc00030aab0, 0x1, 0x1)
+	/usr/local/go/src/net/rawconn.go:43 +0x57
+golang.org/x/net/internal/socket.(*Conn).recvMsg(0xc0000ac7e0, 0xc0003a7cb8, 0x0, 0x0, 0xc000010ae0)
+	/Users/jb/go/pkg/mod/golang.org/x/[email protected]/internal/socket/rawconn_msg.go:32 +0x20e
+golang.org/x/net/internal/socket.(*Conn).RecvMsg(...)
+	/Users/jb/go/pkg/mod/golang.org/x/[email protected]/internal/socket/socket.go:252
+golang.org/x/net/ipv6.(*payloadHandler).readFrom(0xc00015e560, 0xc002e76000, 0x10000, 0x10000, 0x4008184, 0xc0000c1980, 0xc0003a7e90, 0x4765000, 0x4557c0f, 0xc000116601)
+	/Users/jb/go/pkg/mod/golang.org/x/[email protected]/ipv6/payload_cmsg_go1_9.go:25 +0x1a7
+golang.org/x/net/ipv6.(*payloadHandler).ReadFrom(0xc00015e560, 0xc002e76000, 0x10000, 0x10000, 0x2, 0x2, 0x4b32940, 0xc00030aa50, 0x0, 0x0)
+	/Users/jb/go/pkg/mod/golang.org/x/[email protected]/ipv6/payload_cmsg.go:19 +0x6d
+github.com/syncthing/syncthing/lib/beacon.(*multicastReader).Serve(0xc00007d600)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/beacon/multicast.go:204 +0x760
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc0002690e0, 0xc000000000, 0x4b31f00, 0xc00007d600)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 83 [chan receive]:
+github.com/syncthing/syncthing/lib/beacon.(*multicastWriter).Serve(0xc00007d640)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/beacon/multicast.go:109 +0x849
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc0002690e0, 0xc000000001, 0x4b31f40, 0xc00007d640)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 103 [runnable]:
+syscall.syscall6(0x40b2b00, 0x27, 0xc0034c6800, 0x28d, 0x1ec396, 0x0, 0x0, 0x28d, 0x0, 0x0)
+	/usr/local/go/src/runtime/sys_darwin.go:74 +0x2e
+syscall.Pread(0x27, 0xc0034c6800, 0x28d, 0x3df, 0x1ec396, 0x18, 0xc0012a6488, 0x402e891)
+	/usr/local/go/src/syscall/zsyscall_darwin_amd64.go:1127 +0x7f
+internal/poll.(*FD).Pread(0xc0031e7200, 0xc0034c6800, 0x28d, 0x3df, 0x1ec396, 0xc0000bc2a0, 0x1, 0x4a46f28)
+	/usr/local/go/src/internal/poll/fd_unix.go:196 +0xa2
+os.(*File).pread(...)
+	/usr/local/go/src/os/file_unix.go:272
+os.(*File).ReadAt(0xc000fa23e0, 0xc0034c6800, 0x28d, 0x3df, 0x1ec396, 0x0, 0x0, 0x0)
+	/usr/local/go/src/os/file.go:126 +0x103
+github.com/syndtr/goleveldb/leveldb/table.(*Reader).readRawBlock(0xc0001e9790, 0x1ec396, 0x288, 0x4847c01, 0x203001, 0xc000ab10a0, 0xc0012a6750, 0x4597d8d, 0xc003f38690)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:564 +0xb2
+github.com/syndtr/goleveldb/leveldb/table.(*Reader).readBlock(0xc0001e9790, 0x1ec396, 0x288, 0x1, 0x8, 0xc0035a4b40, 0xc000fc4000)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:603 +0x55
+github.com/syndtr/goleveldb/leveldb/table.(*Reader).readBlockCached.func1(0xc000ab10c0, 0xc0000b6280, 0xc003830480)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:627 +0x51
+github.com/syndtr/goleveldb/leveldb/cache.(*Cache).Get(0xc0000b6280, 0x1eb91, 0x1ec396, 0xc000fc4000, 0x0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/cache/cache.go:389 +0x1d7
+github.com/syndtr/goleveldb/leveldb/cache.(*NamespaceGetter).Get(...)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/cache/cache.go:58
+github.com/syndtr/goleveldb/leveldb/table.(*Reader).readBlockCached(0xc0001e9790, 0x1ec396, 0x288, 0x4590101, 0xc000248400, 0xc0012a6880, 0x402dfef, 0x8, 0xc0000c99c0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:625 +0x158
+github.com/syndtr/goleveldb/leveldb/table.(*Reader).getDataIter(0xc0001e9790, 0x1ec396, 0x288, 0x0, 0x101, 0x288, 0x2)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:765 +0x5b
+github.com/syndtr/goleveldb/leveldb/table.(*Reader).getDataIterErr(0xc0001e9790, 0x1ec396, 0x288, 0x0, 0x101, 0x0, 0x0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:780 +0x12e
+github.com/syndtr/goleveldb/leveldb/table.(*indexIter).Get(0xc000aa7720, 0x459fa94, 0xc000248400)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/table/reader.go:507 +0x2b6
+github.com/syndtr/goleveldb/leveldb/iterator.(*indexedIterator).setData(0xc0031e72c0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/iterator/indexed_iter.go:39 +0x41
+github.com/syndtr/goleveldb/leveldb/iterator.(*indexedIterator).Next(0xc0031e72c0, 0x20)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/iterator/indexed_iter.go:160 +0xb6
+github.com/syndtr/goleveldb/leveldb/iterator.(*indexedIterator).Next(0xc0031e7080, 0x20)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/iterator/indexed_iter.go:149 +0x17e
+github.com/syndtr/goleveldb/leveldb/iterator.(*mergedIterator).Next(0xc00014cc80, 0x0)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/iterator/merged_iter.go:169 +0xe2
+github.com/syndtr/goleveldb/leveldb.(*dbIter).Next(0xc00016c510, 0x1)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_iter.go:240 +0x97
+github.com/syncthing/syncthing/lib/db.(*instance).withNeed(0xc00046e1e0, 0xc0004be9e0, 0xb, 0x10, 0xc0012a6da0, 0x20, 0x20, 0x1, 0xc0004bd400)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/db/instance.go:331 +0x2b6
+github.com/syncthing/syncthing/lib/db.(*FileSet).WithNeedTruncated(0xc0000b6b00, 0x303a58f8b941897c, 0xe0c6153d5b38dadd, 0x6308f7c9a45b6725, 0x9c959a98f78344e6, 0xc003338740)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/db/set.go:162 +0x1ee
+github.com/syncthing/syncthing/lib/model.(*model).Completion(0xc0001a6160, 0x303a58f8b941897c, 0xe0c6153d5b38dadd, 0x6308f7c9a45b6725, 0x9c959a98f78344e6, 0xc0002482e6, 0xb, 0x0, 0x0, 0x0, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/model.go:693 +0x34a
+github.com/syncthing/syncthing/lib/api.(*service).getDBCompletion(0xc000118dc0, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:654 +0x13e
+net/http.HandlerFunc.ServeHTTP(0xc002ef2080, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a880, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.getPostHandler.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:426 +0xe6
+net/http.HandlerFunc.ServeHTTP(0xc00030abd0, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.metricsMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:518 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc000010bc0, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.noCacheMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:541 +0x3f4
+net/http.HandlerFunc.ServeHTTP(0xc000010be0, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a940, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.csrfMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_csrf.go:78 +0x208
+net/http.HandlerFunc.ServeHTTP(0xc0001713f0, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.withDetailsMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:549 +0x2ce
+net/http.HandlerFunc.ServeHTTP(0xc0000c94c0, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.basicAuthAndSessionMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_auth.go:51 +0xa64
+net/http.HandlerFunc.ServeHTTP(0xc0001e9040, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.redirectToHTTPSMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:531 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc0000ad700, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.corsMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:510 +0x2a6
+net/http.HandlerFunc.ServeHTTP(0xc0000ad720, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.debugMiddleware.func1(0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:438 +0xa7
+net/http.HandlerFunc.ServeHTTP(0xc0000ad740, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.serverHandler.ServeHTTP(0xc0001e9110, 0x4b39580, 0xc001116380, 0xc003ce0d00)
+	/usr/local/go/src/net/http/server.go:2774 +0xa8
+net/http.(*conn).serve(0xc0013546e0, 0x4b3b4c0, 0xc000ea0000)
+	/usr/local/go/src/net/http/server.go:1878 +0x851
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 139 [IO wait]:
+internal/poll.runtime_pollWait(0x57402d8, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc001002718, 0x72, 0x600, 0x62b, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc001002700, 0xc00035f100, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc001002700, 0xc00035f100, 0x62b, 0x62b, 0xc003f3b8c0, 0x8, 0x4a46af0)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc001199ae0, 0xc00035f100, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+github.com/syncthing/syncthing/lib/tlsutil.(*UnionedConnection).Read(0xc00138c8a0, 0xc00035f100, 0x62b, 0x62b, 0xc003f3b900, 0xc003f3b9a8, 0x10)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:223 +0xc1
+crypto/tls.(*atLeastReader).Read(0xc000239200, 0xc00035f100, 0x62b, 0x62b, 0xc00409a005, 0xc001002718, 0xc003f3b9d8)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc000036958, 0x4b2a4a0, 0xc000239200, 0x400bc55, 0x47b4f40, 0x4812c80)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc000036700, 0x572adf0, 0xc00138c8a0, 0x5, 0xc00138c8a0, 0x1e0)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc000036700, 0x4a46f00, 0xc000036838, 0x431b6ba)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc000036700, 0xc000b31000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*connReader).Read(0xc00375e3f0, 0xc000b31000, 0x1000, 0x1000, 0x425ea21, 0xc00138c8a0, 0xbf2f613ef969b548)
+	/usr/local/go/src/net/http/server.go:787 +0x107
+bufio.(*Reader).fill(0xc0007eec00)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc0007eec00, 0x4, 0x435c71b54, 0x50bfa40, 0x0, 0x0, 0x50bfa40)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*conn).serve(0xc0013545a0, 0x4b3b4c0, 0xc001778f00)
+	/usr/local/go/src/net/http/server.go:1903 +0x9bf
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 86 [select]:
+github.com/syncthing/syncthing/lib/fs.(*BasicFilesystem).watchLoop(0xc0000c3b70, 0x4880b35, 0x1, 0xc0000383c0, 0x12, 0xc0000bd320, 0xc00026b140, 0x4b32140, 0xc00017a090, 0x4b3b4c0, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/basicfs_watch.go:81 +0x2c8
+created by github.com/syncthing/syncthing/lib/fs.(*BasicFilesystem).Watch
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/basicfs_watch.go:59 +0x3e7
+
+goroutine 72 [select]:
+github.com/thejerf/suture.(*Supervisor).Serve(0xc000268f00)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:454 +0x350
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268e10, 0xc000000000, 0x5741508, 0xc0000cbda0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 73 [IO wait]:
+internal/poll.runtime_pollWait(0x5740bc8, 0x72, 0x0)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc00014e198, 0x72, 0x10000, 0x10000, 0x0)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).ReadFrom(0xc00014e180, 0xc002d06000, 0x10000, 0x10000, 0x0, 0x0, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:219 +0x194
+net.(*netFD).readFrom(0xc00014e180, 0xc002d06000, 0x10000, 0x10000, 0x4, 0xc00044df40, 0xc0000d3500, 0xc00044de50, 0x400725c)
+	/usr/local/go/src/net/fd_unix.go:208 +0x5b
+net.(*UDPConn).readFrom(0xc0000be100, 0xc002d06000, 0x10000, 0x10000, 0xc0000c18c0, 0xc00044de88, 0x4008184, 0xc0000c18c0)
+	/usr/local/go/src/net/udpsock_posix.go:47 +0x6a
+net.(*UDPConn).ReadFrom(0xc0000be100, 0xc002d06000, 0x10000, 0x10000, 0x2, 0x2, 0xc0001e74a0, 0x0, 0x0)
+	/usr/local/go/src/net/udpsock.go:121 +0x5e
+github.com/syncthing/syncthing/lib/beacon.(*broadcastReader).Serve(0xc00007d540)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/beacon/broadcast.go:196 +0x371
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268f00, 0xc000000000, 0x4b31e80, 0xc00007d540)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 74 [chan receive]:
+github.com/syncthing/syncthing/lib/beacon.(*broadcastWriter).Serve(0xc00007d580)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/beacon/broadcast.go:102 +0x3ca
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268f00, 0xc000000001, 0x4b31ec0, 0xc00007d580)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 140 [IO wait]:
+internal/poll.runtime_pollWait(0x5740208, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc000b88418, 0x72, 0x600, 0x62b, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc000b88400, 0xc00035e300, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc000b88400, 0xc00035e300, 0x62b, 0x62b, 0xc003f3d8c0, 0x8, 0xc000242f00)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc000fa3568, 0xc00035e300, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+github.com/syncthing/syncthing/lib/tlsutil.(*UnionedConnection).Read(0xc00138ce60, 0xc00035e300, 0x62b, 0x62b, 0xc003f3d900, 0xc003f3d9a8, 0x10)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:223 +0xc1
+crypto/tls.(*atLeastReader).Read(0xc003301b00, 0xc00035e300, 0x62b, 0x62b, 0xc00337e1e5, 0xc000b88418, 0xc003f3d9d8)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc000037058, 0x4b2a4a0, 0xc003301b00, 0x400bc55, 0x47b4f40, 0x4812c80)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc000036e00, 0x572adf0, 0xc00138ce60, 0x5, 0xc00138ce60, 0x1e0)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc000036e00, 0x4a46f00, 0xc000036f38, 0x431b6ba)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc000036e00, 0xc001b79000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*connReader).Read(0xc000ab8360, 0xc001b79000, 0x1000, 0x1000, 0x425ea21, 0xc00138ce60, 0xbf2f613ef99ccb68)
+	/usr/local/go/src/net/http/server.go:787 +0x107
+bufio.(*Reader).fill(0xc000fcc300)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc000fcc300, 0x4, 0x435fa3146, 0x50bfa40, 0x0, 0x0, 0x50bfa40)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*conn).serve(0xc001354640, 0x4b3b4c0, 0xc0013bfec0)
+	/usr/local/go/src/net/http/server.go:1903 +0x9bf
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 79 [sleep]:
+runtime.goparkunlock(...)
+	/usr/local/go/src/runtime/proc.go:307
+time.Sleep(0x2540be400)
+	/usr/local/go/src/runtime/time.go:105 +0x159
+main.standbyMonitor()
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:1154 +0x6b
+created by main.syncthingMain
+	/Users/jb/dev/github.com/syncthing/syncthing/cmd/syncthing/main.go:884 +0x2608
+
+goroutine 63 [IO wait]:
+internal/poll.runtime_pollWait(0x5740af8, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc00014e218, 0x72, 0x600, 0x611, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc00014e200, 0xc0036e9500, 0x611, 0x611, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc00014e200, 0xc0036e9500, 0x611, 0x611, 0x203000, 0x411558d, 0xc0005b00a5)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc0000126e0, 0xc0036e9500, 0x611, 0x611, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+crypto/tls.(*atLeastReader).Read(0xc0000ced60, 0xc0036e9500, 0x611, 0x611, 0x42af5ef, 0xc003717140, 0xc00044c938)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc0000365d8, 0x4b2a4a0, 0xc0000ced60, 0x400bc55, 0x47b4f40, 0x4852500)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc000036380, 0x5712c90, 0xc0000126e0, 0x5, 0xc0000126e0, 0x203000)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc000036380, 0x4a46f00, 0xc0000364b8, 0xc00044cdd0)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc000036380, 0xc0005b0000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*persistConn).Read(0xc0000d0b40, 0xc0005b0000, 0x1000, 0x1000, 0x405bd50, 0xc00044ce88, 0x2)
+	/usr/local/go/src/net/http/transport.go:1524 +0x7b
+bufio.(*Reader).fill(0xc003717140)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc003717140, 0x1, 0x2, 0x0, 0x0, 0xc003ef3800, 0x0)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*persistConn).readLoop(0xc0000d0b40)
+	/usr/local/go/src/net/http/transport.go:1677 +0x1a3
+created by net/http.(*Transport).dialConn
+	/usr/local/go/src/net/http/transport.go:1357 +0xae8
+
+goroutine 130 [select]:
+github.com/syncthing/syncthing/lib/sync.(*TimeoutCondWaiter).Wait(0xc001200e58, 0x4a45d00)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/sync/sync.go:292 +0x115
+github.com/syncthing/syncthing/lib/events.(*bufferedSubscription).Since(0xc0002820f0, 0x0, 0x50e2748, 0x0, 0x0, 0xdf8475800, 0x0, 0x0, 0x0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/events/events.go:461 +0x4db
+github.com/syncthing/syncthing/lib/api.(*service).getEvents(0xc000118dc0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700, 0x4b2a820, 0xc0002820f0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:1191 +0x2a4
+github.com/syncthing/syncthing/lib/api.(*service).getDiskEvents(0xc000118dc0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:1167 +0x74
+net/http.HandlerFunc.ServeHTTP(0xc002ef2140, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a880, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.getPostHandler.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:426 +0xe6
+net/http.HandlerFunc.ServeHTTP(0xc00030abd0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.metricsMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:518 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc000010bc0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.noCacheMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:541 +0x3f4
+net/http.HandlerFunc.ServeHTTP(0xc000010be0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a940, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.csrfMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_csrf.go:78 +0x208
+net/http.HandlerFunc.ServeHTTP(0xc0001713f0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.withDetailsMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:549 +0x2ce
+net/http.HandlerFunc.ServeHTTP(0xc0000c94c0, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.basicAuthAndSessionMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_auth.go:51 +0xa64
+net/http.HandlerFunc.ServeHTTP(0xc0001e9040, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.redirectToHTTPSMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:531 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc0000ad700, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.corsMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:510 +0x2a6
+net/http.HandlerFunc.ServeHTTP(0xc0000ad720, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.debugMiddleware.func1(0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:438 +0xa7
+net/http.HandlerFunc.ServeHTTP(0xc0000ad740, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.serverHandler.ServeHTTP(0xc0001e9110, 0x4b39580, 0xc0014cc2a0, 0xc00013a700)
+	/usr/local/go/src/net/http/server.go:2774 +0xa8
+net/http.(*conn).serve(0xc0010ac5a0, 0x4b3b4c0, 0xc00330e000)
+	/usr/local/go/src/net/http/server.go:1878 +0x851
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 92 [runnable]:
+github.com/syndtr/goleveldb/leveldb/iterator.(*indexedIterator).Value(0xc0031e6060, 0x2e, 0x80, 0x1)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/iterator/indexed_iter.go:205 +0x81
+github.com/syndtr/goleveldb/leveldb/iterator.(*mergedIterator).Value(0xc00014c680, 0xc00322ca80, 0x2e, 0x40)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/iterator/merged_iter.go:251 +0x74
+github.com/syndtr/goleveldb/leveldb.(*dbIter).next(0xc0002502d0, 0x1)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_iter.go:213 +0x330
+github.com/syndtr/goleveldb/leveldb.(*dbIter).Next(0xc0002502d0, 0x1)
+	/Users/jb/go/pkg/mod/github.com/syndtr/[email protected]/leveldb/db_iter.go:245 +0xd9
+github.com/syncthing/syncthing/lib/db.(*instance).withNeed(0xc00046e1e0, 0xc000953c10, 0xb, 0x10, 0xc0012a2da0, 0x20, 0x20, 0x1, 0xc003492290)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/db/instance.go:331 +0x2b6
+github.com/syncthing/syncthing/lib/db.(*FileSet).WithNeedTruncated(0xc0000b6b00, 0x5d4a557355ee1a96, 0x95ccd59f7d44241, 0x23a9c69bae83ac87, 0x6ee52bc80a137f7b, 0xc00133c280)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/db/set.go:162 +0x1ee
+github.com/syncthing/syncthing/lib/model.(*model).Completion(0xc0001a6160, 0x5d4a557355ee1a96, 0x95ccd59f7d44241, 0x23a9c69bae83ac87, 0x6ee52bc80a137f7b, 0xc000cd6ae6, 0xb, 0x0, 0x0, 0x0, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/model.go:693 +0x34a
+github.com/syncthing/syncthing/lib/api.(*service).getDBCompletion(0xc000118dc0, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:654 +0x13e
+net/http.HandlerFunc.ServeHTTP(0xc002ef2080, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a880, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.getPostHandler.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:426 +0xe6
+net/http.HandlerFunc.ServeHTTP(0xc00030abd0, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.metricsMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:518 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc000010bc0, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.noCacheMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:541 +0x3f4
+net/http.HandlerFunc.ServeHTTP(0xc000010be0, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a940, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.csrfMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_csrf.go:78 +0x208
+net/http.HandlerFunc.ServeHTTP(0xc0001713f0, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.withDetailsMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:549 +0x2ce
+net/http.HandlerFunc.ServeHTTP(0xc0000c94c0, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.basicAuthAndSessionMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_auth.go:51 +0xa64
+net/http.HandlerFunc.ServeHTTP(0xc0001e9040, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.redirectToHTTPSMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:531 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc0000ad700, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.corsMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:510 +0x2a6
+net/http.HandlerFunc.ServeHTTP(0xc0000ad720, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.debugMiddleware.func1(0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:438 +0xa7
+net/http.HandlerFunc.ServeHTTP(0xc0000ad740, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.serverHandler.ServeHTTP(0xc0001e9110, 0x4b39580, 0xc002e5f500, 0xc003ce0000)
+	/usr/local/go/src/net/http/server.go:2774 +0xa8
+net/http.(*conn).serve(0xc00303ad20, 0x4b3b4c0, 0xc00330e880)
+	/usr/local/go/src/net/http/server.go:1878 +0x851
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 84 [select]:
+github.com/syncthing/syncthing/lib/fs.(*BasicFilesystem).watchLoop(0xc0000c3b90, 0x4880b35, 0x1, 0xc0000384c0, 0x14, 0xc000080600, 0xc0000d64e0, 0x4b32140, 0xc00017a120, 0x4b3b4c0, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/basicfs_watch.go:81 +0x2c8
+created by github.com/syncthing/syncthing/lib/fs.(*BasicFilesystem).Watch
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/fs/basicfs_watch.go:59 +0x3e7
+
+goroutine 85 [select]:
+github.com/syncthing/syncthing/lib/watchaggregator.(*aggregator).mainLoop(0xc0000b8d00, 0xc0000d64e0, 0xc0000d6420, 0x4b4aac0, 0xc000059800)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/watchaggregator/aggregator.go:148 +0x38e
+created by github.com/syncthing/syncthing/lib/watchaggregator.Aggregate
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/watchaggregator/aggregator.go:132 +0xdc
+
+goroutine 87 [select]:
+github.com/syncthing/syncthing/lib/watchaggregator.(*aggregator).mainLoop(0xc0000b8ea0, 0xc00026b140, 0xc00026af00, 0x4b4aac0, 0xc000059800)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/watchaggregator/aggregator.go:148 +0x38e
+created by github.com/syncthing/syncthing/lib/watchaggregator.Aggregate
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/watchaggregator/aggregator.go:132 +0xdc
+
+goroutine 49 [select]:
+github.com/syncthing/syncthing/lib/model.(*folderSummaryService).listenForUpdates(0xc0000f2960)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder_summary.go:158 +0x352
+github.com/syncthing/syncthing/lib/model.serviceFunc.Serve(0xc0000347b0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder_summary.go:311 +0x25
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc0002691d0, 0xc000000000, 0x4b33780, 0xc0000347b0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 98 [select]:
+github.com/syncthing/syncthing/lib/model.(*folderSummaryService).calculateSummaries(0xc0000f2960)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder_summary.go:229 +0x10c
+github.com/syncthing/syncthing/lib/model.serviceFunc.Serve(0xc0000347c0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/model/folder_summary.go:311 +0x25
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc0002691d0, 0xc000000001, 0x4b33780, 0xc0000347c0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 99 [select]:
+github.com/syncthing/syncthing/lib/api.(*service).Serve(0xc000118dc0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:362 +0x2424
+github.com/thejerf/suture.(*Supervisor).runService.func1(0xc000268000, 0xc000000006, 0x5741e50, 0xc000118dc0)
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:600 +0x47
+created by github.com/thejerf/suture.(*Supervisor).runService
+	/Users/jb/go/pkg/mod/github.com/thejerf/[email protected]+incompatible/supervisor.go:588 +0x5b
+
+goroutine 62 [IO wait]:
+internal/poll.runtime_pollWait(0x5740a28, 0x72, 0x0)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc00014e298, 0x72, 0x0, 0x0, 0x4882e05)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Accept(0xc00014e280, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:384 +0x1ba
+net.(*netFD).accept(0xc00014e280, 0xc00007b000, 0xc000272dc8, 0x400dbf9)
+	/usr/local/go/src/net/fd_unix.go:238 +0x42
+net.(*TCPListener).accept(0xc0000be170, 0x8, 0x2f8, 0xc000160320)
+	/usr/local/go/src/net/tcpsock_posix.go:139 +0x32
+net.(*TCPListener).Accept(0xc0000be170, 0x203000, 0x0, 0x405dea2, 0x404d39c)
+	/usr/local/go/src/net/tcpsock.go:260 +0x48
+github.com/syncthing/syncthing/lib/tlsutil.(*DowngradingListener).AcceptNoWrapTLS(0xc000010b20, 0x100000001, 0xc000272e48, 0x404f208, 0x460f5132d1e8, 0xc03447be48)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:189 +0x37
+github.com/syncthing/syncthing/lib/tlsutil.(*DowngradingListener).Accept(0xc000010b20, 0xc000272ec0, 0x18, 0xc0002c9500, 0x4325f54)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:170 +0x2f
+net/http.(*Server).Serve(0xc0001e9110, 0x4b38dc0, 0xc000010b20, 0x0, 0x0)
+	/usr/local/go/src/net/http/server.go:2859 +0x22d
+github.com/syncthing/syncthing/lib/api.(*service).Serve.func1(0xc000080ae0, 0xc0001e9110, 0x4b38dc0, 0xc000010b20)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:357 +0x3f
+created by github.com/syncthing/syncthing/lib/api.(*service).Serve
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:356 +0x2363
+
+goroutine 64 [select]:
+net/http.(*persistConn).writeLoop(0xc0000d0b40)
+	/usr/local/go/src/net/http/transport.go:1958 +0x113
+created by net/http.(*Transport).dialConn
+	/usr/local/go/src/net/http/transport.go:1358 +0xb0d
+
+goroutine 156 [IO wait]:
+internal/poll.runtime_pollWait(0x5740068, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc001002298, 0x72, 0x600, 0x62b, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc001002280, 0xc000c12000, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc001002280, 0xc000c12000, 0x62b, 0x62b, 0xc0002f58c0, 0x8, 0xc000f71c80)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc003046190, 0xc000c12000, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+github.com/syncthing/syncthing/lib/tlsutil.(*UnionedConnection).Read(0xc003482ea0, 0xc000c12000, 0x62b, 0x62b, 0xc0002f5900, 0xc0002f59a8, 0x10)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:223 +0xc1
+crypto/tls.(*atLeastReader).Read(0xc0002392c0, 0xc000c12000, 0x62b, 0x62b, 0xc00409a5a5, 0xc001002298, 0xc0002f59d8)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc0000b1e58, 0x4b2a4a0, 0xc0002392c0, 0x400bc55, 0x47b4f40, 0x4812c80)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc0000b1c00, 0x572adf0, 0xc003482ea0, 0x5, 0xc003482ea0, 0x1e0)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc0000b1c00, 0x4a46f00, 0xc0000b1d38, 0x431b6ba)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc0000b1c00, 0xc002e1c000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*connReader).Read(0xc000a76510, 0xc002e1c000, 0x1000, 0x1000, 0x425ea21, 0xc003482ea0, 0xbf2f613ef999e150)
+	/usr/local/go/src/net/http/server.go:787 +0x107
+bufio.(*Reader).fill(0xc003716e40)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc003716e40, 0x4, 0x435f745b6, 0x50bfa40, 0x0, 0x0, 0x50bfa40)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*conn).serve(0xc000160320, 0x4b3b4c0, 0xc00317a000)
+	/usr/local/go/src/net/http/server.go:1903 +0x9bf
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 93 [runnable]:
+io.(*LimitedReader).Read(0xc003a0e840, 0xc003fca000, 0x8000, 0x8000, 0x8000, 0x0, 0x0)
+	/usr/local/go/src/io/io.go:441 +0xc9
+io.copyBuffer(0x5741150, 0xc000a35030, 0x4b2aee0, 0xc003a0e840, 0xc003fca000, 0x8000, 0x8000, 0x20000, 0x0, 0x0)
+	/usr/local/go/src/io/io.go:402 +0x122
+io.CopyBuffer(...)
+	/usr/local/go/src/io/io.go:375
+github.com/syncthing/syncthing/lib/scanner.Blocks(0x4b3b500, 0xc0000c4030, 0x4b2a360, 0xc000c47ec0, 0x20000, 0x200000, 0x4b2a940, 0x50e2748, 0x4059500, 0xc003384300, ...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/scanner/blocks.go:69 +0x330
+github.com/syncthing/syncthing/lib/ur.cpuBenchOnce(0x7735940, 0xc0014d2000, 0xc0014d2000, 0x200000, 0x200000, 0x4051e3d70a3d70a4)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/ur/usage_report.go:468 +0x153
+github.com/syncthing/syncthing/lib/ur.CpuBench(0x5, 0x7735940, 0x488a100, 0xe)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/ur/usage_report.go:453 +0xd4
+github.com/syncthing/syncthing/lib/ur.(*Service).reportData(0xc0002461c0, 0x3, 0x1, 0x4b2b580)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/ur/usage_report.go:111 +0x8ad
+github.com/syncthing/syncthing/lib/ur.(*Service).ReportDataPreview(...)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/ur/usage_report.go:75
+github.com/syncthing/syncthing/lib/api.(*service).getReport(0xc000118dc0, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:1102 +0x7f
+net/http.HandlerFunc.ServeHTTP(0xc002ef2190, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a880, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.getPostHandler.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:426 +0xe6
+net/http.HandlerFunc.ServeHTTP(0xc00030abd0, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.metricsMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:518 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc000010bc0, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.noCacheMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:541 +0x3f4
+net/http.HandlerFunc.ServeHTTP(0xc000010be0, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.(*ServeMux).ServeHTTP(0xc00022a940, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:2375 +0x1d6
+github.com/syncthing/syncthing/lib/api.csrfMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_csrf.go:78 +0x208
+net/http.HandlerFunc.ServeHTTP(0xc0001713f0, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.withDetailsMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:549 +0x2ce
+net/http.HandlerFunc.ServeHTTP(0xc0000c94c0, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.basicAuthAndSessionMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api_auth.go:51 +0xa64
+net/http.HandlerFunc.ServeHTTP(0xc0001e9040, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.redirectToHTTPSMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:531 +0x117
+net/http.HandlerFunc.ServeHTTP(0xc0000ad700, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.corsMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:510 +0x2a6
+net/http.HandlerFunc.ServeHTTP(0xc0000ad720, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+github.com/syncthing/syncthing/lib/api.debugMiddleware.func1(0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/api/api.go:438 +0xa7
+net/http.HandlerFunc.ServeHTTP(0xc0000ad740, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:1995 +0x44
+net/http.serverHandler.ServeHTTP(0xc0001e9110, 0x4b39580, 0xc0011160e0, 0xc003ce0900)
+	/usr/local/go/src/net/http/server.go:2774 +0xa8
+net/http.(*conn).serve(0xc00303bc20, 0x4b3b4c0, 0xc00133d380)
+	/usr/local/go/src/net/http/server.go:1878 +0x851
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 121 [IO wait]:
+internal/poll.runtime_pollWait(0x57403a8, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc001729d98, 0x72, 0x600, 0x62b, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc001729d80, 0xc0034c2300, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc001729d80, 0xc0034c2300, 0x62b, 0x62b, 0xc003f3f8c0, 0x8, 0xc000357200)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc0011999f0, 0xc0034c2300, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+github.com/syncthing/syncthing/lib/tlsutil.(*UnionedConnection).Read(0xc00368a0e0, 0xc0034c2300, 0x62b, 0x62b, 0xc003f3f900, 0xc003f3f9a8, 0x10)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:223 +0xc1
+crypto/tls.(*atLeastReader).Read(0xc000239240, 0xc0034c2300, 0x62b, 0x62b, 0xc00337e005, 0xc001729d98, 0xc003f3f9d8)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc0000b1058, 0x4b2a4a0, 0xc000239240, 0x400bc55, 0x47b4f40, 0x4812c80)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc0000b0e00, 0x572adf0, 0xc00368a0e0, 0x5, 0xc00368a0e0, 0x1e0)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc0000b0e00, 0x4a46f00, 0xc0000b0f38, 0x431b6ba)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc0000b0e00, 0xc003882000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*connReader).Read(0xc0036d64e0, 0xc003882000, 0x1000, 0x1000, 0x425ea21, 0xc00368a0e0, 0xbf2f613ef96cb2e8)
+	/usr/local/go/src/net/http/server.go:787 +0x107
+bufio.(*Reader).fill(0xc0034beae0)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc0034beae0, 0x4, 0x435ca1921, 0x50bfa40, 0x0, 0x0, 0x50bfa40)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*conn).serve(0xc00303b540, 0x4b3b4c0, 0xc00330e600)
+	/usr/local/go/src/net/http/server.go:1903 +0x9bf
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 119 [IO wait]:
+internal/poll.runtime_pollWait(0x57406e8, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc00014d518, 0x72, 0x600, 0x62b, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc00014d500, 0xc00382ce00, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc00014d500, 0xc00382ce00, 0x62b, 0x62b, 0xc0002f98c0, 0x8, 0xc0002c9380)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc0030465e0, 0xc00382ce00, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+github.com/syncthing/syncthing/lib/tlsutil.(*UnionedConnection).Read(0xc002d80ae0, 0xc00382ce00, 0x62b, 0x62b, 0xc0002f9900, 0xc0002f99a8, 0x10)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:223 +0xc1
+crypto/tls.(*atLeastReader).Read(0xc003d98a40, 0xc00382ce00, 0x62b, 0x62b, 0xc0000f0a05, 0xc00014d518, 0xc0002f99d8)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc0000b0cd8, 0x4b2a4a0, 0xc003d98a40, 0x400bc55, 0x47b4f40, 0x4812c80)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc0000b0a80, 0x572adf0, 0xc002d80ae0, 0x5, 0xc002d80ae0, 0x1500)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc0000b0a80, 0x4a46f00, 0xc0000b0bb8, 0x431b6ba)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc0000b0a80, 0xc00129a000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*connReader).Read(0xc000e40d20, 0xc00129a000, 0x1000, 0x1000, 0x425ea21, 0xc002d80ae0, 0xbf2f613ef998b488)
+	/usr/local/go/src/net/http/server.go:787 +0x107
+bufio.(*Reader).fill(0xc000fcc240)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc000fcc240, 0x4, 0x435f619b6, 0x50bfa40, 0x0, 0x0, 0x50bfa40)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*conn).serve(0xc0000f34a0, 0x4b3b4c0, 0xc00389d680)
+	/usr/local/go/src/net/http/server.go:1903 +0x9bf
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 184 [IO wait]:
+internal/poll.runtime_pollWait(0x5740478, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc0005e5b98, 0x72, 0x600, 0x611, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc0005e5b80, 0xc002e3c000, 0x611, 0x611, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc0005e5b80, 0xc002e3c000, 0x611, 0x611, 0x203000, 0x0, 0xc000000034)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc003046048, 0xc002e3c000, 0x611, 0x611, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+crypto/tls.(*atLeastReader).Read(0xc003d98b40, 0xc002e3c000, 0x611, 0x611, 0x2, 0xa, 0xc0013c8938)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc0000b13d8, 0x4b2a4a0, 0xc003d98b40, 0x400bc55, 0x47b4f40, 0x4852500)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc0000b1180, 0x5712c90, 0xc003046048, 0x5, 0xc003046048, 0x203000)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc0000b1180, 0x4a46f00, 0xc0000b12b8, 0xc0013c8b88)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc0000b1180, 0xc001ab5000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*persistConn).Read(0xc00019cd80, 0xc001ab5000, 0x1000, 0x1000, 0xc0013c8c88, 0x4006d35, 0xc000baa960)
+	/usr/local/go/src/net/http/transport.go:1524 +0x7b
+bufio.(*Reader).fill(0xc000fccf60)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc000fccf60, 0x1, 0x0, 0x0, 0x1, 0xc000baa800, 0x0)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*persistConn).readLoop(0xc00019cd80)
+	/usr/local/go/src/net/http/transport.go:1677 +0x1a3
+created by net/http.(*Transport).dialConn
+	/usr/local/go/src/net/http/transport.go:1357 +0xae8
+
+goroutine 154 [IO wait]:
+internal/poll.runtime_pollWait(0x5740138, 0x72, 0xffffffffffffffff)
+	/usr/local/go/src/runtime/netpoll.go:182 +0x56
+internal/poll.(*pollDesc).wait(0xc0000cd698, 0x72, 0x600, 0x62b, 0xffffffffffffffff)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:87 +0x9b
+internal/poll.(*pollDesc).waitRead(...)
+	/usr/local/go/src/internal/poll/fd_poll_runtime.go:92
+internal/poll.(*FD).Read(0xc0000cd680, 0xc0034c0000, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/internal/poll/fd_unix.go:169 +0x1f2
+net.(*netFD).Read(0xc0000cd680, 0xc0034c0000, 0x62b, 0x62b, 0xc0037a98c0, 0x8, 0x4a46af0)
+	/usr/local/go/src/net/fd_unix.go:202 +0x4f
+net.(*conn).Read(0xc000b1f630, 0xc0034c0000, 0x62b, 0x62b, 0x0, 0x0, 0x0)
+	/usr/local/go/src/net/net.go:177 +0x69
+github.com/syncthing/syncthing/lib/tlsutil.(*UnionedConnection).Read(0xc003482b40, 0xc0034c0000, 0x62b, 0x62b, 0xc0037a9900, 0xc0037a99a8, 0x10)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/tlsutil/tlsutil.go:223 +0xc1
+crypto/tls.(*atLeastReader).Read(0xc003d98a20, 0xc0034c0000, 0x62b, 0x62b, 0xc00409a3c5, 0xc0000cd698, 0xc0037a99d8)
+	/usr/local/go/src/crypto/tls/conn.go:761 +0x60
+bytes.(*Buffer).ReadFrom(0xc0000b1ad8, 0x4b2a4a0, 0xc003d98a20, 0x400bc55, 0x47b4f40, 0x4812c80)
+	/usr/local/go/src/bytes/buffer.go:207 +0xbd
+crypto/tls.(*Conn).readFromUntil(0xc0000b1880, 0x572adf0, 0xc003482b40, 0x5, 0xc003482b40, 0x1e0)
+	/usr/local/go/src/crypto/tls/conn.go:783 +0xf8
+crypto/tls.(*Conn).readRecordOrCCS(0xc0000b1880, 0x4a46f00, 0xc0000b19b8, 0x431b6ba)
+	/usr/local/go/src/crypto/tls/conn.go:590 +0x125
+crypto/tls.(*Conn).readRecord(...)
+	/usr/local/go/src/crypto/tls/conn.go:558
+crypto/tls.(*Conn).Read(0xc0000b1880, 0xc0017cf000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
+	/usr/local/go/src/crypto/tls/conn.go:1236 +0x137
+net/http.(*connReader).Read(0xc000a774d0, 0xc0017cf000, 0x1000, 0x1000, 0x425ea21, 0xc003482b40, 0xbf2f613ef9985ab0)
+	/usr/local/go/src/net/http/server.go:787 +0x107
+bufio.(*Reader).fill(0xc0037171a0)
+	/usr/local/go/src/bufio/bufio.go:100 +0x10f
+bufio.(*Reader).Peek(0xc0037171a0, 0x4, 0x435f5c239, 0x50bfa40, 0x0, 0x0, 0x50bfa40)
+	/usr/local/go/src/bufio/bufio.go:138 +0x4f
+net/http.(*conn).serve(0xc000160000, 0x4b3b4c0, 0xc00317a100)
+	/usr/local/go/src/net/http/server.go:1903 +0x9bf
+created by net/http.(*Server).Serve
+	/usr/local/go/src/net/http/server.go:2884 +0x2f4
+
+goroutine 185 [select]:
+net/http.(*persistConn).writeLoop(0xc00019cd80)
+	/usr/local/go/src/net/http/transport.go:1958 +0x113
+created by net/http.(*Transport).dialConn
+	/usr/local/go/src/net/http/transport.go:1358 +0xb0d
+
+goroutine 172 [semacquire]:
+sync.runtime_Semacquire(0xc003d18518)
+	/usr/local/go/src/runtime/sema.go:56 +0x39
+sync.(*WaitGroup).Wait(0xc003d18510)
+	/usr/local/go/src/sync/waitgroup.go:130 +0x65
+github.com/syncthing/syncthing/lib/connections.(*service).dialParallel.func2(0xc003d18510, 0xc0034bf3e0)
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:849 +0x2b
+created by github.com/syncthing/syncthing/lib/connections.(*service).dialParallel
+	/Users/jb/dev/github.com/syncthing/syncthing/lib/connections/service.go:848 +0x494

+ 160 - 0
cmd/stcrashreceiver/sentry.go

@@ -0,0 +1,160 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package main
+
+import (
+	"bytes"
+	"errors"
+	"io/ioutil"
+	"regexp"
+	"strings"
+
+	raven "github.com/getsentry/raven-go"
+	"github.com/maruel/panicparse/stack"
+)
+
+const reportServer = "https://crash.syncthing.net/report/"
+
+func sendReport(dsn, path string, report []byte) error {
+	pkt, err := parseReport(path, report)
+	if err != nil {
+		return err
+	}
+
+	cli, err := raven.New(dsn)
+	if err != nil {
+		return err
+	}
+
+	// The client sets release and such on the packet before sending, in the
+	// misguided idea that it knows this better than than the packet we give
+	// it. So we copy the values from the packet to the client first...
+	cli.SetRelease(pkt.Release)
+	cli.SetEnvironment(pkt.Environment)
+
+	_, errC := cli.Capture(pkt, nil)
+	return <-errC
+}
+
+func parseReport(path string, report []byte) (*raven.Packet, error) {
+	parts := bytes.SplitN(report, []byte("\n"), 2)
+	if len(parts) != 2 {
+		return nil, errors.New("no first line")
+	}
+
+	version, err := parseVersion(string(parts[0]))
+	if err != nil {
+		return nil, err
+	}
+	report = parts[1]
+
+	foundPanic := false
+	var subjectLine []byte
+	for {
+		parts = bytes.SplitN(report, []byte("\n"), 2)
+		if len(parts) != 2 {
+			return nil, errors.New("no panic line found")
+		}
+
+		line := parts[0]
+		report = parts[1]
+
+		if foundPanic {
+			// The previous line was our "Panic at ..." header. We are now
+			// at the beginning of the real panic trace and this is our
+			// subject line.
+			subjectLine = line
+			break
+		} else if bytes.HasPrefix(line, []byte("Panic at")) {
+			foundPanic = true
+		}
+	}
+
+	r := bytes.NewReader(report)
+	ctx, err := stack.ParseDump(r, ioutil.Discard, false)
+	if err != nil {
+		return nil, err
+	}
+
+	var trace raven.Stacktrace
+	for _, gr := range ctx.Goroutines {
+		if gr.First {
+			trace.Frames = make([]*raven.StacktraceFrame, len(gr.Stack.Calls))
+			for i, sc := range gr.Stack.Calls {
+				trace.Frames[len(trace.Frames)-1-i] = &raven.StacktraceFrame{
+					Function: sc.Func.Name(),
+					Module:   sc.Func.PkgName(),
+					Filename: sc.SrcPath,
+					Lineno:   sc.Line,
+				}
+			}
+			break
+		}
+	}
+
+	pkt := &raven.Packet{
+		Message:  string(subjectLine),
+		Platform: "go",
+		Release:  version.tag,
+		Tags: raven.Tags{
+			raven.Tag{Key: "version", Value: version.version},
+			raven.Tag{Key: "tag", Value: version.tag},
+			raven.Tag{Key: "commit", Value: version.commit},
+			raven.Tag{Key: "codename", Value: version.codename},
+			raven.Tag{Key: "runtime", Value: version.runtime},
+			raven.Tag{Key: "goos", Value: version.goos},
+			raven.Tag{Key: "goarch", Value: version.goarch},
+			raven.Tag{Key: "builder", Value: version.builder},
+		},
+		Extra: raven.Extra{
+			"url": reportServer + path,
+		},
+		Interfaces: []raven.Interface{&trace},
+	}
+
+	return pkt, nil
+}
+
+// syncthing v1.1.4-rc.1+30-g6aaae618-dirty-crashrep "Erbium Earthworm" (go1.12.5 darwin-amd64) [email protected] 2019-05-23 16:08:14 UTC
+var longVersionRE = regexp.MustCompile(`syncthing\s+(v[^\s]+)\s+"([^"]+)"\s\(([^\s]+)\s+([^-]+)-([^)]+)\)\s+([^\s]+)`)
+
+type version struct {
+	version  string // "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep"
+	tag      string // "v1.1.4-rc.1"
+	commit   string // "6aaae618", blank when absent
+	codename string // "Erbium Earthworm"
+	runtime  string // "go1.12.5"
+	goos     string // "darwin"
+	goarch   string // "amd64"
+	builder  string // "[email protected]"
+}
+
+func parseVersion(line string) (version, error) {
+	m := longVersionRE.FindStringSubmatch(line)
+	if len(m) == 0 {
+		return version{}, errors.New("unintelligeble version string")
+	}
+
+	v := version{
+		version:  m[1],
+		codename: m[2],
+		runtime:  m[3],
+		goos:     m[4],
+		goarch:   m[5],
+		builder:  m[6],
+	}
+	parts := strings.Split(v.version, "+")
+	v.tag = parts[0]
+	if len(parts) > 1 {
+		fields := strings.Split(parts[1], "-")
+		if len(fields) >= 2 && strings.HasPrefix(fields[1], "g") {
+			v.commit = fields[1][1:]
+		}
+	}
+
+	return v, nil
+}

+ 64 - 0
cmd/stcrashreceiver/sentry_test.go

@@ -0,0 +1,64 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"testing"
+)
+
+func TestParseVersion(t *testing.T) {
+	cases := []struct {
+		longVersion string
+		parsed      version
+	}{
+		{
+			longVersion: `syncthing v1.1.4-rc.1+30-g6aaae618-dirty-crashrep "Erbium Earthworm" (go1.12.5 darwin-amd64) [email protected] 2019-05-23 16:08:14 UTC`,
+			parsed: version{
+				version:  "v1.1.4-rc.1+30-g6aaae618-dirty-crashrep",
+				tag:      "v1.1.4-rc.1",
+				commit:   "6aaae618",
+				codename: "Erbium Earthworm",
+				runtime:  "go1.12.5",
+				goos:     "darwin",
+				goarch:   "amd64",
+				builder:  "[email protected]",
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		v, err := parseVersion(tc.longVersion)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+		if v != tc.parsed {
+			t.Error(v)
+		}
+	}
+}
+
+func TestParseReport(t *testing.T) {
+	bs, err := ioutil.ReadFile("_testdata/panic.log")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pkt, err := parseReport("1/2/345", bs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	bs, err = pkt.JSON()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fmt.Printf("%s\n", bs)
+}

+ 135 - 0
cmd/stcrashreceiver/stcrashreceiver.go

@@ -0,0 +1,135 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+// Command stcrashreceiver is a trivial HTTP server that allows two things:
+//
+// - uploading files (crash reports) named like a SHA256 hash using a PUT request
+// - checking whether such file exists using a HEAD request
+//
+// Typically this should be deployed behind something that manages HTTPS.
+package main
+
+import (
+	"flag"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+)
+
+const maxRequestSize = 1 << 20 // 1 MiB
+
+func main() {
+	dir := flag.String("dir", ".", "Directory to store reports in")
+	dsn := flag.String("dsn", "", "Sentry DSN")
+	listen := flag.String("listen", ":22039", "HTTP listen address")
+	flag.Parse()
+
+	cr := &crashReceiver{
+		dir: *dir,
+		dsn: *dsn,
+	}
+
+	log.SetOutput(os.Stdout)
+	if err := http.ListenAndServe(*listen, cr); err != nil {
+		log.Fatalln("HTTP serve:", err)
+	}
+}
+
+type crashReceiver struct {
+	dir string
+	dsn string
+}
+
+func (r *crashReceiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	// The final path component should be a SHA256 hash in hex, so 64 hex
+	// characters. We don't care about case on the request but use lower
+	// case internally.
+	base := strings.ToLower(path.Base(req.URL.Path))
+	if len(base) != 64 {
+		http.Error(w, "Bad request", http.StatusBadRequest)
+		return
+	}
+	for _, c := range base {
+		if c >= 'a' && c <= 'f' {
+			continue
+		}
+		if c >= '0' && c <= '9' {
+			continue
+		}
+		http.Error(w, "Bad request", http.StatusBadRequest)
+		return
+	}
+
+	switch req.Method {
+	case http.MethodHead:
+		r.serveHead(base, w, req)
+	case http.MethodPut:
+		r.servePut(base, w, req)
+	default:
+		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+	}
+}
+
+// serveHead responds to HEAD requests by checking if the named report
+// already exists in the system.
+func (r *crashReceiver) serveHead(base string, w http.ResponseWriter, _ *http.Request) {
+	path := filepath.Join(r.dirFor(base), base)
+	if _, err := os.Lstat(path); err != nil {
+		http.Error(w, "Not found", http.StatusNotFound)
+	}
+	// 200 OK
+}
+
+// servePut accepts and stores the given report.
+func (r *crashReceiver) servePut(base string, w http.ResponseWriter, req *http.Request) {
+	path := filepath.Join(r.dirFor(base), base)
+	fullPath := filepath.Join(r.dir, path)
+
+	// Ensure the destination directory exists
+	if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
+		log.Printf("Creating directory for report %s: %v", base, err)
+		http.Error(w, "Internal server error", http.StatusInternalServerError)
+		return
+	}
+
+	// Read at most maxRequestSize of report data.
+	log.Println("Receiving report", base)
+	lr := io.LimitReader(req.Body, maxRequestSize)
+	bs, err := ioutil.ReadAll(lr)
+	if err != nil {
+		log.Println("Reading report:", err)
+		http.Error(w, "Internal server error", http.StatusInternalServerError)
+		return
+	}
+
+	// Create an output file
+	err = ioutil.WriteFile(fullPath, bs, 0644)
+	if err != nil {
+		log.Printf("Creating file for report %s: %v", base, err)
+		http.Error(w, "Internal server error", http.StatusInternalServerError)
+		return
+	}
+
+	// Send the report to Sentry
+	if r.dsn != "" {
+		go func() {
+			// There's no need for the client to have to wait for this part.
+			if err := sendReport(r.dsn, path, bs); err != nil {
+				log.Println("Failed to send report:", err)
+			}
+		}()
+	}
+}
+
+// 01234567890abcdef... => 01/23
+func (r *crashReceiver) dirFor(base string) string {
+	return filepath.Join(base[0:2], base[2:4])
+}

+ 152 - 0
cmd/syncthing/crash_reporting.go

@@ -0,0 +1,152 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package main
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"time"
+
+	"github.com/syncthing/syncthing/lib/sha256"
+)
+
+const (
+	headRequestTimeout = 10 * time.Second
+	putRequestTimeout  = time.Minute
+)
+
+// uploadPanicLogs attempts to upload all the panic logs in the named
+// directory to the crash reporting server as urlBase. Uploads are attempted
+// with the newest log first.
+//
+// This can can block for a long time. The context can set a final deadline
+// for this.
+func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
+	files, err := filepath.Glob(filepath.Join(dir, "panic-*.log"))
+	if err != nil {
+		l.Warnln("Failed to list panic logs:", err)
+		return
+	}
+
+	sort.Sort(sort.Reverse(sort.StringSlice(files)))
+	for _, file := range files {
+		if strings.Contains(file, ".reported.") {
+			// We've already sent this file. It'll be cleaned out at some
+			// point.
+			continue
+		}
+
+		if err := uploadPanicLog(ctx, urlBase, file); err != nil {
+			l.Warnln("Reporting crash:", err)
+		} else {
+			// Rename the log so we don't have to try to report it again. This
+			// succeeds, or it does not. There is no point complaining about it.
+			_ = os.Rename(file, strings.Replace(file, ".log", ".reported.log", 1))
+		}
+	}
+}
+
+// uploadPanicLog attempts to upload the named panic log to the crash
+// reporting server at urlBase. The panic ID is constructed as the sha256 of
+// the log contents. A HEAD request is made to see if the log has already
+// been reported. If not, a PUT is made with the log contents.
+func uploadPanicLog(ctx context.Context, urlBase, file string) error {
+	data, err := ioutil.ReadFile(file)
+	if err != nil {
+		return err
+	}
+
+	// Remove log lines, for privacy.
+	data = filterLogLines(data)
+
+	hash := fmt.Sprintf("%x", sha256.Sum256(data))
+	l.Infof("Reporting crash found in %s (report ID %s) ...\n", filepath.Base(file), hash[:8])
+
+	url := fmt.Sprintf("%s/%s", urlBase, hash)
+	headReq, err := http.NewRequest(http.MethodHead, url, nil)
+	if err != nil {
+		return err
+	}
+
+	// Set a reasonable timeout on the HEAD request
+	headCtx, headCancel := context.WithTimeout(ctx, headRequestTimeout)
+	defer headCancel()
+	headReq = headReq.WithContext(headCtx)
+
+	resp, err := http.DefaultClient.Do(headReq)
+	if err != nil {
+		return err
+	}
+	resp.Body.Close()
+	if resp.StatusCode == http.StatusOK {
+		// It's known, we're done
+		return nil
+	}
+
+	putReq, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
+	if err != nil {
+		return err
+	}
+
+	// Set a reasonable timeout on the PUT request
+	putCtx, putCancel := context.WithTimeout(ctx, putRequestTimeout)
+	defer putCancel()
+	putReq = putReq.WithContext(putCtx)
+
+	resp, err = http.DefaultClient.Do(putReq)
+	if err != nil {
+		return err
+	}
+	resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("upload: %s", resp.Status)
+	}
+
+	return nil
+}
+
+// filterLogLines returns the data without any log lines between the first
+// line and the panic trace. This is done in-place: the original data slice
+// is destroyed.
+func filterLogLines(data []byte) []byte {
+	filtered := data[:0]
+	matched := false
+	for _, line := range bytes.Split(data, []byte("\n")) {
+		switch {
+		case !matched && bytes.HasPrefix(line, []byte("Panic ")):
+			// This begins the panic trace, set the matched flag and append.
+			matched = true
+			fallthrough
+		case len(filtered) == 0 || matched:
+			// This is the first line or inside the panic trace.
+			if len(filtered) > 0 {
+				// We add the newline before rather than after because
+				// bytes.Split sees the \n as *separator* and not line
+				// ender, so ir will generate a last empty line that we
+				// don't really want. (We want to keep blank lines in the
+				// middle of the trace though.)
+				filtered = append(filtered, '\n')
+			}
+			// Remove the device ID prefix. The "plus two" stuff is because
+			// the line will look like "[foo] whatever" and the end variable
+			// will end up pointing at the ] and we want to step over that
+			// and the following space.
+			if end := bytes.Index(line, []byte("]")); end > 1 && end < len(line)-2 && bytes.HasPrefix(line, []byte("[")) {
+				line = line[end+2:]
+			}
+			filtered = append(filtered, line...)
+		}
+	}
+	return filtered
+}

+ 37 - 0
cmd/syncthing/crash_reporting_test.go

@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package main
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestFilterLogLines(t *testing.T) {
+	in := []byte(`[ABCD123] syncthing version whatever
+here is more log data
+and more
+...
+and some more
+yet more
+Panic detected at like right now
+here is panic data
+and yet more panic stuff
+`)
+
+	filtered := []byte(`syncthing version whatever
+Panic detected at like right now
+here is panic data
+and yet more panic stuff
+`)
+
+	result := filterLogLines(in)
+	if !bytes.Equal(result, filtered) {
+		t.Logf("%q\n", result)
+		t.Error("it should have been filtered")
+	}
+}

+ 48 - 0
cmd/syncthing/monitor.go

@@ -8,6 +8,7 @@ package main
 
 import (
 	"bufio"
+	"context"
 	"io"
 	"os"
 	"os/exec"
@@ -33,6 +34,8 @@ const (
 	loopThreshold         = 60 * time.Second
 	logFileAutoCloseDelay = 5 * time.Second
 	logFileMaxOpenTime    = time.Minute
+	panicUploadMaxWait    = 30 * time.Second
+	panicUploadNoticeWait = 10 * time.Second
 )
 
 func monitorMain(runtimeOptions RuntimeOptions) {
@@ -72,6 +75,8 @@ func monitorMain(runtimeOptions RuntimeOptions) {
 	childEnv := childEnv()
 	first := true
 	for {
+		maybeReportPanics()
+
 		if t := time.Since(restarts[0]); t < loopThreshold {
 			l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
 			os.Exit(exitError)
@@ -173,6 +178,13 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
 	br := bufio.NewReader(stderr)
 
 	var panicFd *os.File
+	defer func() {
+		if panicFd != nil {
+			_ = panicFd.Close()
+			maybeReportPanics()
+		}
+	}()
+
 	for {
 		line, err := br.ReadString('\n')
 		if err != nil {
@@ -430,3 +442,39 @@ func childEnv() []string {
 	env = append(env, "STMONITORED=yes")
 	return env
 }
+
+// maybeReportPanics tries to figure out if crash reporting is on or off,
+// and reports any panics it can find if it's enabled. We spend at most
+// panicUploadMaxWait uploading panics...
+func maybeReportPanics() {
+	// Try to get a config to see if/where panics should be reported.
+	cfg, err := loadOrDefaultConfig()
+	if err != nil {
+		l.Warnln("Couldn't load config; not reporting crash")
+		return
+	}
+
+	// Bail if we're not supposed to report panics.
+	opts := cfg.Options()
+	if !opts.CREnabled {
+		return
+	}
+
+	// Set up a timeout on the whole operation.
+	ctx, cancel := context.WithTimeout(context.Background(), panicUploadMaxWait)
+	defer cancel()
+
+	// Print a notice if the upload takes a long time.
+	go func() {
+		select {
+		case <-ctx.Done():
+			return
+		case <-time.After(panicUploadNoticeWait):
+			l.Warnln("Uploading crash reports is taking a while, please wait...")
+		}
+	}()
+
+	// Report the panics.
+	dir := locations.GetBaseDir(locations.ConfigBaseDir)
+	uploadPanicLogs(ctx, opts.CRURL, dir)
+}

+ 3 - 0
go.mod

@@ -8,9 +8,11 @@ require (
 	github.com/calmh/du v1.0.1
 	github.com/calmh/xdr v1.1.0
 	github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
+	github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 // indirect
 	github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5
 	github.com/d4l3k/messagediff v1.2.1
 	github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
+	github.com/getsentry/raven-go v0.2.0
 	github.com/gobwas/glob v0.0.0-20170212200151-51eb1ee00b6d
 	github.com/gogo/protobuf v1.2.1
 	github.com/golang/groupcache v0.0.0-20171101203131-84a468cf14b4
@@ -20,6 +22,7 @@ require (
 	github.com/kr/pretty v0.1.0 // indirect
 	github.com/lib/pq v1.1.1
 	github.com/lucas-clemente/quic-go v0.11.2
+	github.com/maruel/panicparse v1.2.1
 	github.com/mattn/go-isatty v0.0.7
 	github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338
 	github.com/oschwald/geoip2-golang v1.3.0

+ 9 - 0
go.sum

@@ -19,6 +19,8 @@ github.com/calmh/xdr v1.1.0 h1:U/Dd4CXNLoo8EiQ4ulJUXkgO1/EyQLgDKLgpY1SOoJE=
 github.com/calmh/xdr v1.1.0/go.mod h1:E8sz2ByAdXC8MbANf1LCRYzedSnnc+/sXXJs/PVqoeg=
 github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=
 github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
+github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 h1:UNOqI3EKhvbqV8f1Vm3NIwkrhq388sGCeAH2Op7w0rc=
+github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
 github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
@@ -34,6 +36,8 @@ github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWT
 github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
+github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -78,10 +82,15 @@ github.com/lucas-clemente/quic-go v0.11.2 h1:Mop0ac3zALaBR3wGs6j8OYe/tcFvFsxTUFM
 github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
 github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
 github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
+github.com/maruel/panicparse v1.2.1 h1:mNlHGiakrixj+AwF/qRpTwnj+zsWYPRLQ7wRqnJsfO0=
+github.com/maruel/panicparse v1.2.1/go.mod h1:vszMjr5QQ4F5FSRfraldcIA/BCw5xrdLL+zEcU2nRBs=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338 h1:USW1+zAUkUSvk097CAX/i8KR3r6f+DHNhk6Xe025Oyw=
 github.com/minio/sha256-simd v0.0.0-20190117184323-cc1980cb0338/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

+ 47 - 0
gui/default/syncthing/core/notifications.html

@@ -69,3 +69,50 @@
     </div>
   </div>
 </notification>
+
+<notification id="crAutoEnabled">
+  <div class="panel panel-success">
+    <div class="panel-heading">
+      <h3 class="panel-title"><span class="fas fa-bolt"></span>&ensp;<span translate>Automatic Crash Reporting</span></h3>
+    </div>
+    <div class="panel-body">
+      <p translate>Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.</p>
+      <p><a href="https://docs.syncthing.net/users/crashrep.html"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
+    </div>
+    <div class="panel-footer clearfix">
+      <div class="pull-right">
+        <button type="button" class="btn btn-danger" ng-click="setCrashReportingEnabled(false); dismissNotification('crAutoEnabled')">
+          <span class="fas fa-times"></span>&nbsp;<span translate>Disable Crash Reporting</span>
+        </button>
+        <button type="button" class="btn btn-default" ng-click="dismissNotification('crAutoEnabled')">
+          <span class="fas fa-check"></span>&nbsp;<span translate>OK</span>
+        </button>
+      </div>
+      <div class="clearfix"></div>
+    </div>
+  </div>
+</notification>
+
+<notification id="crAutoDisabled">
+  <div class="panel panel-success">
+    <div class="panel-heading">
+      <h3 class="panel-title"><span class="fas fa-bolt"></span>&ensp;<span translate>Automatic Crash Reporting</span></h3>
+    </div>
+    <div class="panel-body">
+      <p translate>Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.</p>
+      <p translate>However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.</p>
+      <p><a href="https://docs.syncthing.net/users/crashrep.html"><span class="fas fa-info-circle"></span>&nbsp;<span translate>Learn more</span></a></p>
+    </div>
+    <div class="panel-footer clearfix">
+      <div class="pull-right">
+        <button type="button" class="btn btn-success" ng-click="setCrashReportingEnabled(true); dismissNotification('crAutoDisabled')">
+          <span class="fas fa-check"></span>&nbsp;<span translate>Enable Crash Reporting</span>
+        </button>
+        <button type="button" class="btn btn-default" ng-click="dismissNotification('crAutoDisabled')">
+          <span class="fas fa-times"></span>&nbsp;<span translate>OK</span>
+        </button>
+      </div>
+      <div class="clearfix"></div>
+    </div>
+  </div>
+</notification>

+ 5 - 0
gui/default/syncthing/core/syncthingController.js

@@ -2426,4 +2426,9 @@ angular.module('syncthing.core')
             var err = status.error.replace(/.+: /, '');
             return err + " (" + time + ")";
         }
+
+        $scope.setCrashReportingEnabled = function (enabled) {
+            $scope.config.options.crashReportingEnabled = enabled;
+            $scope.saveConfig();
+        };
     });

+ 1 - 1
lib/config/config.go

@@ -29,7 +29,7 @@ import (
 
 const (
 	OldestHandledVersion = 10
-	CurrentVersion       = 28
+	CurrentVersion       = 29
 	MaxRescanIntervalS   = 365 * 24 * 60 * 60
 )
 

+ 12 - 10
lib/config/config_test.go

@@ -69,6 +69,8 @@ func TestDefaultValues(t *testing.T) {
 		UnackedNotificationIDs:  []string{},
 		DefaultFolderPath:       "~",
 		SetLowPriority:          true,
+		CRURL:                   "https://crash.syncthing.net/newcrash",
+		CREnabled:               true,
 		StunKeepaliveStartS:     180,
 		StunKeepaliveMinS:       20,
 		StunServers:             []string{"default"},
@@ -203,7 +205,8 @@ func TestOverriddenValues(t *testing.T) {
 		ProgressUpdateIntervalS: 10,
 		LimitBandwidthInLan:     true,
 		MinHomeDiskFree:         Size{5.2, "%"},
-		URSeen:                  2,
+		URSeen:                  8,
+		URAccepted:              4,
 		URURL:                   "https://localhost/newdata",
 		URInitialDelayS:         800,
 		URPostInsecurely:        true,
@@ -211,15 +214,14 @@ func TestOverriddenValues(t *testing.T) {
 		AlwaysLocalNets:         []string{},
 		OverwriteRemoteDevNames: true,
 		TempIndexMinBlocks:      100,
-		UnackedNotificationIDs: []string{
-			"channelNotification",   // added in 17->18 migration
-			"fsWatcherNotification", // added in 27->28 migration
-		},
-		DefaultFolderPath:   "/media/syncthing",
-		SetLowPriority:      false,
-		StunKeepaliveStartS: 9000,
-		StunKeepaliveMinS:   900,
-		StunServers:         []string{"foo"},
+		UnackedNotificationIDs:  []string{"asdfasdf"},
+		DefaultFolderPath:       "/media/syncthing",
+		SetLowPriority:          false,
+		CRURL:                   "https://localhost/newcrash",
+		CREnabled:               false,
+		StunKeepaliveStartS:     9000,
+		StunKeepaliveMinS:       900,
+		StunServers:             []string{"foo"},
 	}
 
 	os.Unsetenv("STNOUPGRADE")

+ 14 - 0
lib/config/migrations.go

@@ -25,6 +25,7 @@ import (
 // update the config version. The order of migrations doesn't matter here,
 // put the newest on top for readability.
 var migrations = migrationSet{
+	{29, migrateToConfigV29},
 	{28, migrateToConfigV28},
 	{27, migrateToConfigV27},
 	{26, nil}, // triggers database update
@@ -83,6 +84,19 @@ func (m migration) apply(cfg *Configuration) {
 	cfg.Version = m.targetVersion
 }
 
+func migrateToConfigV29(cfg *Configuration) {
+	// The new crash reporting option should follow the state of global
+	// discovery / usage reporting, and we should display an appropriate
+	// notification.
+	if cfg.Options.GlobalAnnEnabled || cfg.Options.URAccepted > 0 {
+		cfg.Options.CREnabled = true
+		cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs, "crAutoEnabled")
+	} else {
+		cfg.Options.CREnabled = false
+		cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs, "crAutoDisabled")
+	}
+}
+
 func migrateToConfigV28(cfg *Configuration) {
 	// Show a notification about enabling filesystem watching
 	cfg.Options.UnackedNotificationIDs = append(cfg.Options.UnackedNotificationIDs, "fsWatcherNotification")

+ 34 - 0
lib/config/migrations_test.go

@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package config
+
+import "testing"
+
+func TestMigrateCrashReporting(t *testing.T) {
+	// When migrating from pre-crash-reporting configs, crash reporting is
+	// enabled if global discovery is enabled or if usage reporting is
+	// enabled (not just undecided).
+	cases := []struct {
+		opts    OptionsConfiguration
+		enabled bool
+	}{
+		{opts: OptionsConfiguration{URAccepted: 0, GlobalAnnEnabled: true}, enabled: true},
+		{opts: OptionsConfiguration{URAccepted: -1, GlobalAnnEnabled: true}, enabled: true},
+		{opts: OptionsConfiguration{URAccepted: 1, GlobalAnnEnabled: true}, enabled: true},
+		{opts: OptionsConfiguration{URAccepted: 0, GlobalAnnEnabled: false}, enabled: false},
+		{opts: OptionsConfiguration{URAccepted: -1, GlobalAnnEnabled: false}, enabled: false},
+		{opts: OptionsConfiguration{URAccepted: 1, GlobalAnnEnabled: false}, enabled: true},
+	}
+
+	for i, tc := range cases {
+		cfg := Configuration{Version: 28, Options: tc.opts}
+		migrations.apply(&cfg)
+		if cfg.Options.CREnabled != tc.enabled {
+			t.Errorf("%d: unexpected result, CREnabled: %v != %v", i, cfg.Options.CREnabled, tc.enabled)
+		}
+	}
+}

+ 7 - 5
lib/config/optionsconfiguration.go

@@ -29,11 +29,11 @@ type OptionsConfiguration struct {
 	NATLeaseM               int      `xml:"natLeaseMinutes" json:"natLeaseMinutes" default:"60"`
 	NATRenewalM             int      `xml:"natRenewalMinutes" json:"natRenewalMinutes" default:"30"`
 	NATTimeoutS             int      `xml:"natTimeoutSeconds" json:"natTimeoutSeconds" default:"10"`
-	URAccepted              int      `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
-	URSeen                  int      `xml:"urSeen" json:"urSeen"`         // Report which the user has been prompted for.
-	URUniqueID              string   `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
-	URURL                   string   `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"`
-	URPostInsecurely        bool     `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"` // For testing
+	URAccepted              int      `xml:"urAccepted" json:"urAccepted"`                                    // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
+	URSeen                  int      `xml:"urSeen" json:"urSeen"`                                            // Report which the user has been prompted for.
+	URUniqueID              string   `xml:"urUniqueID" json:"urUniqueId"`                                    // Unique ID for reporting purposes, regenerated when UR is turned on.
+	URURL                   string   `xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata"` // usage reporting URL
+	URPostInsecurely        bool     `xml:"urPostInsecurely" json:"urPostInsecurely" default:"false"`        // For testing
 	URInitialDelayS         int      `xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800"`
 	RestartOnWakeup         bool     `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" restart:"true"`
 	AutoUpgradeIntervalH    int      `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" restart:"true"` // 0 for off
@@ -52,6 +52,8 @@ type OptionsConfiguration struct {
 	DefaultFolderPath       string   `xml:"defaultFolderPath" json:"defaultFolderPath" default:"~"`
 	SetLowPriority          bool     `xml:"setLowPriority" json:"setLowPriority" default:"true"`
 	MaxConcurrentScans      int      `xml:"maxConcurrentScans" json:"maxConcurrentScans"`
+	CRURL                   string   `xml:"crashReportingURL" json:"crURL" default:"https://crash.syncthing.net/newcrash"` // crash reporting URL
+	CREnabled               bool     `xml:"crashReportingEnabled" json:"crashReportingEnabled" default:"true" restart:"true"`
 	StunKeepaliveStartS     int      `xml:"stunKeepaliveStartS" json:"stunKeepaliveStartS" default:"180"` // 0 for off
 	StunKeepaliveMinS       int      `xml:"stunKeepaliveMinS" json:"stunKeepaliveMinS" default:"20"`      // 0 for off
 	StunServers             []string `xml:"stunServer" json:"stunServers" default:"default"`

+ 7 - 2
lib/config/testdata/overridenvalues.xml

@@ -1,4 +1,4 @@
-<configuration version="14">
+<configuration version="29">
     <options>
         <listenAddress>tcp://:23000</listenAddress>
         <allowDelete>false</allowDelete>
@@ -27,8 +27,10 @@
         <symlinksEnabled>false</symlinksEnabled>
         <limitBandwidthInLan>true</limitBandwidthInLan>
         <databaseBlockCacheMiB>42</databaseBlockCacheMiB>
-        <minHomeDiskFreePct>5.2</minHomeDiskFreePct>
+        <minHomeDiskFree unit="%">5.2</minHomeDiskFree>
         <urURL>https://localhost/newdata</urURL>
+        <urSeen>8</urSeen>
+        <urAccepted>4</urAccepted>
         <urInitialDelayS>800</urInitialDelayS>
         <urPostInsecurely>true</urPostInsecurely>
         <releasesURL>https://localhost/releases</releasesURL>
@@ -36,8 +38,11 @@
         <tempIndexMinBlocks>100</tempIndexMinBlocks>
         <defaultFolderPath>/media/syncthing</defaultFolderPath>
         <setLowPriority>false</setLowPriority>
+        <crashReportingURL>https://localhost/newcrash</crashReportingURL>
+        <crashReportingEnabled>false</crashReportingEnabled>
         <stunKeepaliveStartS>9000</stunKeepaliveStartS>
         <stunKeepaliveMinS>900</stunKeepaliveMinS>
         <stunServer>foo</stunServer>
+        <unackedNotificationID>asdfasdf</unackedNotificationID>
     </options>
 </configuration>

+ 16 - 0
lib/config/testdata/v29.xml

@@ -0,0 +1,16 @@
+<configuration version="28">
+    <folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" fsWatcherEnabled="false" fsWatcherDelayS="10" autoNormalize="true">
+        <filesystemType>basic</filesystemType>
+        <device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
+        <device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
+        <minDiskFree unit="%">1</minDiskFree>
+        <maxConflicts>-1</maxConflicts>
+        <fsync>true</fsync>
+    </folder>
+    <device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
+        <address>tcp://a</address>
+    </device>
+    <device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
+        <address>tcp://b</address>
+    </device>
+</configuration>

+ 2 - 1
lib/ur/usage_report.go

@@ -66,7 +66,8 @@ func New(cfg config.Wrapper, m model.Model, connectionsService connections.Servi
 // ReportData returns the data to be sent in a usage report with the currently
 // configured usage reporting version.
 func (s *Service) ReportData() map[string]interface{} {
-	return s.reportData(Version, false)
+	urVersion := s.cfg.Options().URAccepted
+	return s.reportData(urVersion, false)
 }
 
 // ReportDataPreview returns a preview of the data to be sent in a usage report