nandi/frqpublic Fork 0
232a45569fb73dc2a845d46df4e20b20014e7cfd
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

wire.clj · 81 lines · 3.8 KBClojure Blame HistoryRaw
Send every byte of a short write, from the right offset 7bc4770 nandi 11d ago1(ns frq.wire
2 "Bytes onto a raw socket, all of them.
3
4 `send(2)` is allowed to accept less than it was given and say so, which is
5 not an error and not rare — it is what a full socket buffer looks like. The
6 caller has to resume from where it stopped, and the only way to say that to
7 the kernel is a pointer further along the buffer: shrinking the length while
8 passing the same address re-sends the head of the line and loses the tail.
9 The peer then gets the right number of bytes, the wrong ones, and a stream
10 that no longer frames.
11
12 Both raw-socket writers in frq had their own version of this loop and both
13 had it wrong, so there is one here instead. TLS does not come through — that
14 transport is `tls/tls-write`, which handles its own record boundaries."
15 (:require [jolt.ffi :as ffi]
16 [jolt.socket :as socket]))
17
18(def ^:private no-signal
19 "MSG_NOSIGNAL: a write to a closed peer returns EPIPE rather than killing
20 the process with SIGPIPE. Read from jolt rather than spelled here, as both
21 call sites already did — the value is the platform's, not frq's."
22 @#'socket/msg-nosignal)
23
Read the syscall result out of the pair jolt now returns 468684e nandi 10d ago24(def ^:private io-call
25 "jolt's own retry wrapper for one blocking-capable socket syscall.
26
27 The `accept`/`recv`/`send` bindings are declared `:capture-native-error`, so
28 they answer `[result errno]` rather than a bare number — a pair that reads as
29 a socket error nowhere and as `class clojure.lang.PersistentVector cannot be
30 cast to class java.lang.Number` the moment a caller asks whether it is
31 positive. Taken from jolt rather than unwrapped here, as with `no-signal`
32 above: the errno is what tells EINTR and EAGAIN from a real failure, and
33 jolt is where that classification lives."
34 @#'socket/io-call)
35
36(defn recv!
37 "One `recv` into `buf`, answering the byte count — negative or zero at end."
38 [fd buf len]
39 (io-call #(socket/c-recv fd buf len 0) fd :read))
40
Take connect through io-call with the other three 77c1a08 nandi 10d ago41(defn connect!
42 "One `connect` to the address at `sa`, answering zero or a negative.
43
44 `connect` is the fourth of these bindings and was left out of the round
45 that fixed the other three: it is only on the plain-socket path, which the
46 TLS default does not take, so its pair reached `neg?` unnoticed. The wait
47 is for writability — a socket that finishes connecting reports itself
48 writable, which is what jolt's poller is being asked about here."
49 [fd sa len]
50 (io-call #(socket/c-connect fd sa len) fd :write))
51
Read the syscall result out of the pair jolt now returns 468684e nandi 10d ago52(defn accept!
53 "One `accept` on a listening fd, answering the connected fd or a negative."
54 [fd]
55 (io-call #(socket/c-accept fd ffi/null ffi/null) fd :read))
56
Send every byte of a short write, from the right offset 7bc4770 nandi 11d ago57(defn send-all!
58 "Write `text` to `fd` until none is left. Throws if the socket does.
59
60 The byte count is taken as UTF-8 explicitly, because that is what
61 `with-c-string` writes: the platform default agrees on every machine frq
62 has run on, but a machine where it did not would send a length measured in
63 one encoding against bytes laid down in another."
64 [fd text]
65 (let [len (count (.getBytes ^String text "UTF-8"))]
66 (ffi/with-c-string [p text]
67 (loop [sent 0]
68 (when (< sent len)
69 ;; The pointer advances with the length. `p` is an address, so this
70 ;; is ordinary arithmetic on it.
Read the syscall result out of the pair jolt now returns 468684e nandi 10d ago71 (let [n (io-call #(socket/c-send fd (+ p sent) (- len sent) no-signal)
72 fd :write)]
Send every byte of a short write, from the right offset 7bc4770 nandi 11d ago73 ;; Anything not positive ends it. Zero especially: recurring on an
74 ;; unchanged `sent` is an infinite loop that sends nothing, which
75 ;; is worse than the failure it is hiding.
Read the syscall result out of the pair jolt now returns 468684e nandi 10d ago76 ;; EINTR and EAGAIN are already gone by here — io-call retries
77 ;; the one and waits out the other — so a non-positive n is the
78 ;; socket's final answer.
Send every byte of a short write, from the right offset 7bc4770 nandi 11d ago79 (when-not (pos? n)
80 (throw (ex-info "send failed" {:fd fd :sent sent :len len :ret n})))
81 (recur (+ sent n))))))))