quic 0.0.5 → 0.0.7

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.
data/ext/quic/stream.c CHANGED
@@ -5,19 +5,44 @@
5
5
 
6
6
  VALUE rb_cQUICStream;
7
7
 
8
- static void quic_stream_free(void *ptr) { xfree(ptr); }
8
+ /* Free the whole send buffer. Safe to call more than once. */
9
+ static void quic_stream_free_send_buffer(quic_stream_t *s) {
10
+ quic_send_chunk *chunk = s->send_head;
11
+ while (chunk != NULL) {
12
+ quic_send_chunk *next = chunk->next;
13
+ xfree(chunk);
14
+ chunk = next;
15
+ }
16
+ s->send_head = NULL;
17
+ s->send_tail = NULL;
18
+ s->send_next = NULL;
19
+ s->send_next_off = 0;
20
+ s->buffered = 0;
21
+ }
22
+
23
+ /* Touches neither the connection nor any Ruby object: the connection may
24
+ have been freed first, in the same GC. */
25
+ static void quic_stream_free(void *ptr) {
26
+ quic_stream_free_send_buffer((quic_stream_t *)ptr);
27
+ xfree(ptr);
28
+ }
9
29
 
10
30
  static size_t quic_stream_size(const void *ptr) {
11
- (void)ptr;
12
- return sizeof(quic_stream_t);
31
+ return sizeof(quic_stream_t) + ((const quic_stream_t *)ptr)->buffered;
32
+ }
33
+
34
+ static void quic_stream_compact(void *ptr) {
35
+ quic_stream_t *s = (quic_stream_t *)ptr;
36
+ s->self = rb_gc_location(s->self);
13
37
  }
14
38
 
