mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
test(stream): fix data race in MediaStreamer transcoding cap tests
The three It blocks that build a tight-cap streamer each spawned a fresh transcoding cache without waiting for its background initialization. The init goroutine reads conf.Server.CacheFolder, which races against SnapshotConfig's pointer-swap restore (Server = &restored) fired by DeferCleanup at the end of the spec. CI tripped the race under -shuffle=on -race; locally it reproduced about 10% of the time. Wait for tightCache.Available() before constructing the streamer, mirroring the outer BeforeEach. For the slot-saturation spec, swap in a blocking io.Pipe-backed mock ffmpeg so the cache's background copyAndClose can't drain the source and release the slot — the previous behavior happened to work only because the cache wasn't yet available and the no-cache path was exercised.
This commit is contained in:
parent
74a5c0c6d1
commit
833c50adc7
@ -64,11 +64,19 @@ var _ = Describe("MediaStreamer", func() {
|
||||
Expect(s.Duration()).To(Equal(float32(257.0)))
|
||||
})
|
||||
It("rejects transcode requests beyond MaxConcurrent with ErrTooManyTranscodes", func() {
|
||||
// Rebuild the streamer with a tight cap. The first request will hold the
|
||||
// ffmpeg reader open (we don't read/close it), saturating the single slot.
|
||||
// Use an ffmpeg whose Read blocks indefinitely so the cache's
|
||||
// background copy can't drain the source and release the slot —
|
||||
// keeping the single transcode slot pinned for this test.
|
||||
pr, pw := io.Pipe()
|
||||
DeferCleanup(func() { _ = pw.Close() })
|
||||
blockingFFmpeg := tests.NewMockFFmpeg("")
|
||||
blockingFFmpeg.Reader = pr
|
||||
|
||||
conf.Server.Transcoding.MaxConcurrent = 1
|
||||
conf.Server.Transcoding.MaxConcurrentPerUser = 0
|
||||
tightStreamer := stream.NewMediaStreamer(ds, ffmpeg, stream.NewTranscodingCache())
|
||||
tightCache := stream.NewTranscodingCache()
|
||||
Eventually(func() bool { return tightCache.Available(context.TODO()) }).Should(BeTrue())
|
||||
tightStreamer := stream.NewMediaStreamer(ds, blockingFFmpeg, tightCache)
|
||||
|
||||
userCtx := request.WithUsername(ctx, "alice")
|
||||
s1, err := tightStreamer.NewStream(userCtx, mf, stream.Request{Format: "mp3", BitRate: 64})
|
||||
@ -83,7 +91,9 @@ var _ = Describe("MediaStreamer", func() {
|
||||
It("releases the slot once the stream is closed", func() {
|
||||
conf.Server.Transcoding.MaxConcurrent = 1
|
||||
conf.Server.Transcoding.MaxConcurrentPerUser = 0
|
||||
tightStreamer := stream.NewMediaStreamer(ds, ffmpeg, stream.NewTranscodingCache())
|
||||
tightCache := stream.NewTranscodingCache()
|
||||
Eventually(func() bool { return tightCache.Available(context.TODO()) }).Should(BeTrue())
|
||||
tightStreamer := stream.NewMediaStreamer(ds, ffmpeg, tightCache)
|
||||
|
||||
userCtx := request.WithUsername(ctx, "alice")
|
||||
s1, err := tightStreamer.NewStream(userCtx, mf, stream.Request{Format: "mp3", BitRate: 64})
|
||||
@ -101,7 +111,9 @@ var _ = Describe("MediaStreamer", func() {
|
||||
It("does not consume a slot for raw streams", func() {
|
||||
conf.Server.Transcoding.MaxConcurrent = 1
|
||||
conf.Server.Transcoding.MaxConcurrentPerUser = 0
|
||||
tightStreamer := stream.NewMediaStreamer(ds, ffmpeg, stream.NewTranscodingCache())
|
||||
tightCache := stream.NewTranscodingCache()
|
||||
Eventually(func() bool { return tightCache.Available(context.TODO()) }).Should(BeTrue())
|
||||
tightStreamer := stream.NewMediaStreamer(ds, ffmpeg, tightCache)
|
||||
|
||||
userCtx := request.WithUsername(ctx, "alice")
|
||||
// First, saturate the single transcode slot.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user