diff options
| author | Philipp Stephani | 2020-03-26 17:22:25 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2020-03-26 21:47:25 +0100 |
| commit | d28b00476890f791a89b65007e5f20682b3eaa0d (patch) | |
| tree | 3bb04c984ed5b74e661291b71579fe8d04070f69 | |
| parent | 934b3c9ecc2b91723b9e5826080424ec1a90f264 (diff) | |
| download | emacs-d28b00476890f791a89b65007e5f20682b3eaa0d.tar.gz emacs-d28b00476890f791a89b65007e5f20682b3eaa0d.zip | |
Add a module function to open a file descriptor connected to a pipe.
A common complaint about the module API is that modules can't
communicate asynchronously with Emacs. While it isn't possible to
call arbitrary Emacs functions asynchronously, writing to a pipe
should always be fine and is a pretty low-hanging fruit.
This patch implements a function that adapts an existing pipe
process. That way, users can use familiar tools like process filters
or 'accept-process-output'.
* src/module-env-28.h: Add 'open_channel' module function.
* src/emacs-module.c (module_open_channel): Provide definition for
'open_channel'.
(initialize_environment): Use it.
* src/process.c (open_channel_for_module): New helper function.
(syms_of_process): Define necessary symbol.
* test/src/emacs-module-tests.el (module/async-pipe): New unit test.
* test/data/emacs-module/mod-test.c (signal_system_error): New helper
function.
(signal_errno): Use it.
(write_to_pipe): New function running in the background.
(Fmod_test_async_pipe): New test module function.
(emacs_module_init): Export it.
* doc/lispref/internals.texi (Module Misc): Document new module
function.
* doc/lispref/processes.texi (Asynchronous Processes): New anchor
for pipe processes.
* etc/NEWS: Document 'open_channel' function.
| -rw-r--r-- | doc/lispref/internals.texi | 14 | ||||
| -rw-r--r-- | doc/lispref/processes.texi | 1 | ||||
| -rw-r--r-- | etc/NEWS | 4 | ||||
| -rw-r--r-- | src/emacs-module.c | 9 | ||||
| -rw-r--r-- | src/module-env-28.h | 3 | ||||
| -rw-r--r-- | src/process.c | 12 | ||||
| -rw-r--r-- | src/process.h | 2 | ||||
| -rw-r--r-- | test/data/emacs-module/mod-test.c | 57 | ||||
| -rw-r--r-- | test/src/emacs-module-tests.el | 14 |
9 files changed, 114 insertions, 2 deletions
diff --git a/doc/lispref/internals.texi b/doc/lispref/internals.texi index 442f6d156b6..0c24dac7775 100644 --- a/doc/lispref/internals.texi +++ b/doc/lispref/internals.texi | |||
| @@ -2022,6 +2022,20 @@ variable values and buffer content may have been modified in arbitrary | |||
| 2022 | ways. | 2022 | ways. |
| 2023 | @end deftypefn | 2023 | @end deftypefn |
| 2024 | 2024 | ||
| 2025 | @anchor{open_channel} | ||
| 2026 | @deftypefun int open_channel (emacs_env *@var{env}, emacs_value @var{pipe_process}) | ||
| 2027 | This function, which is available since Emacs 27, opens a channel to | ||
| 2028 | an existing pipe process. @var{pipe_process} must refer to an | ||
| 2029 | existing pipe process created by @code{make-pipe-process}. @ref{Pipe | ||
| 2030 | Processes}. If successful, the return value will be a new file | ||
| 2031 | descriptor that you can use to write to the pipe. Unlike all other | ||
| 2032 | module functions, you can use the returned file descriptor from | ||
| 2033 | arbitrary threads, even if no module environment is active. You can | ||
| 2034 | use the @code{write} function to write to the file descriptor. Once | ||
| 2035 | done, close the file descriptor using @code{close}. @ref{Low-Level | ||
| 2036 | I/O,,,libc}. | ||
| 2037 | @end deftypefun | ||
| 2038 | |||
| 2025 | @node Module Nonlocal | 2039 | @node Module Nonlocal |
| 2026 | @subsection Nonlocal Exits in Modules | 2040 | @subsection Nonlocal Exits in Modules |
| 2027 | @cindex nonlocal exits, in modules | 2041 | @cindex nonlocal exits, in modules |
diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi index f515213615e..14cd079c563 100644 --- a/doc/lispref/processes.texi +++ b/doc/lispref/processes.texi | |||
| @@ -743,6 +743,7 @@ Some file name handlers may not support @code{make-process}. In such | |||
| 743 | cases, this function does nothing and returns @code{nil}. | 743 | cases, this function does nothing and returns @code{nil}. |
| 744 | @end defun | 744 | @end defun |
| 745 | 745 | ||
| 746 | @anchor{Pipe Processes} | ||
| 746 | @defun make-pipe-process &rest args | 747 | @defun make-pipe-process &rest args |
| 747 | This function creates a bidirectional pipe which can be attached to a | 748 | This function creates a bidirectional pipe which can be attached to a |
| 748 | child process. This is useful with the @code{:stderr} keyword of | 749 | child process. This is useful with the @code{:stderr} keyword of |
| @@ -258,6 +258,10 @@ called when the function object is garbage-collected. Use | |||
| 258 | 'set_function_finalizer' to set the finalizer and | 258 | 'set_function_finalizer' to set the finalizer and |
| 259 | 'get_function_finalizer' to retrieve it. | 259 | 'get_function_finalizer' to retrieve it. |
| 260 | 260 | ||
| 261 | ** Modules can now open a channel to an existing pipe process using | ||
| 262 | the new module function 'open_channel'. Modules can use this | ||
| 263 | functionality to asynchronously send data back to Emacs. | ||
| 264 | |||
| 261 | ** 'file-modes', 'set-file-modes', and 'set-file-times' now have an | 265 | ** 'file-modes', 'set-file-modes', and 'set-file-times' now have an |
| 262 | optional argument specifying whether to follow symbolic links. | 266 | optional argument specifying whether to follow symbolic links. |
| 263 | 267 | ||
diff --git a/src/emacs-module.c b/src/emacs-module.c index 60f16418efa..cdcbe061b53 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c | |||
| @@ -88,6 +88,7 @@ To add a new module function, proceed as follows: | |||
| 88 | #include "dynlib.h" | 88 | #include "dynlib.h" |
| 89 | #include "coding.h" | 89 | #include "coding.h" |
| 90 | #include "keyboard.h" | 90 | #include "keyboard.h" |
| 91 | #include "process.h" | ||
| 91 | #include "syssignal.h" | 92 | #include "syssignal.h" |
| 92 | #include "sysstdio.h" | 93 | #include "sysstdio.h" |
| 93 | #include "thread.h" | 94 | #include "thread.h" |
| @@ -977,6 +978,13 @@ module_make_big_integer (emacs_env *env, int sign, | |||
| 977 | return lisp_to_value (env, make_integer_mpz ()); | 978 | return lisp_to_value (env, make_integer_mpz ()); |
| 978 | } | 979 | } |
| 979 | 980 | ||
| 981 | static int | ||
| 982 | module_open_channel (emacs_env *env, emacs_value pipe_process) | ||
| 983 | { | ||
| 984 | MODULE_FUNCTION_BEGIN (-1); | ||
| 985 | return open_channel_for_module (value_to_lisp (pipe_process)); | ||
| 986 | } | ||
| 987 | |||
| 980 | 988 | ||
| 981 | /* Subroutines. */ | 989 | /* Subroutines. */ |
| 982 | 990 | ||
| @@ -1391,6 +1399,7 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv) | |||
| 1391 | env->make_big_integer = module_make_big_integer; | 1399 | env->make_big_integer = module_make_big_integer; |
| 1392 | env->get_function_finalizer = module_get_function_finalizer; | 1400 | env->get_function_finalizer = module_get_function_finalizer; |
| 1393 | env->set_function_finalizer = module_set_function_finalizer; | 1401 | env->set_function_finalizer = module_set_function_finalizer; |
| 1402 | env->open_channel = module_open_channel; | ||
| 1394 | Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); | 1403 | Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); |
| 1395 | return env; | 1404 | return env; |
| 1396 | } | 1405 | } |
diff --git a/src/module-env-28.h b/src/module-env-28.h index a2479a8f744..5d884c148c4 100644 --- a/src/module-env-28.h +++ b/src/module-env-28.h | |||
| @@ -9,3 +9,6 @@ | |||
| 9 | void (*set_function_finalizer) (emacs_env *env, emacs_value arg, | 9 | void (*set_function_finalizer) (emacs_env *env, emacs_value arg, |
| 10 | void (*fin) (void *) EMACS_NOEXCEPT) | 10 | void (*fin) (void *) EMACS_NOEXCEPT) |
| 11 | EMACS_ATTRIBUTE_NONNULL (1); | 11 | EMACS_ATTRIBUTE_NONNULL (1); |
| 12 | |||
| 13 | int (*open_channel) (emacs_env *env, emacs_value pipe_process) | ||
| 14 | EMACS_ATTRIBUTE_NONNULL (1); | ||
diff --git a/src/process.c b/src/process.c index e4e5e57aeee..07881d6c5d3 100644 --- a/src/process.c +++ b/src/process.c | |||
| @@ -8200,6 +8200,17 @@ restore_nofile_limit (void) | |||
| 8200 | #endif | 8200 | #endif |
| 8201 | } | 8201 | } |
| 8202 | 8202 | ||
| 8203 | int | ||
| 8204 | open_channel_for_module (Lisp_Object process) | ||
| 8205 | { | ||
| 8206 | CHECK_PROCESS (process); | ||
| 8207 | CHECK_TYPE (PIPECONN_P (process), Qpipe_process_p, process); | ||
| 8208 | int fd = dup (XPROCESS (process)->open_fd[SUBPROCESS_STDOUT]); | ||
| 8209 | if (fd == -1) | ||
| 8210 | report_file_error ("Cannot duplicate file descriptor", Qnil); | ||
| 8211 | return fd; | ||
| 8212 | } | ||
| 8213 | |||
| 8203 | 8214 | ||
| 8204 | /* This is not called "init_process" because that is the name of a | 8215 | /* This is not called "init_process" because that is the name of a |
| 8205 | Mach system call, so it would cause problems on Darwin systems. */ | 8216 | Mach system call, so it would cause problems on Darwin systems. */ |
| @@ -8446,6 +8457,7 @@ amounts of data in one go. */); | |||
| 8446 | DEFSYM (Qinterrupt_process_functions, "interrupt-process-functions"); | 8457 | DEFSYM (Qinterrupt_process_functions, "interrupt-process-functions"); |
| 8447 | 8458 | ||
| 8448 | DEFSYM (Qnull, "null"); | 8459 | DEFSYM (Qnull, "null"); |
| 8460 | DEFSYM (Qpipe_process_p, "pipe-process-p"); | ||
| 8449 | 8461 | ||
| 8450 | defsubr (&Sprocessp); | 8462 | defsubr (&Sprocessp); |
| 8451 | defsubr (&Sget_process); | 8463 | defsubr (&Sget_process); |
diff --git a/src/process.h b/src/process.h index 7884efc5494..a783a31cb86 100644 --- a/src/process.h +++ b/src/process.h | |||
| @@ -300,6 +300,8 @@ extern Lisp_Object remove_slash_colon (Lisp_Object); | |||
| 300 | extern void update_processes_for_thread_death (Lisp_Object); | 300 | extern void update_processes_for_thread_death (Lisp_Object); |
| 301 | extern void dissociate_controlling_tty (void); | 301 | extern void dissociate_controlling_tty (void); |
| 302 | 302 | ||
| 303 | extern int open_channel_for_module (Lisp_Object); | ||
| 304 | |||
| 303 | INLINE_HEADER_END | 305 | INLINE_HEADER_END |
| 304 | 306 | ||
| 305 | #endif /* EMACS_PROCESS_H */ | 307 | #endif /* EMACS_PROCESS_H */ |
diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c index ec6948921f2..61733f1ef49 100644 --- a/test/data/emacs-module/mod-test.c +++ b/test/data/emacs-module/mod-test.c | |||
| @@ -30,6 +30,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 30 | #include <string.h> | 30 | #include <string.h> |
| 31 | #include <time.h> | 31 | #include <time.h> |
| 32 | 32 | ||
| 33 | #include <pthread.h> | ||
| 34 | #include <unistd.h> | ||
| 35 | |||
| 33 | #ifdef HAVE_GMP | 36 | #ifdef HAVE_GMP |
| 34 | #include <gmp.h> | 37 | #include <gmp.h> |
| 35 | #else | 38 | #else |
| @@ -320,9 +323,9 @@ Fmod_test_invalid_finalizer (emacs_env *env, ptrdiff_t nargs, emacs_value *args, | |||
| 320 | } | 323 | } |
| 321 | 324 | ||
| 322 | static void | 325 | static void |
| 323 | signal_errno (emacs_env *env, const char *function) | 326 | signal_system_error (emacs_env *env, int error, const char *function) |
| 324 | { | 327 | { |
| 325 | const char *message = strerror (errno); | 328 | const char *message = strerror (error); |
| 326 | emacs_value message_value = env->make_string (env, message, strlen (message)); | 329 | emacs_value message_value = env->make_string (env, message, strlen (message)); |
| 327 | emacs_value symbol = env->intern (env, "file-error"); | 330 | emacs_value symbol = env->intern (env, "file-error"); |
| 328 | emacs_value elements[2] | 331 | emacs_value elements[2] |
| @@ -331,6 +334,12 @@ signal_errno (emacs_env *env, const char *function) | |||
| 331 | env->non_local_exit_signal (env, symbol, data); | 334 | env->non_local_exit_signal (env, symbol, data); |
| 332 | } | 335 | } |
| 333 | 336 | ||
| 337 | static void | ||
| 338 | signal_errno (emacs_env *env, const char *function) | ||
| 339 | { | ||
| 340 | signal_system_error (env, errno, function); | ||
| 341 | } | ||
| 342 | |||
| 334 | /* A long-running operation that occasionally calls `should_quit' or | 343 | /* A long-running operation that occasionally calls `should_quit' or |
| 335 | `process_input'. */ | 344 | `process_input'. */ |
| 336 | 345 | ||
| @@ -533,6 +542,49 @@ Fmod_test_function_finalizer_calls (emacs_env *env, ptrdiff_t nargs, | |||
| 533 | return env->funcall (env, Flist, 2, list_args); | 542 | return env->funcall (env, Flist, 2, list_args); |
| 534 | } | 543 | } |
| 535 | 544 | ||
| 545 | static void * | ||
| 546 | write_to_pipe (void *arg) | ||
| 547 | { | ||
| 548 | /* We sleep a bit to test that writing to a pipe is indeed possible | ||
| 549 | if no environment is active. */ | ||
| 550 | const struct timespec sleep = {0, 500000000}; | ||
| 551 | if (nanosleep (&sleep, NULL) != 0) | ||
| 552 | perror ("nanosleep"); | ||
| 553 | FILE *stream = arg; | ||
| 554 | if (fputs ("data from thread", stream) < 0) | ||
| 555 | perror ("fputs"); | ||
| 556 | if (fclose (stream) != 0) | ||
| 557 | perror ("close"); | ||
| 558 | return NULL; | ||
| 559 | } | ||
| 560 | |||
| 561 | static emacs_value | ||
| 562 | Fmod_test_async_pipe (emacs_env *env, ptrdiff_t nargs, emacs_value *args, | ||
| 563 | void *data) | ||
| 564 | { | ||
| 565 | assert (nargs == 1); | ||
| 566 | int fd = env->open_channel (env, args[0]); | ||
| 567 | if (env->non_local_exit_check (env) != emacs_funcall_exit_return) | ||
| 568 | return NULL; | ||
| 569 | FILE *stream = fdopen (fd, "w"); | ||
| 570 | if (stream == NULL) | ||
| 571 | { | ||
| 572 | signal_errno (env, "fdopen"); | ||
| 573 | return NULL; | ||
| 574 | } | ||
| 575 | pthread_t thread; | ||
| 576 | int error | ||
| 577 | = pthread_create (&thread, NULL, write_to_pipe, stream); | ||
| 578 | if (error != 0) | ||
| 579 | { | ||
| 580 | signal_system_error (env, error, "pthread_create"); | ||
| 581 | if (fclose (stream) != 0) | ||
| 582 | perror ("fclose"); | ||
| 583 | return NULL; | ||
| 584 | } | ||
| 585 | return env->intern (env, "nil"); | ||
| 586 | } | ||
| 587 | |||
| 536 | /* Lisp utilities for easier readability (simple wrappers). */ | 588 | /* Lisp utilities for easier readability (simple wrappers). */ |
| 537 | 589 | ||
| 538 | /* Provide FEATURE to Emacs. */ | 590 | /* Provide FEATURE to Emacs. */ |
| @@ -614,6 +666,7 @@ emacs_module_init (struct emacs_runtime *ert) | |||
| 614 | Fmod_test_make_function_with_finalizer, 0, 0, NULL, NULL); | 666 | Fmod_test_make_function_with_finalizer, 0, 0, NULL, NULL); |
| 615 | DEFUN ("mod-test-function-finalizer-calls", | 667 | DEFUN ("mod-test-function-finalizer-calls", |
| 616 | Fmod_test_function_finalizer_calls, 0, 0, NULL, NULL); | 668 | Fmod_test_function_finalizer_calls, 0, 0, NULL, NULL); |
| 669 | DEFUN ("mod-test-async-pipe", Fmod_test_async_pipe, 1, 1, NULL, NULL); | ||
| 617 | 670 | ||
| 618 | #undef DEFUN | 671 | #undef DEFUN |
| 619 | 672 | ||
diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el index 48d2e86a605..1f91795e1e6 100644 --- a/test/src/emacs-module-tests.el +++ b/test/src/emacs-module-tests.el | |||
| @@ -424,4 +424,18 @@ See Bug#36226." | |||
| 424 | ;; but at least one. | 424 | ;; but at least one. |
| 425 | (should (> valid-after valid-before))))) | 425 | (should (> valid-after valid-before))))) |
| 426 | 426 | ||
| 427 | (ert-deftest module/async-pipe () | ||
| 428 | "Check that writing data from another thread works." | ||
| 429 | (with-temp-buffer | ||
| 430 | (let ((process (make-pipe-process :name "module/async-pipe" | ||
| 431 | :buffer (current-buffer) | ||
| 432 | :coding 'utf-8-unix | ||
| 433 | :noquery t))) | ||
| 434 | (unwind-protect | ||
| 435 | (progn | ||
| 436 | (mod-test-async-pipe process) | ||
| 437 | (should (accept-process-output process 1)) | ||
| 438 | (should (equal (buffer-string) "data from thread"))) | ||
| 439 | (delete-process process))))) | ||
| 440 | |||
| 427 | ;;; emacs-module-tests.el ends here | 441 | ;;; emacs-module-tests.el ends here |