@scriptc/runtime 0.0.11 → 0.0.13
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.
- package/package.json +1 -1
- package/src/scr_assert.c +13 -13
- package/src/scr_async.c +53 -8
- package/src/scr_async_dyn.c +163 -47
- package/src/scr_bytes_io.c +5 -5
- package/src/scr_dc.c +8 -8
- package/src/scr_dgram.c +1 -1
- package/src/scr_dyn_handle.c +5 -5
- package/src/scr_dyn_invoke.c +42 -18
- package/src/scr_error.c +3 -3
- package/src/scr_events_emitter.c +1 -1
- package/src/scr_http.c +1080 -34
- package/src/scr_http2.c +53 -3
- package/src/scr_inspect.c +3 -3
- package/src/scr_island.c +134 -25
- package/src/scr_json.c +124 -68
- package/src/scr_net.c +254 -35
- package/src/scr_qs.c +1 -1
- package/src/scr_runtime.h +278 -124
- package/src/scr_stream.c +4 -4
- package/src/scr_tls.c +42 -5
- package/src/scr_tls_ca.c +288 -0
- package/src/scr_web.c +1 -1
package/src/scr_net.c
CHANGED
|
@@ -464,12 +464,34 @@ struct ScrNetSocket {
|
|
|
464
464
|
* dial_ips[dial_i]; the LAST failure's message surfaces (Node's
|
|
465
465
|
* autoSelectFamily aggregate is the documented divergence) */
|
|
466
466
|
bool lookup_wait;
|
|
467
|
+
/* a deferred dial (the http agent's maxSockets queue): registered and
|
|
468
|
+
* "connecting" (writes buffer), the actual dial starts on
|
|
469
|
+
* scr_net_sock_dial_start */
|
|
470
|
+
bool dial_deferred;
|
|
467
471
|
ScrStr **dial_ips;
|
|
468
472
|
size_t dial_n, dial_i;
|
|
469
473
|
/* the in-flight dial chain's LAST failure — surfaced (moved into
|
|
470
474
|
* pending_err) only when the list exhausts; a later success drops it */
|
|
471
475
|
ScrStr *dial_err;
|
|
472
476
|
ScrNetLs data_ls, end_ls, close_ls, err_ls, conn_ls, timeout_ls, readable_ls;
|
|
477
|
+
/* Flow control (pause()/resume()): user_paused holds reads OFF even
|
|
478
|
+
* with consumers (kernel/TCP backpressure is the buffer); flowing
|
|
479
|
+
* counts as a consumer even with no 'data' listener
|
|
480
|
+
* (resume()'s flow-and-discard drain, how Node reaches 'end' on an
|
|
481
|
+
* unconsumed stream). resume() clears user_paused and sets flowing. */
|
|
482
|
+
bool user_paused;
|
|
483
|
+
bool flowing;
|
|
484
|
+
/* destroySoon(): end() now, destroy once the FIN actually went out */
|
|
485
|
+
bool destroy_on_finish;
|
|
486
|
+
/* socket.bytesWritten: every byte accepted by the write paths (user
|
|
487
|
+
* writes, protocol heads, pipe deliveries — plaintext on TLS sockets) */
|
|
488
|
+
size_t bytes_written;
|
|
489
|
+
/* end(callback)/write(chunk, callback): 'finish'-shaped callbacks fire
|
|
490
|
+
* from the SWEEP (never the registering stack — Node defers them too).
|
|
491
|
+
* finish_ls waits for the FIN (wr_done); wcb_ls waits for the write
|
|
492
|
+
* buffer to drain (this surface's flush moment). */
|
|
493
|
+
ScrNetLs finish_ls, wcb_ls;
|
|
494
|
+
bool finish_pending; /* wr_done seen: fire finish_ls at the next sweep */
|
|
473
495
|
/* a server whose 'connection' waits for this socket's handshake — the
|
|
474
496
|
* TLS defer target (+1; the socket's own `server` backref stays the
|
|
475
497
|
* ACCEPTING server, which may be the demux wrapper) */
|
|
@@ -564,6 +586,8 @@ void scr_net_sock_release(ScrNetSocket *s) {
|
|
|
564
586
|
scr_net_ls_drop(&s->conn_ls);
|
|
565
587
|
scr_net_ls_drop(&s->timeout_ls);
|
|
566
588
|
scr_net_ls_drop(&s->readable_ls);
|
|
589
|
+
scr_net_ls_drop(&s->finish_ls);
|
|
590
|
+
scr_net_ls_drop(&s->wcb_ls);
|
|
567
591
|
scr_str_release(s->pending_err);
|
|
568
592
|
scr_str_release(s->remote_cache);
|
|
569
593
|
for (size_t i = 0; i < s->dial_n; i++) scr_str_release(s->dial_ips[i]);
|
|
@@ -751,8 +775,9 @@ static ScrNetSocket *scr_net_sock_new(void) {
|
|
|
751
775
|
/* Consumer-driven read arming (the stdin discipline: never read a byte
|
|
752
776
|
* nobody consumes — the socket buffer is the backpressure). */
|
|
753
777
|
static void scr_net_sock_update_read(ScrNetSocket *s) {
|
|
754
|
-
bool want = s->fd >= 0 && !s->connecting && !s->rd_eof &&
|
|
778
|
+
bool want = s->fd >= 0 && !s->connecting && !s->rd_eof && !s->user_paused &&
|
|
755
779
|
(s->data_ls.n > 0 || s->pipe_dst != NULL || s->native_data != NULL ||
|
|
780
|
+
s->flowing /* resume(): flow (and discard sans listeners) */ ||
|
|
756
781
|
s->readable_ls.n > 0 /* paused-mode consumer: bytes buffer */ ||
|
|
757
782
|
s->tops != NULL /* a transport is ALWAYS a consumer: the TLS
|
|
758
783
|
* layer must process protocol frames (the
|
|
@@ -804,6 +829,13 @@ static void scr_net_sock_maybe_finish_write(ScrNetSocket *s) {
|
|
|
804
829
|
if (s->tops && s->t_est) s->tops->shutdown_write(s->tctx);
|
|
805
830
|
shutdown(s->fd, SHUT_WR);
|
|
806
831
|
s->wr_done = true;
|
|
832
|
+
if (s->finish_ls.n > 0) s->finish_pending = true; /* end(cb): the sweep fires it */
|
|
833
|
+
if (s->destroy_on_finish) {
|
|
834
|
+
/* destroySoon(): the FIN is out — tear down now (Node's 'finish'-then-
|
|
835
|
+
* destroy). The close emits through the ordinary sweep. */
|
|
836
|
+
scr_net_sock_close_fd(s);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
807
839
|
if (s->rd_eof) scr_net_sock_close_fd(s);
|
|
808
840
|
else scr_net_sock_update_read(s); /* the drain-to-EOF mode arms here */
|
|
809
841
|
}
|
|
@@ -833,8 +865,15 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) {
|
|
|
833
865
|
* dead or ended write half (Node fires 'error' there; this slice drops —
|
|
834
866
|
* SEMANTICS.md documents the bound). */
|
|
835
867
|
static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len) {
|
|
836
|
-
|
|
837
|
-
|
|
868
|
+
/* A socket whose dial hasn't STARTED yet (the agent's deferred dial,
|
|
869
|
+
* the caller-lookup wait) has no fd but is logically connecting —
|
|
870
|
+
* bytes buffer and flush at establishment like any mid-connect write. */
|
|
871
|
+
bool pre_dial = s->fd < 0 && (s->dial_deferred || s->lookup_wait) &&
|
|
872
|
+
!s->wr_ending && !s->close_emitted;
|
|
873
|
+
if (!pre_dial && (s->fd < 0 || s->wr_ending || s->close_emitted)) return;
|
|
874
|
+
if (len == 0) return;
|
|
875
|
+
s->bytes_written += len; /* accepted bytes (Node counts buffered ones too) */
|
|
876
|
+
if (pre_dial || s->connecting || s->wlen > s->whead || (s->tops && !s->t_est)) {
|
|
838
877
|
/* mid-connect and mid-handshake bytes buffer; the flush after
|
|
839
878
|
* 'connect'/establishment sends them */
|
|
840
879
|
scr_net_sock_buffer(s, data, len);
|
|
@@ -959,8 +998,8 @@ static void scr_net_sock_transport_pump(ScrNetSocket *s);
|
|
|
959
998
|
static void scr_net_sock_read(ScrNetSocket *s) {
|
|
960
999
|
if (s->tops && !s->t_est) return; /* readiness feeds the handshake pump instead */
|
|
961
1000
|
for (;;) {
|
|
962
|
-
if (s->fd < 0) return;
|
|
963
|
-
bool flowing = s->data_ls.n > 0 || s->pipe_dst || s->native_data;
|
|
1001
|
+
if (s->fd < 0 || s->user_paused) return;
|
|
1002
|
+
bool flowing = s->data_ls.n > 0 || s->pipe_dst || s->native_data || s->flowing;
|
|
964
1003
|
/* buffered (peeked/unshifted) bytes first — they precede the
|
|
965
1004
|
* kernel's in the stream; a TLS engine drains them through its bio */
|
|
966
1005
|
if (!s->tops && flowing && s->rlen > s->rhead) {
|
|
@@ -1532,18 +1571,13 @@ ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/) {
|
|
|
1532
1571
|
return scr_str_new(ip, strlen(ip));
|
|
1533
1572
|
}
|
|
1534
1573
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h);
|
|
1543
|
-
if (!scr_net_poller_init()) {
|
|
1544
|
-
fputs("scriptc: event poller init failed\n", stderr);
|
|
1545
|
-
abort();
|
|
1546
|
-
}
|
|
1574
|
+
/* The dial itself (peer_ip/peer_port already set): the sockaddr arms,
|
|
1575
|
+
* socket(), and the nonblocking connect(). Shared by the immediate dial
|
|
1576
|
+
* (scr_net_connect) and the DEFERRED one (the http agent's maxSockets
|
|
1577
|
+
* queue — the socket exists, buffers writes, and dials when a slot
|
|
1578
|
+
* frees). */
|
|
1579
|
+
static void scr_net_sock_dial_peer(ScrNetSocket *s) {
|
|
1580
|
+
const char *h = s->peer_ip;
|
|
1547
1581
|
struct sockaddr_in a4;
|
|
1548
1582
|
struct sockaddr_in6 a6;
|
|
1549
1583
|
struct sockaddr *sa = NULL;
|
|
@@ -1569,8 +1603,8 @@ ScrNetSocket *scr_net_connect(double port, ScrStr *host /*borrowed, nullable*/,
|
|
|
1569
1603
|
s->pending_err = scr_str_new(msg, strlen(msg));
|
|
1570
1604
|
s->had_error = true;
|
|
1571
1605
|
s->emit_close = true;
|
|
1572
|
-
|
|
1573
|
-
return
|
|
1606
|
+
s->connecting = false;
|
|
1607
|
+
return;
|
|
1574
1608
|
}
|
|
1575
1609
|
int fd = socket(sa->sa_family, SOCK_STREAM, 0);
|
|
1576
1610
|
if (fd < 0) {
|
|
@@ -1596,14 +1630,30 @@ ScrNetSocket *scr_net_connect(double port, ScrStr *host /*borrowed, nullable*/,
|
|
|
1596
1630
|
s->peer_port);
|
|
1597
1631
|
s->pending_err = scr_str_new(msg, strlen(msg));
|
|
1598
1632
|
s->had_error = true;
|
|
1633
|
+
s->connecting = false;
|
|
1599
1634
|
scr_net_sock_close_fd(s);
|
|
1600
1635
|
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
ScrNetSocket *scr_net_connect(double port, ScrStr *host /*borrowed, nullable*/,
|
|
1639
|
+
ScrClosure *cb /*moves, nullable*/) {
|
|
1640
|
+
ScrNetSocket *s = scr_net_sock_new();
|
|
1641
|
+
if (cb) scr_net_ls_add(&s->conn_ls, cb, NULL, true);
|
|
1642
|
+
s->peer_port = (int)port;
|
|
1643
|
+
const char *h = host && host->len > 0 ? host->data : "localhost";
|
|
1644
|
+
if (strcmp(h, "localhost") == 0) h = "127.0.0.1";
|
|
1645
|
+
snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h);
|
|
1646
|
+
if (!scr_net_poller_init()) {
|
|
1647
|
+
fputs("scriptc: event poller init failed\n", stderr);
|
|
1648
|
+
abort();
|
|
1649
|
+
}
|
|
1650
|
+
scr_net_sock_dial_peer(s);
|
|
1601
1651
|
scr_net_sock_register(s);
|
|
1602
1652
|
return s;
|
|
1603
1653
|
}
|
|
1604
1654
|
|
|
1605
1655
|
/* ── the connect option-bag validation ladders (checked-dynamic lane) ──
|
|
1606
|
-
* Node-order validation over
|
|
1656
|
+
* Node-order validation over dyn option values with Node's exact typed
|
|
1607
1657
|
* errors; the honest tail (connect for the validated forms, the
|
|
1608
1658
|
* compiler-rendered fence for bags with unmodeled keys) runs only after
|
|
1609
1659
|
* every validation passes. */
|
|
@@ -1713,6 +1763,31 @@ void scr_net_connect_opts_chk(const ScrDyn *opts, const ScrStr *fence) {
|
|
|
1713
1763
|
scr_throw_lowering_fence(fence);
|
|
1714
1764
|
}
|
|
1715
1765
|
|
|
1766
|
+
/* A dial the caller starts LATER (the http agent's maxSockets queue):
|
|
1767
|
+
* the socket exists, registers, and buffers writes as "connecting" —
|
|
1768
|
+
* scr_net_sock_dial_start runs the actual dial when a slot frees. */
|
|
1769
|
+
ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nullable*/) {
|
|
1770
|
+
ScrNetSocket *s = scr_net_sock_new();
|
|
1771
|
+
s->peer_port = (int)port;
|
|
1772
|
+
const char *h = host && host->len > 0 ? host->data : "localhost";
|
|
1773
|
+
if (strcmp(h, "localhost") == 0) h = "127.0.0.1";
|
|
1774
|
+
snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h);
|
|
1775
|
+
if (!scr_net_poller_init()) {
|
|
1776
|
+
fputs("scriptc: event poller init failed\n", stderr);
|
|
1777
|
+
abort();
|
|
1778
|
+
}
|
|
1779
|
+
s->connecting = true; /* logically connecting while the dial waits */
|
|
1780
|
+
s->dial_deferred = true;
|
|
1781
|
+
scr_net_sock_register(s);
|
|
1782
|
+
return s;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
void scr_net_sock_dial_start(ScrNetSocket *s) {
|
|
1786
|
+
if (!s->dial_deferred || s->close_emitted || s->emit_close || s->fd >= 0) return;
|
|
1787
|
+
s->dial_deferred = false;
|
|
1788
|
+
scr_net_sock_dial_peer(s);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1716
1791
|
/* ── the caller-lookup dial (net.connect with a lookup option) ─────────
|
|
1717
1792
|
*
|
|
1718
1793
|
* portless's createLoopbackConnection: connect({ host, port,
|
|
@@ -1891,6 +1966,78 @@ void scr_net_sock_end_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) {
|
|
|
1891
1966
|
scr_net_sock_end(s);
|
|
1892
1967
|
}
|
|
1893
1968
|
|
|
1969
|
+
/* socket.pause(): reads stay off — the kernel buffer (and TCP flow
|
|
1970
|
+
* control) holds arrived bytes. Delivery restarts on
|
|
1971
|
+
* resume(), always from the poller/sweep, never this stack. Answers the
|
|
1972
|
+
* socket (+1) — Node's chaining. */
|
|
1973
|
+
ScrNetSocket *scr_net_sock_pause(ScrNetSocket *s) {
|
|
1974
|
+
s->user_paused = true;
|
|
1975
|
+
scr_net_sock_update_read(s);
|
|
1976
|
+
return scr_net_sock_retain(s);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
/* socket.resume(): flowing mode — a consumer even with no 'data'
|
|
1980
|
+
* listener (arrived bytes discard, so 'end' can be reached, Node's
|
|
1981
|
+
* resumed-but-unconsumed stream). Buffered bytes deliver from the next
|
|
1982
|
+
* sweep (flags_pending sees the flowing consumer), not this stack.
|
|
1983
|
+
* Answers the socket (+1) — Node's chaining. */
|
|
1984
|
+
ScrNetSocket *scr_net_sock_resume(ScrNetSocket *s) {
|
|
1985
|
+
s->user_paused = false;
|
|
1986
|
+
s->flowing = true;
|
|
1987
|
+
scr_net_sock_update_read(s);
|
|
1988
|
+
return scr_net_sock_retain(s);
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
/* socket.setNoDelay(enable): TCP_NODELAY on the live fd (client dials
|
|
1992
|
+
* already set it — Node's default there is off, a documented divergence
|
|
1993
|
+
* in the dial path; this call makes the state explicit either way).
|
|
1994
|
+
* Answers the socket (+1) — Node's chaining. */
|
|
1995
|
+
ScrNetSocket *scr_net_sock_set_nodelay(ScrNetSocket *s, bool enable) {
|
|
1996
|
+
if (s->fd >= 0) {
|
|
1997
|
+
int v = enable ? 1 : 0;
|
|
1998
|
+
setsockopt(s->fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof v);
|
|
1999
|
+
}
|
|
2000
|
+
return scr_net_sock_retain(s);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/* socket.destroySoon(): end the write half now, destroy once the FIN is
|
|
2004
|
+
* actually out (buffered bytes flush first — Node's 'finish'-then-destroy). */
|
|
2005
|
+
void scr_net_sock_destroy_soon(ScrNetSocket *s) {
|
|
2006
|
+
if (s->fd < 0) return;
|
|
2007
|
+
if (s->wr_done) {
|
|
2008
|
+
scr_net_sock_close_fd(s);
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
s->destroy_on_finish = true;
|
|
2012
|
+
s->wr_ending = true;
|
|
2013
|
+
scr_net_sock_maybe_finish_write(s);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
/* end(callback): fires once the FIN went out ('finish'), from the sweep. */
|
|
2017
|
+
void scr_net_sock_on_finish(ScrNetSocket *s, ScrClosure *cb /*moves*/) {
|
|
2018
|
+
if (s->close_emitted) {
|
|
2019
|
+
scr_closure_release(cb);
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
scr_net_ls_add(&s->finish_ls, cb, NULL, true);
|
|
2023
|
+
if (s->wr_done) s->finish_pending = true; /* already finished: next sweep */
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
/* write(chunk, callback): fires when the write buffer drains (this
|
|
2027
|
+
* surface's flush moment), from the sweep. */
|
|
2028
|
+
void scr_net_sock_on_write_flush(ScrNetSocket *s, ScrClosure *cb /*moves*/) {
|
|
2029
|
+
if (s->close_emitted) {
|
|
2030
|
+
scr_closure_release(cb);
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
scr_net_ls_add(&s->wcb_ls, cb, NULL, true);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
double scr_net_sock_bytes_written(ScrNetSocket *s) { return (double)s->bytes_written; }
|
|
2037
|
+
|
|
2038
|
+
/* socket.readable: true until the read half is done (peer FIN / destroy). */
|
|
2039
|
+
bool scr_net_sock_readable(ScrNetSocket *s) { return s->fd >= 0 && !s->rd_eof; }
|
|
2040
|
+
|
|
1894
2041
|
/* socket.setTimeout(ms): ms > 0 arms the idle timer NOW (Node starts the
|
|
1895
2042
|
* clock at the call — connecting time counts), ms <= 0 disables. */
|
|
1896
2043
|
void scr_net_sock_set_timeout(ScrNetSocket *s, double ms) {
|
|
@@ -2151,6 +2298,19 @@ void scr_net_data_thunk_dyn(ScrClosure *cb, ScrBytes *chunk) {
|
|
|
2151
2298
|
((void (*)(ScrClosure *, ScrDyn *))cb->fn)(cb, d);
|
|
2152
2299
|
}
|
|
2153
2300
|
|
|
2301
|
+
/* The string-TYPED data listener ((chunk: string) => void — the
|
|
2302
|
+
* setEncoding('utf8') shape): the chunk decodes as UTF-8 (WHATWG
|
|
2303
|
+
* replacement, Buffer.toString's algorithm) into the string the
|
|
2304
|
+
* annotation promises. Without setEncoding Node would hand a Buffer —
|
|
2305
|
+
* a string-typed listener there mistypes its own input, and the decode
|
|
2306
|
+
* is the honest reading of the annotation. */
|
|
2307
|
+
void scr_net_data_thunk_str(ScrClosure *cb, ScrBytes *chunk) {
|
|
2308
|
+
ScrStr *enc = scr_str_new("utf8", 4);
|
|
2309
|
+
ScrStr *text = scr_bytes_to_str(chunk, enc);
|
|
2310
|
+
scr_str_release(enc);
|
|
2311
|
+
((void (*)(ScrClosure *, ScrStr *))cb->fn)(cb, text);
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2154
2314
|
/* The connection-handler adapters: the socket arrives +1 from the firing
|
|
2155
2315
|
* site; the zero-param shape releases it. */
|
|
2156
2316
|
void scr_net_conn_thunk0(ScrClosure *cb, ScrNetSocket *sock) {
|
|
@@ -2351,10 +2511,13 @@ static bool scr_net_flags_pending(void) {
|
|
|
2351
2511
|
if (scr_net_proto_pending()) return true;
|
|
2352
2512
|
for (ScrNetSocket *s = scr_net_socks; s; s = s->next) {
|
|
2353
2513
|
if (s->pending_err || (s->emit_close && !s->close_emitted)) return true;
|
|
2514
|
+
if (s->finish_pending && s->finish_ls.n > 0) return true;
|
|
2515
|
+
if (s->wcb_ls.n > 0 && (s->wlen == s->whead || s->fd < 0)) return true;
|
|
2354
2516
|
/* bytes already out of the kernel with a consumer waiting — decrypted
|
|
2355
2517
|
* plaintext inside a transport, or unshifted bytes in the receive
|
|
2356
2518
|
* buffer: the poller can't re-signal them, the sweep must deliver */
|
|
2357
|
-
bool consumers = s->data_ls.n > 0 || s->pipe_dst || s->native_data
|
|
2519
|
+
bool consumers = (s->data_ls.n > 0 || s->pipe_dst || s->native_data || s->flowing) &&
|
|
2520
|
+
!s->user_paused;
|
|
2358
2521
|
if (consumers && s->fd >= 0) {
|
|
2359
2522
|
if (s->tops && s->t_est && s->tops->pending && s->tops->pending(s->tctx)) return true;
|
|
2360
2523
|
if (!s->tops && s->rlen > s->rhead) return true;
|
|
@@ -2393,7 +2556,8 @@ static void scr_net_sweep(void) {
|
|
|
2393
2556
|
while (sock) {
|
|
2394
2557
|
ScrNetSocket *next = sock->next; /* may unregister below */
|
|
2395
2558
|
scr_net_sock_retain(sock); /* callbacks may drop every other ref */
|
|
2396
|
-
if (sock->fd >= 0 &&
|
|
2559
|
+
if (sock->fd >= 0 && !sock->user_paused &&
|
|
2560
|
+
(sock->data_ls.n > 0 || sock->pipe_dst || sock->native_data || sock->flowing) &&
|
|
2397
2561
|
((sock->tops && sock->t_est && sock->tops->pending && sock->tops->pending(sock->tctx)) ||
|
|
2398
2562
|
(!sock->tops && sock->rlen > sock->rhead))) {
|
|
2399
2563
|
/* deliver bytes the kernel can't re-signal (see flags_pending) */
|
|
@@ -2403,6 +2567,26 @@ static void scr_net_sweep(void) {
|
|
|
2403
2567
|
return;
|
|
2404
2568
|
}
|
|
2405
2569
|
}
|
|
2570
|
+
if (sock->wcb_ls.n > 0 && (sock->wlen == sock->whead || sock->fd < 0)) {
|
|
2571
|
+
/* write(chunk, cb): the buffer drained (this surface's flush
|
|
2572
|
+
* moment) — the callbacks fire off the sweep, never the writing
|
|
2573
|
+
* stack. A dead socket fires them too (the buffered bytes are
|
|
2574
|
+
* gone either way; Node errors them — documented divergence,
|
|
2575
|
+
* the no-backpressure stance). */
|
|
2576
|
+
scr_net_fire0_this(&sock->wcb_ls, sock, SCR_DYNH_NET_SOCKET);
|
|
2577
|
+
if (scr_exc_pending()) {
|
|
2578
|
+
scr_net_sock_release(sock);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
if (sock->finish_pending) {
|
|
2583
|
+
sock->finish_pending = false;
|
|
2584
|
+
scr_net_fire0_this(&sock->finish_ls, sock, SCR_DYNH_NET_SOCKET);
|
|
2585
|
+
if (scr_exc_pending()) {
|
|
2586
|
+
scr_net_sock_release(sock);
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2406
2590
|
if (sock->pending_err) {
|
|
2407
2591
|
ScrStr *msg = sock->pending_err;
|
|
2408
2592
|
sock->pending_err = NULL;
|
|
@@ -2454,6 +2638,8 @@ static void scr_net_sweep(void) {
|
|
|
2454
2638
|
scr_net_ls_drop(&sock->conn_ls);
|
|
2455
2639
|
scr_net_ls_drop(&sock->timeout_ls);
|
|
2456
2640
|
scr_net_ls_drop(&sock->readable_ls);
|
|
2641
|
+
scr_net_ls_drop(&sock->finish_ls);
|
|
2642
|
+
scr_net_ls_drop(&sock->wcb_ls);
|
|
2457
2643
|
if (sock->pipe_dst) {
|
|
2458
2644
|
scr_net_sock_release(sock->pipe_dst);
|
|
2459
2645
|
sock->pipe_dst = NULL;
|
|
@@ -2718,6 +2904,9 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho
|
|
|
2718
2904
|
scr_net_sock_on_timeout(s, scr_dyn_listener_closure0(cb), once);
|
|
2719
2905
|
} else if (scr_net_dynh_name_is(name, "readable")) {
|
|
2720
2906
|
scr_net_sock_on_readable(s, scr_dyn_listener_closure0(cb), once);
|
|
2907
|
+
} else if (scr_net_dynh_name_is(name, "finish")) {
|
|
2908
|
+
/* fires once when the FIN goes out — once either way */
|
|
2909
|
+
scr_net_sock_on_finish(s, scr_dyn_listener_closure0(cb));
|
|
2721
2910
|
} else if (scr_net_dynh_name_is(name, "drain")) {
|
|
2722
2911
|
/* This surface never backpressures (write answers true) — an
|
|
2723
2912
|
* accepted, never-fired registration is the consistent answer. */
|
|
@@ -2743,28 +2932,51 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho
|
|
|
2743
2932
|
scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk);
|
|
2744
2933
|
return NULL;
|
|
2745
2934
|
}
|
|
2746
|
-
if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) {
|
|
2747
|
-
scr_net_dynh_unsupported("write", "write(chunk, callback) is not modeled");
|
|
2748
|
-
return NULL;
|
|
2749
|
-
}
|
|
2750
2935
|
if (chunk->kind == SCR_DYN_STR) scr_net_sock_write_str(s, chunk->v.str);
|
|
2751
2936
|
else scr_net_sock_write_bytes(s, chunk->v.bytes);
|
|
2937
|
+
if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) {
|
|
2938
|
+
/* write(chunk, cb): fires when the buffer drains (sweep-deferred) */
|
|
2939
|
+
scr_net_sock_on_write_flush(s, scr_dyn_listener_closure0(args[1]));
|
|
2940
|
+
}
|
|
2752
2941
|
return scr_dyn_new_bool(true); /* backpressure is not modeled (SEMANTICS.md) */
|
|
2753
2942
|
}
|
|
2754
2943
|
if (strcmp(method, "end") == 0) {
|
|
2755
2944
|
const ScrDyn *chunk = argc > 0 ? args[0] : scr_dyn_undefined();
|
|
2945
|
+
const ScrDyn *cb = NULL;
|
|
2946
|
+
if (chunk->kind == SCR_DYN_FUNC) { /* end(callback) */
|
|
2947
|
+
cb = chunk;
|
|
2948
|
+
chunk = scr_dyn_undefined();
|
|
2949
|
+
} else if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) {
|
|
2950
|
+
cb = args[1]; /* end(chunk, callback) */
|
|
2951
|
+
}
|
|
2952
|
+
if (cb != NULL) scr_net_sock_on_finish(s, scr_dyn_listener_closure0(cb));
|
|
2756
2953
|
if (chunk->kind == SCR_DYN_STR) scr_net_sock_end_str(s, chunk->v.str);
|
|
2757
2954
|
else if (chunk->kind == SCR_DYN_BYTES) scr_net_sock_end_bytes(s, chunk->v.bytes);
|
|
2758
2955
|
else if (chunk->kind == SCR_DYN_UNDEF || argc == 0) scr_net_sock_end(s);
|
|
2759
|
-
else
|
|
2760
|
-
scr_net_dynh_unsupported("end", "end(callback) is not modeled");
|
|
2761
|
-
return NULL;
|
|
2762
|
-
} else {
|
|
2956
|
+
else {
|
|
2763
2957
|
scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk);
|
|
2764
2958
|
return NULL;
|
|
2765
2959
|
}
|
|
2766
2960
|
return scr_dyn_retain(self);
|
|
2767
2961
|
}
|
|
2962
|
+
if (strcmp(method, "pause") == 0) {
|
|
2963
|
+
scr_net_sock_release(scr_net_sock_pause(s)); /* the chaining +1; self answers */
|
|
2964
|
+
return scr_dyn_retain(self);
|
|
2965
|
+
}
|
|
2966
|
+
if (strcmp(method, "resume") == 0) {
|
|
2967
|
+
scr_net_sock_release(scr_net_sock_resume(s));
|
|
2968
|
+
return scr_dyn_retain(self);
|
|
2969
|
+
}
|
|
2970
|
+
if (strcmp(method, "setNoDelay") == 0) {
|
|
2971
|
+
/* setNoDelay([enable]) — missing/undefined means true, Node */
|
|
2972
|
+
bool enable = argc == 0 || args[0]->kind == SCR_DYN_UNDEF || scr_dyn_truthy(args[0]);
|
|
2973
|
+
scr_net_sock_release(scr_net_sock_set_nodelay(s, enable));
|
|
2974
|
+
return scr_dyn_retain(self);
|
|
2975
|
+
}
|
|
2976
|
+
if (strcmp(method, "destroySoon") == 0) {
|
|
2977
|
+
scr_net_sock_destroy_soon(s);
|
|
2978
|
+
return scr_dyn_retain(scr_dyn_undefined());
|
|
2979
|
+
}
|
|
2768
2980
|
if (strcmp(method, "destroy") == 0) {
|
|
2769
2981
|
if (argc > 0 && args[0]->kind != SCR_DYN_UNDEF) {
|
|
2770
2982
|
scr_net_dynh_unsupported("destroy", "destroy(error) carries a payload this surface does not model");
|
|
@@ -2826,10 +3038,10 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho
|
|
|
2826
3038
|
if (scr_net_dynh_tls->invoke(s, method, args, argc, &out)) return out;
|
|
2827
3039
|
}
|
|
2828
3040
|
{
|
|
2829
|
-
static const char *const known[] = { "connect", "
|
|
2830
|
-
"address", "
|
|
3041
|
+
static const char *const known[] = { "connect", "setKeepAlive",
|
|
3042
|
+
"address", "ref", "unref", "cork", "uncork", "read", "unpipe",
|
|
2831
3043
|
"off", "removeListener", "removeAllListeners", "emit", "prependListener",
|
|
2832
|
-
"prependOnceListener", "listenerCount", "listeners", "resetAndDestroy",
|
|
3044
|
+
"prependOnceListener", "listenerCount", "listeners", "resetAndDestroy", NULL };
|
|
2833
3045
|
for (size_t i = 0; known[i]; i++) {
|
|
2834
3046
|
if (strcmp(method, known[i]) == 0) {
|
|
2835
3047
|
scr_net_dynh_unsupported(method, NULL);
|
|
@@ -2862,14 +3074,21 @@ static ScrDyn *scr_net_dynh_sock_get(void *h, const char *key, size_t key_len) {
|
|
|
2862
3074
|
* undefined — the static lane's false→undefined mapping. */
|
|
2863
3075
|
return scr_net_sock_encrypted(s) ? scr_dyn_new_bool(true) : NULL;
|
|
2864
3076
|
}
|
|
3077
|
+
if (strcmp(key, "bytesWritten") == 0) return scr_dyn_new_num(scr_net_sock_bytes_written(s));
|
|
3078
|
+
if (strcmp(key, "readable") == 0) return scr_dyn_new_bool(scr_net_sock_readable(s));
|
|
3079
|
+
if (strcmp(key, "writableHighWaterMark") == 0 || strcmp(key, "readableHighWaterMark") == 0) {
|
|
3080
|
+
/* Node's default stream highWaterMark — a constant here (backpressure
|
|
3081
|
+
* is not modeled; SEMANTICS.md) */
|
|
3082
|
+
return scr_dyn_new_num(16384);
|
|
3083
|
+
}
|
|
2865
3084
|
if (scr_net_dynh_tls != NULL) {
|
|
2866
3085
|
ScrDyn *out = NULL;
|
|
2867
3086
|
if (scr_net_dynh_tls->get(s, key, &out)) return out;
|
|
2868
3087
|
}
|
|
2869
3088
|
{
|
|
2870
3089
|
static const char *const known[] = { "remotePort", "remoteFamily", "localAddress",
|
|
2871
|
-
"localPort", "localFamily", "bytesRead", "
|
|
2872
|
-
"readyState", "bufferSize", "timeout", "
|
|
3090
|
+
"localPort", "localFamily", "bytesRead", "connecting", "pending",
|
|
3091
|
+
"readyState", "bufferSize", "timeout", "closed", "errored", NULL };
|
|
2873
3092
|
for (size_t i = 0; known[i]; i++) {
|
|
2874
3093
|
if (strcmp(key, known[i]) == 0) {
|
|
2875
3094
|
scr_net_dynh_unsupported(key, NULL);
|
package/src/scr_qs.c
CHANGED
|
@@ -429,7 +429,7 @@ void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
|
|
|
429
429
|
|
|
430
430
|
/* ── stringify ───────────────────────────────────────────────────────── */
|
|
431
431
|
|
|
432
|
-
/* Node's encodeStringified over one
|
|
432
|
+
/* Node's encodeStringified over one dyn value: strings escape, finite
|
|
433
433
|
* numbers render then escape, booleans are bare, everything else is the
|
|
434
434
|
* empty value. Appends to b. */
|
|
435
435
|
static void qs_stringify_value(QsBuf *b, const ScrDyn *v) {
|