quic 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/LICENSE-DEPENDENCIES.txt +2 -2
- data/README.md +1 -1
- data/ext/quic/connection_common.c +73 -12
- data/ext/quic/connection_common.h +8 -0
- data/ext/quic/ext_api.c +7 -5
- data/ext/quic/extconf.rb +5 -5
- data/ext/quic/include/quic/ext.h +22 -1
- data/ext/quic/stream.c +25 -6
- data/ext/quic/stream.h +6 -0
- data/lib/quic/version.rb +1 -1
- data/sig/quic.rbs +8 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd02d782aebbc1128a0fca8bda34bc2651b5d677e682d126ab0575b332b4f556
|
|
4
|
+
data.tar.gz: 9a5155a1b4f75a342d7476895641e5b2e06a6f7d5e99b4bd6f1830c90e747cda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f94809c98904f20281edbc702f57db0c0014e53243fb2d478e02c1c54870fb205bad185aadf3ab319d4f2294484aca46cf58b72f4d5a486138584426cd513808
|
|
7
|
+
data.tar.gz: d1f7925f594bc307201a295aed815084000da0e0c2a057dc6ad403d587888b42ae4a478a5d4949603a5d5d70f3c51b9cc2fa1224d1783956c9b2330743e2e467
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [0.0.6] - 2026-11-03
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- `quic/ext.h` is ABI 1.1: a stream handler's new `recv_stop_sending` is called as soon as the peer's STOP_SENDING arrives, instead of the handler learning of it when the stream closes. Handlers built for ABI 1.0 keep working. ([#23](https://github.com/unasuke/quic-ruby/pull/23))
|
|
5
|
+
|
|
6
|
+
### Changed
|
|
7
|
+
- ngtcp2 is now built from 1.26.0 (was 1.25.0), and picotls from `a2d95de`, the revision ngtcp2 1.26.0 is tested against. ([#24](https://github.com/unasuke/quic-ruby/pull/24))
|
|
8
|
+
|
|
9
|
+
## [0.0.5] - 2026-10-24
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- When the peer sends STOP_SENDING on a stream, what was queued on it but not yet sent is dropped, and `QUIC::Stream#write` and `#write_nonblock` raise `QUIC::Error::StreamClosed` carrying the STOP_SENDING's error code, a `#write` waiting for the peer's window included. Before, every `#write_pkt` after it raised `QUIC::Error::StreamClosed` while the stream had bytes left to send, and a waiting `#write` returned only at the idle timeout. ([#21](https://github.com/unasuke/quic-ruby/pull/21))
|
|
13
|
+
- `#write_pkt` no longer raises `QUIC::Error::StreamClosed` after `QUIC::Stream#reset` on a stream with bytes queued; those bytes are dropped. ([#21](https://github.com/unasuke/quic-ruby/pull/21))
|
|
14
|
+
|
|
1
15
|
## [0.0.4] - 2026-10-22
|
|
2
16
|
|
|
3
17
|
### Added
|
data/LICENSE-DEPENDENCIES.txt
CHANGED
|
@@ -9,7 +9,7 @@ bundled nor redistributed. The quic gem itself is MIT licensed; see
|
|
|
9
9
|
LICENSE.txt.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
ngtcp2 1.
|
|
12
|
+
ngtcp2 1.26.0 -- https://github.com/ngtcp2/ngtcp2
|
|
13
13
|
-------------------------------------------------
|
|
14
14
|
|
|
15
15
|
The MIT License
|
|
@@ -53,7 +53,7 @@ the statically linked libngtcp2 and are covered by their own terms:
|
|
|
53
53
|
COPYING text above.
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
picotls
|
|
56
|
+
picotls a2d95de -- https://github.com/h2o/picotls
|
|
57
57
|
-------------------------------------------------
|
|
58
58
|
|
|
59
59
|
picotls has no top-level license file; the terms are stated in the header of
|
data/README.md
CHANGED
|
@@ -147,7 +147,7 @@ It applies to clients, `Connection::Server.accept` and every connection of an En
|
|
|
147
147
|
|
|
148
148
|
### From other native extensions
|
|
149
149
|
|
|
150
|
-
Another native extension can drive a connection's streams from C instead of through `QUIC::Stream`, as the [http3](https://github.com/unasuke/http3-ruby) gem does. `quic/ext.h` declares the function table (ABI 1.0), and `QUIC::EXT_INCLUDE_DIR` tells an `extconf.rb` where it is:
|
|
150
|
+
Another native extension can drive a connection's streams from C instead of through `QUIC::Stream`, as the [http3](https://github.com/unasuke/http3-ruby) gem does. `quic/ext.h` declares the function table (ABI 1.1; a stream handler built against ABI 1.0 keeps working), and `QUIC::EXT_INCLUDE_DIR` tells an `extconf.rb` where it is:
|
|
151
151
|
|
|
152
152
|
```ruby
|
|
153
153
|
require "mkmf"
|
|
@@ -508,7 +508,9 @@ static VALUE quic_acked_stream_data_offset_body(VALUE arg) {
|
|
|
508
508
|
/* Shift any head Chunks of @pending_chunks whose end-offset is fully
|
|
509
509
|
covered by acked_offset. Each Chunk's start-offset in the stream is
|
|
510
510
|
pending_shifted (after previous shifts); its end-offset is
|
|
511
|
-
pending_shifted + RSTRING_LEN(head).
|
|
511
|
+
pending_shifted + RSTRING_LEN(head). A stopped stream's
|
|
512
|
+
@pending_chunks is empty, and acks for what it sent before shift
|
|
513
|
+
nothing. */
|
|
512
514
|
VALUE pending = rb_ivar_get(stream, rb_intern("@pending_chunks"));
|
|
513
515
|
while (RARRAY_LEN(pending) > 0) {
|
|
514
516
|
VALUE head = RARRAY_AREF(pending, 0);
|
|
@@ -626,9 +628,59 @@ static int quic_stream_reset_cb(ngtcp2_conn *conn, int64_t stream_id,
|
|
|
626
628
|
return quic_conn_protect(c, quic_stream_reset_body, &a);
|
|
627
629
|
}
|
|
628
630
|
|
|
631
|
+
/* Nothing queued on stream can go out any more: drop it all, and have
|
|
632
|
+
#write_pkt skip the stream from now on. ngtcp2 retransmits no STREAM
|
|
633
|
+
frame of a stream it has reset, so it no longer reads the bytes it was
|
|
634
|
+
given either. */
|
|
635
|
+
static void quic_stream_stop(VALUE stream) {
|
|
636
|
+
quic_stream_t *s;
|
|
637
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
638
|
+
s->stopped = true;
|
|
639
|
+
rb_ary_clear(rb_ivar_get(stream, rb_intern("@pending_chunks")));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
static VALUE quic_recv_stop_sending_body(VALUE arg) {
|
|
643
|
+
struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
|
|
644
|
+
VALUE stream = quic_conn_lookup_stream(a->c, a->stream_id);
|
|
645
|
+
if (NIL_P(stream))
|
|
646
|
+
return Qnil;
|
|
647
|
+
|
|
648
|
+
quic_stream_t *s;
|
|
649
|
+
TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
|
|
650
|
+
s->stop_app_error_code = a->app_error_code;
|
|
651
|
+
quic_stream_stop(stream);
|
|
652
|
+
return Qnil;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/* The peer sent STOP_SENDING. ngtcp2 shuts the send side and sends
|
|
656
|
+
RESET_STREAM itself once this returns. A stream handler hears of it here
|
|
657
|
+
(recv_stop_sending). One built against ABI 1.0 lacks that member, and
|
|
658
|
+
learns of it only when #write_pkt finds the send side shut
|
|
659
|
+
(stream_shut_write) or the stream closes. */
|
|
660
|
+
static int quic_recv_stop_sending_cb(ngtcp2_conn *conn, int64_t stream_id,
|
|
661
|
+
uint64_t app_error_code, void *user_data,
|
|
662
|
+
void *stream_user_data) {
|
|
663
|
+
(void)conn;
|
|
664
|
+
(void)stream_user_data;
|
|
665
|
+
quic_conn_t *c = (quic_conn_t *)user_data;
|
|
666
|
+
if (c->handler) {
|
|
667
|
+
if (!QUIC_EXT_HANDLER_HAS(c->handler, recv_stop_sending))
|
|
668
|
+
return 0;
|
|
669
|
+
return c->handler->recv_stop_sending(c->handler_ud, stream_id,
|
|
670
|
+
app_error_code) < 0
|
|
671
|
+
? NGTCP2_ERR_CALLBACK_FAILURE
|
|
672
|
+
: 0;
|
|
673
|
+
}
|
|
674
|
+
struct quic_stream_cb_args a = {
|
|
675
|
+
.c = c, .stream_id = stream_id, .app_error_code = app_error_code};
|
|
676
|
+
return quic_conn_protect(c, quic_recv_stop_sending_body, &a);
|
|
677
|
+
}
|
|
678
|
+
|
|
629
679
|
/* The callbacks below matter only to a stream handler. Without one, the
|
|
630
680
|
QUIC::Stream mode needs none of them. */
|
|
631
681
|
|
|
682
|
+
/* This endpoint stopped reading the stream, and STOP_SENDING is going out.
|
|
683
|
+
Not to be confused with recv_stop_sending above. */
|
|
632
684
|
static int quic_stream_stop_sending_cb(ngtcp2_conn *conn, int64_t stream_id,
|
|
633
685
|
uint64_t app_error_code, void *user_data,
|
|
634
686
|
void *stream_user_data) {
|
|
@@ -701,6 +753,7 @@ void quic_conn_fill_common_callbacks(ngtcp2_callbacks *callbacks) {
|
|
|
701
753
|
callbacks->acked_stream_data_offset = quic_acked_stream_data_offset_cb;
|
|
702
754
|
callbacks->stream_close = quic_stream_close_cb;
|
|
703
755
|
callbacks->stream_reset = quic_stream_reset_cb;
|
|
756
|
+
callbacks->recv_stop_sending = quic_recv_stop_sending_cb;
|
|
704
757
|
callbacks->stream_stop_sending = quic_stream_stop_sending_cb;
|
|
705
758
|
callbacks->extend_max_stream_data = quic_extend_max_stream_data_cb;
|
|
706
759
|
callbacks->extend_max_remote_streams_bidi =
|
|
@@ -708,11 +761,12 @@ void quic_conn_fill_common_callbacks(ngtcp2_callbacks *callbacks) {
|
|
|
708
761
|
callbacks->handshake_completed = quic_handshake_completed_cb;
|
|
709
762
|
}
|
|
710
763
|
|
|
711
|
-
/* Find the first stream in @streams that has either
|
|
712
|
-
pending FIN to flush. Phase 4 minimum: linear scan,
|
|
713
|
-
(no round-robin). Sets *out_datav to the byte slice
|
|
714
|
-
to 0 or 1, *out_flags to
|
|
715
|
-
Returns the stream's Ruby
|
|
764
|
+
/* Find the first stream in @streams, stopped ones aside, that has either
|
|
765
|
+
pending bytes or a pending FIN to flush. Phase 4 minimum: linear scan,
|
|
766
|
+
take the first match (no round-robin). Sets *out_datav to the byte slice
|
|
767
|
+
to send, *out_datavcnt to 0 or 1, *out_flags to
|
|
768
|
+
NGTCP2_WRITE_STREAM_FLAG_FIN when applicable. Returns the stream's Ruby
|
|
769
|
+
VALUE (or Qnil if no candidate). */
|
|
716
770
|
static VALUE quic_conn_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
|
|
717
771
|
size_t *out_datavcnt,
|
|
718
772
|
uint32_t *out_flags,
|
|
@@ -724,7 +778,7 @@ static VALUE quic_conn_pick_send_stream(VALUE self, ngtcp2_vec *out_datav,
|
|
|
724
778
|
VALUE st = RARRAY_AREF(stream_values, i);
|
|
725
779
|
quic_stream_t *s;
|
|
726
780
|
TypedData_Get_Struct(st, quic_stream_t, &quic_stream_data_type, s);
|
|
727
|
-
if (s->fin_flushed)
|
|
781
|
+
if (s->fin_flushed || s->stopped)
|
|
728
782
|
continue;
|
|
729
783
|
|
|
730
784
|
VALUE pending = rb_ivar_get(st, rb_intern("@pending_chunks"));
|
|
@@ -794,11 +848,18 @@ static ngtcp2_ssize quic_conn_write_pkt_streams(VALUE self, quic_conn_t *c,
|
|
|
794
848
|
c->conn, path, pi, dest, destlen, &pdatalen, writev_flags, send_stream_id,
|
|
795
849
|
&datav, datavcnt, c->now);
|
|
796
850
|
|
|
797
|
-
if (n == NGTCP2_ERR_STREAM_DATA_BLOCKED
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
peer extends its window (MAX_STREAM_DATA / MAX_DATA).
|
|
851
|
+
if ((n == NGTCP2_ERR_STREAM_DATA_BLOCKED || n == NGTCP2_ERR_STREAM_SHUT_WR) &&
|
|
852
|
+
NIL_P(c->pending_exc)) {
|
|
853
|
+
/* DATA_BLOCKED: the stream's queued data is beyond what the peer's flow
|
|
854
|
+
control lets through right now. Keep it queued; it follows once the
|
|
855
|
+
peer extends its window (MAX_STREAM_DATA / MAX_DATA).
|
|
856
|
+
SHUT_WR: ngtcp2 shut the stream's send side without recv_stop_sending
|
|
857
|
+
marking the stream, as #reset does. What is queued can never go out,
|
|
858
|
+
so drop it.
|
|
859
|
+
Either way, build the packet without the stream's data, so that ACKs
|
|
860
|
+
and other frames still go out. */
|
|
861
|
+
if (n == NGTCP2_ERR_STREAM_SHUT_WR)
|
|
862
|
+
quic_stream_stop(selected_stream);
|
|
802
863
|
selected_stream = Qnil;
|
|
803
864
|
datavcnt = 0;
|
|
804
865
|
writev_flags = 0;
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
minimum destlen ngtcp2 accepts. Revisit when PMTUD is enabled (Phase 4+). */
|
|
15
15
|
#define QUIC_WRITE_PKT_BUFLEN NGTCP2_MAX_UDP_PAYLOAD_SIZE
|
|
16
16
|
|
|
17
|
+
/* Whether the stream handler h has member, judged by the size it was built
|
|
18
|
+
with: a handler built against an older quic/ext.h is shorter, and its
|
|
19
|
+
memory ends before the members added since. Compared with the end of the
|
|
20
|
+
member, not the start of the next one, so trailing padding does not
|
|
21
|
+
count. */
|
|
22
|
+
#define QUIC_EXT_HANDLER_HAS(h, member) \
|
|
23
|
+
((h)->size >= offsetof(quic_ext_stream_handler, member) + sizeof((h)->member))
|
|
24
|
+
|
|
17
25
|
/* A QUIC::Endpoint (endpoint.c). */
|
|
18
26
|
typedef struct quic_endpoint quic_endpoint_t;
|
|
19
27
|
|
data/ext/quic/ext_api.c
CHANGED
|
@@ -15,14 +15,16 @@ static quic_ext_conn *quic_ext_conn_get(VALUE conn) {
|
|
|
15
15
|
|
|
16
16
|
/* Hand every stream of the connection to h. Allowed once, and only before
|
|
17
17
|
the connection has a QUIC::Stream: the two modes cannot share streams.
|
|
18
|
-
Every member of the ABI 1.0 handler is called, so h must
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Every member of the ABI 1.0 handler is called, so h must hold them all
|
|
19
|
+
(handshake_completed is the last). A handler built against an older
|
|
20
|
+
quic/ext.h than this one is shorter than sizeof(quic_ext_stream_handler),
|
|
21
|
+
and is called only for the members its size covers. @stream_handler marks
|
|
22
|
+
the connection for the Ruby side, which then refuses #accept_stream. */
|
|
21
23
|
static int quic_ext_set_stream_handler(quic_ext_conn *ec,
|
|
22
24
|
const quic_ext_stream_handler *h,
|
|
23
25
|
void *ud, VALUE owner) {
|
|
24
26
|
quic_conn_t *c = quic_ext_conn_struct(ec);
|
|
25
|
-
if (h == NULL || h
|
|
27
|
+
if (h == NULL || !QUIC_EXT_HANDLER_HAS(h, handshake_completed))
|
|
26
28
|
return QUIC_EXT_ERR_OTHER;
|
|
27
29
|
if (c->handler != NULL)
|
|
28
30
|
return QUIC_EXT_ERR_INVALID_STATE;
|
|
@@ -93,7 +95,7 @@ static int quic_ext_set_application_error(quic_ext_conn *ec,
|
|
|
93
95
|
|
|
94
96
|
const quic_ext_api quic_ext_api_table = {
|
|
95
97
|
.abi_major = QUIC_EXT_ABI_MAJOR,
|
|
96
|
-
.abi_minor =
|
|
98
|
+
.abi_minor = QUIC_EXT_ABI_MINOR,
|
|
97
99
|
.size = sizeof(quic_ext_api),
|
|
98
100
|
.conn_get = quic_ext_conn_get,
|
|
99
101
|
.set_stream_handler = quic_ext_set_stream_handler,
|
data/ext/quic/extconf.rb
CHANGED
|
@@ -5,11 +5,11 @@ require "mini_portile2"
|
|
|
5
5
|
require "shellwords"
|
|
6
6
|
require "fileutils"
|
|
7
7
|
|
|
8
|
-
NGTCP2_VERSION = "1.
|
|
9
|
-
NGTCP2_SHA256 = "
|
|
10
|
-
# The picotls revision that ngtcp2 1.
|
|
11
|
-
PICOTLS_COMMIT = "
|
|
12
|
-
PICOTLS_SHA256 = "
|
|
8
|
+
NGTCP2_VERSION = "1.26.0"
|
|
9
|
+
NGTCP2_SHA256 = "85174d674e7f8fd911a827c6e483fd417b21b1f54d7853dac464cee446c8d35f"
|
|
10
|
+
# The picotls revision that ngtcp2 1.26.0 is tested against (see ngtcp2's README).
|
|
11
|
+
PICOTLS_COMMIT = "a2d95de4c875d6dcb27658e476e3b624f4381bf0"
|
|
12
|
+
PICOTLS_SHA256 = "d54263601c4652ef28451533bb2e2fae3f5f44aff54338cf5110b9c12c4bab3a"
|
|
13
13
|
# Bump when ext/quic/patches/picotls/ or PicotlsRecipe::SOURCES changes;
|
|
14
14
|
# installed? only checks for the archive.
|
|
15
15
|
PICOTLS_PATCH_LEVEL = "p2"
|
data/ext/quic/include/quic/ext.h
CHANGED
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
* the table's. The structs below grow only by appending, which bumps
|
|
15
15
|
* abi_minor; reordering, removing or retyping a member bumps abi_major.
|
|
16
16
|
* Check `size` before calling a member added after the version you were
|
|
17
|
-
* built against. The handler struct follows the same rules
|
|
17
|
+
* built against. The handler struct follows the same rules:
|
|
18
|
+
* set_stream_handler takes any handler at least as large as the ABI 1.0
|
|
19
|
+
* one, and quic.so calls a member added later only when the handler's
|
|
20
|
+
* `size` covers it. Every member `size` covers must be non-NULL.
|
|
21
|
+
* A member added after ABI 1.0 is marked with the minor that brought it
|
|
22
|
+
* (abi_minor N), and quic.so calls a handler member only when the table's
|
|
23
|
+
* abi_minor is at least that N: an older quic.so leaves it uncalled.
|
|
18
24
|
* - Exceptions: no function in the table and no handler function raises a
|
|
19
25
|
* Ruby exception; they return 0 or a negative error code. The one
|
|
20
26
|
* exception is conn_get, which raises TypeError for a wrong object and is
|
|
@@ -37,6 +43,8 @@
|
|
|
37
43
|
#include <stdint.h>
|
|
38
44
|
|
|
39
45
|
#define QUIC_EXT_ABI_MAJOR 1
|
|
46
|
+
/* The abi_minor of the structs as this header declares them. */
|
|
47
|
+
#define QUIC_EXT_ABI_MINOR 1
|
|
40
48
|
|
|
41
49
|
#define QUIC_EXT_ERR_INVALID_STATE (-1)
|
|
42
50
|
#define QUIC_EXT_ERR_STREAM_ID_BLOCKED (-2)
|
|
@@ -67,6 +75,19 @@ typedef struct {
|
|
|
67
75
|
int (*stream_close)(void *ud, int64_t id, int has_code,
|
|
68
76
|
uint64_t app_error_code);
|
|
69
77
|
int (*handshake_completed)(void *ud);
|
|
78
|
+
/* abi_minor 1 */
|
|
79
|
+
/* The peer sent STOP_SENDING on stream id, which this endpoint sends on
|
|
80
|
+
(stream_stop_sending is the other way: this endpoint stopped reading).
|
|
81
|
+
Called once per stream. Once it returns 0, ngtcp2 shuts the send side
|
|
82
|
+
and sends RESET_STREAM carrying app_error_code, unless all that was
|
|
83
|
+
sent, FIN included, has been acknowledged. next_stream_data should no
|
|
84
|
+
longer offer the stream; if it does, stream_shut_write follows. It can
|
|
85
|
+
come before any recv_stream_data, for a stream the peer has just
|
|
86
|
+
opened, and also comes for a unidirectional stream this endpoint
|
|
87
|
+
opened. stream_close may follow within the same #read_pkt. Calling
|
|
88
|
+
shutdown_stream_write here sends RESET_STREAM with that code instead,
|
|
89
|
+
but stream_close still reports app_error_code. */
|
|
90
|
+
int (*recv_stop_sending)(void *ud, int64_t id, uint64_t app_error_code);
|
|
70
91
|
} quic_ext_stream_handler;
|
|
71
92
|
|
|
72
93
|
typedef struct {
|
data/ext/quic/stream.c
CHANGED
|
@@ -45,6 +45,19 @@ quic_stream_new(int64_t stream_id, VALUE owner) {
|
|
|
45
45
|
return self;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/* Raise QUIC::Error::StreamClosed if nothing more can be written to the
|
|
49
|
+
stream: it was reset or has closed, or the peer sent STOP_SENDING. */
|
|
50
|
+
static void quic_stream_check_writable(quic_stream_t *s) {
|
|
51
|
+
if (s->reset || s->closed) {
|
|
52
|
+
rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
|
|
53
|
+
}
|
|
54
|
+
if (s->stopped) {
|
|
55
|
+
rb_raise(rb_eQUICErrorStreamClosed,
|
|
56
|
+
"the peer asked to stop sending (error code %llu)",
|
|
57
|
+
(unsigned long long)s->stop_app_error_code);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
/* Append a binary copy of |data| to @pending_chunks and optionally set the
|
|
49
62
|
fin_sent flag. Flow control is the caller's concern (#write blocks in
|
|
50
63
|
quic_stream_write_m and #write_nonblock returns a partial byte count in
|
|
@@ -55,9 +68,7 @@ static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
|
|
|
55
68
|
quic_stream_t *s;
|
|
56
69
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
rb_raise(rb_eQUICErrorStreamClosed, "stream is closed");
|
|
60
|
-
}
|
|
71
|
+
quic_stream_check_writable(s);
|
|
61
72
|
if (s->fin_sent) {
|
|
62
73
|
rb_raise(rb_eQUICErrorStreamClosed, "stream FIN already sent");
|
|
63
74
|
}
|
|
@@ -94,7 +105,9 @@ static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
|
|
|
94
105
|
per-stream and the per-connection flow control limits ngtcp2 tracks, less
|
|
95
106
|
the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
|
|
96
107
|
ngtcp2 only counts what it has sent, so without the subtraction a run of
|
|
97
|
-
writes could queue far more than the peer allows.
|
|
108
|
+
writes could queue far more than the peer allows. Callers check the
|
|
109
|
+
stream is writable first: a stopped stream's @pending_chunks no longer
|
|
110
|
+
matches its offsets. */
|
|
98
111
|
static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
|
|
99
112
|
quic_stream_t *s) {
|
|
100
113
|
ngtcp2_conn *conn = quic_conn_ptr(client_v);
|
|
@@ -117,8 +130,10 @@ static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
|
|
|
117
130
|
/* Blocking write: block by repeatedly invoking #pump_once on the owning
|
|
118
131
|
connection (@client) until the peer's flow control window has enough room
|
|
119
132
|
for the full payload, then enqueue. IO#write-compatible: always queues all
|
|
120
|
-
of `data`.
|
|
121
|
-
|
|
133
|
+
of `data`. A window that can no longer open, because the peer sent
|
|
134
|
+
STOP_SENDING or the stream ended meanwhile, raises
|
|
135
|
+
QUIC::Error::StreamClosed. Bare Streams (built via QUIC::Stream.allocate
|
|
136
|
+
for unit tests, with @client = nil) skip the window check entirely. */
|
|
122
137
|
static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
|
|
123
138
|
VALUE data;
|
|
124
139
|
bool fin = quic_stream_fin_kwarg(argc, argv, &data);
|
|
@@ -139,8 +154,10 @@ static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
|
|
|
139
154
|
/* Loop until the full payload would fit in the current window. pump_once
|
|
140
155
|
raises QUIC::Error::NotBound if the connection has not been #bind'ed;
|
|
141
156
|
that error surfaces verbatim. */
|
|
157
|
+
quic_stream_check_writable(s);
|
|
142
158
|
while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
|
|
143
159
|
rb_funcall(client_v, rb_intern("pump_once"), 0);
|
|
160
|
+
quic_stream_check_writable(s);
|
|
144
161
|
}
|
|
145
162
|
}
|
|
146
163
|
|
|
@@ -172,6 +189,8 @@ static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
|
|
|
172
189
|
|
|
173
190
|
quic_stream_t *s;
|
|
174
191
|
TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
|
|
192
|
+
/* Before the window: a stream that cannot be written to never gets one. */
|
|
193
|
+
quic_stream_check_writable(s);
|
|
175
194
|
uint64_t avail = quic_stream_window_left(self, client_v, s);
|
|
176
195
|
|
|
177
196
|
if (avail == 0) {
|
data/ext/quic/stream.h
CHANGED
|
@@ -29,6 +29,12 @@ typedef struct {
|
|
|
29
29
|
bool closed; /* stream_close callback fired */
|
|
30
30
|
uint64_t close_app_error_code;
|
|
31
31
|
bool close_has_app_error_code;
|
|
32
|
+
/* The send side is shut: the peer's STOP_SENDING arrived, or ngtcp2
|
|
33
|
+
reported the send side shut while writing. @pending_chunks has been
|
|
34
|
+
emptied, so the offsets above no longer describe it, and #write_pkt
|
|
35
|
+
skips the stream. */
|
|
36
|
+
bool stopped;
|
|
37
|
+
uint64_t stop_app_error_code; /* the STOP_SENDING's, 0 if none arrived */
|
|
32
38
|
/* Set once a peer-initiated stream has been pushed onto the owning
|
|
33
39
|
connection's @accept_queue, so recv_stream_data does not enqueue it
|
|
34
40
|
again on subsequent data arrivals. */
|
data/lib/quic/version.rb
CHANGED
data/sig/quic.rbs
CHANGED
|
@@ -323,15 +323,19 @@ module QUIC
|
|
|
323
323
|
# Server) until the peer's flow control window has room for the full
|
|
324
324
|
# payload, then queues all bytes and returns data.bytesize. Requires the
|
|
325
325
|
# owning connection to be bound to a socket via #bind; raises
|
|
326
|
-
# QUIC::Error::NotBound otherwise.
|
|
326
|
+
# QUIC::Error::NotBound otherwise. Raises QUIC::Error::StreamClosed,
|
|
327
|
+
# while waiting too, once the stream has been reset or closed, or the
|
|
328
|
+
# peer has sent STOP_SENDING. STOP_SENDING also drops what was queued
|
|
329
|
+
# but not yet sent.
|
|
327
330
|
def write: (String data, ?fin: bool) -> Integer
|
|
328
331
|
|
|
329
332
|
# Non-blocking variant. Queues as much of `data` as the current flow
|
|
330
333
|
# control window allows and returns the byte count actually queued.
|
|
331
334
|
# Raises QUIC::Error::WaitWritable (mixed in with IO::WaitWritable) when
|
|
332
|
-
# the window is zero and the payload is non-empty
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
+
# the window is zero and the payload is non-empty, and
|
|
336
|
+
# QUIC::Error::StreamClosed as #write does. If only a partial prefix is
|
|
337
|
+
# accepted, `fin: true` is NOT propagated; the caller must re-issue with
|
|
338
|
+
# `fin: true` on the trailing chunk.
|
|
335
339
|
def write_nonblock: (String data, ?fin: bool) -> Integer
|
|
336
340
|
def eof?: () -> bool
|
|
337
341
|
|