1
0
mirror of https://github.com/janet-lang/janet synced 2025-11-22 10:14:49 +00:00

Compare commits

...

41 Commits

Author SHA1 Message Date
Calvin Rose
c5b3da1ffe Inter 2025-05-16 18:35:33 -05:00
Calvin Rose
a8e2c8e5b8 Address #1596 - Use atomic intrinsics to check allow_interrupt flag.
Use a relaxed memory order if possible to mitigate performance issues as
much as possible. relaxed memory order should be sufficient.
2025-05-16 18:26:43 -05:00
Calvin Rose
3d3e880f52 Allow configuration of JANET_THREAD_LOCAL. Address #1595
This is to better allow configuration on various, unknown compilers.
Previously, we hardcoded how thread local storage was specified for a
few different compilers, but we were not following and C standard. In
C11, there is a standardized storage specifier _Thread_local for this
storage class, however this is now deprecated in various C++ compilers
for a new keyword, confusingly. Janet also does not claim to require the
C11 standard, so for maximum flexibilty, the storage specifier must be
specified at configure time.
2025-05-11 08:37:15 -05:00
Calvin Rose
bef8ba5e06 Merge pull request #1594 from sogaiu/tweak-docstrings
Tweak docstrings for better handling
2025-05-08 17:00:32 -05:00
Calvin Rose
523639362c Merge pull request #1593 from agent-kilo/thread-message-size-check
Try to fix cfun registry size check for cross-thread messages
2025-05-08 17:00:22 -05:00
sogaiu
4b6d5e5671 Tweak docstrings for better handling 2025-05-02 18:54:18 +09:00
Agent Kilo
a695454dae Try to fix cfun registry size check for cross-thread messages 2025-04-28 17:00:23 +08:00
Calvin Rose
f2eaa5dee8 Remove ev/to-stream.
The function has more problems than initially expected, both on Posix
systems and on Windows. Given all the caveats, it is probably best not
to include. Any function that can obtain files can use os/open instead.
The standard FILE objects also will not work anyway, and different
operating systems have different work arounds.
2025-04-18 18:20:27 -07:00
Calvin Rose
b27c830d90 Use win32 DuplicatHandle instead of _dup
There are constraints when using the posix API on win32
that aren't present with normal win32.
2025-04-11 21:36:08 -05:00
Calvin Rose
92a852f2df Add ev/to-stream.
This function is symmetrical to ev/to-file and can convert a blocking
file to a stream (with caveats).
2025-04-11 21:10:49 -05:00
Calvin Rose
647e218bed make format 2025-04-09 20:39:23 -05:00
Calvin Rose
5ebe945ffd Merge pull request #1582 from pyrmont/bugfix.termux-pthread_cancel
Fix absence of pthread_cancel in Android
2025-04-09 20:16:57 -05:00
Michael Camilleri
6254fffad0 Update changelog to include change to ev/deadline 2025-04-09 10:40:19 +09:00
Michael Camilleri
5705b2f6c7 Fix absence of pthread_cancel in Android 2025-04-09 10:37:46 +09:00
Calvin Rose
90a33bc88a Add some systems that support addchdir. 2025-04-07 21:12:28 -05:00
Calvin Rose
1ba077c87d Add ifdefs for various platforms to check for the support of addchdir.
This will need to be expanded but should support most modern systems.
2025-04-07 21:12:28 -05:00
Calvin Rose
34629ae314 Fix signature on windows. 2025-04-07 21:12:28 -05:00
Calvin Rose
3edc4f35b2 Add :cd argument to os/execute and os/spawn. 2025-04-07 21:12:28 -05:00
Calvin Rose
06d01c099f Merge pull request #1581 from sogaiu/fix-mapcat-and-keep-docstrings
Fix mapcat and keep docstrings
2025-04-07 21:11:14 -05:00
sogaiu
d493eaf485 Fix mapcat and keep docstrings 2025-04-06 20:48:52 +09:00
Calvin Rose
332f123abe Replace if not errorlevel 0 with if errorlevel 1
The former is just bad syntax.
2025-04-05 20:59:45 -05:00
Calvin Rose
38e841fc5c Get rid of test error. 2025-04-05 20:52:47 -05:00
Calvin Rose
e8187fdee5 Fix windows build 2025-04-05 20:26:57 -05:00
Calvin Rose
2fedb67cb3 formatting 2025-04-05 17:32:07 -05:00
Calvin Rose
bdab93c999 Add {:err :out} option to os/spawn.
This special case in the os/spawn interface allows easily
redirecting both stderr and stdout to the same pipe.
2025-04-05 17:30:46 -05:00
Calvin Rose
a9ff8b388f Add os/getpid 2025-04-05 16:20:17 -05:00
Calvin Rose
b12dfd784e Merge pull request #1578 from sogaiu/tweak-some-docstrings
Tweak doc for mapcat, count, keep, all, some
2025-04-04 21:45:09 -05:00
sogaiu
e2cc8f2965 Tweak doc for mapcat, count, keep, all, some 2025-04-05 10:42:04 +09:00
Calvin Rose
17524d2ed3 Merge pull request #1577 from sogaiu/cleanup-unix-domain-socket
Remove unix domain socket after test
2025-04-04 07:11:42 -05:00
sogaiu
d2ee4aa074 Remove unix domain socket after test 2025-04-04 20:22:10 +09:00
Calvin Rose
363e32d455 Adopt docstring from #1574 2025-04-03 20:52:40 -05:00
Calvin Rose
31920e574d Add explicit Alpine Linux directions.
Statically linking Janet is quite useful, especially for users who want
to use jpm and janet-pm to build standalone binaries.
2025-04-01 19:47:03 -05:00
Calvin Rose
cf714ed591 Notify user when script is installed during a bundle installation.
Lets user know what to add to PATH.
2025-04-01 18:52:23 -05:00
Calvin Rose
b458404b41 Update ffi/write to append to a buffer by default. 2025-03-28 10:00:40 -05:00
Calvin Rose
707463a645 Merge pull request #1575 from pyrmont/feature.ev-interrupt2
Expand scope of code that works with `ev/deadline` again
2025-03-26 09:42:51 -05:00
Michael Camilleri
eac37ab869 Avoid leaking memory used for JanetThreadedTimeout struct 2025-03-25 22:02:53 +09:00
Michael Camilleri
a24e5b1eaa Prevent deadline timeout being less than zero 2025-03-25 21:41:59 +09:00
Michael Camilleri
09ac85b1b9 Move janet_timeout_* function definitions before first use 2025-03-25 21:15:28 +09:00
Michael Camilleri
87c1eab7d4 Define JanetTimeout only if event loop enabled 2025-03-25 21:14:21 +09:00
Michael Camilleri
5a29a28c11 Include <windows.h> in state.h 2025-03-25 20:34:45 +09:00
Michael Camilleri
2ed186664f Support using a background thread to monitor deadlines 2025-03-25 20:27:44 +09:00
18 changed files with 397 additions and 94 deletions

