Skip to content

Add (eql :closed) no-op method on apply-stream-priority#12

Closed
jcmallery wants to merge 33 commits into
zellerin:masterfrom
jcmallery:apply-stream-priority-closed-sentinel
Closed

Add (eql :closed) no-op method on apply-stream-priority#12
jcmallery wants to merge 33 commits into
zellerin:masterfrom
jcmallery:apply-stream-priority-closed-sentinel

Conversation

@jcmallery

Copy link
Copy Markdown

Problem

FIND-JUST-STREAM-BY-ID (core/frames/http2-stream.lisp) returns the
keyword :CLOSED when the stream-id is absent from the streams table --
the stream was already closed and reaped. Then APPLY-STREAM-PRIORITY
runs the default method, which calls (SETF (GET-WEIGHT stream) WEIGHT)
and (SETF (GET-DEPENDS-ON stream) ...). Neither has a method
specialised on a keyword, so the writer thread crashes with:

SIMPLE-ERROR -- No applicable methods for
#<STANDARD-GENERIC-FUNCTION (SETF GET-WEIGHT) ...>
with args (WEIGHT :CLOSED)

This is reachable in two ways:

  1. A PRIORITY frame arrives on a stream-id whose entry has aged out of
    (get-streams connection).
  2. A HEADERS frame with the PRIORITY flag set arrives via
    parse-header-frame* after find-just-stream-by-id returned :closed.

Hit in a production CL-HTTP/SBCL deployment after a long-running
session.

Fix

Add an (eql :closed) no-op method on apply-stream-priority,
mirroring the existing (eql :closed) no-op methods already present
on the three other generics that find-just-stream-by-id's keyword
return value reaches:

Generic Closed-method site
update-window-size core/frames/data.lisp:226
peer-resets-stream core/frames/rst-and-goaway.lisp:10
get-peer-window-size core/frames/data.lisp:362
apply-stream-priority (this PR)

RFC 9113 sec 5.5 explicitly permits PRIORITY frames on closed streams,
and RFC 9218 deprecates stream priority entirely, so a no-op is the
correct semantics. The default method's docstring already acknowledges
this: "Does nothing, as priorities are deprecated in RFC9113 anyway."

Verification

Direct call against a closed-stream sentinel now returns NIL instead of
signalling:

(http2/core:apply-stream-priority :closed nil 219 0)
;; => NIL  (was: SIMPLE-ERROR no-applicable-methods)

11 lines of source change; no behaviour change on live stream objects.

zellerin and others added 30 commits December 20, 2025 14:31
…nd regions

Global buffer pool for octet buffers with three size classes:
  Small (16B, max 64 free), Medium (1KB, max 32), Large (16KB, max 32).

Lock-free via CAS (Treiber stack): sb-ext:cas on SBCL,
sys:compare-and-swap on LW, generic lock fallback.

allocate-buffer returns fill-pointer arrays with fp = requested size.
(length buffer) returns the requested size; array-total-size returns
the size-class capacity.  deallocate-buffer resets fp before pooling.

with-resource-usage-region: scope-based deallocation for buffers that
escape local management (e.g., HEADERS continuation closures).
region-track-buffer transfers ownership to the region.

Measured on SBCL (50 H2 GET + 10 POST, Apple M3):
  Small:  2 allocated, 2402 recycled (1201:1)
  Medium: 5 allocated, 1419 recycled (284:1)
  Large:  1 allocated, 9 recycled (9:1)
- Add to .asd and package.lisp
- Move documentation to mgl-pax.
- Align file and package name
- Use buffer-pool package from core
HTTP-STREAM-ERROR now derives from ERROR. There is a specific subclass to be
raised when we receive the stream error from the peer, as opposed from when we
detect it.
New generic function QUEUE-FRAME-REGION to write only part of the provided data.
Falls back to QUEUE-FRAME.

WRITE-VECTOR-FRAME can be now used to send prepared data vector without copying
it, providing QUEUE-FRAME-REGION is specialized for the connection to avoid
copying.

QUEUE-FRAME-REGION uses WRITE-VECTOR-FRAME now.
There were two functions with same interpretation and it did not work.
Relocate the method, scheme, authority, path slots from server-stream
up to the http2-stream common superclass.  Both server-stream and
client-stream instances now inherit the slots (and their accessor
generic functions).

Rationale: log-closed-stream in core/frames/headers.lisp calls
(get-path stream), (get-method stream) etc. on any h2-stream during
http-stream-error handling.  The slots were only on server-stream,
so these calls signalled no-applicable-method-error on client
streams, killing client reactors.

Moving to http2-stream keeps the existing server-stream API (all
slots still accessible via the same accessor GFs) while making the
accessors work uniformly on client streams for diagnostics and
debugging.  Backward-compatible: server-stream instances retain
all slots via inheritance, and no existing code paths change.
…aders

Add request pseudo-header slots to client-stream

Should fix LOG-CLOSED-STREAM on client streams (presently raises error)
jcmallery and others added 3 commits April 13, 2026 07:08
Frame parsing, HPACK encoding/decoding, stream management, and
connection processing had no optimization declarations. Default
optimization runs generic dispatch, type checks, and no inlining
on the H2 framing hot path.

13 core files: classes, frames (main + 6 frame types), hpack,
stream-based-connections, payload-streams, pipe, utils.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The mode line attribute said Package: http2/resources but the
in-package form uses http2/buffer-pool.  Genera reads the mode
line to set the package, so the mismatch causes compilation in
the wrong package.
FIND-JUST-STREAM-BY-ID returns the keyword :CLOSED when the
stream-id is absent from the streams table (the stream was already
closed and reaped).  APPLY-STREAM-PRIORITY then ran the default
method, which calls (SETF (GET-WEIGHT stream) WEIGHT) and (SETF
(GET-DEPENDS-ON stream) ...) -- neither has a method specialised
on a keyword.

Symptom in production: a PRIORITY-bearing frame (or a HEADERS
frame with the priority flag) for a closed stream made the H2
writer thread die with

  SIMPLE-ERROR -- No applicable methods for
  #<STANDARD-GENERIC-FUNCTION (SETF GET-WEIGHT) ...>
  with args (WEIGHT :CLOSED)

RFC 9113 sec 5.5 explicitly permits PRIORITY frames on closed
streams, and RFC 9218 deprecates stream priority entirely, so the
right behaviour is "do nothing" -- there is no per-stream state
to update.

The library already follows this pattern for the three other
generics that FIND-JUST-STREAM-BY-ID's keyword return value
reaches:

  - update-window-size      (eql :closed)
  - peer-resets-stream      (eql :closed)
  - get-peer-window-size    (eql :closed)

apply-stream-priority was missing one.  Adding it fixes the
crash without changing behaviour on live streams.
@zellerin

zellerin commented May 20, 2026

Copy link
Copy Markdown
Owner

Pushed to v2.0.5 branch. Thanks.

@zellerin zellerin closed this May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants