diff --git a/CHANGELOG.md b/CHANGELOG.md index 26252642..d9f78e14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All notable changes to this project will be documented in this file. +## ??? - Unreleased +- Make imperative arithmetic macros variadic +- `ev/connect` now yields to the event loop instead of blocking while waiting for an ACK. + ## 1.28.0 - 2023-05-13 - Various bug fixes - Make nested short-fn's behave a bit more predictably (it is still not recommended to nest short-fns). diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 0ccda788..3f953c43 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -147,10 +147,10 @@ (defn dec "Returns x - 1." [x] (- x 1)) (defmacro ++ "Increments the var x by 1." [x] ~(set ,x (,+ ,x ,1))) (defmacro -- "Decrements the var x by 1." [x] ~(set ,x (,- ,x ,1))) -(defmacro += "Increments the var x by n." [x n] ~(set ,x (,+ ,x ,n))) -(defmacro -= "Decrements the var x by n." [x n] ~(set ,x (,- ,x ,n))) -(defmacro *= "Shorthand for (set x (\\* x n))." [x n] ~(set ,x (,* ,x ,n))) -(defmacro /= "Shorthand for (set x (/ x n))." [x n] ~(set ,x (,/ ,x ,n))) +(defmacro += "Increments the var x by n." [x & ns] ~(set ,x (,+ ,x ,;ns))) +(defmacro -= "Decrements the var x by n." [x & ns] ~(set ,x (,- ,x ,;ns))) +(defmacro *= "Shorthand for (set x (\\* x n))." [x & ns] ~(set ,x (,* ,x ,;ns))) +(defmacro /= "Shorthand for (set x (/ x n))." [x & ns] ~(set ,x (,/ ,x ,;ns))) (defmacro %= "Shorthand for (set x (% x n))." [x n] ~(set ,x (,% ,x ,n))) (defmacro assert @@ -2142,6 +2142,19 @@ :buffer (string x) x)) +(defn thaw + `Thaw an object (make it mutable) and do a deep copy, making + child value also mutable. Closures, fibers, and abstract + types will not be recursively thawed, but all other types will` + [ds] + (case (type ds) + :array (walk-ind thaw ds) + :tuple (walk-ind thaw ds) + :table (walk-dict thaw (table/proto-flatten ds)) + :struct (walk-dict thaw (struct/proto-flatten ds)) + :string (buffer ds) + ds)) + (defn macex ``Expand macros completely. `on-binding` is an optional callback for whenever a normal symbolic binding diff --git a/src/core/ev.c b/src/core/ev.c index 8a904745..fd3a7b9a 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -1502,6 +1502,10 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp to) { state = state->_next; } } + /* Close the stream if requested and no more listeners are left */ + if ((stream->flags & JANET_STREAM_TOCLOSE) && !stream->state) { + janet_stream_close(stream); + } } } } @@ -1656,6 +1660,10 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { janet_unlisten(state, 0); state = next_state; } + /* Close the stream if requested and no more listeners are left */ + if ((stream->flags & JANET_STREAM_TOCLOSE) && !stream->state) { + janet_stream_close(stream); + } } } } @@ -1854,6 +1862,10 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { state = next_state; } + /* Close the stream if requested and no more listeners are left */ + if ((stream->flags & JANET_STREAM_TOCLOSE) && !stream->state) { + janet_stream_close(stream); + } } } } @@ -1957,6 +1969,7 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { JanetAsyncStatus status3 = JANET_ASYNC_STATUS_NOT_DONE; JanetAsyncStatus status4 = JANET_ASYNC_STATUS_NOT_DONE; state->event = pfd; + JanetStream *stream = state->stream; if (mask & POLLOUT) status1 = state->machine(state, JANET_ASYNC_EVENT_WRITE); if (mask & POLLIN) @@ -1970,6 +1983,10 @@ void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) { status3 == JANET_ASYNC_STATUS_DONE || status4 == JANET_ASYNC_STATUS_DONE) janet_unlisten(state, 0); + /* Close the stream if requested and no more listeners are left */ + if ((stream->flags & JANET_STREAM_TOCLOSE) && !stream->state) { + janet_stream_close(stream); + } } } @@ -2456,7 +2473,8 @@ void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, in typedef enum { JANET_ASYNC_WRITEMODE_WRITE, JANET_ASYNC_WRITEMODE_SEND, - JANET_ASYNC_WRITEMODE_SENDTO + JANET_ASYNC_WRITEMODE_SENDTO, + JANET_ASYNC_WRITEMODE_CONNECT } JanetWriteMode; typedef struct { @@ -2480,6 +2498,31 @@ typedef struct { #endif } StateWrite; +static JanetAsyncStatus handle_connect(JanetListenerState *s) { +#ifdef JANET_WINDOWS + int res = 0; + int size = sizeof(res); + int r = getsockopt((SOCKET)s->stream->handle, SOL_SOCKET, SO_ERROR, (char *)&res, &size); +#else + int res = 0; + socklen_t size = sizeof res; + int r = getsockopt(s->stream->handle, SOL_SOCKET, SO_ERROR, &res, &size); +#endif + if (r == 0) { + if (res == 0) { + janet_schedule(s->fiber, janet_wrap_abstract(s->stream)); + } else { + s->stream->flags |= JANET_STREAM_TOCLOSE; + janet_cancel(s->fiber, janet_cstringv(strerror(res))); + } + } else { + s->stream->flags |= JANET_STREAM_TOCLOSE; + janet_cancel(s->fiber, janet_ev_lasterr()); + } + return JANET_ASYNC_STATUS_DONE; +} + + JanetAsyncStatus ev_machine_write(JanetListenerState *s, JanetAsyncEvent event) { StateWrite *state = (StateWrite *) s; switch (event) { @@ -2509,6 +2552,11 @@ JanetAsyncStatus ev_machine_write(JanetListenerState *s, JanetAsyncEvent event) } break; case JANET_ASYNC_EVENT_USER: { +#ifdef JANET_NET + if (state->mode == JANET_ASYNC_WRITEMODE_CONNECT) { + return handle_connect(s); + } +#endif /* Begin write */ int32_t len; const uint8_t *bytes; @@ -2572,6 +2620,11 @@ JanetAsyncStatus ev_machine_write(JanetListenerState *s, JanetAsyncEvent event) janet_cancel(s->fiber, janet_cstringv("stream hup")); return JANET_ASYNC_STATUS_DONE; case JANET_ASYNC_EVENT_WRITE: { +#ifdef JANET_NET + if (state->mode == JANET_ASYNC_WRITEMODE_CONNECT) { + return handle_connect(s); + } +#endif int32_t start, len; const uint8_t *bytes; start = state->start; @@ -2674,6 +2727,10 @@ void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, i void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags) { janet_ev_write_generic(stream, (void *) str, dest, JANET_ASYNC_WRITEMODE_SENDTO, 0, flags); } + +void janet_ev_connect(JanetStream *stream, int flags) { + janet_ev_write_generic(stream, NULL, NULL, JANET_ASYNC_WRITEMODE_CONNECT, 0, flags); +} #endif /* For a pipe ID */ diff --git a/src/core/net.c b/src/core/net.c index 843f36f8..e628bce1 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -477,14 +477,20 @@ JANET_CORE_FN(cfun_net_connect, } } + /* Wrap socket in abstract type JanetStream */ + JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); + + /* Set up the socket for non-blocking IO before connecting */ + janet_net_socknoblock(sock); + /* Connect to socket */ #ifdef JANET_WINDOWS int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL); - Janet lasterr = janet_ev_lasterr(); + int err = WSAGetLastError(); freeaddrinfo(ai); #else int status = connect(sock, addr, addrlen); - Janet lasterr = janet_ev_lasterr(); + int err = errno; if (is_unix) { janet_free(ai); } else { @@ -492,17 +498,22 @@ JANET_CORE_FN(cfun_net_connect, } #endif - if (status == -1) { - JSOCKCLOSE(sock); - janet_panicf("could not connect socket: %V", lasterr); + if (status != 0) { +#ifdef JANET_WINDOWS + if (err != WSAEWOULDBLOCK) { +#else + if (err != EINPROGRESS) { +#endif + JSOCKCLOSE(sock); + Janet lasterr = janet_ev_lasterr(); + janet_panicf("could not connect socket: %V", lasterr); + } } - /* Set up the socket for non-blocking IO after connect - TODO - non-blocking connect? */ - janet_net_socknoblock(sock); + /* Handle the connect() result in the event loop*/ + janet_ev_connect(stream, MSG_NOSIGNAL); - /* Wrap socket in abstract type JanetStream */ - JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); - return janet_wrap_abstract(stream); + janet_await(); } static const char *serverify_socket(JSock sfd) { diff --git a/src/core/os.c b/src/core/os.c index 55c4c7d3..8f076cbc 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1380,14 +1380,32 @@ JANET_CORE_FN(os_time, } JANET_CORE_FN(os_clock, - "(os/clock)", - "Return the number of whole + fractional seconds since some fixed point in time. The clock " - "is guaranteed to be non-decreasing in real time.") { + "(os/clock &opt source)", + "Return the number of whole + fractional seconds of the requested clock source.\n\n" + "The `source` argument selects the clock source to use, when not specified the default " + "is `:realtime`:\n" + "- :realtime: Return the real (i.e., wall-clock) time. This clock is affected by discontinuous " + " jumps in the system time\n" + "- :monotonic: Return the number of whole + fractional seconds since some fixed point in " + " time. The clock is guaranteed to be non-decreasing in real time.\n" + "- :cputime: Return the CPU time consumed by this process (i.e. all threads in the process)\n") { janet_sandbox_assert(JANET_SANDBOX_HRTIME); - janet_fixarity(argc, 0); - (void) argv; + janet_arity(argc, 0, 1); + enum JanetTimeSource source = JANET_TIME_REALTIME; + if (argc == 1) { + JanetKeyword sourcestr = janet_getkeyword(argv, 0); + if (janet_cstrcmp(sourcestr, "realtime") == 0) { + source = JANET_TIME_REALTIME; + } else if (janet_cstrcmp(sourcestr, "monotonic") == 0) { + source = JANET_TIME_MONOTONIC; + } else if (janet_cstrcmp(sourcestr, "cputime") == 0) { + source = JANET_TIME_CPUTIME; + } else { + janet_panicf("expected :realtime, :monotonic, or :cputime, got %v", argv[0]); + } + } struct timespec tv; - if (janet_gettime(&tv)) janet_panic("could not get time"); + if (janet_gettime(&tv, source)) janet_panic("could not get time"); double dtime = tv.tv_sec + (tv.tv_nsec / 1E9); return janet_wrap_number(dtime); } diff --git a/src/core/util.c b/src/core/util.c index 3c50bc94..a699965e 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -875,34 +875,73 @@ int32_t janet_sorted_keys(const JanetKV *dict, int32_t cap, int32_t *index_buffe /* Clock shims for various platforms */ #ifdef JANET_GETTIME #ifdef JANET_WINDOWS -int janet_gettime(struct timespec *spec) { - FILETIME ftime; - GetSystemTimeAsFileTime(&ftime); - int64_t wintime = (int64_t)(ftime.dwLowDateTime) | ((int64_t)(ftime.dwHighDateTime) << 32); - /* Windows epoch is January 1, 1601 apparently */ - wintime -= 116444736000000000LL; - spec->tv_sec = wintime / 10000000LL; - /* Resolution is 100 nanoseconds. */ - spec->tv_nsec = wintime % 10000000LL * 100; +#include +int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { + if (source == JANET_TIME_REALTIME) { + FILETIME ftime; + GetSystemTimeAsFileTime(&ftime); + int64_t wintime = (int64_t)(ftime.dwLowDateTime) | ((int64_t)(ftime.dwHighDateTime) << 32); + /* Windows epoch is January 1, 1601 apparently */ + wintime -= 116444736000000000LL; + spec->tv_sec = wintime / 10000000LL; + /* Resolution is 100 nanoseconds. */ + spec->tv_nsec = wintime % 10000000LL * 100; + } else if (source == JANET_TIME_MONOTONIC) { + LARGE_INTEGER count; + LARGE_INTEGER perf_freq; + QueryPerformanceCounter(&count); + QueryPerformanceFrequency(&perf_freq); + spec->tv_sec = count.QuadPart / perf_freq.QuadPart; + spec->tv_nsec = (long)((count.QuadPart % perf_freq.QuadPart) * 1000000000 / perf_freq.QuadPart); + } else if (source == JANET_TIME_CPUTIME) { + FILETIME creationTime, exitTime, kernelTime, userTime; + GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime); + int64_t tmp = ((int64_t)userTime.dwHighDateTime << 32) + userTime.dwLowDateTime; + spec->tv_sec = tmp / 10000000LL; + spec->tv_nsec = tmp % 10000000LL * 100; + } return 0; } /* clock_gettime() wasn't available on Mac until 10.12. */ #elif defined(JANET_APPLE) && !defined(MAC_OS_X_VERSION_10_12) #include #include -int janet_gettime(struct timespec *spec) { - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - spec->tv_sec = mts.tv_sec; - spec->tv_nsec = mts.tv_nsec; +int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { + if (source == JANET_TIME_REALTIME) { + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + spec->tv_sec = mts.tv_sec; + spec->tv_nsec = mts.tv_nsec; + } else if (source == JANET_TIME_MONOTONIC) { + clock_serv_t cclock; + int nsecs; + mach_msg_type_number_t count; + host_get_clock_service(mach_host_self(), clock, &cclock); + clock_get_attributes(cclock, CLOCK_GET_TIME_RES, (clock_attr_t)&nsecs, &count); + mach_port_deallocate(mach_task_self(), cclock); + clock_getres(CLOCK_MONOTONIC, spec); + } + if (source == JANET_TIME_CPUTIME) { + clock_t tmp = clock(); + spec->tv_sec = tmp; + spec->tv_nsec = (tmp - spec->tv_sec) * 1.0e9; + } return 0; } #else -int janet_gettime(struct timespec *spec) { - return clock_gettime(CLOCK_REALTIME, spec); +int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { + clockid_t cid = JANET_TIME_REALTIME; + if (source == JANET_TIME_REALTIME) { + cid = CLOCK_REALTIME; + } else if (source == JANET_TIME_MONOTONIC) { + cid = CLOCK_MONOTONIC; + } else if (source == JANET_TIME_CPUTIME) { + cid = CLOCK_PROCESS_CPUTIME_ID; + } + return clock_gettime(cid, spec); } #endif #endif diff --git a/src/core/util.h b/src/core/util.h index b8f9cc90..3fe7b858 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -126,7 +126,12 @@ void janet_core_cfuns_ext(JanetTable *env, const char *regprefix, const JanetReg /* Clock gettime */ #ifdef JANET_GETTIME -int janet_gettime(struct timespec *spec); +enum JanetTimeSource { + JANET_TIME_REALTIME, + JANET_TIME_MONOTONIC, + JANET_TIME_CPUTIME +}; +int janet_gettime(struct timespec *spec, enum JanetTimeSource source); #endif /* strdup */ diff --git a/src/include/janet.h b/src/include/janet.h index 941a6c35..1064380b 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -568,6 +568,7 @@ typedef void *JanetAbstract; #define JANET_STREAM_WRITABLE 0x400 #define JANET_STREAM_ACCEPTABLE 0x800 #define JANET_STREAM_UDPSERVER 0x1000 +#define JANET_STREAM_TOCLOSE 0x10000 typedef enum { JANET_ASYNC_EVENT_INIT, @@ -1479,6 +1480,7 @@ JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t JANET_API void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); +JANET_API void janet_ev_connect(JanetStream *stream, int flags); #endif /* Write async to a stream */ diff --git a/test/suite0007.janet b/test/suite0007.janet index c60a9678..e59b049c 100644 --- a/test/suite0007.janet +++ b/test/suite0007.janet @@ -333,4 +333,29 @@ (assert (pos? (length (gensym))) "gensym not empty, regression #753") + +# os/clock. These tests might prove fragile under CI because they +# rely on measured time. We'll see. + +(defmacro measure-time [clocks & body] + (def [t1 t2] [(gensym) (gensym)]) + ~(do + (def ,t1 (map |(os/clock $) ,clocks)) + ,;body + (def ,t2 (map |(os/clock $) ,clocks)) + (zipcoll ,clocks (map |(- ;$) (map tuple ,t2 ,t1)))) +) + +# Spin for 0.1 seconds +(def dt (measure-time [:realtime :monotonic :cputime] + (def t1 (os/clock :monotonic)) + (while (< (- (os/clock :monotonic) t1) 0.1) true))) +(assert (> (dt :monotonic) 0.10)) +(assert (> (dt :cputime) 0.05)) + +# Sleep for 0.1 seconds +(def dt (measure-time [:realtime :monotonic :cputime] (os/sleep 0.1))) +(assert (> (dt :monotonic) 0.10)) +(assert (< (dt :cputime) 0.05)) + (end-suite) diff --git a/test/suite0010.janet b/test/suite0010.janet index 5ac0cd16..b41142e8 100644 --- a/test/suite0010.janet +++ b/test/suite0010.janet @@ -253,4 +253,13 @@ # Check missing struct proto bug. (assert (struct/getproto (struct/with-proto {:a 1} :b 2 :c nil)) "missing struct proto") +# Test thaw and freeze +(def table-to-freeze @{:c 22 :b [1 2 3 4] :d @"test" :e "test2"}) +(def table-to-freeze-with-inline-proto @{:a @[1 2 3] :b @[1 2 3 4] :c 22 :d @"test" :e @"test2"}) +(def struct-to-thaw (struct/with-proto {:a [1 2 3]} :c 22 :b [1 2 3 4] :d "test" :e "test2")) +(table/setproto table-to-freeze @{:a @[1 2 3]}) +(assert (deep= {:a [1 2 3] :b [1 2 3 4] :c 22 :d "test" :e "test2"} (freeze table-to-freeze))) +(assert (deep= table-to-freeze-with-inline-proto (thaw table-to-freeze))) +(assert (deep= table-to-freeze-with-inline-proto (thaw struct-to-thaw))) + (end-suite)