View File

@@ -1,6 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.
## Unreleased - ???
- Allow configuring `JANET_THREAD_LOCAL` during builds to allow multi-threading on unknown compilers.
- Make `ffi/write` append to a buffer instead of insert at 0 by default.
- Add `os/getpid` to get the current process id.
- Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes.
Add `interrupt?` argument to `ev/deadline` to use VM interruptions.
## 1.38.0 - 2025-03-18
- Add `bundle/replace`
- Add CLI flags for the `bundle/` module to install and manage bundles.

View File

@@ -165,6 +165,21 @@ make install-jpm-git
Find out more about the available make targets by running `make help`.
### Alpine Linux
To build a statically-linked build of Janet, Alpine Linux + MUSL is a good combination. Janet can also
be built inside a docker container or similar in this manner.
```sh
docker run -it --rm alpine /bin/ash
$ apk add make gcc musl-dev git
$ git clone https://github.com/janet-lang/janet.git
$ cd janet
$ make -j10
$ make test
$ make install
```
### 32-bit Haiku
32-bit Haiku build instructions are the same as the UNIX-like build instructions,

View File

@@ -41,34 +41,34 @@ if not exist build\boot mkdir build\boot
@rem Build the bootstrap interpreter
for %%f in (src\core\*.c) do (
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
)
for %%f in (src\boot\*.c) do (
%JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
)
%JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
build\janet_boot . > build\c\janet.c
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Build the sources
%JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
%JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Build the resources
rc /nologo /fobuild\janet_win.res janet_win.rc
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Link everything to main client
%JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
@rem Build static library (libjanet.lib)
%JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj
@if not errorlevel 0 goto :BUILDFAIL
@if errorlevel 1 goto :BUILDFAIL
echo === Successfully built janet.exe for Windows ===
echo === Run 'build_win test' to run tests. ==
@@ -102,7 +102,7 @@ exit /b 0
:TEST
for %%f in (test/suite*.janet) do (
janet.exe test\%%f
@if not errorlevel 0 goto TESTFAIL
@if errorlevel 1 goto TESTFAIL
)
exit /b 0

View File

@@ -105,6 +105,9 @@ endif
if get_option('arch_name') != ''
conf.set('JANET_ARCH_NAME', get_option('arch_name'))
endif
if get_option('thread_local_prefix') != ''
conf.set('JANET_THREAD_LOCAL', get_option('thread_local_prefix'))
endif
jconf = configure_file(output : 'janetconf.h',
configuration : conf)

View File

@@ -30,6 +30,7 @@ option('max_macro_expand', type : 'integer', min : 1, max : 8000, value : 200)
option('stack_max', type : 'integer', min : 8096, max : 0x7fffffff, value : 0x7fffffff)
option('arch_name', type : 'string', value: '')
option('thread_local_prefix', type : 'string', value: '')
option('os_name', type : 'string', value: '')
option('shared', type : 'boolean', value: true)
option('cryptorand', type : 'boolean', value: true)

View File