15
- const rb_data_type_t quic_stream_data_type = {
39
+ static const rb_data_type_t quic_stream_data_type = {
16
40
  "QUIC::Stream",
17
41
  {
18
42
  NULL,
19
43
  quic_stream_free,
20
44
  quic_stream_size,
45
+ quic_stream_compact,
21
46
  },
22
47
  NULL,
23
48
  NULL,
@@ -27,11 +52,16 @@ const rb_data_type_t quic_stream_data_type = {
27
52
  static VALUE quic_stream_alloc(VALUE klass) {
28
53
  quic_stream_t *s = ALLOC(quic_stream_t);
29
54
  memset(s, 0, sizeof(*s));
30
- return TypedData_Wrap_Struct(klass, &quic_stream_data_type, s);
55
+ VALUE self = TypedData_Wrap_Struct(klass, &quic_stream_data_type, s);
56
+ s->self = self;
57
+ return self;
31
58
  }
32
59
 
33
- VALUE
34
- quic_stream_new(int64_t stream_id, VALUE owner) {
60
+ /* Allocate a new QUIC::Stream Ruby object for stream_id with a
61
+ zero-initialized quic_stream_t, and set its ivars (@id / @client /
62
+ @recv_buffer). @client is owner, the Client or Server the stream belongs
63
+ to; the ivar keeps its name for both. */
64
+ static VALUE quic_stream_new(int64_t stream_id, VALUE owner) {
35
65
  VALUE self = quic_stream_alloc(rb_cQUICStream);
36
66
  quic_stream_t *s;
37
67
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
@@ -39,7 +69,6 @@ quic_stream_new(int64_t stream_id, VALUE owner) {
39
69
 
40
70
  rb_ivar_set(self, rb_intern("@id"), LL2NUM(stream_id));
41
71
  rb_ivar_set(self, rb_intern("@client"), owner);
42
- rb_ivar_set(self, rb_intern("@pending_chunks"), rb_ary_new());
43
72
  /* recv_buffer is a binary String; rb_str_buf_new returns ASCII-8BIT. */
44
73
  rb_ivar_set(self, rb_intern("@recv_buffer"), rb_str_buf_new(0));
45
74
  return self;
@@ -58,11 +87,11 @@ static void quic_stream_check_writable(quic_stream_t *s) {
58
87
  }
59
88
  }
60
89
 
61
- /* Append a binary copy of |data| to @pending_chunks and optionally set the
62
- fin_sent flag. Flow control is the caller's concern (#write blocks in
63
- quic_stream_write_m and #write_nonblock returns a partial byte count in
64
- quic_stream_write_nonblock_m). This helper is reached only after the
65
- caller has confirmed the bytes fit in the current send window, so it
90
+ /* Append a copy of |data| to the send buffer as one chunk, and optionally
91
+ set the fin_sent flag. Flow control is the caller's concern (#write
92
+ blocks in quic_stream_write_m and #write_nonblock returns a partial byte
93
+ count in quic_stream_write_nonblock_m). This helper is reached only after
94
+ the caller has confirmed the bytes fit in the current send window, so it
66
95
  never raises QUIC::Error::WaitWritable on its own. */
67
96
  static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
68
97
  quic_stream_t *s;
@@ -77,12 +106,31 @@ static VALUE quic_stream_enqueue(VALUE self, VALUE data, bool fin) {
77
106
  long len = RSTRING_LEN(data);
78
107
 
79
108
  if (len > 0) {
80
- VALUE chunk = rb_str_new(RSTRING_PTR(data), len);
81
- /* rb_str_new returns ASCII-8BIT, so no encoding adjustment is needed.
82
- Force it explicitly to be defensive against future CRuby changes. */
83
- rb_enc_associate_index(chunk, rb_ascii8bit_encindex());
84
- VALUE pending = rb_ivar_get(self, rb_intern("@pending_chunks"));
85
- rb_ary_push(pending, chunk);
109
+ /* A bare Stream has no connection to count its bytes against. */
110
+ VALUE client_v = rb_ivar_get(self, rb_intern("@client"));
111
+ quic_conn_t *c = NIL_P(client_v) ? NULL : quic_conn_get(client_v);
112
+
113
+ /* len is a long, so the header cannot overflow size_t. */
114
+ size_t size = offsetof(quic_send_chunk, data) + (size_t)len;
115
+ quic_send_chunk *chunk = (quic_send_chunk *)xmalloc(size);
116
+ chunk->next = NULL;
117
+ chunk->len = (size_t)len;
118
+ memcpy(chunk->data, RSTRING_PTR(data), (size_t)len);
119
+ if (s->send_tail != NULL) {
120
+ s->send_tail->next = chunk;
121
+ } else {
122
+ s->send_head = chunk;
123
+ s->head_offset = s->queued_offset;
124
+ }
125
+ s->send_tail = chunk;
126
+ if (s->send_next == NULL) {
127
+ s->send_next = chunk;
128
+ s->send_next_off = 0;
129
+ }
130
+ s->queued_offset += (uint64_t)len;
131
+ s->buffered += size;
132
+ if (c != NULL)
133
+ c->stream_unsent += (uint64_t)len;
86
134
  }
87
135
 
88
136
  if (fin) {
@@ -102,29 +150,365 @@ static bool quic_stream_fin_kwarg(int argc, VALUE *argv, VALUE *data) {
102
150
  }
103
151
 
104
152
  /* Return the effective send window for this stream: the smaller of the
105
- per-stream and the per-connection flow control limits ngtcp2 tracks, less
106
- the bytes already queued in @pending_chunks but not yet handed to ngtcp2.
107
- ngtcp2 only counts what it has sent, so without the subtraction a run of
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. */
111
- static uint64_t quic_stream_window_left(VALUE self, VALUE client_v,
112
- quic_stream_t *s) {
113
- ngtcp2_conn *conn = quic_conn_ptr(client_v);
153
+ per-stream and the per-connection flow control limits ngtcp2 tracks,
154
+ each less the bytes already queued against it but not yet handed to
155
+ ngtcp2 (this stream's, and every stream's of the connection). ngtcp2 only
156
+ counts what it has sent, so without the subtraction a run of writes could
157
+ queue far more than the peer allows. Callers check the stream is
158
+ writable first. */
159
+ static uint64_t quic_stream_window_left(VALUE client_v, quic_stream_t *s) {
160
+ quic_conn_t *c = quic_conn_get(client_v);
114
161
  uint64_t stream_left =
115
- ngtcp2_conn_get_max_stream_data_left2(conn, s->stream_id);
116
- uint64_t conn_left = ngtcp2_conn_get_max_data_left2(conn);
117
- uint64_t left = stream_left < conn_left ? stream_left : conn_left;
118
-
119
- /* @pending_chunks starts at stream offset pending_shifted, and the bytes
120
- up to sent_offset have been sent. */
121
- VALUE pending = rb_ivar_get(self, rb_intern("@pending_chunks"));
122
- uint64_t queued_end = s->pending_shifted;
123
- for (long i = 0; i < RARRAY_LEN(pending); i++) {
124
- queued_end += (uint64_t)RSTRING_LEN(RARRAY_AREF(pending, i));
162
+ ngtcp2_conn_get_max_stream_data_left2(c->conn, s->stream_id);
163
+ uint64_t conn_left = ngtcp2_conn_get_max_data_left2(c->conn);
164
+
165
+ uint64_t unsent = s->queued_offset - s->sent_offset;
166
+ stream_left = stream_left > unsent ? stream_left - unsent : 0;
167
+ conn_left = conn_left > c->stream_unsent ? conn_left - c->stream_unsent : 0;
168
+ return stream_left < conn_left ? stream_left : conn_left;
169
+ }
170
+
171
+ /* What s has to send next: the unsent part of the chunk at send_next in
172
+ *datav (*datavcnt = 1), or nothing but a FIN (*datavcnt = 0). *flags gets
173
+ NGTCP2_WRITE_STREAM_FLAG_FIN when the FIN goes with it, that is when the
174
+ chunk is the last one queued. Returns false when s has nothing to send,
175
+ which includes a stopped stream. */
176
+ static bool quic_stream_next_send(const quic_stream_t *s, ngtcp2_vec *datav,
177
+ size_t *datavcnt, uint32_t *flags) {
178
+ if (s->stopped || s->fin_flushed)
179
+ return false;
180
+
181
+ if (s->send_next != NULL) {
182
+ datav->base = s->send_next->data + s->send_next_off;
183
+ datav->len = s->send_next->len - s->send_next_off;
184
+ *datavcnt = 1;
185
+ /* FIN goes with the last chunk only if ngtcp2 frames all of it; when
186
+ it encodes less, FIN is not flushed and the next call retries. */
187
+ *flags = (s->fin_sent && s->send_next == s->send_tail)
188
+ ? NGTCP2_WRITE_STREAM_FLAG_FIN
189
+ : 0;
190
+ return true;
191
+ }
192
+
193
+ if (s->fin_sent) {
194
+ /* No data to send but a pending FIN-only frame. */
195
+ datav->base = NULL;
196
+ datav->len = 0;
197
+ *datavcnt = 0;
198
+ *flags = NGTCP2_WRITE_STREAM_FLAG_FIN;
199
+ return true;
200
+ }
201
+ return false;
202
+ }
203
+
204
+ /* Account for a packet ngtcp2_conn_writev_stream built for c from what
205
+ quic_stream_next_send offered: datalen bytes (0 for a FIN alone) with
206
+ flags, of which ngtcp2 framed pdatalen, in a packet of n bytes. */
207
+ static void quic_stream_sent(quic_conn_t *c, quic_stream_t *s, size_t datalen,
208
+ uint32_t flags, ngtcp2_ssize pdatalen,
209
+ ngtcp2_ssize n) {
210
+ if (pdatalen > 0) {
211
+ s->sent_offset += (uint64_t)pdatalen;
212
+ c->stream_unsent -= (uint64_t)pdatalen;
213
+ s->send_next_off += (size_t)pdatalen;
214
+ /* Leave send_next on no fully handed over chunk: an acknowledgement
215
+ of all that was sent would free it under us. */
216
+ if (s->send_next_off == s->send_next->len) {
217
+ s->send_next = s->send_next->next;
218
+ s->send_next_off = 0;
219
+ }
220
+ /* FIN is only actually written when all requested data fits in the
221
+ frame (see ngtcp2 docs on NGTCP2_WRITE_STREAM_FLAG_FIN). */
222
+ if ((flags & NGTCP2_WRITE_STREAM_FLAG_FIN) && (size_t)pdatalen == datalen)
223
+ s->fin_flushed = true;
224
+ } else if (datalen == 0 && n > 0 && (flags & NGTCP2_WRITE_STREAM_FLAG_FIN)) {
225
+ /* FIN-only packet (no data) was successfully written. */
226
+ s->fin_flushed = true;
227
+ }
228
+ }
229
+
230
+ /* Shut the send side of s, a stream of c (NULL for a bare Stream): nothing
231
+ it has queued goes out any more. Does nothing the second time. The send
232
+ buffer stays until the stream closes. On STOP_SENDING ngtcp2 calls
233
+ recv_stop_sending before it drops the frames it queued for the stream,
234
+ which still point into the buffer, and should it fail in between it keeps
235
+ them. */
236
+ static void quic_stream_stop(quic_conn_t *c, quic_stream_t *s) {
237
+ if (s->stopped)
238
+ return;
239
+ s->stopped = true;
240
+ if (c != NULL)
241
+ c->stream_unsent -= s->queued_offset - s->sent_offset;
242
+ }
243
+
244
+ /* Make a QUIC::Stream for stream_id, a stream ngtcp2 has just opened on the
245
+ connection owner (wrapping c), put it in @streams, and only then hand its
246
+ quic_stream_t to ngtcp2 as the stream's stream_user_data: ngtcp2 must
247
+ never point at a Stream that @streams does not keep alive (see
248
+ quic_conn_t.owner). Returns the Stream. */
249
+ static VALUE quic_stream_add(VALUE owner, quic_conn_t *c, int64_t stream_id) {
250
+ VALUE stream = quic_stream_new(stream_id, owner);
251
+ VALUE streams = rb_ivar_get(owner, rb_intern("@streams"));
252
+ rb_hash_aset(streams, LL2NUM(stream_id), stream);
253
+
254
+ quic_stream_t *s;
255
+ TypedData_Get_Struct(stream, quic_stream_t, &quic_stream_data_type, s);
256
+ int rv = ngtcp2_conn_set_stream_user_data(c->conn, stream_id, s);
257
+ if (rv != 0)
258
+ quic_raise_ngtcp2_error(rv);
259
+ return stream;
260
+ }
261
+
262
+ VALUE quic_stream_open_local(VALUE self, quic_conn_t *c, int64_t stream_id) {
263
+ return quic_stream_add(self, c, stream_id);
264
+ }
265
+
266
+ /* The callbacks that touch Ruby objects (on_open, on_recv_data and
267
+ on_close) do that in a *_body function run under rb_protect by
268
+ quic_conn_protect, so that an exception (a frozen @streams, an allocation
269
+ failure, ...) never unwinds through ngtcp2. The callback then returns
270
+ NGTCP2_ERR_CALLBACK_FAILURE and the exception is re-raised once ngtcp2
271
+ has returned. */
272
+
273
+ /* The arguments of a stream callback, handed to its body through
274
+ rb_protect's single VALUE. Each body reads only the fields it needs. */
275
+ struct quic_stream_cb_args {
276
+ quic_conn_t *c;
277
+ quic_stream_t *s;
278
+ int64_t stream_id;
279
+ uint32_t flags;
280
+ const uint8_t *data;
281
+ size_t datalen;
282
+ uint64_t app_error_code;
283
+ };
284
+
285
+ static VALUE quic_stream_on_open_body(VALUE arg) {
286
+ struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
287
+ quic_stream_add(a->c->owner, a->c, a->stream_id);
288
+ return Qnil;
289
+ }
290
+
291
+ /* Called only for a stream the peer opened. */
292
+ int quic_stream_on_open(quic_conn_t *c, int64_t stream_id) {
293
+ struct quic_stream_cb_args a = {.c = c, .stream_id = stream_id};
294
+ return quic_conn_protect(c, quic_stream_on_open_body, &a);
295
+ }
296
+
297
+ static VALUE quic_stream_on_recv_data_body(VALUE arg) {
298
+ struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
299
+ quic_conn_t *c = a->c;
300
+ quic_stream_t *s = a->s;
301
+ VALUE stream = s->self;
302
+
303
+ /* Surface peer-initiated streams to #accept_stream. The low stream-id bit
304
+ marks the initiator (0 == client, 1 == server), so a client queues the
305
+ odd ids and a server the even ones. Push once, on first data arrival,
306
+ then mark so later data does not re-enqueue. */
307
+ bool peer_initiated =
308
+ c->is_server ? !(s->stream_id & 0x01) : (s->stream_id & 0x01);
309
+ if (!s->accept_queued && peer_initiated) {
310
+ VALUE accept_queue = rb_ivar_get(c->owner, rb_intern("@accept_queue"));
311
+ rb_ary_push(accept_queue, stream);
312
+ s->accept_queued = true;
313
+ }
314
+
315
+ if (a->datalen > 0) {
316
+ VALUE recv_buffer = rb_ivar_get(stream, rb_intern("@recv_buffer"));
317
+ rb_str_buf_cat(recv_buffer, (const char *)a->data, (long)a->datalen);
318
+ }
319
+ if (a->flags & NGTCP2_STREAM_DATA_FLAG_FIN) {
320
+ s->fin_received = true;
321
+ }
322
+ return Qnil;
323
+ }
324
+
325
+ int quic_stream_on_recv_data(quic_conn_t *c, quic_stream_t *s, uint32_t flags,
326
+ const uint8_t *data, size_t datalen) {
327
+ if (s == NULL)
328
+ return 0;
329
+ struct quic_stream_cb_args a = {
330
+ .c = c,
331
+ .s = s,
332
+ .flags = flags,
333
+ .data = data,
334
+ .datalen = datalen,
335
+ };
336
+ return quic_conn_protect(c, quic_stream_on_recv_data_body, &a);
337
+ }
338
+
339
+ /* The peer acknowledged [offset, offset + datalen): free the chunks that
340
+ end at or before offset + datalen. */
341
+ void quic_stream_on_acked(quic_stream_t *s, uint64_t offset, uint64_t datalen) {
342
+ if (s == NULL)
343
+ return;
344
+ /* ngtcp2 reports the acknowledged ranges of a stream in order and
345
+ without overlap; datalen is 0 only for a FIN alone. */
346
+ s->acked_offset = offset + datalen;
347
+ while (s->send_head != NULL &&
348
+ s->head_offset + s->send_head->len <= s->acked_offset) {
349
+ quic_send_chunk *chunk = s->send_head;
350
+ s->head_offset += chunk->len;
351
+ s->send_head = chunk->next;
352
+ if (s->send_head == NULL)
353
+ s->send_tail = NULL;
354
+ s->buffered -= offsetof(quic_send_chunk, data) + chunk->len;
355
+ xfree(chunk);
356
+ }
357
+ }
358
+
359
+ static VALUE quic_stream_on_close_body(VALUE arg) {
360
+ struct quic_stream_cb_args *a = (struct quic_stream_cb_args *)arg;
361
+ quic_stream_t *s = a->s;
362
+ s->closed = true;
363
+ if (a->flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
364
+ s->close_has_app_error_code = true;
365
+ s->close_app_error_code = a->app_error_code;
366
+ }
367
+ /* Usually stopped already, or with nothing left unsent. */
368
+ quic_stream_stop(a->c, s);
369
+ /* ngtcp2 never reads the stream's data again. */
370
+ quic_stream_free_send_buffer(s);
371
+ /* A stream still waiting in @accept_queue holds on to what it received.
372
+ Were the peer given its stream back now, it could open and close
373
+ streams until the queue had used up our memory, so it gets the stream
374
+ back only once the application has accepted this one
375
+ (QUIC::Stream#_accepted). */
376
+ if (s->accept_queued && !s->accepted) {
377
+ s->peer_stream_held = true;
378
+ } else {
379
+ quic_conn_give_back_peer_stream(a->c->conn, a->stream_id);
125
380
  }
126
- uint64_t unsent = queued_end - s->sent_offset;
127
- return left > unsent ? left - unsent : 0;
381
+ /* Last, and nothing that can fail after it: should this callback fail,
382
+ ngtcp2 keeps the stream, and the Stream must stay in @streams. */
383
+ VALUE streams = rb_ivar_get(a->c->owner, rb_intern("@streams"));
384
+ rb_hash_delete(streams, LL2NUM(a->stream_id));
385
+ return Qnil;
386
+ }
387
+
388
+ int quic_stream_on_close(quic_conn_t *c, quic_stream_t *s, int64_t stream_id,
389
+ uint32_t flags, uint64_t app_error_code) {
390
+ if (s == NULL) {
391
+ quic_conn_give_back_peer_stream(c->conn, stream_id);
392
+ return 0;
393
+ }
394
+ struct quic_stream_cb_args a = {
395
+ .c = c,
396
+ .s = s,
397
+ .stream_id = stream_id,
398
+ .flags = flags,
399
+ .app_error_code = app_error_code,
400
+ };
401
+ return quic_conn_protect(c, quic_stream_on_close_body, &a);
402
+ }
403
+
404
+ void quic_stream_on_reset(quic_stream_t *s, uint64_t app_error_code) {
405
+ if (s == NULL)
406
+ return;
407
+ s->reset = true;
408
+ s->close_app_error_code = app_error_code;
409
+ s->close_has_app_error_code = true;
410
+ }
411
+
412
+ void quic_stream_on_recv_stop_sending(quic_conn_t *c, quic_stream_t *s,
413
+ uint64_t app_error_code) {
414
+ if (s == NULL)
415
+ return;
416
+ s->stop_app_error_code = app_error_code;
417
+ quic_stream_stop(c, s);
418
+ }
419
+
420
+ struct quic_pick_send_args {
421
+ quic_stream_t *picked;
422
+ ngtcp2_vec *datav;
423
+ size_t *datavcnt;
424
+ uint32_t *flags;
425
+ };
426
+
427
+ static int quic_streams_pick_send_i(VALUE key, VALUE value, VALUE arg) {
428
+ (void)key;
429
+ struct quic_pick_send_args *a = (struct quic_pick_send_args *)arg;
430
+ if (!rb_typeddata_is_kind_of(value, &quic_stream_data_type))
431
+ return ST_CONTINUE;
432
+ quic_stream_t *s = (quic_stream_t *)RTYPEDDATA_DATA(value);
433
+ if (!quic_stream_next_send(s, a->datav, a->datavcnt, a->flags))
434
+ return ST_CONTINUE;
435
+ a->picked = s;
436
+ return ST_STOP;
437
+ }
438
+
439
+ /* Find the first stream in @streams that has either pending bytes or a
440
+ pending FIN to flush (linear scan, first match, no round-robin), and
441
+ what it has to send (quic_stream_next_send). Returns NULL if no stream
442
+ has anything. Raises nothing, so that #write_pkt fails only for what
443
+ ngtcp2 reports: a value that is not a QUIC::Stream is skipped, and an
444
+ @streams that is not a Hash holds no stream (only a test puts either
445
+ there). rb_hash_foreach raises only when the Hash changes while it
446
+ iterates, which the callback never does. */
447
+ static quic_stream_t *quic_streams_pick_send(VALUE self, ngtcp2_vec *out_datav,
448
+ size_t *out_datavcnt,
449
+ uint32_t *out_flags) {
450
+ VALUE streams = rb_ivar_get(self, rb_intern("@streams"));
451
+ if (!RB_TYPE_P(streams, T_HASH))
452
+ return NULL;
453
+
454
+ struct quic_pick_send_args a = {
455
+ .picked = NULL,
456
+ .datav = out_datav,
457
+ .datavcnt = out_datavcnt,
458
+ .flags = out_flags,
459
+ };
460
+ rb_hash_foreach(streams, quic_streams_pick_send_i, (VALUE)&a);
461
+ return a.picked;
462
+ }
463
+
464
+ /* If any stream has pending bytes or a pending FIN, route the packet
465
+ through ngtcp2_conn_writev_stream so the stream data gets framed
466
+ alongside connection-level frames. Falls back to conn-only when no stream
467
+ is queued. */
468
+ ngtcp2_ssize quic_streams_write_pkt(VALUE self, quic_conn_t *c,
469
+ ngtcp2_path *path, ngtcp2_pkt_info *pi,
470
+ uint8_t *dest, size_t destlen) {
471
+ ngtcp2_vec datav;
472
+ size_t datavcnt = 0;
473
+ uint32_t writev_flags = 0;
474
+ quic_stream_t *selected =
475
+ quic_streams_pick_send(self, &datav, &datavcnt, &writev_flags);
476
+
477
+ ngtcp2_ssize pdatalen = -1;
478
+ ngtcp2_ssize n = ngtcp2_conn_writev_stream(
479
+ c->conn, path, pi, dest, destlen, &pdatalen, writev_flags,
480
+ selected != NULL ? selected->stream_id : -1, &datav, datavcnt, c->now);
481
+
482
+ if ((n == NGTCP2_ERR_STREAM_DATA_BLOCKED || n == NGTCP2_ERR_STREAM_SHUT_WR) &&
483
+ NIL_P(c->pending_exc)) {
484
+ /* DATA_BLOCKED: the stream's queued data is beyond what the peer's flow
485
+ control lets through right now. Keep it queued; it follows once the
486
+ peer extends its window (MAX_STREAM_DATA / MAX_DATA).
487
+ SHUT_WR: ngtcp2 shut the stream's send side, and neither
488
+ recv_stop_sending nor #reset stopped the stream. What is queued can
489
+ never go out, so stop it now.
490
+ Either way, build the packet without the stream's data, so that ACKs
491
+ and other frames still go out. */
492
+ if (n == NGTCP2_ERR_STREAM_SHUT_WR)
493
+ quic_stream_stop(c, selected);
494
+ selected = NULL;
495
+ datavcnt = 0;
496
+ writev_flags = 0;
497
+ pdatalen = -1;
498
+ n = ngtcp2_conn_writev_stream(c->conn, path, pi, dest, destlen, &pdatalen,
499
+ 0, -1, NULL, 0, c->now);
500
+ }
501
+
502
+ if (n < 0 || !NIL_P(c->pending_exc))
503
+ return n;
504
+
505
+ /* pdatalen >= 0 means ngtcp2 framed that many data bytes into the packet;
506
+ if 0, no data was framed (but the packet may still contain ACK or other
507
+ frames). */
508
+ if (selected != NULL)
509
+ quic_stream_sent(c, selected, datavcnt > 0 ? datav.len : 0, writev_flags,
510
+ pdatalen, n);
511
+ return n;
128
512
  }
129
513
 
130
514
  /* Blocking write: block by repeatedly invoking #pump_once on the owning
@@ -155,7 +539,7 @@ static VALUE quic_stream_write_m(int argc, VALUE *argv, VALUE self) {
155
539
  raises QUIC::Error::NotBound if the connection has not been #bind'ed;
156
540
  that error surfaces verbatim. */
157
541
  quic_stream_check_writable(s);
158
- while ((uint64_t)needed > quic_stream_window_left(self, client_v, s)) {
542
+ while ((uint64_t)needed > quic_stream_window_left(client_v, s)) {
159
543
  rb_funcall(client_v, rb_intern("pump_once"), 0);
160
544
  quic_stream_check_writable(s);
161
545
  }
@@ -191,7 +575,7 @@ static VALUE quic_stream_write_nonblock_m(int argc, VALUE *argv, VALUE self) {
191
575
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);
192
576
  /* Before the window: a stream that cannot be written to never gets one. */
193
577
  quic_stream_check_writable(s);
194
- uint64_t avail = quic_stream_window_left(self, client_v, s);
578
+ uint64_t avail = quic_stream_window_left(client_v, s);
195
579
 
196
580
  if (avail == 0) {
197
581
  rb_raise(rb_eQUICErrorWaitWritable, "stream send window is full");
@@ -336,17 +720,22 @@ static VALUE quic_stream_reset_m(int argc, VALUE *argv, VALUE self) {
336
720
  return Qnil;
337
721
  }
338
722
 
339
- ngtcp2_conn *conn = quic_conn_ptr(client_v);
340
- int rv = ngtcp2_conn_shutdown_stream_write(conn, 0, s->stream_id, error_code);
723
+ quic_conn_t *c = quic_conn_get(client_v);
724
+ int rv =
725
+ ngtcp2_conn_shutdown_stream_write(c->conn, 0, s->stream_id, error_code);
341
726
  if (rv != 0)
342
727
  quic_raise_ngtcp2_error(rv);
343
728
  s->reset = true;
729
+ /* Now, not at the next #write_pkt: until then, what it had queued would
730
+ keep the other streams' #write_nonblock out of the window. */
731
+ quic_stream_stop(c, s);
344
732
  return Qnil;
345
733
  }
346
734
 
347
735
  /* Called by #accept_stream / #accept_stream_nonblock for the stream they
348
736
  take off @accept_queue. A stream that closed while it waited there gives
349
- the peer back the stream it used up now (see quic_stream_close_body). */
737
+ the peer back the stream it used up now (see
738
+ quic_stream_on_close_body). */
350
739
  static VALUE quic_stream_accepted_m(VALUE self) {
351
740
  quic_stream_t *s;
352
741
  TypedData_Get_Struct(self, quic_stream_t, &quic_stream_data_type, s);