Six defects on byte-serving paths, all of which made the PR's monitoring and
kill-switch claims narrower than documented.
#2/GAP-10 -- the ABS in-flight kill switch was a production no-op. accessLog
wraps every ABS route, and its statusRecorder implemented Write, WriteHeader,
Hijack and Flush but not Unwrap, so http.NewResponseController dead-ended and
SetWriteDeadline returned ErrNotSupported. A multi-GB audiobook pour survived a
RevokeUser. The existing test passed throughout because it called handlers
directly and never saw the middleware; the new test drives the mounted router
over a real socket, and both new assertions fail if Unwrap is removed again.
GAP-11 -- ebook, comic and PDF serving was invisible and un-killable: no meter,
no transfer record, no Refuse, no watcher, on a route that serves cbz/cbr/pdf
files routinely 100 MB-1 GB+. It now follows the ABS file-handler idiom. Note
guardRevocationCut is deliberately *not* reused here: it keys on a session_id
URL param and an ?st= token this route does not carry, so it would have
compiled and silently guarded nothing. Cap-exempt per decision A4 -- admission
is untouched and neither route consumes a stream slot.
#10 -- the no-proxy remote transcode hop forwarded segments through a bare
RollingDeadlineWriter, so bytes on the API hop went unaccounted for a supported
topology. Metering is scoped to media bodies; manifests are excluded so a
rewritten playlist is not counted as media, and a mid-copy failure is no longer
silently discarded.
#16 -- native, proxy and compat subtitle pours were entry-gated only. They now
carry a transport span, a meter and an in-flight watcher. Proxy subtitle bytes
are attributed only when a tracker record already exists: taking Track/Remove
lifecycle ownership per subtitle request would walk straight into the
overlapping-request defect (#1) that Batch 2 addresses. Compat subtitle
extraction is buffered and rejects bitmap formats, so a cut stops delivery but
not extraction already in progress.
M2 -- the proxy deferred tracker Remove with the request context, which is
already canceled on client disconnect, so the Redis DEL never happened and the
key lingered until TTL -- a false over-cap window that could get a legitimate
stream killed. Cleanup now uses a short bounded context.
GAP-13 -- mergeStreams took the freshest record wholesale and never merged
BytesServed, so a stream that poured 8 GiB at an edge could report 0. Merged as
a max, not a sum: the records are two observers of one pour. Fixed in
DedupeSessionInfos too, which had the same hole and feeds the admin view.
#11 needed no behaviour change -- that route was already metered, registered and
watched, and commit c24d8396 plus this Unwrap fix are what make its cut work.
Its comment claimed download-class exemption while the comment above it said the
?token= form is for iOS streaming; both facts and decision A4 are now stated.
Part of #305.
64 lines
1.5 KiB
Go
64 lines
1.5 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/nodesessions"
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
type deleteCaptureHook struct {
|
|
called bool
|
|
contextErr error
|
|
}
|
|
|
|
func (h *deleteCaptureHook) DialHook(next redis.DialHook) redis.DialHook {
|
|
return next
|
|
}
|
|
|
|
func (h *deleteCaptureHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
|
if cmd.Name() == "del" {
|
|
h.called = true
|
|
h.contextErr = ctx.Err()
|
|
return nil
|
|
}
|
|
return next(ctx, cmd)
|
|
}
|
|
}
|
|
|
|
func (h *deleteCaptureHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
|
return next
|
|
}
|
|
|
|
func TestRemoveTrackedUsesLiveContextAfterRequestCancellation(t *testing.T) {
|
|
rdb := redis.NewClient(&redis.Options{
|
|
Dialer: func(context.Context, string, string) (net.Conn, error) {
|
|
t.Fatal("DEL should be intercepted before dialing")
|
|
return nil, nil
|
|
},
|
|
})
|
|
t.Cleanup(func() { _ = rdb.Close() })
|
|
hook := &deleteCaptureHook{}
|
|
rdb.AddHook(hook)
|
|
server := &Server{tracker: nodesessions.NewTracker(rdb, "http://node", "node", "proxy")}
|
|
|
|
requestCtx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if requestCtx.Err() == nil {
|
|
t.Fatal("request context was not canceled")
|
|
}
|
|
func() {
|
|
defer server.removeTracked("session-1")
|
|
}()
|
|
|
|
if !hook.called {
|
|
t.Fatal("Redis DEL was not attempted")
|
|
}
|
|
if hook.contextErr != nil {
|
|
t.Fatalf("DEL context error = %v, want live bounded cleanup context", hook.contextErr)
|
|
}
|
|
}
|