@@ -1084,16 +1084,29 @@
(map-aggregator ,maptype ,res (,f x ;call-buffer)))))))
(defn map
`Map a function over every value in a data structure and
return an array of the results.`
```
Map a function `f` over every value in a data structure `ind`
and return an array of results, but only if no `inds` are
provided. Multiple data structures can be handled if each
`inds` is a data structure and `f` is a function of arity
one more than the number of `inds`. The resulting array has
a length that is the shortest of `ind` and each of `inds`.
```
[f ind & inds]
(def res @[])
(map-template :map res f ind inds)
res)
(defn mapcat
``Map a function over every element in an array or tuple and
use `array/concat` to concatenate the results.``
```
Map a function `f` over every value in a data structure `ind`
and use `array/concat` to concatenate the results, but only if
no `inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `f` is a function of
arity one more than the number of `inds`. Note that `f` is only
applied to values at indeces up to the largest index of the
shortest of `ind` and each of `inds`.
```
[f ind & inds]
(def res @[])
(map-template :mapcat res f ind inds)
@@ -1110,18 +1123,30 @@
res)
(defn count
``Count the number of items in `ind` for which `(pred item)`
is true.``
```
Count the number of values in a data structure `ind` for which
applying `pred` yields a truthy value, but only if no `inds` are
provided. Multiple data structures can be handled if each `inds`
is a data structure and `pred` is a function of arity one more
than the number of `inds`. Note that `pred` is only applied to
values at indeces up to the largest index of the shortest of
`ind` and each of `inds`.
```
[pred ind & inds]
(var res 0)
(map-template :count res pred ind inds)
res)
(defn keep
``Given a predicate `pred`, return a new array containing the truthy results
of applying `pred` to each element in the indexed collection `ind`. This is
different from `filter` which returns an array of the original elements where
the predicate is truthy.``
```
Given a predicate `pred`, return a new array containing the
truthy results of applying `pred` to each value in the data
structure `ind`, but only if no `inds` are provided. Multiple
data structures can be handled if each `inds` is a data
structure and `pred` is a function of arity one more than the
number of `inds`. The resulting array has a length that is no
longer than the shortest of `ind` and each of `inds`.
```
[pred ind & inds]
(def res @[])
(map-template :keep res pred ind inds)
@@ -1908,7 +1933,7 @@
that will match any value without creating a binding.
While a symbol pattern will ordinarily match any value, the pattern `(@ <sym>)`,
where <sym> is any symbol, will attempt to match `x` against a value
where `<sym>` is any symbol, will attempt to match `x` against a value
already bound to `<sym>`, rather than matching and rebinding it.
Any other value pattern will only match if it is equal to `x`.
@@ -2203,17 +2228,32 @@
ret)
(defn all
``Returns true if `(pred item)` is truthy for every item in `ind`.
Otherwise, returns the first falsey result encountered.
Returns true if `ind` is empty.``
```
Returns true if applying `pred` to every value in a data
structure `ind` results in only truthy values, but only if no
`inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `pred` is a function
of arity one more than the number of `inds`. Returns the first
falsey result encountered. Note that `pred` is only called as
many times as the length of the shortest of `ind` and each of
`inds`. If `ind` or any of `inds` are empty, returns true.
```
[pred ind & inds]
(var res true)
(map-template :all res pred ind inds)
res)
(defn some
``Returns nil if `(pred item)` is false or nil for every item in `ind`.
Otherwise, returns the first truthy result encountered.``
```
Returns nil if applying `pred` to every value in a data
structure `ind` results in only falsey values, but only if no
`inds` are provided. Multiple data structures can be handled
if each `inds` is a data structure and `pred` is a function
of arity one more than the number of `inds`. Returns the first
truthy result encountered. Note that `pred` is only called as
many times as the length of the shortest of `ind` and each of
`inds`. If `ind` or any of `inds` are empty, returns nil.
```
[pred ind & inds]
(var res nil)
(map-template :some res pred ind inds)
@@ -2534,7 +2574,7 @@
* `:env` -- the environment to compile against - default is the current env
* `:source` -- source path for better errors (use keywords for non-paths) - default
is :<anonymous>
is `:<anonymous>`
* `:on-compile-error` -- callback when compilation fails - default is bad-compile
@@ -4314,6 +4354,9 @@
(when check
(do-hook module bundle-name :check man)))
(print "installed " bundle-name)
(when (get man :has-bin-script)
(def binpath (string (dyn *syspath*) s "bin"))
(eprintf "executable scripts have been installed to %s" binpath))
bundle-name)
(defn- bundle/pack
@@ -4424,7 +4467,7 @@
(defn bundle/add
"Add files and directories during a bundle install relative to `(dyn *syspath*)`.
Added paths will be recorded in the bundle manifest such that they are properly tracked
Added files and directories will be recorded in the bundle manifest such that they are properly tracked
and removed during an upgrade or uninstall."
[manifest src &opt dest chmod-mode]
(default dest src)
@@ -4440,13 +4483,16 @@
(errorf "bad path %s - file is a %s" src mode)))
(defn bundle/add-bin
`Shorthand for adding scripts during an install. Scripts will be installed to
(string (dyn *syspath*) "/bin") by default and will be set to be executable.`
``
Shorthand for adding scripts during an install. Scripts will be installed to
`(string (dyn *syspath*) "/bin")` by default and will be set to be executable.
``
[manifest src &opt dest chmod-mode]
(def s (sep))
(default dest (last (string/split s src)))
(default chmod-mode 8r755)
(os/mkdir (string (dyn *syspath*) s "bin"))
(put manifest :has-bin-script true)
(bundle/add-file manifest src (string "bin" s dest) chmod-mode))
(defn bundle/update-all

View File

@@ -13,6 +13,7 @@
/* These settings all affect linking, so use cautiously. */
/* #define JANET_SINGLE_THREADED */
/* #define JANET_THREAD_LOCAL _Thread_local */
/* #define JANET_NO_DYNAMIC_MODULES */
/* #define JANET_NO_NANBOX */
/* #define JANET_API __attribute__((visibility ("default"))) */

View File

@@ -589,6 +589,16 @@ JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) {
#endif
}
JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x) {
#ifdef _MSC_VER
return _InterlockedOr(x, 0);
#elif defined(JANET_USE_STDATOMIC)
return atomic_load_explicit(x, memory_order_relaxed);
#else
return __atomic_load_n(x, __ATOMIC_RELAXED);
#endif
}
/* Some definitions for function-like macros */
JANET_API JanetStructHead *(janet_struct_head)(JanetStruct st) {

View File

@@ -112,6 +112,13 @@ typedef struct {
JanetHandle write_pipe;
} JanetEVThreadInit;
/* Structure used to initialize threads that run timeouts */
typedef struct {
double sec;
JanetVM *vm;
JanetFiber *fiber;
} JanetThreadedTimeout;
#define JANET_MAX_Q_CAPACITY 0x7FFFFFF
static void janet_q_init(JanetQueue *q) {
@@ -346,21 +353,22 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod
static void janet_stream_close_impl(JanetStream *stream) {
stream->flags |= JANET_STREAM_CLOSED;
int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE);
#ifdef JANET_WINDOWS
if (stream->handle != INVALID_HANDLE_VALUE) {
#ifdef JANET_NET
if (stream->flags & JANET_STREAM_SOCKET) {
closesocket((SOCKET) stream->handle);
if (canclose) closesocket((SOCKET) stream->handle);
} else
#endif
{
CloseHandle(stream->handle);
if (canclose) CloseHandle(stream->handle);
}
stream->handle = INVALID_HANDLE_VALUE;
}
#else
if (stream->handle != -1) {
close(stream->handle);
if (canclose) close(stream->handle);
stream->handle = -1;
#ifdef JANET_EV_POLL
uint32_t i = stream->index;
@@ -623,6 +631,7 @@ void janet_addtimeout(double sec) {
to.curr_fiber = NULL;
to.sched_id = fiber->sched_id;
to.is_error = 1;
to.has_worker = 0;
add_timeout(to);
}
@@ -635,9 +644,68 @@ void janet_addtimeout_nil(double sec) {
to.curr_fiber = NULL;
to.sched_id = fiber->sched_id;
to.is_error = 0;
to.has_worker = 0;
add_timeout(to);
}
#ifdef JANET_WINDOWS
static VOID CALLBACK janet_timeout_stop(ULONG_PTR ptr) {
UNREFERENCED_PARAMETER(ptr);
ExitThread(0);
}
#elif JANET_ANDROID
static void janet_timeout_stop(int sig_num) {
if (sig_num == SIGUSR1) {
pthread_exit(0);
}
}
#endif
static void janet_timeout_cb(JanetEVGenericMessage msg) {
(void) msg;
janet_interpreter_interrupt_handled(&janet_vm);
}
#ifdef JANET_WINDOWS
static DWORD WINAPI janet_timeout_body(LPVOID ptr) {
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr);
SleepEx((DWORD)(tto.sec * 1000), TRUE);
if (janet_fiber_can_resume(tto.fiber)) {
janet_interpreter_interrupt(tto.vm);
JanetEVGenericMessage msg = {0};
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
}
return 0;
}
#else
static void *janet_timeout_body(void *ptr) {
#ifdef JANET_ANDROID
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = &janet_timeout_stop;
sigaction(SIGUSR1, &action, NULL);
#endif
JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr;
janet_free(ptr);
struct timespec ts;
ts.tv_sec = (time_t) tto.sec;
ts.tv_nsec = (tto.sec <= UINT32_MAX)
? (long)((tto.sec - ((uint32_t)tto.sec)) * 1000000000)
: 0;
nanosleep(&ts, &ts);
if (janet_fiber_can_resume(tto.fiber)) {
janet_interpreter_interrupt(tto.vm);
JanetEVGenericMessage msg = {0};
janet_ev_post_event(tto.vm, janet_timeout_cb, msg);
}
return NULL;
}
#endif
void janet_ev_inc_refcount(void) {
janet_atomic_inc(&janet_vm.listener_count);
}
@@ -1389,7 +1457,7 @@ JanetFiber *janet_loop1(void) {
/* Run scheduled fibers unless interrupts need to be handled. */
while (janet_vm.spawn.head != janet_vm.spawn.tail) {
/* Don't run until all interrupts have been marked as handled by calling janet_interpreter_interrupt_handled */
if (janet_vm.auto_suspend) break;
if (janet_atomic_load_relaxed(&janet_vm.auto_suspend)) break;
JanetTask task = {NULL, janet_wrap_nil(), JANET_SIGNAL_OK, 0};
janet_q_pop(&janet_vm.spawn, &task, sizeof(task));
if (task.fiber->gc.flags & JANET_FIBER_EV_FLAG_SUSPENDED) janet_ev_dec_refcount();
@@ -1431,6 +1499,21 @@ JanetFiber *janet_loop1(void) {
while ((has_timeout = peek_timeout(&to))) {
if (to.curr_fiber != NULL) {
if (!janet_fiber_can_resume(to.curr_fiber)) {
if (to.has_worker) {
#ifdef JANET_WINDOWS
QueueUserAPC(janet_timeout_stop, to.worker, 0);
WaitForSingleObject(to.worker, INFINITE);
CloseHandle(to.worker);
#else
#ifdef JANET_ANDROID
pthread_kill(to.worker, SIGUSR1);
#else
pthread_cancel(to.worker);
#endif
void *res;
pthread_join(to.worker, &res);
#endif
}
janet_table_remove(&janet_vm.active_tasks, janet_wrap_fiber(to.curr_fiber));
pop_timeout(0);
continue;
@@ -2932,7 +3015,8 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
uint32_t count1;
memcpy(&count1, nextbytes, sizeof(count1));
size_t count = (size_t) count1;
if (count > (endbytes - nextbytes) * sizeof(JanetCFunRegistry)) {
/* Use division to avoid overflowing size_t */
if (count > (endbytes - nextbytes - sizeof(count1)) / sizeof(JanetCFunRegistry)) {
janet_panic("thread message invalid");
}
janet_vm.registry_count = count;
@@ -3103,26 +3187,57 @@ JANET_CORE_FN(cfun_ev_sleep,
}
JANET_CORE_FN(cfun_ev_deadline,
"(ev/deadline sec &opt tocancel tocheck)",
"Schedules the event loop to try to cancel the `tocancel` "
"task as with `ev/cancel`. After `sec` seconds, the event "
"loop will attempt cancellation of `tocancel` if the "
"`tocheck` fiber is resumable. `sec` is a number that can "
"have a fractional part. `tocancel` defaults to "
"`(fiber/root)`, but if specified, must be a task (root "
"fiber). `tocheck` defaults to `(fiber/current)`, but if "
"specified, should be a fiber. Returns `tocancel` "
"immediately.") {
janet_arity(argc, 1, 3);
"(ev/deadline sec &opt tocancel tocheck intr?)",
"Schedules the event loop to try to cancel the `tocancel` task as with `ev/cancel`. "
"After `sec` seconds, the event loop will attempt cancellation of `tocancel` if the "
"`tocheck` fiber is resumable. `sec` is a number that can have a fractional part. "
"`tocancel` defaults to `(fiber/root)`, but if specified, must be a task (root "
"fiber). `tocheck` defaults to `(fiber/current)`, but if specified, must be a fiber. "
"Returns `tocancel` immediately. If `interrupt?` is set to true, will create a "
"background thread to try to interrupt the VM if the timeout expires.") {
janet_arity(argc, 1, 4);
double sec = janet_getnumber(argv, 0);
sec = (sec < 0) ? 0 : sec;
JanetFiber *tocancel = janet_optfiber(argv, argc, 1, janet_vm.root_fiber);
JanetFiber *tocheck = janet_optfiber(argv, argc, 2, janet_vm.fiber);
int use_interrupt = janet_optboolean(argv, argc, 3, 0);
JanetTimeout to;
to.when = ts_delta(ts_now(), sec);
to.fiber = tocancel;
to.curr_fiber = tocheck;
to.is_error = 0;
to.sched_id = to.fiber->sched_id;
if (use_interrupt) {
#ifdef JANET_ANDROID
janet_sandbox_assert(JANET_SANDBOX_SIGNAL);
#endif
JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout));
if (NULL == tto) {
JANET_OUT_OF_MEMORY;
}
tto->sec = sec;
tto->vm = &janet_vm;
tto->fiber = tocheck;
#ifdef JANET_WINDOWS
HANDLE worker = CreateThread(NULL, 0, janet_timeout_body, tto, 0, NULL);
if (NULL == worker) {
janet_free(tto);
janet_panic("failed to create thread");
}
#else
pthread_t worker;
int err = pthread_create(&worker, NULL, janet_timeout_body, tto);
if (err) {
janet_free(tto);
janet_panicf("%s", janet_strerror(err));
}
janet_assert(!pthread_detach(worker), "pthread_detach");
#endif
to.has_worker = 1;
to.worker = worker;
} else {
to.has_worker = 0;
}
add_timeout(to);
return janet_wrap_fiber(tocancel);
}

View File

@@ -1686,7 +1686,7 @@ JANET_CORE_FN(cfun_ffi_buffer_write,
JanetFFIType type = decode_ffi_type(argv[0]);
uint32_t el_size = (uint32_t) type_size(type);
JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, el_size);
int32_t index = janet_optnat(argv, argc, 3, 0);
int32_t index = janet_optnat(argv, argc, 3, buffer->count);
int32_t old_count = buffer->count;
if (index > old_count) janet_panic("index out of bounds");
buffer->count = index;

View File

@@ -599,33 +599,33 @@ JANET_CORE_FN(cfun_filewatch_make,
JANET_CORE_FN(cfun_filewatch_add,
"(filewatch/add watcher path &opt flags)",
"Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n"
"Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n"
"Windows/MINGW (flags correspond to `FILE_NOTIFY_CHANGE_*` flags in win32 documentation):\n\n"
"* `:all` - trigger an event for all of the below triggers.\n\n"
"* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n"
"* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n"
"* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n"
"* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n"
"* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n"
"* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n"
"* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n"
"* `:attributes` - `FILE_NOTIFY_CHANGE_ATTRIBUTES`\n\n"
"* `:creation` - `FILE_NOTIFY_CHANGE_CREATION`\n\n"
"* `:dir-name` - `FILE_NOTIFY_CHANGE_DIR_NAME`\n\n"
"* `:last-access` - `FILE_NOTIFY_CHANGE_LAST_ACCESS`\n\n"
"* `:last-write` - `FILE_NOTIFY_CHANGE_LAST_WRITE`\n\n"
"* `:security` - `FILE_NOTIFY_CHANGE_SECURITY`\n\n"
"* `:size` - `FILE_NOTIFY_CHANGE_SIZE`\n\n"
"* `:recursive` - watch subdirectories recursively\n\n"
"Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n"
"* `:access` - IN_ACCESS\n\n"
"* `:all` - IN_ALL_EVENTS\n\n"
"* `:attrib` - IN_ATTRIB\n\n"
"* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n"
"* `:close-write` - IN_CLOSE_WRITE\n\n"
"* `:create` - IN_CREATE\n\n"
"* `:delete` - IN_DELETE\n\n"
"* `:delete-self` - IN_DELETE_SELF\n\n"
"* `:ignored` - IN_IGNORED\n\n"
"* `:modify` - IN_MODIFY\n\n"
"* `:move-self` - IN_MOVE_SELF\n\n"
"* `:moved-from` - IN_MOVED_FROM\n\n"
"* `:moved-to` - IN_MOVED_TO\n\n"
"* `:open` - IN_OPEN\n\n"
"* `:q-overflow` - IN_Q_OVERFLOW\n\n"
"* `:unmount` - IN_UNMOUNT\n\n\n"
"Linux (flags correspond to `IN_*` flags from <sys/inotify.h>):\n\n"
"* `:access` - `IN_ACCESS`\n\n"
"* `:all` - `IN_ALL_EVENTS`\n\n"
"* `:attrib` - `IN_ATTRIB`\n\n"
"* `:close-nowrite` - `IN_CLOSE_NOWRITE`\n\n"
"* `:close-write` - `IN_CLOSE_WRITE`\n\n"
"* `:create` - `IN_CREATE`\n\n"
"* `:delete` - `IN_DELETE`\n\n"
"* `:delete-self` - `IN_DELETE_SELF`\n\n"
"* `:ignored` - `IN_IGNORED`\n\n"
"* `:modify` - `IN_MODIFY`\n\n"
"* `:move-self` - `IN_MOVE_SELF`\n\n"
"* `:moved-from` - `IN_MOVED_FROM`\n\n"
"* `:moved-to` - `IN_MOVED_TO`\n\n"
"* `:open` - `IN_OPEN`\n\n"
"* `:q-overflow` - `IN_Q_OVERFLOW`\n\n"
"* `:unmount` - `IN_UNMOUNT`\n\n\n"
"On Windows, events will have the following possible types:\n\n"
"* `:unknown`\n\n"
"* `:added`\n\n"

View File

@@ -657,7 +657,7 @@ JANET_CORE_FN(cfun_net_listen,
"The type parameter specifies the type of network connection, either "
"a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is "
":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will "
"disable the use of SO_REUSEADDR and SO_REUSEPORT when creating a server on some operating systems.") {
"disable the use of `SO_REUSEADDR` and `SO_REUSEPORT` when creating a server on some operating systems.") {
janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN);
janet_arity(argc, 2, 4);

View File

@@ -55,6 +55,7 @@
#include <sys/utime.h>
#include <io.h>
#include <process.h>
#define JANET_SPAWN_CHDIR
#else
#include <spawn.h>
#include <utime.h>
@@ -73,6 +74,20 @@ extern char **environ;
#endif
#endif
/* Detect availability of posix_spawn_file_actions_addchdir_np. Since
* this doesn't seem to follow any standard, just a common extension, we
* must enumerate supported systems for availability. Define JANET_SPAWN_NO_CHDIR
* to disable this. */
#ifndef JANET_SPAWN_NO_CHDIR
#ifdef __GLIBC__
#define JANET_SPAWN_CHDIR
#elif defined(JANET_APPLE) /* Some older versions may not work here. */
#define JANET_SPAWN_CHDIR
#elif defined(__FreeBSD__) /* Not all BSDs work, for example openBSD doesn't seem to support this */
#define JANET_SPAWN_CHDIR
#endif
#endif
/* Not POSIX, but all Unixes but Solaris have this function. */
#if defined(JANET_POSIX) && !defined(__sun)
time_t timegm(struct tm *tm);
@@ -813,6 +828,19 @@ JANET_CORE_FN(os_proc_close,
#endif
}
JANET_CORE_FN(os_proc_getpid,
"(os/getpid)",
"Get the process ID of the current process.") {
janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS);
janet_fixarity(argc, 0);
(void) argv;
#ifdef JANET_WINDOWS
return janet_wrap_number((double) _getpid());
#else
return janet_wrap_number((double) getpid());
#endif
}
static void swap_handles(JanetHandle *handles) {
JanetHandle temp = handles[0];
handles[0] = handles[1];
@@ -1137,6 +1165,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL;
JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
int stderr_is_stdout = 0;
int pipe_errflag = 0; /* Track errors setting up pipes */
int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0;
@@ -1161,11 +1190,28 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) {
new_err = make_pipes(&pipe_err, 0, &pipe_errflag);
pipe_owner_flags |= JANET_PROC_OWNS_STDERR;
} else if (is_spawn && janet_keyeq(maybe_stderr, "out")) {
stderr_is_stdout = 1;
} else if (!janet_checktype(maybe_stderr, JANET_NIL)) {
new_err = janet_getjstream(&maybe_stderr, 0, &orig_err);
}
}
/* Optional working directory. Available for both os/execute and os/spawn. */
const char *chdir_path = NULL;
if (argc > 2) {
JanetDictView tab = janet_getdictionary(argv, 2);
Janet workdir = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("cd"));
if (janet_checktype(workdir, JANET_STRING)) {
chdir_path = (const char *) janet_unwrap_string(workdir);
#ifndef JANET_SPAWN_CHDIR
janet_panicf(":cd argument not supported on this system - %s", chdir_path);
#endif
} else if (!janet_checktype(workdir, JANET_NIL)) {
janet_panicf("expected string for :cd argumnet, got %v", workdir);
}
}
/* Clean up if any of the pipes have any issues */
if (pipe_errflag) {
if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in);
@@ -1180,6 +1226,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo;
LPCSTR lpCurrentDirectory = NULL;
memset(&saAttr, 0, sizeof(saAttr));
memset(&processInfo, 0, sizeof(processInfo));
memset(&startupInfo, 0, sizeof(startupInfo));
@@ -1196,6 +1243,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
}
const char *path = (const char *) janet_unwrap_string(exargs.items[0]);
if (chdir_path != NULL) {
lpCurrentDirectory = chdir_path;
}
/* Do IO redirection */
if (pipe_in != JANET_HANDLE_NONE) {
@@ -1203,7 +1254,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_in != JANET_HANDLE_NONE) {
startupInfo.hStdInput = new_in;
} else {
startupInfo.hStdInput = (HANDLE) _get_osfhandle(0);
startupInfo.hStdInput = (HANDLE) _get_osfhandle(_fileno(stdin));
}
if (pipe_out != JANET_HANDLE_NONE) {
@@ -1211,15 +1262,17 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_out != JANET_HANDLE_NONE) {
startupInfo.hStdOutput = new_out;
} else {
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(1);
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(_fileno(stdout));
}
if (pipe_err != JANET_HANDLE_NONE) {
startupInfo.hStdError = pipe_err;
} else if (new_err != NULL) {
startupInfo.hStdError = new_err;
} else if (stderr_is_stdout) {
startupInfo.hStdError = startupInfo.hStdOutput;
} else {
startupInfo.hStdError = (HANDLE) _get_osfhandle(2);
startupInfo.hStdError = (HANDLE) _get_osfhandle(_fileno(stderr));
}
int cp_failed = 0;
@@ -1230,7 +1283,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
TRUE, /* handle inheritance */
0, /* flags */
use_environ ? NULL : envp, /* pass in environment */
NULL, /* use parents starting directory */
lpCurrentDirectory,
&startupInfo,
&processInfo)) {
cp_failed = 1;
@@ -1287,6 +1340,15 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
/* Posix spawn setup */
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
#ifdef JANET_SPAWN_CHDIR
if (chdir_path != NULL) {
#ifdef JANET_SPAWN_CHDIR_NO_NP
posix_spawn_file_actions_addchdir(&actions, chdir_path);
#else
posix_spawn_file_actions_addchdir_np(&actions, chdir_path);
#endif
}
#endif
if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
posix_spawn_file_actions_addclose(&actions, pipe_in);
@@ -1309,6 +1371,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) {
} else if (new_err != JANET_HANDLE_NONE && new_err != 2) {
posix_spawn_file_actions_adddup2(&actions, new_err, 2);
posix_spawn_file_actions_addclose(&actions, new_err);
} else if (stderr_is_stdout) {
posix_spawn_file_actions_adddup2(&actions, 1, 2);
}
pid_t pid;
@@ -1414,7 +1478,8 @@ JANET_CORE_FN(os_spawn,
"`:pipe` may fail if there are too many active file descriptors. The caller is "
"responsible for closing pipes created by `:pipe` (either individually or using "
"`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring "
"pipes do not cause the program to block and deadlock.") {
"pipes do not cause the program to block and deadlock. As a special case, the stream passed to `:err` "
"can be the keyword `:out` to redirect stderr to stdout in the subprocess.") {
return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN);
}
@@ -2797,6 +2862,7 @@ void janet_lib_os(JanetTable *env) {
JANET_CORE_REG("os/proc-wait", os_proc_wait),
JANET_CORE_REG("os/proc-kill", os_proc_kill),
JANET_CORE_REG("os/proc-close", os_proc_close),
JANET_CORE_REG("os/getpid", os_proc_getpid),
#endif
/* high resolution timers */

View File

@@ -27,7 +27,9 @@
#include <stdint.h>
#ifdef JANET_EV
#ifndef JANET_WINDOWS
#ifdef JANET_WINDOWS
#include <windows.h>
#else
#include <pthread.h>
#endif
#endif
@@ -53,13 +55,21 @@ typedef struct {
void *data;
} JanetQueue;
#ifdef JANET_EV
typedef struct {
JanetTimestamp when;
JanetFiber *fiber;
JanetFiber *curr_fiber;
uint32_t sched_id;
int is_error;
int has_worker;
#ifdef JANET_WINDOWS
HANDLE worker;
#else
pthread_t worker;
#endif
} JanetTimeout;
#endif
/* Registry table for C functions - contains metadata that can
* be looked up by cfunction pointer. All strings here are pointing to

View File

@@ -115,7 +115,7 @@
#define vm_maybe_auto_suspend(COND)
#else
#define vm_maybe_auto_suspend(COND) do { \
if ((COND) && janet_vm.auto_suspend) { \
if ((COND) && janet_atomic_load_relaxed(&janet_vm.auto_suspend)) { \
fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \
vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \
} \

View File

@@ -67,6 +67,11 @@ extern "C" {
#define JANET_LINUX 1
#endif
/* Check for Android */
#ifdef __ANDROID__
#define JANET_ANDROID 1
#endif
/* Check for Cygwin */
#if defined(__CYGWIN__)
#define JANET_CYGWIN 1
@@ -165,14 +170,12 @@ extern "C" {
/* Also enable the thread library only if not single-threaded */
#ifdef JANET_SINGLE_THREADED
#define JANET_THREAD_LOCAL
#undef JANET_THREADS
#elif defined(__GNUC__)
#elif !(defined(JANET_THREAD_LOCAL)) && defined(__GNUC__)
#define JANET_THREAD_LOCAL __thread
#elif defined(_MSC_BUILD)
#elif !(defined(JANET_THREAD_LOCAL)) && defined(_MSC_BUILD)
#define JANET_THREAD_LOCAL __declspec(thread)
#else
#elif !(defined(JANET_THREAD_LOCAL))
#define JANET_THREAD_LOCAL
#undef JANET_THREADS
#endif
/* Enable or disable dynamic module loading. Enabled by default. */
@@ -591,6 +594,7 @@ typedef void *JanetAbstract;
#define JANET_STREAM_WRITABLE 0x400
#define JANET_STREAM_ACCEPTABLE 0x800
#define JANET_STREAM_UDPSERVER 0x1000
#define JANET_STREAM_NOT_CLOSEABLE 0x2000
#define JANET_STREAM_TOCLOSE 0x10000
typedef enum {
@@ -663,6 +667,7 @@ typedef int32_t JanetAtomicInt;
JANET_API JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x);
JANET_API JanetAtomicInt janet_atomic_load_relaxed(JanetAtomicInt volatile *x);
/* We provide three possible implementations of Janets. The preferred
* nanboxing approach, for 32 or 64 bits, and the standard C version. Code in the rest of the

View File

@@ -430,13 +430,7 @@
# Now do our telnet chat
(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")
(do
(def fbob (ev/to-file bob))
(file/write fbob "bob")
(file/flush fbob)
(:close fbob)))
(net/write bob "bob")
(expect-read bob "Welcome bob\n")
(def alice (assert (net/connect test-host test-port)))
(expect-read alice "Whats your name?\n")
@@ -501,8 +495,10 @@
# soreuseport on unix domain sockets
(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))))
(let [uds-path "./unix-domain-socket"]
(defer (os/rm uds-path)
(let [s (net/listen :unix uds-path :stream)]
(:close s))))))
# net/accept-loop level triggering
(gccollect)
@@ -550,4 +546,25 @@
(ev/sleep 0.15)
(assert (not terminated-normally) "early termination failure 3"))
(let [f (coro (forever :foo))]
(ev/deadline 0.01 nil f true)
(assert-error "deadline expired" (resume f)))
# Use :err :stdout
(def- subproc-code '(do (eprint "hi") (eflush) (print "there") (flush)))
(defn ev/slurp
[f &opt buf]
(default buf @"")
(if (ev/read f 0x10000 buf)
(ev/slurp f buf)
buf))
(def p (os/spawn [;run janet "-e" (string/format "%j" subproc-code)] :px {:out :pipe :err :out}))
(def [exit-code data]
(ev/gather
(os/proc-wait p)
(ev/slurp (p :out))))
(def data (string/replace-all "\r" "" data))
(assert (zero? exit-code) "subprocess ran")
(assert (= data "hi\nthere\n") "output is correct")
(end-suite)

View File

@@ -55,4 +55,11 @@
(compwhen has-ffi
(assert-error "bad struct issue #1512" (ffi/struct :void)))
(compwhen has-ffi
(def buf @"")
(ffi/write :u8 10 buf)
(assert (= 1 (length buf)))
(ffi/write :u8 10 buf)
(assert (= 2 (length buf))))
(end-suite)