From 2e6001316a9873935c80bb1157168a07e02df25a Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 15 Feb 2025 23:22:12 -0600 Subject: [PATCH] Work on bug #1556 --- src/boot/boot.janet | 4 ++-- src/core/ev.c | 18 ++++++++++++++++- src/core/net.c | 5 ++++- test/suite-ev.janet | 48 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index fa417a07..0d8a5f34 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -3877,8 +3877,8 @@ (compwhen (dyn 'net/listen) (defn net/server "Start a server asynchronously with `net/listen` and `net/accept-loop`. Returns the new server stream." - [host port &opt handler type] - (def s (net/listen host port type)) + [host port &opt handler type no-reuse] + (def s (net/listen host port type no-reuse)) (if handler (ev/go (fn [] (net/accept-loop s handler)))) s)) diff --git a/src/core/ev.c b/src/core/ev.c index 0303a36e..4cde9ff8 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1,5 +1,5 @@ /* -* Copyright (c) 2024 Calvin Rose +* Copyright (c) 2025 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -1789,6 +1789,22 @@ void janet_stream_edge_triggered(JanetStream *stream) { } void janet_stream_level_triggered(JanetStream *stream) { + /* On macos, we seem to need to delete any registered events before re-registering without + * EV_CLEAR, otherwise the new event will still have EV_CLEAR set erroneously. This could be a + * kernel bug, but unfortunately the specification is vague here, esp. in regards to where and when + * EV_CLEAR is set automatically. */ + struct kevent kevs[2]; + int length = 0; + if (stream->flags & (JANET_STREAM_READABLE | JANET_STREAM_ACCEPTABLE)) { + EV_SETx(&kevs[length++], stream->handle, EVFILT_READ, EV_DELETE, 0, 0, stream); + } + if (stream->flags & JANET_STREAM_WRITABLE) { + EV_SETx(&kevs[length++], stream->handle, EVFILT_WRITE, EV_DELETE, 0, 0, stream); + } + int status; + do { + status = kevent(janet_vm.kq, kevs, length, NULL, 0, NULL); + } while (status == -1 && errno == EINTR); janet_register_stream_impl(stream, 0); } diff --git a/src/core/net.c b/src/core/net.c index 799976fa..be338b5f 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -554,7 +554,10 @@ JANET_CORE_FN(cfun_net_connect, int err = WSAGetLastError(); freeaddrinfo(ai); #else - int status = connect(sock, addr, addrlen); + int status; + do { + status = connect(sock, addr, addrlen); + } while (status == -1 && errno == EINTR); int err = errno; if (is_unix) { janet_free(ai); diff --git a/test/suite-ev.janet b/test/suite-ev.janet index 2e6f8833..3e3c2502 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -1,4 +1,4 @@ -# Copyright (c) 2023 Calvin Rose & contributors +# Copyright (c) 2025 Calvin Rose & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to @@ -199,7 +199,7 @@ (assert s "made server 1") (defn test-echo [msg] - (with [conn (net/connect test-host test-port)] + (with [conn (assert (net/connect test-host test-port))] (net/write conn msg) (def res (net/read conn 1024)) (assert (= (string res) msg) (string "echo " msg)))) @@ -213,6 +213,7 @@ # Test on both server and client # 504411e +(var iterations 0) (defn names-handler [stream] (defer (:close stream) @@ -220,21 +221,26 @@ (ev/read stream 1) (def [host port] (net/localname stream)) (assert (= host test-host) "localname host server") - (assert (= port (scan-number test-port)) "localname port server"))) + (assert (= port (scan-number test-port)) "localname port server") + (++ iterations) + (ev/write stream " "))) # Test localname and peername # 077bf5eba (repeat 10 (with [s (net/server test-host test-port names-handler)] (repeat 10 - (with [conn (net/connect test-host test-port)] + (with [conn (assert (net/connect test-host test-port))] (def [host port] (net/peername conn)) (assert (= host test-host) "peername host client ") (assert (= port (scan-number test-port)) "peername port client") - # let server close - (ev/write conn " ")))) + (++ iterations) + (ev/write conn " ") + (ev/read conn 1)))) (gccollect)) +(assert (= iterations 200) "localname and peername not enough checks") + # Create pipe # 12f09ad2d (var pipe-counter 0) @@ -422,7 +428,7 @@ (assert (= result text) (string/format "expected %v, got %v" text result))) # Now do our telnet chat -(def bob (net/connect test-host test-port :stream)) +(def bob (assert (net/connect test-host test-port :stream))) (expect-read bob "Whats your name?\n") (if (= :mingw (os/which)) (net/write bob "bob") @@ -432,7 +438,7 @@ (file/flush fbob) (:close fbob))) (expect-read bob "Welcome bob\n") -(def alice (net/connect test-host test-port)) +(def alice (assert (net/connect test-host test-port))) (expect-read alice "Whats your name?\n") (net/write alice "alice") (expect-read alice "Welcome alice\n") @@ -446,7 +452,7 @@ (expect-read bob "[alice]:hi\n") # Ted joins the chat server -(def ted (net/connect test-host test-port)) +(def ted (assert (net/connect test-host test-port))) (expect-read ted "Whats your name?\n") (net/write ted "ted") (expect-read ted "Welcome ted\n") @@ -485,9 +491,31 @@ (ev/chan-close c) # soreuseport on unix domain sockets -(compwhen (= :linux (os/which)) +(compwhen (or (= :macos (os/which)) (= :linux (os/which))) (assert-no-error "unix-domain socket reuseaddr" (let [s (net/listen :unix "./unix-domain-socket" :stream)] (:close s)))) +# net/accept-loop level triggering +(gccollect) +(def maxconn 50) +(var connect-count 0) +(defn level-trigger-handling + [conn &] + (with [conn conn] + (ev/write conn (ev/read conn 4096)) + (++ connect-count))) +(def s (assert (net/server test-host test-port level-trigger-handling))) +(def cons @[]) +(repeat maxconn (array/push cons (assert (net/connect test-host test-port)))) +(assert (= maxconn (length cons))) +(defn do-connect [i] + (with [c (get cons i)] + (ev/write c "abc123") + (ev/read c 4096))) +(for i 0 maxconn (ev/spawn (do-connect i))) +(ev/sleep 0.1) +(assert (= maxconn connect-count)) +(:close s) + (end-suite)